forked from StrafesNET/strafe-project
Compare commits
98 Commits
Unquatific
...
master
Author | SHA1 | Date | |
---|---|---|---|
be9eb30a5c | |||
42310c5d2b | |||
988750a3c6 | |||
2f26662dda | |||
6f739dba8d | |||
2eaddd493d | |||
d5a6f8e1bc | |||
95fb43b908 | |||
3e814cb41a | |||
ea854a548f | |||
5f2cf8f32e | |||
b6b090de78 | |||
affbada62e | |||
8d2ba28700 | |||
3eb4e76ab2 | |||
5d31419370 | |||
44a58044c7 | |||
83ac776b78 | |||
871aadb78a | |||
65b49d2726 | |||
ed70b7c484 | |||
c7575f2e21 | |||
c2f78eab48 | |||
195014400f | |||
ee9585a4c2 | |||
77012d6caa | |||
f9509353dd | |||
5bce4a84cf | |||
4bbccd68ed | |||
8be3be4002 | |||
76bafa4d0a | |||
a612d1f864 | |||
ad9ef141bf | |||
4c11980989 | |||
091da88b5c | |||
045e540020 | |||
c14f8975fc | |||
8e228033e0 | |||
eaecbc5b73 | |||
91a1b3d65e | |||
e1d51ff2da | |||
c5f2395b3a | |||
77aa83d6ac | |||
184b12a9cc | |||
9ba3484e25 | |||
adcd7db4f1 | |||
710670d78e | |||
70e1514ef9 | |||
45e9b3d0ce | |||
1eac734c35 | |||
25e1312fe4 | |||
cc8f6b059d | |||
4cd287dbb8 | |||
40ea0e6c7d | |||
b45b92c627 | |||
e90f53a111 | |||
6beb6c5f9a | |||
a1507d4f26 | |||
77db5a7a6b | |||
75c8bc2bbb | |||
d49a6b2f0a | |||
19778ac7aa | |||
5b62052222 | |||
5a8bc141d3 | |||
83a067320b | |||
2faa61225f | |||
28499800cb | |||
57cc49dc1a | |||
d517b78a8c | |||
e6d1d69241 | |||
b28fa25279 | |||
713b235816 | |||
d2002383cb | |||
52f7de809d | |||
4efe1209b8 | |||
15a9136fc4 | |||
035736e7af | |||
7f9a16a56d | |||
814e573d91 | |||
6fa0f1c83e | |||
870cb56dac | |||
1aac2a9303 | |||
38661b3a68 | |||
7ad76740d4 | |||
c2d6af8bda | |||
fbacef83b9 | |||
2a9e848541 | |||
3413ec8740 | |||
168d6708d1 | |||
13cae4c7c5 | |||
0dc462a3b1 | |||
ca003edbc3 | |||
16abe23e97 | |||
67f8569178 | |||
121c9c5258 | |||
411b997b87 | |||
4d587b6c0b | |||
6ff74f08ab |
@ -1,2 +1,2 @@
|
||||
[registries.strafesnet]
|
||||
index = "sparse+https://git.itzana.me/api/packages/strafesnet/cargo/"
|
||||
index = "sparse+https://git.itzana.me/api/packages/strafesnet/cargo/"
|
||||
|
504
Cargo.lock
generated
504
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@ -1,5 +1,10 @@
|
||||
[workspace]
|
||||
members = [
|
||||
"engine/graphics",
|
||||
"engine/physics",
|
||||
"engine/session",
|
||||
"engine/settings",
|
||||
"integration-testing",
|
||||
"lib/bsp_loader",
|
||||
"lib/common",
|
||||
"lib/deferred_loader",
|
||||
|
@ -3,6 +3,9 @@
|
||||
# Strafe Project
|
||||
Monorepo for working on projects related to strafe client.
|
||||
|
||||
## Try it out
|
||||
See [releases](https://git.itzana.me/StrafesNET/strafe-project/releases) for downloads.
|
||||
|
||||
## How to build and run
|
||||
1. Have rust and git installed
|
||||
2. `git clone https://git.itzana.me/StrafesNET/strafe-project`
|
||||
@ -10,4 +13,4 @@ Monorepo for working on projects related to strafe client.
|
||||
4. `cargo run --release --bin strafe-client`
|
||||
|
||||
## Licenses
|
||||
Each project has its own license. Most crates are MIT/Apache but notably the Strafe Client has a sole proprietor license.
|
||||
Each project has its own license. Most crates are MIT/Apache but notably the Strafe Client and engine crates have a sole proprietor license.
|
||||
|
14
engine/graphics/Cargo.toml
Normal file
14
engine/graphics/Cargo.toml
Normal file
@ -0,0 +1,14 @@
|
||||
[package]
|
||||
name = "strafesnet_graphics"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
bytemuck = { version = "1.13.1", features = ["derive"] }
|
||||
ddsfile = "0.5.1"
|
||||
glam = "0.29.0"
|
||||
id = { version = "0.1.0", registry = "strafesnet" }
|
||||
strafesnet_common = { path = "../../lib/common", registry = "strafesnet" }
|
||||
strafesnet_session = { path = "../session", registry = "strafesnet" }
|
||||
strafesnet_settings = { path = "../settings", registry = "strafesnet" }
|
||||
wgpu = "24.0.0"
|
8
engine/graphics/LICENSE
Normal file
8
engine/graphics/LICENSE
Normal file
@ -0,0 +1,8 @@
|
||||
/*******************************************************
|
||||
* Copyright (C) 2023-2024 Rhys Lloyd <krakow20@gmail.com>
|
||||
*
|
||||
* This file is part of the StrafesNET bhop/surf client.
|
||||
*
|
||||
* StrafesNET can not be copied and/or distributed
|
||||
* without the express permission of Rhys Lloyd
|
||||
*******************************************************/
|
988
engine/graphics/src/graphics.rs
Normal file
988
engine/graphics/src/graphics.rs
Normal file
@ -0,0 +1,988 @@
|
||||
use std::borrow::Cow;
|
||||
use std::collections::{HashSet,HashMap};
|
||||
use strafesnet_common::map;
|
||||
use strafesnet_settings::settings;
|
||||
use strafesnet_session::session;
|
||||
use strafesnet_common::model::{self, ColorId, NormalId, PolygonIter, PositionId, RenderConfigId, TextureCoordinateId, VertexId};
|
||||
use wgpu::{util::DeviceExt,AstcBlock,AstcChannel};
|
||||
use crate::model::{self as model_graphics,IndexedGraphicsMeshOwnedRenderConfig,IndexedGraphicsMeshOwnedRenderConfigId,GraphicsMeshOwnedRenderConfig,GraphicsModelColor4,GraphicsModelOwned,GraphicsVertex};
|
||||
|
||||
pub fn required_limits()->wgpu::Limits{
|
||||
wgpu::Limits::default()
|
||||
}
|
||||
|
||||
struct Indices{
|
||||
count:u32,
|
||||
buf:wgpu::Buffer,
|
||||
format:wgpu::IndexFormat,
|
||||
}
|
||||
impl Indices{
|
||||
fn new<T:bytemuck::Pod>(device:&wgpu::Device,indices:&Vec<T>,format:wgpu::IndexFormat)->Self{
|
||||
Self{
|
||||
buf:device.create_buffer_init(&wgpu::util::BufferInitDescriptor{
|
||||
label:Some("Index"),
|
||||
contents:bytemuck::cast_slice(indices),
|
||||
usage:wgpu::BufferUsages::INDEX,
|
||||
}),
|
||||
count:indices.len() as u32,
|
||||
format,
|
||||
}
|
||||
}
|
||||
}
|
||||
struct GraphicsModel{
|
||||
indices:Indices,
|
||||
vertex_buf:wgpu::Buffer,
|
||||
bind_group:wgpu::BindGroup,
|
||||
instance_count:u32,
|
||||
}
|
||||
|
||||
struct GraphicsSamplers{
|
||||
repeat:wgpu::Sampler,
|
||||
}
|
||||
|
||||
struct GraphicsBindGroupLayouts{
|
||||
model:wgpu::BindGroupLayout,
|
||||
}
|
||||
|
||||
struct GraphicsBindGroups{
|
||||
camera:wgpu::BindGroup,
|
||||
skybox_texture:wgpu::BindGroup,
|
||||
}
|
||||
|
||||
struct GraphicsPipelines{
|
||||
skybox:wgpu::RenderPipeline,
|
||||
model:wgpu::RenderPipeline,
|
||||
}
|
||||
|
||||
struct GraphicsCamera{
|
||||
screen_size:glam::UVec2,
|
||||
fov:glam::Vec2,//slope
|
||||
//camera angles and such are extrapolated and passed in every time
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn perspective_rh(fov_x_slope:f32,fov_y_slope:f32,z_near:f32,z_far:f32)->glam::Mat4{
|
||||
//glam_assert!(z_near > 0.0 && z_far > 0.0);
|
||||
let r=z_far/(z_near-z_far);
|
||||
glam::Mat4::from_cols(
|
||||
glam::Vec4::new(1.0/fov_x_slope,0.0,0.0,0.0),
|
||||
glam::Vec4::new(0.0,1.0/fov_y_slope,0.0,0.0),
|
||||
glam::Vec4::new(0.0,0.0,r,-1.0),
|
||||
glam::Vec4::new(0.0,0.0,r*z_near,0.0),
|
||||
)
|
||||
}
|
||||
impl GraphicsCamera{
|
||||
pub fn proj(&self)->glam::Mat4{
|
||||
perspective_rh(self.fov.x,self.fov.y,0.4,4000.0)
|
||||
}
|
||||
pub fn world(&self,pos:glam::Vec3,angles:glam::Vec2)->glam::Mat4{
|
||||
//f32 good enough for view matrix
|
||||
glam::Mat4::from_translation(pos)*glam::Mat4::from_euler(glam::EulerRot::YXZ,angles.x,angles.y,0f32)
|
||||
}
|
||||
|
||||
pub fn to_uniform_data(&self,pos:glam::Vec3,angles:glam::Vec2)->[f32;16*4]{
|
||||
let proj=self.proj();
|
||||
let proj_inv=proj.inverse();
|
||||
let view_inv=self.world(pos,angles);
|
||||
let view=view_inv.inverse();
|
||||
|
||||
let mut raw=[0f32; 16 * 4];
|
||||
raw[..16].copy_from_slice(&AsRef::<[f32; 16]>::as_ref(&proj)[..]);
|
||||
raw[16..32].copy_from_slice(&AsRef::<[f32; 16]>::as_ref(&proj_inv)[..]);
|
||||
raw[32..48].copy_from_slice(&AsRef::<[f32; 16]>::as_ref(&view)[..]);
|
||||
raw[48..64].copy_from_slice(&AsRef::<[f32; 16]>::as_ref(&view_inv)[..]);
|
||||
raw
|
||||
}
|
||||
}
|
||||
impl std::default::Default for GraphicsCamera{
|
||||
fn default()->Self{
|
||||
Self{
|
||||
screen_size:glam::UVec2::ONE,
|
||||
fov:glam::Vec2::ONE,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct GraphicsState{
|
||||
pipelines:GraphicsPipelines,
|
||||
bind_groups:GraphicsBindGroups,
|
||||
bind_group_layouts:GraphicsBindGroupLayouts,
|
||||
samplers:GraphicsSamplers,
|
||||
camera:GraphicsCamera,
|
||||
camera_buf:wgpu::Buffer,
|
||||
temp_squid_texture_view:wgpu::TextureView,
|
||||
models:Vec<GraphicsModel>,
|
||||
depth_view:wgpu::TextureView,
|
||||
staging_belt:wgpu::util::StagingBelt,
|
||||
}
|
||||
|
||||
impl GraphicsState{
|
||||
const DEPTH_FORMAT:wgpu::TextureFormat=wgpu::TextureFormat::Depth24Plus;
|
||||
fn create_depth_texture(
|
||||
config:&wgpu::SurfaceConfiguration,
|
||||
device:&wgpu::Device,
|
||||
)->wgpu::TextureView{
|
||||
let depth_texture=device.create_texture(&wgpu::TextureDescriptor{
|
||||
size:wgpu::Extent3d{
|
||||
width:config.width,
|
||||
height:config.height,
|
||||
depth_or_array_layers:1,
|
||||
},
|
||||
mip_level_count:1,
|
||||
sample_count:1,
|
||||
dimension:wgpu::TextureDimension::D2,
|
||||
format:Self::DEPTH_FORMAT,
|
||||
usage:wgpu::TextureUsages::RENDER_ATTACHMENT,
|
||||
label:None,
|
||||
view_formats:&[],
|
||||
});
|
||||
|
||||
depth_texture.create_view(&wgpu::TextureViewDescriptor::default())
|
||||
}
|
||||
pub fn clear(&mut self){
|
||||
self.models.clear();
|
||||
}
|
||||
pub fn load_user_settings(&mut self,user_settings:&settings::UserSettings){
|
||||
self.camera.fov=user_settings.calculate_fov(1.0,&self.camera.screen_size).as_vec2();
|
||||
}
|
||||
pub fn generate_models(&mut self,device:&wgpu::Device,queue:&wgpu::Queue,map:&map::CompleteMap){
|
||||
//generate texture view per texture
|
||||
let texture_views:HashMap<strafesnet_common::model::TextureId,wgpu::TextureView>=map.textures.iter().enumerate().filter_map(|(texture_id,texture_data)|{
|
||||
let texture_id=model::TextureId::new(texture_id as u32);
|
||||
let image=match ddsfile::Dds::read(std::io::Cursor::new(texture_data)){
|
||||
Ok(image)=>image,
|
||||
Err(e)=>{
|
||||
println!("Error loading texture: {e}");
|
||||
return None;
|
||||
},
|
||||
};
|
||||
|
||||
let (mut width,mut height)=(image.get_width(),image.get_height());
|
||||
|
||||
let format=match image.header10.unwrap().dxgi_format{
|
||||
ddsfile::DxgiFormat::R8G8B8A8_UNorm_sRGB=>wgpu::TextureFormat::Rgba8UnormSrgb,
|
||||
ddsfile::DxgiFormat::BC7_UNorm_sRGB =>{
|
||||
//floor(w,4),should be ceil(w,4)
|
||||
width=width/4*4;
|
||||
height=height/4*4;
|
||||
wgpu::TextureFormat::Bc7RgbaUnormSrgb
|
||||
},
|
||||
other=>{
|
||||
println!("unsupported texture format{:?}",other);
|
||||
return None;
|
||||
},
|
||||
};
|
||||
|
||||
let size=wgpu::Extent3d{
|
||||
width,
|
||||
height,
|
||||
depth_or_array_layers:1,
|
||||
};
|
||||
|
||||
let layer_size=wgpu::Extent3d{
|
||||
depth_or_array_layers:1,
|
||||
..size
|
||||
};
|
||||
let max_mips=layer_size.max_mips(wgpu::TextureDimension::D2);
|
||||
|
||||
let texture=device.create_texture_with_data(
|
||||
queue,
|
||||
&wgpu::TextureDescriptor{
|
||||
size,
|
||||
mip_level_count:max_mips,
|
||||
sample_count:1,
|
||||
dimension:wgpu::TextureDimension::D2,
|
||||
format,
|
||||
usage:wgpu::TextureUsages::TEXTURE_BINDING|wgpu::TextureUsages::COPY_DST,
|
||||
label:Some(format!("Texture{}",texture_id.get()).as_str()),
|
||||
view_formats:&[],
|
||||
},
|
||||
wgpu::util::TextureDataOrder::LayerMajor,
|
||||
&image.data,
|
||||
);
|
||||
Some((texture_id,texture.create_view(&wgpu::TextureViewDescriptor{
|
||||
label:Some(format!("Texture{} View",texture_id.get()).as_str()),
|
||||
dimension:Some(wgpu::TextureViewDimension::D2),
|
||||
..wgpu::TextureViewDescriptor::default()
|
||||
})))
|
||||
}).collect();
|
||||
let num_textures=texture_views.len();
|
||||
|
||||
//split groups with different textures into separate models
|
||||
//the models received here are supposed to be tightly packed,i.e. no code needs to check if two models are using the same groups.
|
||||
let indexed_models_len=map.models.len();
|
||||
//models split into graphics_group.RenderConfigId
|
||||
let mut owned_mesh_id_from_mesh_id_render_config_id:HashMap<model::MeshId,HashMap<RenderConfigId,IndexedGraphicsMeshOwnedRenderConfigId>>=HashMap::new();
|
||||
let mut unique_render_config_models:Vec<IndexedGraphicsMeshOwnedRenderConfig>=Vec::with_capacity(indexed_models_len);
|
||||
for model in &map.models{
|
||||
//wow
|
||||
let instance=GraphicsModelOwned{
|
||||
transform:model.transform.into(),
|
||||
normal_transform:glam::Mat3::from_cols_array_2d(&model.transform.matrix3.to_array().map(|row|row.map(Into::into))).inverse().transpose(),
|
||||
color:GraphicsModelColor4::new(model.color),
|
||||
};
|
||||
//get or create owned mesh map
|
||||
let owned_mesh_map=owned_mesh_id_from_mesh_id_render_config_id
|
||||
.entry(model.mesh).or_insert_with(||{
|
||||
let mut owned_mesh_map=HashMap::new();
|
||||
//add mesh if renderid never before seen for this model
|
||||
//add instance
|
||||
//convert Model into GraphicsModelOwned
|
||||
//check each group, if it's using a new render config then make a new clone of the model
|
||||
if let Some(mesh)=map.meshes.get(model.mesh.get() as usize){
|
||||
for graphics_group in mesh.graphics_groups.iter(){
|
||||
//get or create owned mesh
|
||||
let owned_mesh_id=owned_mesh_map
|
||||
.entry(graphics_group.render).or_insert_with(||{
|
||||
//create
|
||||
let owned_mesh_id=IndexedGraphicsMeshOwnedRenderConfigId::new(unique_render_config_models.len() as u32);
|
||||
unique_render_config_models.push(IndexedGraphicsMeshOwnedRenderConfig{
|
||||
unique_pos:mesh.unique_pos.iter().map(|v|v.to_array().map(Into::into)).collect(),
|
||||
unique_tex:mesh.unique_tex.iter().map(|v|*v.as_ref()).collect(),
|
||||
unique_normal:mesh.unique_normal.iter().map(|v|v.to_array().map(Into::into)).collect(),
|
||||
unique_color:mesh.unique_color.iter().map(|v|*v.as_ref()).collect(),
|
||||
unique_vertices:mesh.unique_vertices.clone(),
|
||||
render_config:graphics_group.render,
|
||||
polys:model::PolygonGroup::PolygonList(model::PolygonList::new(Vec::new())),
|
||||
instances:Vec::new(),
|
||||
});
|
||||
owned_mesh_id
|
||||
});
|
||||
let owned_mesh=unique_render_config_models.get_mut(owned_mesh_id.get() as usize).unwrap();
|
||||
match &mut owned_mesh.polys{
|
||||
model::PolygonGroup::PolygonList(polygon_list)=>polygon_list.extend(
|
||||
graphics_group.groups.iter().flat_map(|polygon_group_id|{
|
||||
mesh.polygon_groups[polygon_group_id.get() as usize].polys()
|
||||
})
|
||||
.map(|vertex_id_slice|
|
||||
vertex_id_slice.to_vec()
|
||||
)
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
owned_mesh_map
|
||||
});
|
||||
for owned_mesh_id in owned_mesh_map.values(){
|
||||
let owned_mesh=unique_render_config_models.get_mut(owned_mesh_id.get() as usize).unwrap();
|
||||
let render_config=&map.render_configs[owned_mesh.render_config.get() as usize];
|
||||
if model.color.w==0.0&&render_config.texture.is_none(){
|
||||
continue;
|
||||
}
|
||||
owned_mesh.instances.push(instance.clone());
|
||||
}
|
||||
}
|
||||
//check every model to see if it's using the same (texture,color) but has few instances,if it is combine it into one model
|
||||
//1. collect unique instances of texture and color,note model id
|
||||
//2. for each model id,check if removing it from the pool decreases both the model count and instance count by more than one
|
||||
//3. transpose all models that stay in the set
|
||||
|
||||
//best plan:benchmark set_bind_group,set_vertex_buffer,set_index_buffer and draw_indexed
|
||||
//check if the estimated render performance is better by transposing multiple model instances into one model instance
|
||||
|
||||
//for now:just deduplicate single models...
|
||||
let mut deduplicated_models=Vec::with_capacity(indexed_models_len);//use indexed_models_len because the list will likely get smaller instead of bigger
|
||||
let mut unique_texture_color=HashMap::new();//texture->color->vec![(model_id,instance_id)]
|
||||
for (model_id,model) in unique_render_config_models.iter().enumerate(){
|
||||
//for now:filter out models with more than one instance
|
||||
if 1<model.instances.len(){
|
||||
continue;
|
||||
}
|
||||
//populate hashmap
|
||||
let unique_color=unique_texture_color
|
||||
.entry(model.render_config)
|
||||
.or_insert_with(||HashMap::new());
|
||||
//separate instances by color
|
||||
for (instance_id,instance) in model.instances.iter().enumerate(){
|
||||
let model_instance_list=unique_color
|
||||
.entry(instance.color)
|
||||
.or_insert_with(||Vec::new());
|
||||
//add model instance to list
|
||||
model_instance_list.push((model_id,instance_id));
|
||||
}
|
||||
}
|
||||
//populate a hashset of models selected for transposition
|
||||
//construct transposed models
|
||||
let mut selected_model_instances=HashSet::new();
|
||||
for (render_config,unique_color) in unique_texture_color.into_iter(){
|
||||
for (color,model_instance_list) in unique_color.into_iter(){
|
||||
//world transforming one model does not meet the definition of deduplicaiton
|
||||
if 1<model_instance_list.len(){
|
||||
//create model
|
||||
let mut unique_pos=Vec::new();
|
||||
let mut pos_id_from=HashMap::new();
|
||||
let mut unique_tex=Vec::new();
|
||||
let mut tex_id_from=HashMap::new();
|
||||
let mut unique_normal=Vec::new();
|
||||
let mut normal_id_from=HashMap::new();
|
||||
let mut unique_color=Vec::new();
|
||||
let mut color_id_from=HashMap::new();
|
||||
let mut unique_vertices=Vec::new();
|
||||
let mut vertex_id_from=HashMap::new();
|
||||
|
||||
let mut polys=Vec::new();
|
||||
//transform instance vertices
|
||||
for (model_id,instance_id) in model_instance_list.into_iter(){
|
||||
//populate hashset to prevent these models from being copied
|
||||
selected_model_instances.insert(model_id);
|
||||
//there is only one instance per model
|
||||
let model=&unique_render_config_models[model_id];
|
||||
let instance=&model.instances[instance_id];
|
||||
//just hash word slices LOL
|
||||
let map_pos_id:Vec<PositionId>=model.unique_pos.iter().map(|untransformed_pos|{
|
||||
let pos=instance.transform.transform_point3(glam::Vec3::from_array(untransformed_pos.clone())).to_array();
|
||||
let h=bytemuck::cast::<[f32;3],[u32;3]>(pos);
|
||||
PositionId::new(*pos_id_from.entry(h).or_insert_with(||{
|
||||
let pos_id=unique_pos.len();
|
||||
unique_pos.push(pos);
|
||||
pos_id
|
||||
}) as u32)
|
||||
}).collect();
|
||||
let map_tex_id:Vec<TextureCoordinateId>=model.unique_tex.iter().map(|&tex|{
|
||||
let h=bytemuck::cast::<[f32;2],[u32;2]>(tex);
|
||||
TextureCoordinateId::new(*tex_id_from.entry(h).or_insert_with(||{
|
||||
let tex_id=unique_tex.len();
|
||||
unique_tex.push(tex);
|
||||
tex_id
|
||||
}) as u32)
|
||||
}).collect();
|
||||
let map_normal_id:Vec<NormalId>=model.unique_normal.iter().map(|untransformed_normal|{
|
||||
let normal=(instance.normal_transform*glam::Vec3::from_array(untransformed_normal.clone())).to_array();
|
||||
let h=bytemuck::cast::<[f32;3],[u32;3]>(normal);
|
||||
NormalId::new(*normal_id_from.entry(h).or_insert_with(||{
|
||||
let normal_id=unique_normal.len();
|
||||
unique_normal.push(normal);
|
||||
normal_id
|
||||
}) as u32)
|
||||
}).collect();
|
||||
let map_color_id:Vec<ColorId>=model.unique_color.iter().map(|&color|{
|
||||
let h=bytemuck::cast::<[f32;4],[u32;4]>(color);
|
||||
ColorId::new(*color_id_from.entry(h).or_insert_with(||{
|
||||
let color_id=unique_color.len();
|
||||
unique_color.push(color);
|
||||
color_id
|
||||
}) as u32)
|
||||
}).collect();
|
||||
//map the indexed vertices onto new indices
|
||||
//creating the vertex map is slightly different because the vertices are directly hashable
|
||||
let map_vertex_id:Vec<VertexId>=model.unique_vertices.iter().map(|unmapped_vertex|{
|
||||
let vertex=model::IndexedVertex{
|
||||
pos:map_pos_id[unmapped_vertex.pos.get() as usize],
|
||||
tex:map_tex_id[unmapped_vertex.tex.get() as usize],
|
||||
normal:map_normal_id[unmapped_vertex.normal.get() as usize],
|
||||
color:map_color_id[unmapped_vertex.color.get() as usize],
|
||||
};
|
||||
VertexId::new(*vertex_id_from.entry(vertex.clone()).or_insert_with(||{
|
||||
let vertex_id=unique_vertices.len();
|
||||
unique_vertices.push(vertex);
|
||||
vertex_id
|
||||
}) as u32)
|
||||
}).collect();
|
||||
polys.extend(model.polys.polys().map(|poly|
|
||||
poly.iter().map(|vertex_id|
|
||||
map_vertex_id[vertex_id.get() as usize]
|
||||
).collect()
|
||||
));
|
||||
}
|
||||
//push model into dedup
|
||||
deduplicated_models.push(IndexedGraphicsMeshOwnedRenderConfig{
|
||||
unique_pos,
|
||||
unique_tex,
|
||||
unique_normal,
|
||||
unique_color,
|
||||
unique_vertices,
|
||||
render_config,
|
||||
polys:model::PolygonGroup::PolygonList(model::PolygonList::new(polys)),
|
||||
instances:vec![GraphicsModelOwned{
|
||||
transform:glam::Mat4::IDENTITY,
|
||||
normal_transform:glam::Mat3::IDENTITY,
|
||||
color
|
||||
}],
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
//fill untouched models
|
||||
for (model_id,model) in unique_render_config_models.into_iter().enumerate(){
|
||||
if !selected_model_instances.contains(&model_id){
|
||||
deduplicated_models.push(model);
|
||||
}
|
||||
}
|
||||
|
||||
//de-index models
|
||||
let deduplicated_models_len=deduplicated_models.len();
|
||||
let models:Vec<GraphicsMeshOwnedRenderConfig>=deduplicated_models.into_iter().map(|model|{
|
||||
let mut vertices=Vec::new();
|
||||
let mut index_from_vertex=HashMap::new();//::<IndexedVertex,usize>
|
||||
//this mut be combined in a more complex way if the models use different render patterns per group
|
||||
let mut indices=Vec::new();
|
||||
for poly in model.polys.polys(){
|
||||
let mut poly_vertices=poly.iter()
|
||||
.map(|&vertex_index|*index_from_vertex.entry(vertex_index).or_insert_with(||{
|
||||
let i=vertices.len();
|
||||
let vertex=&model.unique_vertices[vertex_index.get() as usize];
|
||||
vertices.push(GraphicsVertex{
|
||||
pos:model.unique_pos[vertex.pos.get() as usize],
|
||||
tex:model.unique_tex[vertex.tex.get() as usize],
|
||||
normal:model.unique_normal[vertex.normal.get() as usize],
|
||||
color:model.unique_color[vertex.color.get() as usize],
|
||||
});
|
||||
i
|
||||
}));
|
||||
|
||||
let a=poly_vertices.next().unwrap();
|
||||
let mut b=poly_vertices.next().unwrap();
|
||||
|
||||
poly_vertices.for_each(|c|{
|
||||
indices.extend([a,b,c]);
|
||||
b=c;
|
||||
});
|
||||
}
|
||||
GraphicsMeshOwnedRenderConfig{
|
||||
instances:model.instances,
|
||||
indices:if (u32::MAX as usize)<vertices.len(){
|
||||
panic!("Model has too many vertices!")
|
||||
}else if (u16::MAX as usize)<vertices.len(){
|
||||
model_graphics::Indices::U32(indices.into_iter().map(|vertex_idx|vertex_idx as u32).collect())
|
||||
}else{
|
||||
model_graphics::Indices::U16(indices.into_iter().map(|vertex_idx|vertex_idx as u16).collect())
|
||||
},
|
||||
vertices,
|
||||
render_config:model.render_config,
|
||||
}
|
||||
}).collect();
|
||||
//.into_iter() the modeldata vec so entities can be /moved/ to models.entities
|
||||
let mut model_count=0;
|
||||
let mut instance_count=0;
|
||||
let uniform_buffer_binding_size=required_limits().max_uniform_buffer_binding_size as usize;
|
||||
let chunk_size=uniform_buffer_binding_size/MODEL_BUFFER_SIZE_BYTES;
|
||||
self.models.reserve(models.len());
|
||||
for model in models.into_iter(){
|
||||
instance_count+=model.instances.len();
|
||||
for instances_chunk in model.instances.rchunks(chunk_size){
|
||||
model_count+=1;
|
||||
let mut model_uniforms=get_instances_buffer_data(instances_chunk);
|
||||
//TEMP: fill with zeroes to pass validation
|
||||
model_uniforms.resize(MODEL_BUFFER_SIZE*512,0.0f32);
|
||||
let model_buf=device.create_buffer_init(&wgpu::util::BufferInitDescriptor{
|
||||
label:Some(format!("Model{} Buf",model_count).as_str()),
|
||||
contents:bytemuck::cast_slice(&model_uniforms),
|
||||
usage:wgpu::BufferUsages::UNIFORM|wgpu::BufferUsages::COPY_DST,
|
||||
});
|
||||
let render_config=&map.render_configs[model.render_config.get() as usize];
|
||||
let texture_view=render_config.texture.and_then(|texture_id|
|
||||
texture_views.get(&texture_id)
|
||||
).unwrap_or(&self.temp_squid_texture_view);
|
||||
let bind_group=device.create_bind_group(&wgpu::BindGroupDescriptor{
|
||||
layout:&self.bind_group_layouts.model,
|
||||
entries:&[
|
||||
wgpu::BindGroupEntry{
|
||||
binding:0,
|
||||
resource:model_buf.as_entire_binding(),
|
||||
},
|
||||
wgpu::BindGroupEntry{
|
||||
binding:1,
|
||||
resource:wgpu::BindingResource::TextureView(texture_view),
|
||||
},
|
||||
wgpu::BindGroupEntry{
|
||||
binding:2,
|
||||
resource:wgpu::BindingResource::Sampler(&self.samplers.repeat),
|
||||
},
|
||||
],
|
||||
label:Some(format!("Model{} Bind Group",model_count).as_str()),
|
||||
});
|
||||
let vertex_buf=device.create_buffer_init(&wgpu::util::BufferInitDescriptor{
|
||||
label:Some("Vertex"),
|
||||
contents:bytemuck::cast_slice(&model.vertices),
|
||||
usage:wgpu::BufferUsages::VERTEX,
|
||||
});
|
||||
//all of these are being moved here
|
||||
self.models.push(GraphicsModel{
|
||||
instance_count:instances_chunk.len() as u32,
|
||||
vertex_buf,
|
||||
indices:match &model.indices{
|
||||
model_graphics::Indices::U32(indices)=>Indices::new(device,indices,wgpu::IndexFormat::Uint32),
|
||||
model_graphics::Indices::U16(indices)=>Indices::new(device,indices,wgpu::IndexFormat::Uint16),
|
||||
},
|
||||
bind_group,
|
||||
});
|
||||
}
|
||||
}
|
||||
println!("Texture References={}",num_textures);
|
||||
println!("Textures Loaded={}",texture_views.len());
|
||||
println!("Indexed Models={}",indexed_models_len);
|
||||
println!("Deduplicated Models={}",deduplicated_models_len);
|
||||
println!("Graphics Objects:{}",self.models.len());
|
||||
println!("Graphics Instances:{}",instance_count);
|
||||
}
|
||||
|
||||
pub fn new(
|
||||
device:&wgpu::Device,
|
||||
queue:&wgpu::Queue,
|
||||
config:&wgpu::SurfaceConfiguration,
|
||||
)->Self{
|
||||
let camera_bind_group_layout=device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor{
|
||||
label:None,
|
||||
entries:&[
|
||||
wgpu::BindGroupLayoutEntry{
|
||||
binding:0,
|
||||
visibility:wgpu::ShaderStages::VERTEX,
|
||||
ty:wgpu::BindingType::Buffer{
|
||||
ty:wgpu::BufferBindingType::Uniform,
|
||||
has_dynamic_offset:false,
|
||||
min_binding_size:None,
|
||||
},
|
||||
count:None,
|
||||
},
|
||||
],
|
||||
});
|
||||
let skybox_texture_bind_group_layout=device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor{
|
||||
label:Some("Skybox Texture Bind Group Layout"),
|
||||
entries:&[
|
||||
wgpu::BindGroupLayoutEntry{
|
||||
binding:0,
|
||||
visibility:wgpu::ShaderStages::FRAGMENT,
|
||||
ty:wgpu::BindingType::Texture{
|
||||
sample_type:wgpu::TextureSampleType::Float{filterable:true},
|
||||
multisampled:false,
|
||||
view_dimension:wgpu::TextureViewDimension::Cube,
|
||||
},
|
||||
count:None,
|
||||
},
|
||||
wgpu::BindGroupLayoutEntry{
|
||||
binding:1,
|
||||
visibility:wgpu::ShaderStages::FRAGMENT,
|
||||
ty:wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
|
||||
count:None,
|
||||
},
|
||||
],
|
||||
});
|
||||
let model_bind_group_layout=device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor{
|
||||
label:Some("Model Bind Group Layout"),
|
||||
entries:&[
|
||||
wgpu::BindGroupLayoutEntry{
|
||||
binding:0,
|
||||
visibility:wgpu::ShaderStages::VERTEX,
|
||||
ty:wgpu::BindingType::Buffer{
|
||||
ty:wgpu::BufferBindingType::Uniform,
|
||||
has_dynamic_offset:false,
|
||||
min_binding_size:None,
|
||||
},
|
||||
count:None,
|
||||
},
|
||||
wgpu::BindGroupLayoutEntry{
|
||||
binding:1,
|
||||
visibility:wgpu::ShaderStages::FRAGMENT,
|
||||
ty:wgpu::BindingType::Texture{
|
||||
sample_type:wgpu::TextureSampleType::Float{filterable:true},
|
||||
multisampled:false,
|
||||
view_dimension:wgpu::TextureViewDimension::D2,
|
||||
},
|
||||
count:None,
|
||||
},
|
||||
wgpu::BindGroupLayoutEntry{
|
||||
binding:2,
|
||||
visibility:wgpu::ShaderStages::FRAGMENT,
|
||||
ty:wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
|
||||
count:None,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
let clamp_sampler=device.create_sampler(&wgpu::SamplerDescriptor{
|
||||
label:Some("Clamp Sampler"),
|
||||
address_mode_u:wgpu::AddressMode::ClampToEdge,
|
||||
address_mode_v:wgpu::AddressMode::ClampToEdge,
|
||||
address_mode_w:wgpu::AddressMode::ClampToEdge,
|
||||
mag_filter:wgpu::FilterMode::Linear,
|
||||
min_filter:wgpu::FilterMode::Linear,
|
||||
mipmap_filter:wgpu::FilterMode::Linear,
|
||||
..Default::default()
|
||||
});
|
||||
let repeat_sampler=device.create_sampler(&wgpu::SamplerDescriptor{
|
||||
label:Some("Repeat Sampler"),
|
||||
address_mode_u:wgpu::AddressMode::Repeat,
|
||||
address_mode_v:wgpu::AddressMode::Repeat,
|
||||
address_mode_w:wgpu::AddressMode::Repeat,
|
||||
mag_filter:wgpu::FilterMode::Linear,
|
||||
min_filter:wgpu::FilterMode::Linear,
|
||||
mipmap_filter:wgpu::FilterMode::Linear,
|
||||
anisotropy_clamp:16,
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
// Create the render pipeline
|
||||
let shader=device.create_shader_module(wgpu::ShaderModuleDescriptor{
|
||||
label:None,
|
||||
source:wgpu::ShaderSource::Wgsl(Cow::Borrowed(include_str!("../../../strafe-client/src/shader.wgsl"))),
|
||||
});
|
||||
|
||||
//load textures
|
||||
let device_features=device.features();
|
||||
|
||||
let skybox_texture_view={
|
||||
let skybox_format=if device_features.contains(wgpu::Features::TEXTURE_COMPRESSION_ASTC){
|
||||
println!("Using ASTC");
|
||||
wgpu::TextureFormat::Astc{
|
||||
block:AstcBlock::B4x4,
|
||||
channel:AstcChannel::UnormSrgb,
|
||||
}
|
||||
}else if device_features.contains(wgpu::Features::TEXTURE_COMPRESSION_ETC2){
|
||||
println!("Using ETC2");
|
||||
wgpu::TextureFormat::Etc2Rgb8UnormSrgb
|
||||
}else if device_features.contains(wgpu::Features::TEXTURE_COMPRESSION_BC){
|
||||
println!("Using BC");
|
||||
wgpu::TextureFormat::Bc1RgbaUnormSrgb
|
||||
}else{
|
||||
println!("Using plain");
|
||||
wgpu::TextureFormat::Bgra8UnormSrgb
|
||||
};
|
||||
|
||||
let bytes=match skybox_format{
|
||||
wgpu::TextureFormat::Astc{
|
||||
block:AstcBlock::B4x4,
|
||||
channel:AstcChannel::UnormSrgb,
|
||||
}=>&include_bytes!("../../../strafe-client/images/astc.dds")[..],
|
||||
wgpu::TextureFormat::Etc2Rgb8UnormSrgb=>&include_bytes!("../../../strafe-client/images/etc2.dds")[..],
|
||||
wgpu::TextureFormat::Bc1RgbaUnormSrgb=>&include_bytes!("../../../strafe-client/images/bc1.dds")[..],
|
||||
wgpu::TextureFormat::Bgra8UnormSrgb=>&include_bytes!("../../../strafe-client/images/bgra.dds")[..],
|
||||
_=>unreachable!(),
|
||||
};
|
||||
|
||||
let skybox_image=ddsfile::Dds::read(&mut std::io::Cursor::new(bytes)).unwrap();
|
||||
|
||||
let size=wgpu::Extent3d{
|
||||
width:skybox_image.get_width(),
|
||||
height:skybox_image.get_height(),
|
||||
depth_or_array_layers:6,
|
||||
};
|
||||
|
||||
let layer_size=wgpu::Extent3d{
|
||||
depth_or_array_layers:1,
|
||||
..size
|
||||
};
|
||||
let max_mips=layer_size.max_mips(wgpu::TextureDimension::D2);
|
||||
|
||||
let skybox_texture=device.create_texture_with_data(
|
||||
queue,
|
||||
&wgpu::TextureDescriptor{
|
||||
size,
|
||||
mip_level_count:max_mips,
|
||||
sample_count:1,
|
||||
dimension:wgpu::TextureDimension::D2,
|
||||
format:skybox_format,
|
||||
usage:wgpu::TextureUsages::TEXTURE_BINDING|wgpu::TextureUsages::COPY_DST,
|
||||
label:Some("Skybox Texture"),
|
||||
view_formats:&[],
|
||||
},
|
||||
wgpu::util::TextureDataOrder::LayerMajor,
|
||||
&skybox_image.data,
|
||||
);
|
||||
|
||||
skybox_texture.create_view(&wgpu::TextureViewDescriptor{
|
||||
label:Some("Skybox Texture View"),
|
||||
dimension:Some(wgpu::TextureViewDimension::Cube),
|
||||
..wgpu::TextureViewDescriptor::default()
|
||||
})
|
||||
};
|
||||
|
||||
//squid
|
||||
let squid_texture_view={
|
||||
let bytes=include_bytes!("../../../strafe-client/images/squid.dds");
|
||||
|
||||
let image=ddsfile::Dds::read(&mut std::io::Cursor::new(bytes)).unwrap();
|
||||
|
||||
let size=wgpu::Extent3d{
|
||||
width:image.get_width(),
|
||||
height:image.get_height(),
|
||||
depth_or_array_layers:1,
|
||||
};
|
||||
|
||||
let layer_size=wgpu::Extent3d{
|
||||
depth_or_array_layers:1,
|
||||
..size
|
||||
};
|
||||
let max_mips=layer_size.max_mips(wgpu::TextureDimension::D2);
|
||||
|
||||
let texture=device.create_texture_with_data(
|
||||
queue,
|
||||
&wgpu::TextureDescriptor{
|
||||
size,
|
||||
mip_level_count:max_mips,
|
||||
sample_count:1,
|
||||
dimension:wgpu::TextureDimension::D2,
|
||||
format:wgpu::TextureFormat::Bc7RgbaUnorm,
|
||||
usage:wgpu::TextureUsages::TEXTURE_BINDING|wgpu::TextureUsages::COPY_DST,
|
||||
label:Some("Squid Texture"),
|
||||
view_formats:&[],
|
||||
},
|
||||
wgpu::util::TextureDataOrder::LayerMajor,
|
||||
&image.data,
|
||||
);
|
||||
|
||||
texture.create_view(&wgpu::TextureViewDescriptor{
|
||||
label:Some("Squid Texture View"),
|
||||
dimension:Some(wgpu::TextureViewDimension::D2),
|
||||
..wgpu::TextureViewDescriptor::default()
|
||||
})
|
||||
};
|
||||
|
||||
let model_pipeline_layout=device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor{
|
||||
label:None,
|
||||
bind_group_layouts:&[
|
||||
&camera_bind_group_layout,
|
||||
&skybox_texture_bind_group_layout,
|
||||
&model_bind_group_layout,
|
||||
],
|
||||
push_constant_ranges:&[],
|
||||
});
|
||||
let sky_pipeline_layout=device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor{
|
||||
label:None,
|
||||
bind_group_layouts:&[
|
||||
&camera_bind_group_layout,
|
||||
&skybox_texture_bind_group_layout,
|
||||
],
|
||||
push_constant_ranges:&[],
|
||||
});
|
||||
|
||||
// Create the render pipelines
|
||||
let sky_pipeline=device.create_render_pipeline(&wgpu::RenderPipelineDescriptor{
|
||||
label:Some("Sky Pipeline"),
|
||||
layout:Some(&sky_pipeline_layout),
|
||||
vertex:wgpu::VertexState{
|
||||
module:&shader,
|
||||
entry_point:Some("vs_sky"),
|
||||
buffers:&[],
|
||||
compilation_options:wgpu::PipelineCompilationOptions::default(),
|
||||
},
|
||||
fragment:Some(wgpu::FragmentState{
|
||||
module:&shader,
|
||||
entry_point:Some("fs_sky"),
|
||||
targets:&[Some(config.view_formats[0].into())],
|
||||
compilation_options:wgpu::PipelineCompilationOptions::default(),
|
||||
}),
|
||||
primitive:wgpu::PrimitiveState{
|
||||
front_face:wgpu::FrontFace::Cw,
|
||||
..Default::default()
|
||||
},
|
||||
depth_stencil:Some(wgpu::DepthStencilState{
|
||||
format:Self::DEPTH_FORMAT,
|
||||
depth_write_enabled:false,
|
||||
depth_compare:wgpu::CompareFunction::LessEqual,
|
||||
stencil:wgpu::StencilState::default(),
|
||||
bias:wgpu::DepthBiasState::default(),
|
||||
}),
|
||||
multisample:wgpu::MultisampleState::default(),
|
||||
multiview:None,
|
||||
cache:None,
|
||||
});
|
||||
let model_pipeline=device.create_render_pipeline(&wgpu::RenderPipelineDescriptor{
|
||||
label:Some("Model Pipeline"),
|
||||
layout:Some(&model_pipeline_layout),
|
||||
vertex:wgpu::VertexState{
|
||||
module:&shader,
|
||||
entry_point:Some("vs_entity_texture"),
|
||||
buffers:&[wgpu::VertexBufferLayout{
|
||||
array_stride:std::mem::size_of::<GraphicsVertex>() as wgpu::BufferAddress,
|
||||
step_mode:wgpu::VertexStepMode::Vertex,
|
||||
attributes:&wgpu::vertex_attr_array![0=>Float32x3,1=>Float32x2,2=>Float32x3,3=>Float32x4],
|
||||
}],
|
||||
compilation_options:wgpu::PipelineCompilationOptions::default(),
|
||||
},
|
||||
fragment:Some(wgpu::FragmentState{
|
||||
module:&shader,
|
||||
entry_point:Some("fs_entity_texture"),
|
||||
targets:&[Some(config.view_formats[0].into())],
|
||||
compilation_options:wgpu::PipelineCompilationOptions::default(),
|
||||
}),
|
||||
primitive:wgpu::PrimitiveState{
|
||||
front_face:wgpu::FrontFace::Cw,
|
||||
cull_mode:Some(wgpu::Face::Front),
|
||||
..Default::default()
|
||||
},
|
||||
depth_stencil:Some(wgpu::DepthStencilState{
|
||||
format:Self::DEPTH_FORMAT,
|
||||
depth_write_enabled:true,
|
||||
depth_compare:wgpu::CompareFunction::LessEqual,
|
||||
stencil:wgpu::StencilState::default(),
|
||||
bias:wgpu::DepthBiasState::default(),
|
||||
}),
|
||||
multisample:wgpu::MultisampleState::default(),
|
||||
multiview:None,
|
||||
cache:None,
|
||||
});
|
||||
|
||||
let camera=GraphicsCamera::default();
|
||||
let camera_uniforms=camera.to_uniform_data(glam::Vec3::ZERO,glam::Vec2::ZERO);
|
||||
let camera_buf=device.create_buffer_init(&wgpu::util::BufferInitDescriptor{
|
||||
label:Some("Camera"),
|
||||
contents:bytemuck::cast_slice(&camera_uniforms),
|
||||
usage:wgpu::BufferUsages::UNIFORM|wgpu::BufferUsages::COPY_DST,
|
||||
});
|
||||
let camera_bind_group=device.create_bind_group(&wgpu::BindGroupDescriptor{
|
||||
layout:&camera_bind_group_layout,
|
||||
entries:&[
|
||||
wgpu::BindGroupEntry{
|
||||
binding:0,
|
||||
resource:camera_buf.as_entire_binding(),
|
||||
},
|
||||
],
|
||||
label:Some("Camera"),
|
||||
});
|
||||
|
||||
let skybox_texture_bind_group=device.create_bind_group(&wgpu::BindGroupDescriptor{
|
||||
layout:&skybox_texture_bind_group_layout,
|
||||
entries:&[
|
||||
wgpu::BindGroupEntry{
|
||||
binding:0,
|
||||
resource:wgpu::BindingResource::TextureView(&skybox_texture_view),
|
||||
},
|
||||
wgpu::BindGroupEntry{
|
||||
binding:1,
|
||||
resource:wgpu::BindingResource::Sampler(&clamp_sampler),
|
||||
},
|
||||
],
|
||||
label:Some("Sky Texture"),
|
||||
});
|
||||
|
||||
let depth_view=Self::create_depth_texture(config,device);
|
||||
|
||||
Self{
|
||||
pipelines:GraphicsPipelines{
|
||||
skybox:sky_pipeline,
|
||||
model:model_pipeline
|
||||
},
|
||||
bind_groups:GraphicsBindGroups{
|
||||
camera:camera_bind_group,
|
||||
skybox_texture:skybox_texture_bind_group,
|
||||
},
|
||||
camera,
|
||||
camera_buf,
|
||||
models:Vec::new(),
|
||||
depth_view,
|
||||
staging_belt:wgpu::util::StagingBelt::new(0x100),
|
||||
bind_group_layouts:GraphicsBindGroupLayouts{model:model_bind_group_layout},
|
||||
samplers:GraphicsSamplers{repeat:repeat_sampler},
|
||||
temp_squid_texture_view:squid_texture_view,
|
||||
}
|
||||
}
|
||||
pub fn resize(
|
||||
&mut self,
|
||||
device:&wgpu::Device,
|
||||
config:&wgpu::SurfaceConfiguration,
|
||||
user_settings:&settings::UserSettings,
|
||||
){
|
||||
self.depth_view=Self::create_depth_texture(config,device);
|
||||
self.camera.screen_size=glam::uvec2(config.width,config.height);
|
||||
self.load_user_settings(user_settings);
|
||||
}
|
||||
pub fn render(
|
||||
&mut self,
|
||||
view:&wgpu::TextureView,
|
||||
device:&wgpu::Device,
|
||||
queue:&wgpu::Queue,
|
||||
frame_state:session::FrameState,
|
||||
){
|
||||
//TODO:use scheduled frame times to create beautiful smoothing simulation physics extrapolation assuming no input
|
||||
|
||||
let mut encoder=device.create_command_encoder(&wgpu::CommandEncoderDescriptor{label:None});
|
||||
|
||||
// update rotation
|
||||
let camera_uniforms=self.camera.to_uniform_data(
|
||||
frame_state.body.extrapolated_position(frame_state.time).map(Into::<f32>::into).to_array().into(),
|
||||
frame_state.camera.simulate_move_angles(glam::IVec2::ZERO)
|
||||
);
|
||||
self.staging_belt
|
||||
.write_buffer(
|
||||
&mut encoder,
|
||||
&self.camera_buf,
|
||||
0,
|
||||
wgpu::BufferSize::new((camera_uniforms.len() * 4) as wgpu::BufferAddress).unwrap(),
|
||||
device,
|
||||
)
|
||||
.copy_from_slice(bytemuck::cast_slice(&camera_uniforms));
|
||||
//This code only needs to run when the uniforms change
|
||||
/*
|
||||
for model in self.models.iter(){
|
||||
let model_uniforms=get_instances_buffer_data(&model.instances);
|
||||
self.staging_belt
|
||||
.write_buffer(
|
||||
&mut encoder,
|
||||
&model.model_buf,//description of where data will be written when command is executed
|
||||
0,//offset in staging belt?
|
||||
wgpu::BufferSize::new((model_uniforms.len() * 4) as wgpu::BufferAddress).unwrap(),
|
||||
device,
|
||||
)
|
||||
.copy_from_slice(bytemuck::cast_slice(&model_uniforms));
|
||||
}
|
||||
*/
|
||||
self.staging_belt.finish();
|
||||
|
||||
{
|
||||
let mut rpass=encoder.begin_render_pass(&wgpu::RenderPassDescriptor{
|
||||
label:None,
|
||||
color_attachments:&[Some(wgpu::RenderPassColorAttachment{
|
||||
view,
|
||||
resolve_target:None,
|
||||
ops:wgpu::Operations{
|
||||
load:wgpu::LoadOp::Clear(wgpu::Color{
|
||||
r:0.1,
|
||||
g:0.2,
|
||||
b:0.3,
|
||||
a:1.0,
|
||||
}),
|
||||
store:wgpu::StoreOp::Store,
|
||||
},
|
||||
})],
|
||||
depth_stencil_attachment:Some(wgpu::RenderPassDepthStencilAttachment{
|
||||
view:&self.depth_view,
|
||||
depth_ops:Some(wgpu::Operations{
|
||||
load:wgpu::LoadOp::Clear(1.0),
|
||||
store:wgpu::StoreOp::Discard,
|
||||
}),
|
||||
stencil_ops:None,
|
||||
}),
|
||||
timestamp_writes:Default::default(),
|
||||
occlusion_query_set:Default::default(),
|
||||
});
|
||||
|
||||
rpass.set_bind_group(0,&self.bind_groups.camera,&[]);
|
||||
rpass.set_bind_group(1,&self.bind_groups.skybox_texture,&[]);
|
||||
|
||||
rpass.set_pipeline(&self.pipelines.model);
|
||||
for model in &self.models{
|
||||
rpass.set_bind_group(2,&model.bind_group,&[]);
|
||||
rpass.set_vertex_buffer(0,model.vertex_buf.slice(..));
|
||||
rpass.set_index_buffer(model.indices.buf.slice(..),model.indices.format);
|
||||
//TODO: loop over triangle strips
|
||||
rpass.draw_indexed(0..model.indices.count,0,0..model.instance_count);
|
||||
}
|
||||
|
||||
rpass.set_pipeline(&self.pipelines.skybox);
|
||||
rpass.draw(0..3,0..1);
|
||||
}
|
||||
|
||||
queue.submit(std::iter::once(encoder.finish()));
|
||||
|
||||
self.staging_belt.recall();
|
||||
}
|
||||
}
|
||||
const MODEL_BUFFER_SIZE:usize=4*4 + 12 + 4;//let size=std::mem::size_of::<ModelInstance>();
|
||||
const MODEL_BUFFER_SIZE_BYTES:usize=MODEL_BUFFER_SIZE*4;
|
||||
fn get_instances_buffer_data(instances:&[GraphicsModelOwned])->Vec<f32>{
|
||||
let mut raw=Vec::with_capacity(MODEL_BUFFER_SIZE*instances.len());
|
||||
for mi in instances{
|
||||
//model transform
|
||||
raw.extend_from_slice(&AsRef::<[f32; 4*4]>::as_ref(&mi.transform)[..]);
|
||||
//normal transform
|
||||
raw.extend_from_slice(AsRef::<[f32; 3]>::as_ref(&mi.normal_transform.x_axis));
|
||||
raw.extend_from_slice(&[0.0]);
|
||||
raw.extend_from_slice(AsRef::<[f32; 3]>::as_ref(&mi.normal_transform.y_axis));
|
||||
raw.extend_from_slice(&[0.0]);
|
||||
raw.extend_from_slice(AsRef::<[f32; 3]>::as_ref(&mi.normal_transform.z_axis));
|
||||
raw.extend_from_slice(&[0.0]);
|
||||
//color
|
||||
raw.extend_from_slice(AsRef::<[f32; 4]>::as_ref(&mi.color.get()));
|
||||
}
|
||||
raw
|
||||
}
|
2
engine/graphics/src/lib.rs
Normal file
2
engine/graphics/src/lib.rs
Normal file
@ -0,0 +1,2 @@
|
||||
pub mod model;
|
||||
pub mod graphics;
|
48
engine/graphics/src/model.rs
Normal file
48
engine/graphics/src/model.rs
Normal file
@ -0,0 +1,48 @@
|
||||
use bytemuck::{Pod,Zeroable};
|
||||
use strafesnet_common::model::{IndexedVertex,PolygonGroup,RenderConfigId};
|
||||
#[derive(Clone,Copy,Pod,Zeroable)]
|
||||
#[repr(C)]
|
||||
pub struct GraphicsVertex{
|
||||
pub pos:[f32;3],
|
||||
pub tex:[f32;2],
|
||||
pub normal:[f32;3],
|
||||
pub color:[f32;4],
|
||||
}
|
||||
#[derive(Clone,Copy,id::Id)]
|
||||
pub struct IndexedGraphicsMeshOwnedRenderConfigId(u32);
|
||||
pub struct IndexedGraphicsMeshOwnedRenderConfig{
|
||||
pub unique_pos:Vec<[f32;3]>,
|
||||
pub unique_tex:Vec<[f32;2]>,
|
||||
pub unique_normal:Vec<[f32;3]>,
|
||||
pub unique_color:Vec<[f32;4]>,
|
||||
pub unique_vertices:Vec<IndexedVertex>,
|
||||
pub render_config:RenderConfigId,
|
||||
pub polys:PolygonGroup,
|
||||
pub instances:Vec<GraphicsModelOwned>,
|
||||
}
|
||||
pub enum Indices{
|
||||
U32(Vec<u32>),
|
||||
U16(Vec<u16>),
|
||||
}
|
||||
pub struct GraphicsMeshOwnedRenderConfig{
|
||||
pub vertices:Vec<GraphicsVertex>,
|
||||
pub indices:Indices,
|
||||
pub render_config:RenderConfigId,
|
||||
pub instances:Vec<GraphicsModelOwned>,
|
||||
}
|
||||
#[derive(Clone,Copy,PartialEq,id::Id)]
|
||||
pub struct GraphicsModelColor4(glam::Vec4);
|
||||
impl std::hash::Hash for GraphicsModelColor4{
|
||||
fn hash<H:std::hash::Hasher>(&self,state:&mut H) {
|
||||
for &f in self.0.as_ref(){
|
||||
bytemuck::cast::<f32,u32>(f).hash(state);
|
||||
}
|
||||
}
|
||||
}
|
||||
impl Eq for GraphicsModelColor4{}
|
||||
#[derive(Clone)]
|
||||
pub struct GraphicsModelOwned{
|
||||
pub transform:glam::Mat4,
|
||||
pub normal_transform:glam::Mat3,
|
||||
pub color:GraphicsModelColor4,
|
||||
}
|
10
engine/physics/Cargo.toml
Normal file
10
engine/physics/Cargo.toml
Normal file
@ -0,0 +1,10 @@
|
||||
[package]
|
||||
name = "strafesnet_physics"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
arrayvec = "0.7.6"
|
||||
glam = "0.29.0"
|
||||
id = { version = "0.1.0", registry = "strafesnet" }
|
||||
strafesnet_common = { path = "../../lib/common", registry = "strafesnet" }
|
8
engine/physics/LICENSE
Normal file
8
engine/physics/LICENSE
Normal file
@ -0,0 +1,8 @@
|
||||
/*******************************************************
|
||||
* Copyright (C) 2023-2024 Rhys Lloyd <krakow20@gmail.com>
|
||||
*
|
||||
* This file is part of the StrafesNET bhop/surf client.
|
||||
*
|
||||
* StrafesNET can not be copied and/or distributed
|
||||
* without the express permission of Rhys Lloyd
|
||||
*******************************************************/
|
160
engine/physics/src/body.rs
Normal file
160
engine/physics/src/body.rs
Normal file
@ -0,0 +1,160 @@
|
||||
use strafesnet_common::aabb;
|
||||
use strafesnet_common::integer::{self,vec3,Time,Planar64,Planar64Vec3};
|
||||
#[derive(Clone,Copy,Debug,Hash)]
|
||||
pub struct Body<T>{
|
||||
pub position:Planar64Vec3,//I64 where 2^32 = 1 u
|
||||
pub velocity:Planar64Vec3,//I64 where 2^32 = 1 u/s
|
||||
pub acceleration:Planar64Vec3,//I64 where 2^32 = 1 u/s/s
|
||||
pub time:Time<T>,//nanoseconds x xxxxD!
|
||||
}
|
||||
impl<T> std::ops::Neg for Body<T>{
|
||||
type Output=Self;
|
||||
fn neg(self)->Self::Output{
|
||||
Self{
|
||||
position:self.position,
|
||||
velocity:-self.velocity,
|
||||
acceleration:self.acceleration,
|
||||
time:-self.time,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Body<T>
|
||||
where Time<T>:Copy,
|
||||
{
|
||||
pub const ZERO:Self=Self::new(vec3::ZERO,vec3::ZERO,vec3::ZERO,Time::ZERO);
|
||||
pub const fn new(position:Planar64Vec3,velocity:Planar64Vec3,acceleration:Planar64Vec3,time:Time<T>)->Self{
|
||||
Self{
|
||||
position,
|
||||
velocity,
|
||||
acceleration,
|
||||
time,
|
||||
}
|
||||
}
|
||||
pub const fn relative_to<'a>(&'a self,body0:&'a Body<T>)->VirtualBody<'a,T>{
|
||||
//(p0,v0,a0,t0)
|
||||
//(p1,v1,a1,t1)
|
||||
VirtualBody{
|
||||
body0,
|
||||
body1:self,
|
||||
}
|
||||
}
|
||||
pub fn extrapolated_position(&self,time:Time<T>)->Planar64Vec3{
|
||||
let dt=time-self.time;
|
||||
self.position
|
||||
+(self.velocity*dt).map(|elem|elem.divide().fix_1())
|
||||
+self.acceleration.map(|elem|(dt*dt*elem/2).divide().fix_1())
|
||||
}
|
||||
pub fn extrapolated_velocity(&self,time:Time<T>)->Planar64Vec3{
|
||||
let dt=time-self.time;
|
||||
self.velocity+(self.acceleration*dt).map(|elem|elem.divide().fix_1())
|
||||
}
|
||||
pub fn advance_time(&mut self,time:Time<T>){
|
||||
self.position=self.extrapolated_position(time);
|
||||
self.velocity=self.extrapolated_velocity(time);
|
||||
self.time=time;
|
||||
}
|
||||
pub fn extrapolated_position_ratio_dt<Num,Den,N1,D1,N2,N3,D2,N4,T1>(&self,dt:integer::Ratio<Num,Den>)->Planar64Vec3
|
||||
where
|
||||
// Why?
|
||||
// All of this can be removed with const generics because the type can be specified as
|
||||
// Ratio<Fixed<N,NF>,Fixed<D,DF>>
|
||||
// which is known to implement all the necessary traits
|
||||
Num:Copy,
|
||||
Den:Copy+core::ops::Mul<i64,Output=D1>,
|
||||
D1:Copy,
|
||||
Num:core::ops::Mul<Planar64,Output=N1>,
|
||||
Planar64:core::ops::Mul<D1,Output=N2>,
|
||||
N1:core::ops::Add<N2,Output=N3>,
|
||||
Num:core::ops::Mul<N3,Output=N4>,
|
||||
Den:core::ops::Mul<D1,Output=D2>,
|
||||
D2:Copy,
|
||||
Planar64:core::ops::Mul<D2,Output=N4>,
|
||||
N4:integer::Divide<D2,Output=T1>,
|
||||
T1:integer::Fix<Planar64>,
|
||||
{
|
||||
// a*dt^2/2 + v*dt + p
|
||||
// (a*dt/2+v)*dt+p
|
||||
(self.acceleration.map(|elem|dt*elem/2)+self.velocity).map(|elem|dt.mul_ratio(elem))
|
||||
.map(|elem|elem.divide().fix())+self.position
|
||||
}
|
||||
pub fn extrapolated_velocity_ratio_dt<Num,Den,N1,T1>(&self,dt:integer::Ratio<Num,Den>)->Planar64Vec3
|
||||
where
|
||||
Num:Copy,
|
||||
Den:Copy,
|
||||
Num:core::ops::Mul<Planar64,Output=N1>,
|
||||
Planar64:core::ops::Mul<Den,Output=N1>,
|
||||
N1:integer::Divide<Den,Output=T1>,
|
||||
T1:integer::Fix<Planar64>,
|
||||
{
|
||||
// a*dt + v
|
||||
self.acceleration.map(|elem|(dt*elem).divide().fix())+self.velocity
|
||||
}
|
||||
pub fn advance_time_ratio_dt(&mut self,dt:crate::model::GigaTime){
|
||||
self.position=self.extrapolated_position_ratio_dt(dt);
|
||||
self.velocity=self.extrapolated_velocity_ratio_dt(dt);
|
||||
self.time+=dt.into();
|
||||
}
|
||||
pub fn infinity_dir(&self)->Option<Planar64Vec3>{
|
||||
if self.velocity==vec3::ZERO{
|
||||
if self.acceleration==vec3::ZERO{
|
||||
None
|
||||
}else{
|
||||
Some(self.acceleration)
|
||||
}
|
||||
}else{
|
||||
Some(self.velocity)
|
||||
}
|
||||
}
|
||||
pub fn grow_aabb(&self,aabb:&mut aabb::Aabb,t0:Time<T>,t1:Time<T>){
|
||||
aabb.grow(self.extrapolated_position(t0));
|
||||
aabb.grow(self.extrapolated_position(t1));
|
||||
//v+a*t==0
|
||||
//goober code
|
||||
if !self.acceleration.x.is_zero(){
|
||||
let t=-self.velocity.x/self.acceleration.x;
|
||||
if t0.to_ratio().lt_ratio(t)&&t.lt_ratio(t1.to_ratio()){
|
||||
aabb.grow(self.extrapolated_position_ratio_dt(t));
|
||||
}
|
||||
}
|
||||
if !self.acceleration.y.is_zero(){
|
||||
let t=-self.velocity.y/self.acceleration.y;
|
||||
if t0.to_ratio().lt_ratio(t)&&t.lt_ratio(t1.to_ratio()){
|
||||
aabb.grow(self.extrapolated_position_ratio_dt(t));
|
||||
}
|
||||
}
|
||||
if !self.acceleration.z.is_zero(){
|
||||
let t=-self.velocity.z/self.acceleration.z;
|
||||
if t0.to_ratio().lt_ratio(t)&&t.lt_ratio(t1.to_ratio()){
|
||||
aabb.grow(self.extrapolated_position_ratio_dt(t));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
impl<T> std::fmt::Display for Body<T>{
|
||||
fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{
|
||||
write!(f,"p({}) v({}) a({}) t({})",self.position,self.velocity,self.acceleration,self.time)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct VirtualBody<'a,T>{
|
||||
body0:&'a Body<T>,
|
||||
body1:&'a Body<T>,
|
||||
}
|
||||
impl<T> VirtualBody<'_,T>
|
||||
where Time<T>:Copy,
|
||||
{
|
||||
pub fn extrapolated_position(&self,time:Time<T>)->Planar64Vec3{
|
||||
self.body1.extrapolated_position(time)-self.body0.extrapolated_position(time)
|
||||
}
|
||||
pub fn extrapolated_velocity(&self,time:Time<T>)->Planar64Vec3{
|
||||
self.body1.extrapolated_velocity(time)-self.body0.extrapolated_velocity(time)
|
||||
}
|
||||
pub fn acceleration(&self)->Planar64Vec3{
|
||||
self.body1.acceleration-self.body0.acceleration
|
||||
}
|
||||
pub fn body(&self,time:Time<T>)->Body<T>{
|
||||
Body::new(self.extrapolated_position(time),self.extrapolated_velocity(time),self.acceleration(),time)
|
||||
}
|
||||
}
|
148
engine/physics/src/face_crawler.rs
Normal file
148
engine/physics/src/face_crawler.rs
Normal file
@ -0,0 +1,148 @@
|
||||
use crate::model::{GigaTime,FEV,MeshQuery,DirectedEdge};
|
||||
use strafesnet_common::integer::{Fixed,Ratio,vec3::Vector3};
|
||||
use crate::physics::{Time,Body};
|
||||
|
||||
enum Transition<M:MeshQuery>{
|
||||
Miss,
|
||||
Next(FEV<M>,GigaTime),
|
||||
Hit(M::Face,GigaTime),
|
||||
}
|
||||
|
||||
pub enum CrawlResult<M:MeshQuery>{
|
||||
Miss(FEV<M>),
|
||||
Hit(M::Face,GigaTime),
|
||||
}
|
||||
impl<M:MeshQuery> CrawlResult<M>{
|
||||
pub fn hit(self)->Option<(M::Face,GigaTime)>{
|
||||
match self{
|
||||
CrawlResult::Miss(_)=>None,
|
||||
CrawlResult::Hit(face,time)=>Some((face,time)),
|
||||
}
|
||||
}
|
||||
pub fn miss(self)->Option<FEV<M>>{
|
||||
match self{
|
||||
CrawlResult::Miss(fev)=>Some(fev),
|
||||
CrawlResult::Hit(_,_)=>None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<F:Copy,M:MeshQuery<Normal=Vector3<F>,Offset=Fixed<4,128>>> FEV<M>
|
||||
where
|
||||
// This is hardcoded for MinkowskiMesh lol
|
||||
M::Face:Copy,
|
||||
M::Edge:Copy,
|
||||
M::Vert:Copy,
|
||||
F:core::ops::Mul<Fixed<1,32>,Output=Fixed<4,128>>,
|
||||
<F as core::ops::Mul<Fixed<1,32>>>::Output:core::iter::Sum,
|
||||
<M as MeshQuery>::Offset:core::ops::Sub<<F as std::ops::Mul<Fixed<1,32>>>::Output>,
|
||||
{
|
||||
fn next_transition(&self,body_time:GigaTime,mesh:&M,body:&Body,mut best_time:GigaTime)->Transition<M>{
|
||||
//conflicting derivative means it crosses in the wrong direction.
|
||||
//if the transition time is equal to an already tested transition, do not replace the current best.
|
||||
let mut best_transition=Transition::Miss;
|
||||
match self{
|
||||
&FEV::Face(face_id)=>{
|
||||
//test own face collision time, ignoring roots with zero or conflicting derivative
|
||||
//n=face.normal d=face.dot
|
||||
//n.a t^2+n.v t+n.p-d==0
|
||||
let (n,d)=mesh.face_nd(face_id);
|
||||
//TODO: use higher precision d value?
|
||||
//use the mesh transform translation instead of baking it into the d value.
|
||||
for dt in Fixed::<4,128>::zeroes2((n.dot(body.position)-d)*2,n.dot(body.velocity)*2,n.dot(body.acceleration)){
|
||||
if body_time.le_ratio(dt)&&dt.lt_ratio(best_time)&&n.dot(body.extrapolated_velocity_ratio_dt(dt)).is_negative(){
|
||||
best_time=dt;
|
||||
best_transition=Transition::Hit(face_id,dt);
|
||||
break;
|
||||
}
|
||||
}
|
||||
//test each edge collision time, ignoring roots with zero or conflicting derivative
|
||||
for &directed_edge_id in mesh.face_edges(face_id).iter(){
|
||||
let edge_n=mesh.directed_edge_n(directed_edge_id);
|
||||
let n=n.cross(edge_n);
|
||||
let verts=mesh.edge_verts(directed_edge_id.as_undirected());
|
||||
//WARNING: d is moved out of the *2 block because of adding two vertices!
|
||||
//WARNING: precision is swept under the rug!
|
||||
for dt in Fixed::<4,128>::zeroes2(n.dot(body.position*2-(mesh.vert(verts[0])+mesh.vert(verts[1]))).fix_4(),n.dot(body.velocity).fix_4()*2,n.dot(body.acceleration).fix_4()){
|
||||
if body_time.le_ratio(dt)&&dt.lt_ratio(best_time)&&n.dot(body.extrapolated_velocity_ratio_dt(dt)).is_negative(){
|
||||
best_time=dt;
|
||||
best_transition=Transition::Next(FEV::Edge(directed_edge_id.as_undirected()),dt);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
//if none:
|
||||
},
|
||||
&FEV::Edge(edge_id)=>{
|
||||
//test each face collision time, ignoring roots with zero or conflicting derivative
|
||||
let edge_n=mesh.edge_n(edge_id);
|
||||
let edge_verts=mesh.edge_verts(edge_id);
|
||||
let delta_pos=body.position*2-(mesh.vert(edge_verts[0])+mesh.vert(edge_verts[1]));
|
||||
for (i,&edge_face_id) in mesh.edge_faces(edge_id).iter().enumerate(){
|
||||
let face_n=mesh.face_nd(edge_face_id).0;
|
||||
//edge_n gets parity from the order of edge_faces
|
||||
let n=face_n.cross(edge_n)*((i as i64)*2-1);
|
||||
//WARNING yada yada d *2
|
||||
for dt in Fixed::<4,128>::zeroes2(n.dot(delta_pos).fix_4(),n.dot(body.velocity).fix_4()*2,n.dot(body.acceleration).fix_4()){
|
||||
if body_time.le_ratio(dt)&&dt.lt_ratio(best_time)&&n.dot(body.extrapolated_velocity_ratio_dt(dt)).is_negative(){
|
||||
best_time=dt;
|
||||
best_transition=Transition::Next(FEV::Face(edge_face_id),dt);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
//test each vertex collision time, ignoring roots with zero or conflicting derivative
|
||||
for (i,&vert_id) in edge_verts.iter().enumerate(){
|
||||
//vertex normal gets parity from vert index
|
||||
let n=edge_n*(1-2*(i as i64));
|
||||
for dt in Fixed::<2,64>::zeroes2((n.dot(body.position-mesh.vert(vert_id)))*2,n.dot(body.velocity)*2,n.dot(body.acceleration)){
|
||||
if body_time.le_ratio(dt)&&dt.lt_ratio(best_time)&&n.dot(body.extrapolated_velocity_ratio_dt(dt)).is_negative(){
|
||||
let dt=Ratio::new(dt.num.fix_4(),dt.den.fix_4());
|
||||
best_time=dt;
|
||||
best_transition=Transition::Next(FEV::Vert(vert_id),dt);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
//if none:
|
||||
},
|
||||
&FEV::Vert(vert_id)=>{
|
||||
//test each edge collision time, ignoring roots with zero or conflicting derivative
|
||||
for &directed_edge_id in mesh.vert_edges(vert_id).iter(){
|
||||
//edge is directed away from vertex, but we want the dot product to turn out negative
|
||||
let n=-mesh.directed_edge_n(directed_edge_id);
|
||||
for dt in Fixed::<2,64>::zeroes2((n.dot(body.position-mesh.vert(vert_id)))*2,n.dot(body.velocity)*2,n.dot(body.acceleration)){
|
||||
if body_time.le_ratio(dt)&&dt.lt_ratio(best_time)&&n.dot(body.extrapolated_velocity_ratio_dt(dt)).is_negative(){
|
||||
let dt=Ratio::new(dt.num.fix_4(),dt.den.fix_4());
|
||||
best_time=dt;
|
||||
best_transition=Transition::Next(FEV::Edge(directed_edge_id.as_undirected()),dt);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
//if none:
|
||||
},
|
||||
}
|
||||
best_transition
|
||||
}
|
||||
pub fn crawl(mut self,mesh:&M,relative_body:&Body,start_time:Time,time_limit:Time)->CrawlResult<M>{
|
||||
let mut body_time={
|
||||
let r=(start_time-relative_body.time).to_ratio();
|
||||
Ratio::new(r.num.fix_4(),r.den.fix_4())
|
||||
};
|
||||
let time_limit={
|
||||
let r=(time_limit-relative_body.time).to_ratio();
|
||||
Ratio::new(r.num.fix_4(),r.den.fix_4())
|
||||
};
|
||||
for _ in 0..20{
|
||||
match self.next_transition(body_time,mesh,relative_body,time_limit){
|
||||
Transition::Miss=>return CrawlResult::Miss(self),
|
||||
Transition::Next(next_fev,next_time)=>(self,body_time)=(next_fev,next_time),
|
||||
Transition::Hit(face,time)=>return CrawlResult::Hit(face,time),
|
||||
}
|
||||
}
|
||||
//TODO: fix all bugs
|
||||
//println!("Too many iterations! Using default behaviour instead of crashing...");
|
||||
CrawlResult::Miss(self)
|
||||
}
|
||||
}
|
45
engine/physics/src/lib.rs
Normal file
45
engine/physics/src/lib.rs
Normal file
@ -0,0 +1,45 @@
|
||||
mod body;
|
||||
mod push_solve;
|
||||
mod face_crawler;
|
||||
mod model;
|
||||
|
||||
pub mod physics;
|
||||
|
||||
// Physics bug fixes can easily desync all bots.
|
||||
//
|
||||
// When replaying a bot, use the exact physics version which it was recorded with.
|
||||
//
|
||||
// When validating a new bot, ignore the version and use the latest version,
|
||||
// and overwrite the version in the file.
|
||||
//
|
||||
// Compatible physics versions should be determined
|
||||
// empirically at development time via leaderboard resimulation.
|
||||
//
|
||||
// Compatible physics versions should result in an identical leaderboard state,
|
||||
// or the only bots which fail are ones exploiting a surgically patched bug.
|
||||
#[derive(Clone,Copy,Hash,Debug,id::Id,Eq,PartialEq,Ord,PartialOrd)]
|
||||
pub struct PhysicsVersion(u32);
|
||||
pub const VERSION:PhysicsVersion=PhysicsVersion(0);
|
||||
const LATEST_COMPATIBLE_VERSION:[u32;1+VERSION.0 as usize]=const{
|
||||
let compat=[0];
|
||||
|
||||
let mut input_version=0;
|
||||
while input_version<compat.len(){
|
||||
// compatible version must be greater than or equal to the input version
|
||||
assert!(input_version as u32<=compat[input_version]);
|
||||
// compatible version must be a version that exists
|
||||
assert!(compat[input_version]<=VERSION.0);
|
||||
input_version+=1;
|
||||
}
|
||||
compat
|
||||
};
|
||||
pub enum PhysicsVersionError{
|
||||
UnknownPhysicsVersion,
|
||||
}
|
||||
pub const fn get_latest_compatible_version(PhysicsVersion(version):PhysicsVersion)->Result<PhysicsVersion,PhysicsVersionError>{
|
||||
if (version as usize)<LATEST_COMPATIBLE_VERSION.len(){
|
||||
Ok(PhysicsVersion(LATEST_COMPATIBLE_VERSION[version as usize]))
|
||||
}else{
|
||||
Err(PhysicsVersionError::UnknownPhysicsVersion)
|
||||
}
|
||||
}
|
1007
engine/physics/src/model.rs
Normal file
1007
engine/physics/src/model.rs
Normal file
File diff suppressed because it is too large
Load Diff
2083
engine/physics/src/physics.rs
Normal file
2083
engine/physics/src/physics.rs
Normal file
File diff suppressed because it is too large
Load Diff
349
engine/physics/src/push_solve.rs
Normal file
349
engine/physics/src/push_solve.rs
Normal file
@ -0,0 +1,349 @@
|
||||
use strafesnet_common::integer::{self,vec3::{self,Vector3},Fixed,Planar64,Planar64Vec3,Ratio};
|
||||
|
||||
// This algorithm is based on Lua code
|
||||
// written by Trey Reynolds in 2021
|
||||
|
||||
// EPSILON=1/2^10
|
||||
|
||||
// A stack-allocated variable-size list that holds up to 4 elements
|
||||
// Direct references are used instead of indices i0, i1, i2, i3
|
||||
type Conts<'a>=arrayvec::ArrayVec<&'a Contact,4>;
|
||||
|
||||
// hack to allow comparing ratios to zero
|
||||
const RATIO_ZERO:Ratio<Fixed<1,32>,Fixed<1,32>>=Ratio::new(Fixed::ZERO,Fixed::EPSILON);
|
||||
|
||||
struct Ray{
|
||||
origin:Planar64Vec3,
|
||||
direction:Planar64Vec3,
|
||||
}
|
||||
impl Ray{
|
||||
fn extrapolate<Num,Den,N1,T1>(&self,t:Ratio<Num,Den>)->Planar64Vec3
|
||||
where
|
||||
Num:Copy,
|
||||
Den:Copy,
|
||||
Num:core::ops::Mul<Planar64,Output=N1>,
|
||||
Planar64:core::ops::Mul<Den,Output=N1>,
|
||||
N1:integer::Divide<Den,Output=T1>,
|
||||
T1:integer::Fix<Planar64>,
|
||||
{
|
||||
self.origin+self.direction.map(|elem|(t*elem).divide().fix())
|
||||
}
|
||||
}
|
||||
|
||||
/// Information about a contact restriction
|
||||
pub struct Contact{
|
||||
pub position:Planar64Vec3,
|
||||
pub velocity:Planar64Vec3,
|
||||
pub normal:Planar64Vec3,
|
||||
}
|
||||
impl Contact{
|
||||
fn relative_to(&self,point:Planar64Vec3)->Self{
|
||||
Self{
|
||||
position:self.position-point,
|
||||
velocity:self.velocity,
|
||||
normal:self.normal,
|
||||
}
|
||||
}
|
||||
fn relative_dot(&self,direction:Planar64Vec3)->Fixed<2,64>{
|
||||
(direction-self.velocity).dot(self.normal)
|
||||
}
|
||||
/// Calculate the time of intersection. (previously get_touch_time)
|
||||
fn solve(&self,ray:&Ray)->Ratio<Fixed<2,64>,Fixed<2,64>>{
|
||||
(self.position-ray.origin).dot(self.normal)/(ray.direction-self.velocity).dot(self.normal)
|
||||
}
|
||||
}
|
||||
|
||||
//note that this is horrible with fixed point arithmetic
|
||||
fn solve1(c0:&Contact)->Option<Ratio<Vector3<Fixed<3,96>>,Fixed<2,64>>>{
|
||||
const EPSILON:Fixed<2,64>=Fixed::from_bits(Fixed::<2,64>::ONE.to_bits().shr(10));
|
||||
let det=c0.normal.dot(c0.velocity);
|
||||
if det.abs()<EPSILON{
|
||||
return None;
|
||||
}
|
||||
let d0=c0.normal.dot(c0.position);
|
||||
Some(c0.normal*d0/det)
|
||||
}
|
||||
fn solve2(c0:&Contact,c1:&Contact)->Option<Ratio<Vector3<Fixed<5,160>>,Fixed<4,128>>>{
|
||||
const EPSILON:Fixed<4,128>=Fixed::from_bits(Fixed::<4,128>::ONE.to_bits().shr(10));
|
||||
let u0_u1=c0.velocity.cross(c1.velocity);
|
||||
let n0_n1=c0.normal.cross(c1.normal);
|
||||
let det=u0_u1.dot(n0_n1);
|
||||
if det.abs()<EPSILON{
|
||||
return None;
|
||||
}
|
||||
let d0=c0.normal.dot(c0.position);
|
||||
let d1=c1.normal.dot(c1.position);
|
||||
Some((c1.normal.cross(u0_u1)*d0+u0_u1.cross(c0.normal)*d1)/det)
|
||||
}
|
||||
fn solve3(c0:&Contact,c1:&Contact,c2:&Contact)->Option<Ratio<Vector3<Fixed<4,128>>,Fixed<3,96>>>{
|
||||
const EPSILON:Fixed<3,96>=Fixed::from_bits(Fixed::<3,96>::ONE.to_bits().shr(10));
|
||||
let n0_n1=c0.normal.cross(c1.normal);
|
||||
let det=c2.normal.dot(n0_n1);
|
||||
if det.abs()<EPSILON{
|
||||
return None;
|
||||
}
|
||||
let d0=c0.normal.dot(c0.position);
|
||||
let d1=c1.normal.dot(c1.position);
|
||||
let d2=c2.normal.dot(c2.position);
|
||||
Some((c1.normal.cross(c2.normal)*d0+c2.normal.cross(c0.normal)*d1+c0.normal.cross(c1.normal)*d2)/det)
|
||||
}
|
||||
|
||||
fn decompose1(point:Planar64Vec3,u0:Planar64Vec3)->Option<[Ratio<Fixed<2,64>,Fixed<2,64>>;1]>{
|
||||
let det=u0.dot(u0);
|
||||
if det==Fixed::ZERO{
|
||||
return None;
|
||||
}
|
||||
let s0=u0.dot(point)/det;
|
||||
Some([s0])
|
||||
}
|
||||
fn decompose2(point:Planar64Vec3,u0:Planar64Vec3,u1:Planar64Vec3)->Option<[Ratio<Fixed<4,128>,Fixed<4,128>>;2]>{
|
||||
let u0_u1=u0.cross(u1);
|
||||
let det=u0_u1.dot(u0_u1);
|
||||
if det==Fixed::ZERO{
|
||||
return None;
|
||||
}
|
||||
let s0=u0_u1.dot(point.cross(u1))/det;
|
||||
let s1=u0_u1.dot(u0.cross(point))/det;
|
||||
Some([s0,s1])
|
||||
}
|
||||
fn decompose3(point:Planar64Vec3,u0:Planar64Vec3,u1:Planar64Vec3,u2:Planar64Vec3)->Option<[Ratio<Fixed<3,96>,Fixed<3,96>>;3]>{
|
||||
let det=u0.cross(u1).dot(u2);
|
||||
if det==Fixed::ZERO{
|
||||
return None;
|
||||
}
|
||||
let s0=point.cross(u1).dot(u2)/det;
|
||||
let s1=u0.cross(point).dot(u2)/det;
|
||||
let s2=u0.cross(u1).dot(point)/det;
|
||||
Some([s0,s1,s2])
|
||||
}
|
||||
|
||||
fn is_space_enclosed_2(
|
||||
a:Planar64Vec3,
|
||||
b:Planar64Vec3,
|
||||
)->bool{
|
||||
a.cross(b)==Vector3::new([Fixed::ZERO;3])
|
||||
&&a.dot(b).is_negative()
|
||||
}
|
||||
fn is_space_enclosed_3(
|
||||
a:Planar64Vec3,
|
||||
b:Planar64Vec3,
|
||||
c:Planar64Vec3
|
||||
)->bool{
|
||||
a.cross(b).dot(c)==Fixed::ZERO
|
||||
&&{
|
||||
let det_abac=a.cross(b).dot(a.cross(c));
|
||||
let det_abbc=a.cross(b).dot(b.cross(c));
|
||||
let det_acbc=a.cross(c).dot(b.cross(c));
|
||||
return!( det_abac*det_abbc).is_positive()
|
||||
&&!( det_abbc*det_acbc).is_positive()
|
||||
&&!(-det_acbc*det_abac).is_positive()
|
||||
||is_space_enclosed_2(a,b)
|
||||
||is_space_enclosed_2(a,c)
|
||||
||is_space_enclosed_2(b,c)
|
||||
}
|
||||
}
|
||||
fn is_space_enclosed_4(
|
||||
a:Planar64Vec3,
|
||||
b:Planar64Vec3,
|
||||
c:Planar64Vec3,
|
||||
d:Planar64Vec3,
|
||||
)->bool{
|
||||
let det_abc=a.cross(b).dot(c);
|
||||
let det_abd=a.cross(b).dot(d);
|
||||
let det_acd=a.cross(c).dot(d);
|
||||
let det_bcd=b.cross(c).dot(d);
|
||||
return( det_abc*det_abd).is_negative()
|
||||
&&(-det_abc*det_acd).is_negative()
|
||||
&&( det_abd*det_acd).is_negative()
|
||||
&&( det_abc*det_bcd).is_negative()
|
||||
&&(-det_abd*det_bcd).is_negative()
|
||||
&&( det_acd*det_bcd).is_negative()
|
||||
||is_space_enclosed_3(a,b,c)
|
||||
||is_space_enclosed_3(a,b,d)
|
||||
||is_space_enclosed_3(a,c,d)
|
||||
||is_space_enclosed_3(b,c,d)
|
||||
}
|
||||
|
||||
const fn get_push_ray_0(point:Planar64Vec3)->Ray{
|
||||
Ray{origin:point,direction:vec3::ZERO}
|
||||
}
|
||||
fn get_push_ray_1(point:Planar64Vec3,c0:&Contact)->Option<Ray>{
|
||||
let direction=solve1(c0)?.divide().fix_1();
|
||||
let [s0]=decompose1(direction,c0.velocity)?;
|
||||
if s0.lt_ratio(RATIO_ZERO){
|
||||
return None;
|
||||
}
|
||||
let origin=point+solve1(
|
||||
&c0.relative_to(point),
|
||||
)?.divide().fix_1();
|
||||
Some(Ray{origin,direction})
|
||||
}
|
||||
fn get_push_ray_2(point:Planar64Vec3,c0:&Contact,c1:&Contact)->Option<Ray>{
|
||||
let direction=solve2(c0,c1)?.divide().fix_1();
|
||||
let [s0,s1]=decompose2(direction,c0.velocity,c1.velocity)?;
|
||||
if s0.lt_ratio(RATIO_ZERO)||s1.lt_ratio(RATIO_ZERO){
|
||||
return None;
|
||||
}
|
||||
let origin=point+solve2(
|
||||
&c0.relative_to(point),
|
||||
&c1.relative_to(point),
|
||||
)?.divide().fix_1();
|
||||
Some(Ray{origin,direction})
|
||||
}
|
||||
fn get_push_ray_3(point:Planar64Vec3,c0:&Contact,c1:&Contact,c2:&Contact)->Option<Ray>{
|
||||
let direction=solve3(c0,c1,c2)?.divide().fix_1();
|
||||
let [s0,s1,s2]=decompose3(direction,c0.velocity,c1.velocity,c2.velocity)?;
|
||||
if s0.lt_ratio(RATIO_ZERO)||s1.lt_ratio(RATIO_ZERO)||s2.lt_ratio(RATIO_ZERO){
|
||||
return None;
|
||||
}
|
||||
let origin=point+solve3(
|
||||
&c0.relative_to(point),
|
||||
&c1.relative_to(point),
|
||||
&c2.relative_to(point),
|
||||
)?.divide().fix_1();
|
||||
Some(Ray{origin,direction})
|
||||
}
|
||||
|
||||
const fn get_best_push_ray_and_conts_0<'a>(point:Planar64Vec3)->(Ray,Conts<'a>){
|
||||
(get_push_ray_0(point),Conts::new_const())
|
||||
}
|
||||
fn get_best_push_ray_and_conts_1(point:Planar64Vec3,c0:&Contact)->Option<(Ray,Conts)>{
|
||||
get_push_ray_1(point,c0)
|
||||
.map(|ray|(ray,Conts::from_iter([c0])))
|
||||
}
|
||||
fn get_best_push_ray_and_conts_2<'a>(point:Planar64Vec3,c0:&'a Contact,c1:&'a Contact)->Option<(Ray,Conts<'a>)>{
|
||||
if is_space_enclosed_2(c0.normal,c1.normal){
|
||||
return None;
|
||||
}
|
||||
if let Some(ray)=get_push_ray_2(point,c0,c1){
|
||||
return Some((ray,Conts::from_iter([c0,c1])));
|
||||
}
|
||||
if let Some(ray)=get_push_ray_1(point,c0){
|
||||
if !c1.relative_dot(ray.direction).is_negative(){
|
||||
return Some((ray,Conts::from_iter([c0])));
|
||||
}
|
||||
}
|
||||
return None;
|
||||
}
|
||||
fn get_best_push_ray_and_conts_3<'a>(point:Planar64Vec3,c0:&'a Contact,c1:&'a Contact,c2:&'a Contact)->Option<(Ray,Conts<'a>)>{
|
||||
if is_space_enclosed_3(c0.normal,c1.normal,c2.normal){
|
||||
return None;
|
||||
}
|
||||
if let Some(ray)=get_push_ray_3(point,c0,c1,c2){
|
||||
return Some((ray,Conts::from_iter([c0,c1,c2])));
|
||||
}
|
||||
if let Some(ray)=get_push_ray_2(point,c0,c1){
|
||||
if !c2.relative_dot(ray.direction).is_negative(){
|
||||
return Some((ray,Conts::from_iter([c0,c1])));
|
||||
}
|
||||
}
|
||||
if let Some(ray)=get_push_ray_2(point,c0,c2){
|
||||
if !c1.relative_dot(ray.direction).is_negative(){
|
||||
return Some((ray,Conts::from_iter([c0,c2])));
|
||||
}
|
||||
}
|
||||
if let Some(ray)=get_push_ray_1(point,c0){
|
||||
if !c1.relative_dot(ray.direction).is_negative()
|
||||
&&!c2.relative_dot(ray.direction).is_negative(){
|
||||
return Some((ray,Conts::from_iter([c0])));
|
||||
}
|
||||
}
|
||||
return None;
|
||||
}
|
||||
fn get_best_push_ray_and_conts_4<'a>(point:Planar64Vec3,c0:&'a Contact,c1:&'a Contact,c2:&'a Contact,c3:&'a Contact)->Option<(Ray,Conts<'a>)>{
|
||||
if is_space_enclosed_4(c0.normal,c1.normal,c2.normal,c3.normal){
|
||||
return None;
|
||||
}
|
||||
|
||||
let (ray012,conts012)=get_best_push_ray_and_conts_3(point,c0,c1,c2)?;
|
||||
let (ray013,conts013)=get_best_push_ray_and_conts_3(point,c0,c1,c3)?;
|
||||
let (ray023,conts023)=get_best_push_ray_and_conts_3(point,c0,c2,c3)?;
|
||||
|
||||
let err012=c3.relative_dot(ray012.direction);
|
||||
let err013=c2.relative_dot(ray013.direction);
|
||||
let err023=c1.relative_dot(ray023.direction);
|
||||
|
||||
let best_err=err012.max(err013).max(err023);
|
||||
|
||||
if best_err==err012{
|
||||
return Some((ray012,conts012))
|
||||
}else if best_err==err013{
|
||||
return Some((ray013,conts013))
|
||||
}else if best_err==err023{
|
||||
return Some((ray023,conts023))
|
||||
}
|
||||
unreachable!()
|
||||
}
|
||||
|
||||
fn get_best_push_ray_and_conts<'a>(
|
||||
point:Planar64Vec3,
|
||||
conts:&[&'a Contact],
|
||||
)->Option<(Ray,Conts<'a>)>{
|
||||
match conts{
|
||||
&[c0,c1,c2,c3]=>get_best_push_ray_and_conts_4(point,c0,c1,c2,c3),
|
||||
&[c0,c1,c2]=>get_best_push_ray_and_conts_3(point,c0,c1,c2),
|
||||
&[c0,c1]=>get_best_push_ray_and_conts_2(point,c0,c1),
|
||||
&[c0]=>get_best_push_ray_and_conts_1(point,c0),
|
||||
&[]=>Some(get_best_push_ray_and_conts_0(point)),
|
||||
_=>unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn get_first_touch<'a>(contacts:&'a [Contact],ray:&Ray,conts:&Conts)->Option<(Ratio<Fixed<2,64>,Fixed<2,64>>,&'a Contact)>{
|
||||
contacts.iter()
|
||||
.filter(|&contact|
|
||||
!conts.iter().any(|&c|std::ptr::eq(c,contact))
|
||||
&&contact.relative_dot(ray.direction).is_negative()
|
||||
)
|
||||
.map(|contact|(contact.solve(ray),contact))
|
||||
.min_by_key(|&(t,_)|t)
|
||||
}
|
||||
|
||||
pub fn push_solve(contacts:&[Contact],point:Planar64Vec3)->Planar64Vec3{
|
||||
let (mut ray,mut conts)=get_best_push_ray_and_conts_0(point);
|
||||
loop{
|
||||
let (next_t,next_cont)=match get_first_touch(contacts,&ray,&conts){
|
||||
Some((t,cont))=>(t,cont),
|
||||
None=>return ray.origin,
|
||||
};
|
||||
|
||||
if RATIO_ZERO.le_ratio(next_t){
|
||||
return ray.origin;
|
||||
}
|
||||
|
||||
//push_front
|
||||
if conts.len()==conts.capacity(){
|
||||
//this is a dead case, new_conts never has more than 3 elements
|
||||
conts.rotate_right(1);
|
||||
conts[0]=next_cont;
|
||||
}else{
|
||||
conts.push(next_cont);
|
||||
conts.rotate_right(1);
|
||||
}
|
||||
|
||||
let meet_point=ray.extrapolate(next_t);
|
||||
match get_best_push_ray_and_conts(meet_point,conts.as_slice()){
|
||||
Some((new_ray,new_conts))=>(ray,conts)=(new_ray,new_conts),
|
||||
None=>return meet_point,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests{
|
||||
use super::*;
|
||||
#[test]
|
||||
fn test_push_solve(){
|
||||
let contacts=vec![
|
||||
Contact{
|
||||
position:vec3::ZERO,
|
||||
velocity:vec3::Y,
|
||||
normal:vec3::Y,
|
||||
}
|
||||
];
|
||||
assert_eq!(
|
||||
vec3::ZERO,
|
||||
push_solve(&contacts,vec3::NEG_Y)
|
||||
);
|
||||
}
|
||||
}
|
12
engine/session/Cargo.toml
Normal file
12
engine/session/Cargo.toml
Normal file
@ -0,0 +1,12 @@
|
||||
[package]
|
||||
name = "strafesnet_session"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
glam = "0.29.0"
|
||||
replace_with = "0.1.7"
|
||||
strafesnet_common = { path = "../../lib/common", registry = "strafesnet" }
|
||||
strafesnet_physics = { path = "../physics", registry = "strafesnet" }
|
||||
strafesnet_settings = { path = "../settings", registry = "strafesnet" }
|
||||
strafesnet_snf = { path = "../../lib/snf", registry = "strafesnet" }
|
8
engine/session/LICENSE
Normal file
8
engine/session/LICENSE
Normal file
@ -0,0 +1,8 @@
|
||||
/*******************************************************
|
||||
* Copyright (C) 2023-2024 Rhys Lloyd <krakow20@gmail.com>
|
||||
*
|
||||
* This file is part of the StrafesNET bhop/surf client.
|
||||
*
|
||||
* StrafesNET can not be copied and/or distributed
|
||||
* without the express permission of Rhys Lloyd
|
||||
*******************************************************/
|
2
engine/session/src/lib.rs
Normal file
2
engine/session/src/lib.rs
Normal file
@ -0,0 +1,2 @@
|
||||
mod mouse_interpolator;
|
||||
pub mod session;
|
281
engine/session/src/mouse_interpolator.rs
Normal file
281
engine/session/src/mouse_interpolator.rs
Normal file
@ -0,0 +1,281 @@
|
||||
use strafesnet_common::mouse::MouseState;
|
||||
use strafesnet_common::physics::{
|
||||
MouseInstruction,SetControlInstruction,ModeInstruction,MiscInstruction,
|
||||
Instruction as PhysicsInstruction,
|
||||
TimeInner as PhysicsTimeInner,
|
||||
Time as PhysicsTime,
|
||||
};
|
||||
use strafesnet_common::session::{Time as SessionTime,TimeInner as SessionTimeInner};
|
||||
use strafesnet_common::instruction::{InstructionConsumer,InstructionEmitter,TimedInstruction};
|
||||
|
||||
type TimedSelfInstruction=TimedInstruction<Instruction,PhysicsTimeInner>;
|
||||
type DoubleTimedSelfInstruction=TimedInstruction<TimedSelfInstruction,SessionTimeInner>;
|
||||
|
||||
type TimedPhysicsInstruction=TimedInstruction<PhysicsInstruction,PhysicsTimeInner>;
|
||||
|
||||
const MOUSE_TIMEOUT:SessionTime=SessionTime::from_millis(10);
|
||||
|
||||
/// To be fed into MouseInterpolator
|
||||
#[derive(Clone,Debug)]
|
||||
pub(crate) enum Instruction{
|
||||
MoveMouse(glam::IVec2),
|
||||
SetControl(SetControlInstruction),
|
||||
Mode(ModeInstruction),
|
||||
Misc(MiscInstruction),
|
||||
Idle,
|
||||
}
|
||||
#[derive(Clone,Debug)]
|
||||
enum UnbufferedInstruction{
|
||||
MoveMouse(glam::IVec2),
|
||||
NonMouse(NonMouseInstruction),
|
||||
}
|
||||
#[derive(Clone,Debug)]
|
||||
enum BufferedInstruction{
|
||||
Mouse(MouseInstruction),
|
||||
NonMouse(NonMouseInstruction),
|
||||
}
|
||||
#[derive(Clone,Debug)]
|
||||
pub(crate) enum NonMouseInstruction{
|
||||
SetControl(SetControlInstruction),
|
||||
Mode(ModeInstruction),
|
||||
Misc(MiscInstruction),
|
||||
Idle,
|
||||
}
|
||||
impl From<Instruction> for UnbufferedInstruction{
|
||||
#[inline]
|
||||
fn from(value:Instruction)->Self{
|
||||
match value{
|
||||
Instruction::MoveMouse(mouse_instruction)=>UnbufferedInstruction::MoveMouse(mouse_instruction),
|
||||
Instruction::SetControl(set_control_instruction)=>UnbufferedInstruction::NonMouse(NonMouseInstruction::SetControl(set_control_instruction)),
|
||||
Instruction::Mode(mode_instruction)=>UnbufferedInstruction::NonMouse(NonMouseInstruction::Mode(mode_instruction)),
|
||||
Instruction::Misc(misc_instruction)=>UnbufferedInstruction::NonMouse(NonMouseInstruction::Misc(misc_instruction)),
|
||||
Instruction::Idle=>UnbufferedInstruction::NonMouse(NonMouseInstruction::Idle),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl From<BufferedInstruction> for PhysicsInstruction{
|
||||
#[inline]
|
||||
fn from(value:BufferedInstruction)->Self{
|
||||
match value{
|
||||
BufferedInstruction::Mouse(mouse_instruction)=>PhysicsInstruction::Mouse(mouse_instruction),
|
||||
BufferedInstruction::NonMouse(non_mouse_instruction)=>match non_mouse_instruction{
|
||||
NonMouseInstruction::SetControl(set_control_instruction)=>PhysicsInstruction::SetControl(set_control_instruction),
|
||||
NonMouseInstruction::Mode(mode_instruction)=>PhysicsInstruction::Mode(mode_instruction),
|
||||
NonMouseInstruction::Misc(misc_instruction)=>PhysicsInstruction::Misc(misc_instruction),
|
||||
NonMouseInstruction::Idle=>PhysicsInstruction::Idle,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) enum StepInstruction{
|
||||
Pop,
|
||||
Timeout,
|
||||
}
|
||||
|
||||
#[derive(Clone,Debug)]
|
||||
enum BufferState{
|
||||
Unbuffered,
|
||||
Initializing(SessionTime,MouseState<PhysicsTimeInner>),
|
||||
Buffered(SessionTime,MouseState<PhysicsTimeInner>),
|
||||
}
|
||||
|
||||
pub struct MouseInterpolator{
|
||||
buffer_state:BufferState,
|
||||
// double timestamped timeline?
|
||||
buffer:std::collections::VecDeque<TimedPhysicsInstruction>,
|
||||
output:std::collections::VecDeque<TimedPhysicsInstruction>,
|
||||
}
|
||||
// Maybe MouseInterpolator manipulation is better expressed using impls
|
||||
// and called from Instruction trait impls in session
|
||||
impl InstructionConsumer<TimedSelfInstruction> for MouseInterpolator{
|
||||
type TimeInner=SessionTimeInner;
|
||||
fn process_instruction(&mut self,ins:DoubleTimedSelfInstruction){
|
||||
self.push_unbuffered_input(ins.time,ins.instruction.time,ins.instruction.instruction.into())
|
||||
}
|
||||
}
|
||||
impl InstructionEmitter<StepInstruction> for MouseInterpolator{
|
||||
type TimeInner=SessionTimeInner;
|
||||
fn next_instruction(&self,time_limit:SessionTime)->Option<TimedInstruction<StepInstruction,Self::TimeInner>>{
|
||||
self.buffered_instruction_with_timeout(time_limit)
|
||||
}
|
||||
}
|
||||
impl MouseInterpolator{
|
||||
pub fn new()->MouseInterpolator{
|
||||
MouseInterpolator{
|
||||
buffer_state:BufferState::Unbuffered,
|
||||
buffer:std::collections::VecDeque::new(),
|
||||
output:std::collections::VecDeque::new(),
|
||||
}
|
||||
}
|
||||
fn push_mouse_and_flush_buffer(&mut self,ins:TimedInstruction<MouseInstruction,PhysicsTimeInner>){
|
||||
self.buffer.push_front(TimedInstruction{
|
||||
time:ins.time,
|
||||
instruction:BufferedInstruction::Mouse(ins.instruction).into(),
|
||||
});
|
||||
// flush buffer to output
|
||||
if self.output.len()==0{
|
||||
// swap buffers
|
||||
core::mem::swap(&mut self.buffer,&mut self.output);
|
||||
}else{
|
||||
// append buffer contents to output
|
||||
self.output.append(&mut self.buffer);
|
||||
}
|
||||
}
|
||||
fn get_mouse_timedout_at(&self,time_limit:SessionTime)->Option<SessionTime>{
|
||||
match &self.buffer_state{
|
||||
BufferState::Unbuffered=>None,
|
||||
BufferState::Initializing(time,_mouse_state)
|
||||
|BufferState::Buffered(time,_mouse_state)=>{
|
||||
let timeout=*time+MOUSE_TIMEOUT;
|
||||
(timeout<time_limit).then_some(timeout)
|
||||
}
|
||||
}
|
||||
}
|
||||
fn timeout_mouse(&mut self,timeout_time:PhysicsTime){
|
||||
// the state always changes to unbuffered
|
||||
let buffer_state=core::mem::replace(&mut self.buffer_state,BufferState::Unbuffered);
|
||||
match buffer_state{
|
||||
BufferState::Unbuffered=>(),
|
||||
BufferState::Initializing(_time,mouse_state)=>{
|
||||
// only a single mouse move was sent in 10ms, this is very much an edge case!
|
||||
self.push_mouse_and_flush_buffer(TimedInstruction{
|
||||
time:mouse_state.time,
|
||||
instruction:MouseInstruction::ReplaceMouse{
|
||||
m1:MouseState{pos:mouse_state.pos,time:timeout_time},
|
||||
m0:mouse_state,
|
||||
},
|
||||
});
|
||||
}
|
||||
BufferState::Buffered(_time,mouse_state)=>{
|
||||
// duplicate the currently buffered mouse state but at a later (future, from the physics perspective) time
|
||||
self.push_mouse_and_flush_buffer(TimedInstruction{
|
||||
time:mouse_state.time,
|
||||
instruction:MouseInstruction::SetNextMouse(MouseState{pos:mouse_state.pos,time:timeout_time}),
|
||||
});
|
||||
},
|
||||
}
|
||||
}
|
||||
fn push_unbuffered_input(&mut self,session_time:SessionTime,physics_time:PhysicsTime,ins:UnbufferedInstruction){
|
||||
// new input
|
||||
// if there is zero instruction buffered, it means the mouse is not moving
|
||||
// case 1: unbuffered
|
||||
// no mouse event is buffered
|
||||
// - ins is mouse event? change to buffered
|
||||
// - ins other -> write to timeline
|
||||
// case 2: buffered
|
||||
// a mouse event is buffered, and exists within the last 10ms
|
||||
// case 3: stop
|
||||
// a mouse event is buffered, but no mouse events have transpired within 10ms
|
||||
|
||||
// replace_with allows the enum variant to safely be replaced
|
||||
// from behind a mutable reference, but a panic in the closure means that
|
||||
// the entire program terminates rather than completing an unwind.
|
||||
let (ins_mouse,ins_other)=replace_with::replace_with_or_abort_and_return(&mut self.buffer_state,|buffer_state|{
|
||||
match ins{
|
||||
UnbufferedInstruction::MoveMouse(pos)=>{
|
||||
let next_mouse_state=MouseState{pos,time:physics_time};
|
||||
match buffer_state{
|
||||
BufferState::Unbuffered=>{
|
||||
((None,None),BufferState::Initializing(session_time,next_mouse_state))
|
||||
},
|
||||
BufferState::Initializing(_time,mouse_state)=>{
|
||||
let ins_mouse=TimedInstruction{
|
||||
time:mouse_state.time,
|
||||
instruction:MouseInstruction::ReplaceMouse{
|
||||
m0:mouse_state,
|
||||
m1:next_mouse_state.clone(),
|
||||
},
|
||||
};
|
||||
((Some(ins_mouse),None),BufferState::Buffered(session_time,next_mouse_state))
|
||||
},
|
||||
BufferState::Buffered(_time,mouse_state)=>{
|
||||
let ins_mouse=TimedInstruction{
|
||||
time:mouse_state.time,
|
||||
instruction:MouseInstruction::SetNextMouse(next_mouse_state.clone()),
|
||||
};
|
||||
((Some(ins_mouse),None),BufferState::Buffered(session_time,next_mouse_state))
|
||||
},
|
||||
}
|
||||
},
|
||||
UnbufferedInstruction::NonMouse(other_instruction)=>((None,Some(TimedInstruction{
|
||||
time:physics_time,
|
||||
instruction:other_instruction,
|
||||
})),buffer_state),
|
||||
}
|
||||
});
|
||||
if let Some(ins)=ins_mouse{
|
||||
self.push_mouse_and_flush_buffer(ins);
|
||||
}
|
||||
if let Some(ins)=ins_other{
|
||||
let instruction=TimedInstruction{
|
||||
time:ins.time,
|
||||
instruction:BufferedInstruction::NonMouse(ins.instruction).into(),
|
||||
};
|
||||
if matches!(self.buffer_state,BufferState::Unbuffered){
|
||||
self.output.push_back(instruction);
|
||||
}else{
|
||||
self.buffer.push_back(instruction);
|
||||
}
|
||||
}
|
||||
}
|
||||
fn buffered_instruction_with_timeout(&self,time_limit:SessionTime)->Option<TimedInstruction<StepInstruction,SessionTimeInner>>{
|
||||
match self.get_mouse_timedout_at(time_limit){
|
||||
Some(timeout)=>Some(TimedInstruction{
|
||||
time:timeout,
|
||||
instruction:StepInstruction::Timeout,
|
||||
}),
|
||||
None=>(self.output.len()!=0).then_some(TimedInstruction{
|
||||
// this timestamp should not matter
|
||||
time:time_limit,
|
||||
instruction:StepInstruction::Pop,
|
||||
}),
|
||||
}
|
||||
}
|
||||
pub fn pop_buffered_instruction(&mut self,ins:TimedInstruction<StepInstruction,PhysicsTimeInner>)->Option<TimedPhysicsInstruction>{
|
||||
match ins.instruction{
|
||||
StepInstruction::Pop=>(),
|
||||
StepInstruction::Timeout=>self.timeout_mouse(ins.time),
|
||||
}
|
||||
self.output.pop_front()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test{
|
||||
use super::*;
|
||||
#[test]
|
||||
fn test(){
|
||||
let mut interpolator=MouseInterpolator::new();
|
||||
|
||||
let timer=strafesnet_common::timer::Timer::<strafesnet_common::timer::Scaled<SessionTimeInner,PhysicsTimeInner>>::unpaused(SessionTime::ZERO,PhysicsTime::from_secs(1000));
|
||||
|
||||
macro_rules! push{
|
||||
($time:expr,$ins:expr)=>{
|
||||
println!("in={:?}",$ins);
|
||||
interpolator.push_unbuffered_input(
|
||||
$time,
|
||||
timer.time($time),
|
||||
$ins,
|
||||
);
|
||||
while let Some(ins)=interpolator.buffered_instruction_with_timeout($time){
|
||||
let ins_retimed=TimedInstruction{
|
||||
time:timer.time(ins.time),
|
||||
instruction:ins.instruction,
|
||||
};
|
||||
let out=interpolator.pop_buffered_instruction(ins_retimed);
|
||||
println!("out={out:?}");
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// test each buffer_state transition
|
||||
let mut t=SessionTime::ZERO;
|
||||
push!(t,UnbufferedInstruction::MoveMouse(glam::ivec2(0,0)));
|
||||
t+=SessionTime::from_millis(5);
|
||||
push!(t,UnbufferedInstruction::MoveMouse(glam::ivec2(0,0)));
|
||||
t+=SessionTime::from_millis(5);
|
||||
push!(t,UnbufferedInstruction::MoveMouse(glam::ivec2(0,0)));
|
||||
t+=SessionTime::from_millis(1);
|
||||
}
|
||||
}
|
443
engine/session/src/session.rs
Normal file
443
engine/session/src/session.rs
Normal file
@ -0,0 +1,443 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use strafesnet_common::gameplay_modes::{ModeId,StageId};
|
||||
use strafesnet_common::instruction::{InstructionConsumer,InstructionEmitter,InstructionFeedback,TimedInstruction};
|
||||
// session represents the non-hardware state of the client.
|
||||
// Ideally it is a deterministic state which is atomically updated by instructions, same as the simulation state.
|
||||
use strafesnet_common::physics::{
|
||||
ModeInstruction,MiscInstruction,
|
||||
Instruction as PhysicsInputInstruction,
|
||||
TimeInner as PhysicsTimeInner,
|
||||
Time as PhysicsTime
|
||||
};
|
||||
use strafesnet_common::timer::{Scaled,Timer};
|
||||
use strafesnet_common::session::{TimeInner as SessionTimeInner,Time as SessionTime};
|
||||
use strafesnet_settings::directories::Directories;
|
||||
|
||||
use crate::mouse_interpolator::{MouseInterpolator,StepInstruction,Instruction as MouseInterpolatorInstruction};
|
||||
use strafesnet_physics::physics::{self,PhysicsContext,PhysicsData};
|
||||
use strafesnet_settings::settings::UserSettings;
|
||||
|
||||
pub enum Instruction<'a>{
|
||||
Input(SessionInputInstruction),
|
||||
Control(SessionControlInstruction),
|
||||
Playback(SessionPlaybackInstruction),
|
||||
ChangeMap(&'a strafesnet_common::map::CompleteMap),
|
||||
LoadReplay(strafesnet_snf::bot::Segment),
|
||||
Idle,
|
||||
}
|
||||
|
||||
pub enum SessionInputInstruction{
|
||||
Mouse(glam::IVec2),
|
||||
SetControl(strafesnet_common::physics::SetControlInstruction),
|
||||
Mode(ImplicitModeInstruction),
|
||||
Misc(strafesnet_common::physics::MiscInstruction),
|
||||
}
|
||||
/// Implicit mode instruction are fed separately to session.
|
||||
/// Session generates the explicit mode instructions interlaced with a SetSensitivity instruction
|
||||
#[derive(Clone,Debug)]
|
||||
pub enum ImplicitModeInstruction{
|
||||
ResetAndRestart,
|
||||
ResetAndSpawn(ModeId,StageId),
|
||||
}
|
||||
|
||||
pub enum SessionControlInstruction{
|
||||
SetPaused(bool),
|
||||
// copy the current session simulation recording into a replay and view it
|
||||
CopyRecordingIntoReplayAndSpectate,
|
||||
StopSpectate,
|
||||
SaveReplay,
|
||||
LoadIntoReplayState,
|
||||
}
|
||||
pub enum SessionPlaybackInstruction{
|
||||
SkipForward,
|
||||
SkipBack,
|
||||
TogglePaused,
|
||||
DecreaseTimescale,
|
||||
IncreaseTimescale,
|
||||
}
|
||||
|
||||
pub struct FrameState{
|
||||
pub body:physics::Body,
|
||||
pub camera:physics::PhysicsCamera,
|
||||
pub time:PhysicsTime,
|
||||
}
|
||||
|
||||
pub struct Simulation{
|
||||
timer:Timer<Scaled<SessionTimeInner,PhysicsTimeInner>>,
|
||||
physics:physics::PhysicsState,
|
||||
}
|
||||
impl Simulation{
|
||||
pub const fn new(
|
||||
timer:Timer<Scaled<SessionTimeInner,PhysicsTimeInner>>,
|
||||
physics:physics::PhysicsState,
|
||||
)->Self{
|
||||
Self{
|
||||
timer,
|
||||
physics,
|
||||
}
|
||||
}
|
||||
pub fn get_frame_state(&self,time:SessionTime)->FrameState{
|
||||
FrameState{
|
||||
body:self.physics.camera_body(),
|
||||
camera:self.physics.camera(),
|
||||
time:self.timer.time(time),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Recording{
|
||||
instructions:Vec<TimedInstruction<PhysicsInputInstruction,PhysicsTimeInner>>,
|
||||
}
|
||||
impl Recording{
|
||||
pub fn new(
|
||||
instructions:Vec<TimedInstruction<PhysicsInputInstruction,PhysicsTimeInner>>,
|
||||
)->Self{
|
||||
Self{instructions}
|
||||
}
|
||||
fn clear(&mut self){
|
||||
self.instructions.clear();
|
||||
}
|
||||
}
|
||||
pub struct Replay{
|
||||
next_instruction_id:usize,
|
||||
recording:Recording,
|
||||
simulation:Simulation,
|
||||
}
|
||||
impl Replay{
|
||||
pub const fn new(
|
||||
recording:Recording,
|
||||
simulation:Simulation,
|
||||
)->Self{
|
||||
Self{
|
||||
next_instruction_id:0,
|
||||
recording,
|
||||
simulation,
|
||||
}
|
||||
}
|
||||
pub fn advance(&mut self,physics_data:&PhysicsData,time_limit:SessionTime){
|
||||
let mut time=self.simulation.timer.time(time_limit);
|
||||
loop{
|
||||
if let Some(ins)=self.recording.instructions.get(self.next_instruction_id){
|
||||
if ins.time<time{
|
||||
PhysicsContext::run_input_instruction(&mut self.simulation.physics,physics_data,ins.clone());
|
||||
self.next_instruction_id+=1;
|
||||
}else{
|
||||
break;
|
||||
}
|
||||
}else{
|
||||
// loop playback
|
||||
self.next_instruction_id=0;
|
||||
// No need to reset physics because the very first instruction is 'Reset'
|
||||
let new_time=self.recording.instructions.first().map_or(PhysicsTime::ZERO,|ins|ins.time);
|
||||
self.simulation.timer.set_time(time_limit,new_time);
|
||||
time=new_time;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone,Copy,Hash,PartialEq,Eq)]
|
||||
struct BotId(u32);
|
||||
//#[derive(Clone,Copy,Hash,PartialEq,Eq)]
|
||||
//struct PlayerId(u32);
|
||||
|
||||
enum ViewState{
|
||||
Play,
|
||||
//Spectate(PlayerId),
|
||||
Replay(BotId),
|
||||
}
|
||||
|
||||
pub struct Session{
|
||||
directories:Directories,
|
||||
user_settings:UserSettings,
|
||||
mouse_interpolator:crate::mouse_interpolator::MouseInterpolator,
|
||||
view_state:ViewState,
|
||||
//gui:GuiState
|
||||
geometry_shared:physics::PhysicsData,
|
||||
simulation:Simulation,
|
||||
// below fields not included in lite session
|
||||
recording:Recording,
|
||||
//players:HashMap<PlayerId,Simulation>,
|
||||
replays:HashMap<BotId,Replay>,
|
||||
}
|
||||
impl Session{
|
||||
pub fn new(
|
||||
user_settings:UserSettings,
|
||||
directories:Directories,
|
||||
simulation:Simulation,
|
||||
)->Self{
|
||||
Self{
|
||||
user_settings,
|
||||
directories,
|
||||
mouse_interpolator:MouseInterpolator::new(),
|
||||
geometry_shared:Default::default(),
|
||||
simulation,
|
||||
view_state:ViewState::Play,
|
||||
recording:Default::default(),
|
||||
replays:HashMap::new(),
|
||||
}
|
||||
}
|
||||
fn clear_recording(&mut self){
|
||||
self.recording.clear();
|
||||
}
|
||||
fn change_map(&mut self,map:&strafesnet_common::map::CompleteMap){
|
||||
self.simulation.physics.clear();
|
||||
self.geometry_shared.generate_models(map);
|
||||
}
|
||||
pub fn get_frame_state(&self,time:SessionTime)->Option<FrameState>{
|
||||
match &self.view_state{
|
||||
ViewState::Play=>Some(self.simulation.get_frame_state(time)),
|
||||
ViewState::Replay(bot_id)=>self.replays.get(bot_id).map(|replay|
|
||||
replay.simulation.get_frame_state(time)
|
||||
),
|
||||
}
|
||||
}
|
||||
pub fn user_settings(&self)->&UserSettings{
|
||||
&self.user_settings
|
||||
}
|
||||
}
|
||||
|
||||
// mouseinterpolator consumes RawInputInstruction
|
||||
// mouseinterpolator emits PhysicsInputInstruction
|
||||
// mouseinterpolator consumes DoStep to move on to the next emitted instruction
|
||||
// Session comsumes SessionInstruction -> forwards RawInputInstruction to mouseinterpolator
|
||||
// Session consumes DoStep -> forwards DoStep to mouseinterpolator
|
||||
// Session emits DoStep
|
||||
|
||||
impl InstructionConsumer<Instruction<'_>> for Session{
|
||||
type TimeInner=SessionTimeInner;
|
||||
fn process_instruction(&mut self,ins:TimedInstruction<Instruction,Self::TimeInner>){
|
||||
// repetitive procedure macro
|
||||
macro_rules! run_mouse_interpolator_instruction{
|
||||
($instruction:expr)=>{
|
||||
self.mouse_interpolator.process_instruction(TimedInstruction{
|
||||
time:ins.time,
|
||||
instruction:TimedInstruction{
|
||||
time:self.simulation.timer.time(ins.time),
|
||||
instruction:$instruction,
|
||||
},
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
// process any timeouts that occured since the last instruction
|
||||
self.process_exhaustive(ins.time);
|
||||
|
||||
match ins.instruction{
|
||||
// send it down to MouseInterpolator with two timestamps, SessionTime and PhysicsTime
|
||||
Instruction::Input(SessionInputInstruction::Mouse(pos))=>{
|
||||
run_mouse_interpolator_instruction!(MouseInterpolatorInstruction::MoveMouse(pos));
|
||||
},
|
||||
Instruction::Input(SessionInputInstruction::SetControl(set_control_instruction))=>{
|
||||
run_mouse_interpolator_instruction!(MouseInterpolatorInstruction::SetControl(set_control_instruction));
|
||||
},
|
||||
Instruction::Input(SessionInputInstruction::Mode(ImplicitModeInstruction::ResetAndRestart))=>{
|
||||
self.clear_recording();
|
||||
let mode_id=self.simulation.physics.mode();
|
||||
run_mouse_interpolator_instruction!(MouseInterpolatorInstruction::Mode(ModeInstruction::Reset));
|
||||
run_mouse_interpolator_instruction!(MouseInterpolatorInstruction::Misc(MiscInstruction::SetSensitivity(self.user_settings().calculate_sensitivity())));
|
||||
run_mouse_interpolator_instruction!(MouseInterpolatorInstruction::Mode(ModeInstruction::Restart(mode_id)));
|
||||
},
|
||||
Instruction::Input(SessionInputInstruction::Mode(ImplicitModeInstruction::ResetAndSpawn(mode_id,spawn_id)))=>{
|
||||
self.clear_recording();
|
||||
run_mouse_interpolator_instruction!(MouseInterpolatorInstruction::Mode(ModeInstruction::Reset));
|
||||
run_mouse_interpolator_instruction!(MouseInterpolatorInstruction::Misc(MiscInstruction::SetSensitivity(self.user_settings().calculate_sensitivity())));
|
||||
run_mouse_interpolator_instruction!(MouseInterpolatorInstruction::Mode(ModeInstruction::Spawn(mode_id,spawn_id)));
|
||||
},
|
||||
Instruction::Input(SessionInputInstruction::Misc(misc_instruction))=>{
|
||||
run_mouse_interpolator_instruction!(MouseInterpolatorInstruction::Misc(misc_instruction));
|
||||
},
|
||||
Instruction::Control(SessionControlInstruction::SetPaused(paused))=>{
|
||||
// don't flush the buffered instructions in the mouse interpolator
|
||||
// until the mouse is confirmed to be not moving at a later time
|
||||
// what if they pause for 5ms lmao
|
||||
_=self.simulation.timer.set_paused(ins.time,paused);
|
||||
},
|
||||
Instruction::Control(SessionControlInstruction::CopyRecordingIntoReplayAndSpectate)=> if let ViewState::Play=self.view_state{
|
||||
// Bind: B
|
||||
|
||||
// pause simulation
|
||||
_=self.simulation.timer.set_paused(ins.time,true);
|
||||
|
||||
// create recording
|
||||
let mut recording=Recording::default();
|
||||
recording.instructions.extend(self.recording.instructions.iter().cloned());
|
||||
|
||||
// create timer starting at first instruction (or zero if the list is empty)
|
||||
let new_time=recording.instructions.first().map_or(PhysicsTime::ZERO,|ins|ins.time);
|
||||
let timer=Timer::unpaused(ins.time,new_time);
|
||||
|
||||
// create default physics state
|
||||
let simulation=Simulation::new(timer,Default::default());
|
||||
|
||||
// invent a new bot id and insert the replay
|
||||
let bot_id=BotId(self.replays.len() as u32);
|
||||
self.replays.insert(bot_id,Replay::new(
|
||||
recording,
|
||||
simulation,
|
||||
));
|
||||
|
||||
// begin spectate
|
||||
self.view_state=ViewState::Replay(bot_id);
|
||||
},
|
||||
Instruction::Control(SessionControlInstruction::StopSpectate)=>{
|
||||
let view_state=core::mem::replace(&mut self.view_state,ViewState::Play);
|
||||
// delete the bot, otherwise it's inaccessible and wastes CPU
|
||||
match view_state{
|
||||
ViewState::Play=>(),
|
||||
ViewState::Replay(bot_id)=>{
|
||||
self.replays.remove(&bot_id);
|
||||
},
|
||||
}
|
||||
_=self.simulation.timer.set_paused(ins.time,false);
|
||||
},
|
||||
Instruction::Control(SessionControlInstruction::SaveReplay)=>{
|
||||
// Bind: N
|
||||
let view_state=core::mem::replace(&mut self.view_state,ViewState::Play);
|
||||
match view_state{
|
||||
ViewState::Play=>(),
|
||||
ViewState::Replay(bot_id)=>if let Some(replay)=self.replays.remove(&bot_id){
|
||||
let mut replays_path=self.directories.replays.clone();
|
||||
let file_name=format!("{}.snfb",ins.time);
|
||||
std::thread::spawn(move ||{
|
||||
std::fs::create_dir_all(replays_path.as_path()).unwrap();
|
||||
replays_path.push(file_name);
|
||||
let file=std::fs::File::create(replays_path).unwrap();
|
||||
strafesnet_snf::bot::write_bot(
|
||||
std::io::BufWriter::new(file),
|
||||
strafesnet_physics::VERSION.get(),
|
||||
replay.recording.instructions
|
||||
).unwrap();
|
||||
println!("Finished writing bot file!");
|
||||
});
|
||||
},
|
||||
}
|
||||
_=self.simulation.timer.set_paused(ins.time,false);
|
||||
},
|
||||
Instruction::Control(SessionControlInstruction::LoadIntoReplayState)=>{
|
||||
// Bind: J
|
||||
let view_state=core::mem::replace(&mut self.view_state,ViewState::Play);
|
||||
match view_state{
|
||||
ViewState::Play=>(),
|
||||
ViewState::Replay(bot_id)=>if let Some(replay)=self.replays.remove(&bot_id){
|
||||
self.recording.instructions=replay.recording.instructions.into_iter().take(replay.next_instruction_id).collect();
|
||||
self.simulation=replay.simulation;
|
||||
},
|
||||
}
|
||||
// don't unpause -- use the replay timer state whether it is pasued or unpaused
|
||||
},
|
||||
Instruction::Playback(SessionPlaybackInstruction::IncreaseTimescale)=>{
|
||||
match &self.view_state{
|
||||
ViewState::Play=>{
|
||||
// allow simulation timescale for fun
|
||||
let scale=self.simulation.timer.get_scale();
|
||||
self.simulation.timer.set_scale(ins.time,strafesnet_common::integer::Ratio64::new(scale.num()*5,scale.den()*4).unwrap());
|
||||
},
|
||||
ViewState::Replay(bot_id)=>if let Some(replay)=self.replays.get_mut(bot_id){
|
||||
let scale=replay.simulation.timer.get_scale();
|
||||
replay.simulation.timer.set_scale(ins.time,strafesnet_common::integer::Ratio64::new(scale.num()*5,scale.den()*4).unwrap());
|
||||
},
|
||||
}
|
||||
},
|
||||
Instruction::Playback(SessionPlaybackInstruction::DecreaseTimescale)=>{
|
||||
match &self.view_state{
|
||||
ViewState::Play=>{
|
||||
// allow simulation timescale for fun
|
||||
let scale=self.simulation.timer.get_scale();
|
||||
self.simulation.timer.set_scale(ins.time,strafesnet_common::integer::Ratio64::new(scale.num()*4,scale.den()*5).unwrap());
|
||||
},
|
||||
ViewState::Replay(bot_id)=>if let Some(replay)=self.replays.get_mut(bot_id){
|
||||
let scale=replay.simulation.timer.get_scale();
|
||||
replay.simulation.timer.set_scale(ins.time,strafesnet_common::integer::Ratio64::new(scale.num()*4,scale.den()*5).unwrap());
|
||||
},
|
||||
}
|
||||
},
|
||||
Instruction::Playback(SessionPlaybackInstruction::SkipForward)=>{
|
||||
match &self.view_state{
|
||||
ViewState::Play=>(),
|
||||
ViewState::Replay(bot_id)=>if let Some(replay)=self.replays.get_mut(bot_id){
|
||||
let time=replay.simulation.timer.time(ins.time+SessionTime::from_secs(5));
|
||||
replay.simulation.timer.set_time(ins.time,time);
|
||||
},
|
||||
}
|
||||
},
|
||||
Instruction::Playback(SessionPlaybackInstruction::SkipBack)=>{
|
||||
match &self.view_state{
|
||||
ViewState::Play=>(),
|
||||
ViewState::Replay(bot_id)=>if let Some(replay)=self.replays.get_mut(bot_id){
|
||||
let time=replay.simulation.timer.time(ins.time+SessionTime::from_secs(5));
|
||||
replay.simulation.timer.set_time(ins.time,time);
|
||||
// resimulate the entire playback lol
|
||||
replay.next_instruction_id=0;
|
||||
},
|
||||
}
|
||||
},
|
||||
Instruction::Playback(SessionPlaybackInstruction::TogglePaused)=>{
|
||||
match &self.view_state{
|
||||
ViewState::Play=>(),
|
||||
ViewState::Replay(bot_id)=>if let Some(replay)=self.replays.get_mut(bot_id){
|
||||
_=replay.simulation.timer.set_paused(ins.time,!replay.simulation.timer.is_paused());
|
||||
},
|
||||
}
|
||||
}
|
||||
Instruction::ChangeMap(complete_map)=>{
|
||||
self.clear_recording();
|
||||
self.change_map(complete_map);
|
||||
},
|
||||
Instruction::LoadReplay(bot)=>{
|
||||
// pause simulation
|
||||
_=self.simulation.timer.set_paused(ins.time,true);
|
||||
|
||||
// create recording
|
||||
let recording=Recording::new(bot.instructions);
|
||||
|
||||
// create timer starting at first instruction (or zero if the list is empty)
|
||||
let new_time=recording.instructions.first().map_or(PhysicsTime::ZERO,|ins|ins.time);
|
||||
let timer=Timer::unpaused(ins.time,new_time);
|
||||
|
||||
// create default physics state
|
||||
let simulation=Simulation::new(timer,Default::default());
|
||||
|
||||
// invent a new bot id and insert the replay
|
||||
let bot_id=BotId(self.replays.len() as u32);
|
||||
self.replays.insert(bot_id,Replay::new(
|
||||
recording,
|
||||
simulation,
|
||||
));
|
||||
|
||||
// begin spectate
|
||||
self.view_state=ViewState::Replay(bot_id);
|
||||
},
|
||||
Instruction::Idle=>{
|
||||
run_mouse_interpolator_instruction!(MouseInterpolatorInstruction::Idle);
|
||||
// this just refreshes the replays
|
||||
for replay in self.replays.values_mut(){
|
||||
// TODO: filter idles from recording, inject new idles in real time
|
||||
replay.advance(&self.geometry_shared,ins.time);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// process all emitted output instructions
|
||||
self.process_exhaustive(ins.time);
|
||||
}
|
||||
}
|
||||
impl InstructionConsumer<StepInstruction> for Session{
|
||||
type TimeInner=SessionTimeInner;
|
||||
fn process_instruction(&mut self,ins:TimedInstruction<StepInstruction,Self::TimeInner>){
|
||||
let time=self.simulation.timer.time(ins.time);
|
||||
if let Some(instruction)=self.mouse_interpolator.pop_buffered_instruction(ins.set_time(time)){
|
||||
//record
|
||||
self.recording.instructions.push(instruction.clone());
|
||||
PhysicsContext::run_input_instruction(&mut self.simulation.physics,&self.geometry_shared,instruction);
|
||||
}
|
||||
}
|
||||
}
|
||||
impl InstructionEmitter<StepInstruction> for Session{
|
||||
type TimeInner=SessionTimeInner;
|
||||
fn next_instruction(&self,time_limit:SessionTime)->Option<TimedInstruction<StepInstruction,Self::TimeInner>>{
|
||||
self.mouse_interpolator.next_instruction(time_limit)
|
||||
}
|
||||
}
|
10
engine/settings/Cargo.toml
Normal file
10
engine/settings/Cargo.toml
Normal file
@ -0,0 +1,10 @@
|
||||
[package]
|
||||
name = "strafesnet_settings"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
configparser = "3.0.2"
|
||||
directories = "6.0.0"
|
||||
glam = "0.29.0"
|
||||
strafesnet_common = { path = "../../lib/common", registry = "strafesnet" }
|
8
engine/settings/LICENSE
Normal file
8
engine/settings/LICENSE
Normal file
@ -0,0 +1,8 @@
|
||||
/*******************************************************
|
||||
* Copyright (C) 2023-2024 Rhys Lloyd <krakow20@gmail.com>
|
||||
*
|
||||
* This file is part of the StrafesNET bhop/surf client.
|
||||
*
|
||||
* StrafesNET can not be copied and/or distributed
|
||||
* without the express permission of Rhys Lloyd
|
||||
*******************************************************/
|
32
engine/settings/src/directories.rs
Normal file
32
engine/settings/src/directories.rs
Normal file
@ -0,0 +1,32 @@
|
||||
use std::path::PathBuf;
|
||||
|
||||
use crate::settings::{UserSettings,load_user_settings};
|
||||
|
||||
pub struct Directories{
|
||||
pub settings:PathBuf,
|
||||
pub maps:PathBuf,
|
||||
pub replays:PathBuf,
|
||||
}
|
||||
impl Directories{
|
||||
pub fn settings(&self)->UserSettings{
|
||||
load_user_settings(&self.settings)
|
||||
}
|
||||
pub fn user()->Option<Self>{
|
||||
let dirs=directories::ProjectDirs::from("net.strafes","StrafesNET","Strafe Client")?;
|
||||
Some(Self{
|
||||
settings:dirs.config_dir().join("settings.conf"),
|
||||
maps:dirs.cache_dir().join("maps"),
|
||||
// separate directory for remote downloaded replays (cache)
|
||||
// bots:dirs.cache_dir().join("bots"),
|
||||
replays:dirs.data_local_dir().join("replays"),
|
||||
})
|
||||
}
|
||||
pub fn portable()->Result<Self,std::io::Error>{
|
||||
let current_dir=std::env::current_dir()?;
|
||||
Ok(Self{
|
||||
settings:current_dir.join("settings.conf"),
|
||||
maps:current_dir.join("maps"),
|
||||
replays:current_dir.join("replays"),
|
||||
})
|
||||
}
|
||||
}
|
2
engine/settings/src/lib.rs
Normal file
2
engine/settings/src/lib.rs
Normal file
@ -0,0 +1,2 @@
|
||||
pub mod settings;
|
||||
pub mod directories;
|
139
engine/settings/src/settings.rs
Normal file
139
engine/settings/src/settings.rs
Normal file
@ -0,0 +1,139 @@
|
||||
use strafesnet_common::integer::{Ratio64,Ratio64Vec2};
|
||||
#[derive(Clone)]
|
||||
struct Ratio{
|
||||
ratio:f64,
|
||||
}
|
||||
#[derive(Clone)]
|
||||
enum DerivedFov{
|
||||
FromScreenAspect,
|
||||
FromAspect(Ratio),
|
||||
}
|
||||
#[derive(Clone)]
|
||||
enum Fov{
|
||||
Exactly{x:f64,y:f64},
|
||||
SpecifyXDeriveY{x:f64,y:DerivedFov},
|
||||
SpecifyYDeriveX{x:DerivedFov,y:f64},
|
||||
}
|
||||
impl Default for Fov{
|
||||
fn default()->Self{
|
||||
Fov::SpecifyYDeriveX{x:DerivedFov::FromScreenAspect,y:1.0}
|
||||
}
|
||||
}
|
||||
#[derive(Clone)]
|
||||
enum DerivedSensitivity{
|
||||
FromRatio(Ratio64),
|
||||
}
|
||||
#[derive(Clone)]
|
||||
enum Sensitivity{
|
||||
Exactly{x:Ratio64,y:Ratio64},
|
||||
SpecifyXDeriveY{x:Ratio64,y:DerivedSensitivity},
|
||||
SpecifyYDeriveX{x:DerivedSensitivity,y:Ratio64},
|
||||
}
|
||||
impl Default for Sensitivity{
|
||||
fn default()->Self{
|
||||
Sensitivity::SpecifyXDeriveY{x:Ratio64::ONE*524288,y:DerivedSensitivity::FromRatio(Ratio64::ONE)}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default,Clone)]
|
||||
pub struct UserSettings{
|
||||
fov:Fov,
|
||||
sensitivity:Sensitivity,
|
||||
}
|
||||
impl UserSettings{
|
||||
pub fn calculate_fov(&self,zoom:f64,screen_size:&glam::UVec2)->glam::DVec2{
|
||||
zoom*match &self.fov{
|
||||
&Fov::Exactly{x,y}=>glam::dvec2(x,y),
|
||||
Fov::SpecifyXDeriveY{x,y}=>match y{
|
||||
DerivedFov::FromScreenAspect=>glam::dvec2(*x,x*(screen_size.y as f64/screen_size.x as f64)),
|
||||
DerivedFov::FromAspect(ratio)=>glam::dvec2(*x,x*ratio.ratio),
|
||||
},
|
||||
Fov::SpecifyYDeriveX{x,y}=>match x{
|
||||
DerivedFov::FromScreenAspect=>glam::dvec2(y*(screen_size.x as f64/screen_size.y as f64),*y),
|
||||
DerivedFov::FromAspect(ratio)=>glam::dvec2(y*ratio.ratio,*y),
|
||||
},
|
||||
}
|
||||
}
|
||||
pub fn calculate_sensitivity(&self)->Ratio64Vec2{
|
||||
match &self.sensitivity{
|
||||
Sensitivity::Exactly{x,y}=>Ratio64Vec2::new(x.clone(),y.clone()),
|
||||
Sensitivity::SpecifyXDeriveY{x,y}=>match y{
|
||||
DerivedSensitivity::FromRatio(ratio)=>Ratio64Vec2::new(x.clone(),x.mul_ref(ratio)),
|
||||
}
|
||||
Sensitivity::SpecifyYDeriveX{x,y}=>match x{
|
||||
DerivedSensitivity::FromRatio(ratio)=>Ratio64Vec2::new(y.mul_ref(ratio),y.clone()),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
//sensitivity is raw input dots (i.e. dpi = dots per inch) to radians conversion factor
|
||||
sensitivity_x=0.001
|
||||
sensitivity_y_from_x_ratio=1
|
||||
Sensitivity::DeriveY{x:0.0.001,y:DerivedSensitivity{ratio:1.0}}
|
||||
*/
|
||||
|
||||
pub fn load_user_settings(path:&std::path::Path)->UserSettings{
|
||||
let mut cfg=configparser::ini::Ini::new();
|
||||
if let Ok(_)=cfg.load(path){
|
||||
let (cfg_fov_x,cfg_fov_y)=(cfg.getfloat("camera","fov_x"),cfg.getfloat("camera","fov_y"));
|
||||
let fov=match(cfg_fov_x,cfg_fov_y){
|
||||
(Ok(Some(fov_x)),Ok(Some(fov_y)))=>Fov::Exactly {
|
||||
x:fov_x,
|
||||
y:fov_y
|
||||
},
|
||||
(Ok(Some(fov_x)),Ok(None))=>Fov::SpecifyXDeriveY{
|
||||
x:fov_x,
|
||||
y:if let Ok(Some(fov_y_from_x_ratio))=cfg.getfloat("camera","fov_y_from_x_ratio"){
|
||||
DerivedFov::FromAspect(Ratio{ratio:fov_y_from_x_ratio})
|
||||
}else{
|
||||
DerivedFov::FromScreenAspect
|
||||
}
|
||||
},
|
||||
(Ok(None),Ok(Some(fov_y)))=>Fov::SpecifyYDeriveX{
|
||||
x:if let Ok(Some(fov_x_from_y_ratio))=cfg.getfloat("camera","fov_x_from_y_ratio"){
|
||||
DerivedFov::FromAspect(Ratio{ratio:fov_x_from_y_ratio})
|
||||
}else{
|
||||
DerivedFov::FromScreenAspect
|
||||
},
|
||||
y:fov_y,
|
||||
},
|
||||
_=>{
|
||||
Fov::default()
|
||||
},
|
||||
};
|
||||
let (cfg_sensitivity_x,cfg_sensitivity_y)=(cfg.getfloat("camera","sensitivity_x"),cfg.getfloat("camera","sensitivity_y"));
|
||||
let sensitivity=match(cfg_sensitivity_x,cfg_sensitivity_y){
|
||||
(Ok(Some(sensitivity_x)),Ok(Some(sensitivity_y)))=>Sensitivity::Exactly {
|
||||
x:Ratio64::try_from(sensitivity_x).unwrap(),
|
||||
y:Ratio64::try_from(sensitivity_y).unwrap(),
|
||||
},
|
||||
(Ok(Some(sensitivity_x)),Ok(None))=>Sensitivity::SpecifyXDeriveY{
|
||||
x:Ratio64::try_from(sensitivity_x).unwrap(),
|
||||
y:if let Ok(Some(sensitivity_y_from_x_ratio))=cfg.getfloat("camera","sensitivity_y_from_x_ratio"){
|
||||
DerivedSensitivity::FromRatio(Ratio64::try_from(sensitivity_y_from_x_ratio).unwrap())
|
||||
}else{
|
||||
DerivedSensitivity::FromRatio(Ratio64::ONE)
|
||||
},
|
||||
},
|
||||
(Ok(None),Ok(Some(sensitivity_y)))=>Sensitivity::SpecifyYDeriveX{
|
||||
x:if let Ok(Some(sensitivity_x_from_y_ratio))=cfg.getfloat("camera","sensitivity_x_from_y_ratio"){
|
||||
DerivedSensitivity::FromRatio(Ratio64::try_from(sensitivity_x_from_y_ratio).unwrap())
|
||||
}else{
|
||||
DerivedSensitivity::FromRatio(Ratio64::ONE)
|
||||
},
|
||||
y:Ratio64::try_from(sensitivity_y).unwrap(),
|
||||
},
|
||||
_=>{
|
||||
Sensitivity::default()
|
||||
},
|
||||
};
|
||||
UserSettings{
|
||||
fov,
|
||||
sensitivity,
|
||||
}
|
||||
}else{
|
||||
UserSettings::default()
|
||||
}
|
||||
}
|
9
integration-testing/Cargo.toml
Normal file
9
integration-testing/Cargo.toml
Normal file
@ -0,0 +1,9 @@
|
||||
[package]
|
||||
name = "integration-testing"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
strafesnet_common = { version = "0.5.2", path = "../lib/common", registry = "strafesnet" }
|
||||
strafesnet_physics = { version = "0.1.0", path = "../engine/physics", registry = "strafesnet" }
|
||||
strafesnet_snf = { path = "../lib/snf", registry = "strafesnet" }
|
221
integration-testing/src/main.rs
Normal file
221
integration-testing/src/main.rs
Normal file
@ -0,0 +1,221 @@
|
||||
|
||||
use std::{io::{Cursor,Read},path::Path};
|
||||
|
||||
use strafesnet_physics::physics::{PhysicsData,PhysicsState,PhysicsContext};
|
||||
|
||||
fn main(){
|
||||
test_determinism().unwrap();
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
#[derive(Debug)]
|
||||
enum ReplayError{
|
||||
IO(std::io::Error),
|
||||
SNF(strafesnet_snf::Error),
|
||||
SNFM(strafesnet_snf::map::Error),
|
||||
SNFB(strafesnet_snf::bot::Error),
|
||||
}
|
||||
impl From<std::io::Error> for ReplayError{
|
||||
fn from(value:std::io::Error)->Self{
|
||||
Self::IO(value)
|
||||
}
|
||||
}
|
||||
impl From<strafesnet_snf::Error> for ReplayError{
|
||||
fn from(value:strafesnet_snf::Error)->Self{
|
||||
Self::SNF(value)
|
||||
}
|
||||
}
|
||||
impl From<strafesnet_snf::map::Error> for ReplayError{
|
||||
fn from(value:strafesnet_snf::map::Error)->Self{
|
||||
Self::SNFM(value)
|
||||
}
|
||||
}
|
||||
impl From<strafesnet_snf::bot::Error> for ReplayError{
|
||||
fn from(value:strafesnet_snf::bot::Error)->Self{
|
||||
Self::SNFB(value)
|
||||
}
|
||||
}
|
||||
|
||||
fn read_entire_file(path:impl AsRef<Path>)->Result<Cursor<Vec<u8>>,std::io::Error>{
|
||||
let mut file=std::fs::File::open(path)?;
|
||||
let mut data=Vec::new();
|
||||
file.read_to_end(&mut data)?;
|
||||
Ok(Cursor::new(data))
|
||||
}
|
||||
|
||||
fn run_replay()->Result<(),ReplayError>{
|
||||
println!("loading map file..");
|
||||
let data=read_entire_file("../tools/bhop_maps/5692113331.snfm")?;
|
||||
let map=strafesnet_snf::read_map(data)?.into_complete_map()?;
|
||||
|
||||
println!("loading bot file..");
|
||||
let data=read_entire_file("../tools/replays/535s+159764769ns.snfb")?;
|
||||
let bot=strafesnet_snf::read_bot(data)?.read_all()?;
|
||||
|
||||
// create recording
|
||||
let mut physics_data=PhysicsData::default();
|
||||
println!("generating models..");
|
||||
physics_data.generate_models(&map);
|
||||
println!("simulating...");
|
||||
let mut physics=PhysicsState::default();
|
||||
for ins in bot.instructions{
|
||||
PhysicsContext::run_input_instruction(&mut physics,&physics_data,ins);
|
||||
}
|
||||
match physics.get_finish_time(){
|
||||
Some(time)=>println!("finish time:{}",time),
|
||||
None=>println!("simulation did not end in finished state"),
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
enum DeterminismResult{
|
||||
Deterministic,
|
||||
NonDeterministic,
|
||||
}
|
||||
fn segment_determinism(bot:strafesnet_snf::bot::Segment,physics_data:&PhysicsData)->DeterminismResult{
|
||||
// create default physics state
|
||||
let mut physics_deterministic=PhysicsState::default();
|
||||
// create a second physics state
|
||||
let mut physics_filtered=PhysicsState::default();
|
||||
|
||||
// invent a new bot id and insert the replay
|
||||
println!("simulating...");
|
||||
|
||||
let mut non_idle_count=0;
|
||||
|
||||
for (i,ins) in bot.instructions.into_iter().enumerate(){
|
||||
let state_deterministic=physics_deterministic.clone();
|
||||
let state_filtered=physics_filtered.clone();
|
||||
PhysicsContext::run_input_instruction(&mut physics_deterministic,&physics_data,ins.clone());
|
||||
match ins{
|
||||
strafesnet_common::instruction::TimedInstruction{instruction:strafesnet_common::physics::Instruction::Idle,..}=>(),
|
||||
other=>{
|
||||
non_idle_count+=1;
|
||||
// run
|
||||
PhysicsContext::run_input_instruction(&mut physics_filtered,&physics_data,other.clone());
|
||||
// check if position matches
|
||||
let b0=physics_deterministic.camera_body();
|
||||
let b1=physics_filtered.camera_body();
|
||||
if b0.position!=b1.position{
|
||||
println!("desync at instruction #{}",i);
|
||||
println!("non idle instructions completed={non_idle_count}");
|
||||
println!("instruction #{i}={:?}",other);
|
||||
println!("deterministic state0:\n{state_deterministic:?}");
|
||||
println!("filtered state0:\n{state_filtered:?}");
|
||||
println!("deterministic state1:\n{:?}",physics_deterministic);
|
||||
println!("filtered state1:\n{:?}",physics_filtered);
|
||||
return DeterminismResult::NonDeterministic;
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
match physics_deterministic.get_finish_time(){
|
||||
Some(time)=>println!("[with idle] finish time:{}",time),
|
||||
None=>println!("[with idle] simulation did not end in finished state"),
|
||||
}
|
||||
match physics_filtered.get_finish_time(){
|
||||
Some(time)=>println!("[filtered] finish time:{}",time),
|
||||
None=>println!("[filtered] simulation did not end in finished state"),
|
||||
}
|
||||
DeterminismResult::Deterministic
|
||||
}
|
||||
type ThreadResult=Result<Option<DeterminismResult>,ReplayError>;
|
||||
fn read_and_run(file_path:std::path::PathBuf,physics_data:&PhysicsData)->ThreadResult{
|
||||
let data=read_entire_file(file_path.as_path())?;
|
||||
let bot=strafesnet_snf::read_bot(data)?.read_all()?;
|
||||
println!("Running {:?}",file_path.file_stem());
|
||||
Ok(Some(segment_determinism(bot,physics_data)))
|
||||
}
|
||||
fn do_thread<'a>(s:&'a std::thread::Scope<'a,'_>,file_path:std::path::PathBuf,send:std::sync::mpsc::Sender<ThreadResult>,physics_data:&'a PhysicsData){
|
||||
s.spawn(move ||{
|
||||
let result=read_and_run(file_path,physics_data);
|
||||
// send when thread is complete
|
||||
send.send(result).unwrap();
|
||||
});
|
||||
}
|
||||
fn get_file_path(dir_entry:std::fs::DirEntry)->Result<Option<std::path::PathBuf>,std::io::Error>{
|
||||
Ok(dir_entry.file_type()?.is_file().then_some(
|
||||
dir_entry.path()
|
||||
))
|
||||
}
|
||||
fn test_determinism()->Result<(),ReplayError>{
|
||||
let thread_limit=std::thread::available_parallelism()?.get();
|
||||
println!("loading map file..");
|
||||
let data=read_entire_file("../tools/bhop_maps/5692113331.snfm")?;
|
||||
let map=strafesnet_snf::read_map(data)?.into_complete_map()?;
|
||||
|
||||
let mut physics_data=PhysicsData::default();
|
||||
println!("generating models..");
|
||||
physics_data.generate_models(&map);
|
||||
|
||||
let (send,recv)=std::sync::mpsc::channel();
|
||||
|
||||
let mut read_dir=std::fs::read_dir("../tools/replays")?;
|
||||
|
||||
// promise that &physics_data will outlive the spawned threads
|
||||
let thread_results=std::thread::scope(|s|{
|
||||
let mut thread_results=Vec::new();
|
||||
|
||||
// spawn threads
|
||||
println!("spawning up to {thread_limit} threads...");
|
||||
let mut active_thread_count=0;
|
||||
while active_thread_count<thread_limit{
|
||||
if let Some(dir_entry_result)=read_dir.next(){
|
||||
if let Some(file_path)=get_file_path(dir_entry_result?)?{
|
||||
active_thread_count+=1;
|
||||
do_thread(s,file_path,send.clone(),&physics_data);
|
||||
}
|
||||
}else{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// spawn another thread every time a message is received from the channel
|
||||
println!("riding parallelism wave...");
|
||||
while let Some(dir_entry_result)=read_dir.next(){
|
||||
if let Some(file_path)=get_file_path(dir_entry_result?)?{
|
||||
// wait for a thread to complete
|
||||
thread_results.push(recv.recv().unwrap());
|
||||
do_thread(s,file_path,send.clone(),&physics_data);
|
||||
}
|
||||
}
|
||||
|
||||
// wait for remaining threads to complete
|
||||
println!("waiting for all threads to complete...");
|
||||
for _ in 0..active_thread_count{
|
||||
thread_results.push(recv.recv().unwrap());
|
||||
}
|
||||
|
||||
println!("done.");
|
||||
Ok::<_,ReplayError>(thread_results)
|
||||
})?;
|
||||
|
||||
// tally results
|
||||
#[derive(Default)]
|
||||
struct Totals{
|
||||
deterministic:u32,
|
||||
nondeterministic:u32,
|
||||
invalid:u32,
|
||||
error:u32,
|
||||
}
|
||||
let Totals{deterministic,nondeterministic,invalid,error}=thread_results.into_iter().fold(Totals::default(),|mut totals,result|{
|
||||
match result{
|
||||
Ok(Some(DeterminismResult::Deterministic))=>totals.deterministic+=1,
|
||||
Ok(Some(DeterminismResult::NonDeterministic))=>totals.nondeterministic+=1,
|
||||
Ok(None)=>totals.invalid+=1,
|
||||
Err(_)=>totals.error+=1,
|
||||
}
|
||||
totals
|
||||
});
|
||||
|
||||
println!("deterministic={deterministic}");
|
||||
println!("nondeterministic={nondeterministic}");
|
||||
println!("invalid={invalid}");
|
||||
println!("error={error}");
|
||||
|
||||
assert!(nondeterministic==0);
|
||||
assert!(invalid==0);
|
||||
assert!(error==0);
|
||||
|
||||
Ok(())
|
||||
}
|
@ -1,150 +1,127 @@
|
||||
use strafesnet_common::{gameplay_attributes, integer, map, model};
|
||||
use strafesnet_common::{map,model,integer,gameplay_attributes};
|
||||
|
||||
const VALVE_SCALE: f32 = 1.0 / 16.0;
|
||||
fn valve_transform([x, y, z]: [f32; 3]) -> integer::Planar64Vec3 {
|
||||
integer::vec3::try_from_f32_array([x * VALVE_SCALE, z * VALVE_SCALE, -y * VALVE_SCALE]).unwrap()
|
||||
const VALVE_SCALE:f32=1.0/16.0;
|
||||
fn valve_transform([x,y,z]:[f32;3])->integer::Planar64Vec3{
|
||||
integer::vec3::try_from_f32_array([x*VALVE_SCALE,z*VALVE_SCALE,-y*VALVE_SCALE]).unwrap()
|
||||
}
|
||||
pub fn convert_bsp<AcquireRenderConfigId, AcquireMeshId>(
|
||||
bsp: &vbsp::Bsp,
|
||||
mut acquire_render_config_id: AcquireRenderConfigId,
|
||||
mut acquire_mesh_id: AcquireMeshId,
|
||||
) -> PartialMap1
|
||||
pub fn convert_bsp<AcquireRenderConfigId,AcquireMeshId>(
|
||||
bsp:&vbsp::Bsp,
|
||||
mut acquire_render_config_id:AcquireRenderConfigId,
|
||||
mut acquire_mesh_id:AcquireMeshId
|
||||
)->PartialMap1
|
||||
where
|
||||
AcquireRenderConfigId: FnMut(Option<&str>) -> model::RenderConfigId,
|
||||
AcquireMeshId: FnMut(&str) -> model::MeshId,
|
||||
AcquireRenderConfigId:FnMut(Option<&str>)->model::RenderConfigId,
|
||||
AcquireMeshId:FnMut(&str)->model::MeshId,
|
||||
{
|
||||
//figure out real attributes later
|
||||
let mut unique_attributes = Vec::new();
|
||||
unique_attributes.push(gameplay_attributes::CollisionAttributes::Decoration);
|
||||
const TEMP_TOUCH_ME_ATTRIBUTE: gameplay_attributes::CollisionAttributesId =
|
||||
gameplay_attributes::CollisionAttributesId::new(0);
|
||||
//figure out real attributes later
|
||||
let mut unique_attributes=Vec::new();
|
||||
unique_attributes.push(gameplay_attributes::CollisionAttributes::Decoration);
|
||||
const TEMP_TOUCH_ME_ATTRIBUTE:gameplay_attributes::CollisionAttributesId=gameplay_attributes::CollisionAttributesId::new(0);
|
||||
|
||||
let mut prop_mesh_count = 0;
|
||||
//declare all prop models to Loader
|
||||
let prop_models = bsp
|
||||
.static_props()
|
||||
.map(|prop| {
|
||||
//get or create mesh_id
|
||||
let mesh_id = acquire_mesh_id(prop.model());
|
||||
//not the most failsafe code but this is just for the map tool lmao
|
||||
if prop_mesh_count == mesh_id.get() {
|
||||
prop_mesh_count += 1;
|
||||
};
|
||||
let placement = prop.as_prop_placement();
|
||||
model::Model {
|
||||
mesh: mesh_id,
|
||||
attributes: TEMP_TOUCH_ME_ATTRIBUTE,
|
||||
transform: integer::Planar64Affine3::new(
|
||||
integer::mat3::try_from_f32_array_2d(
|
||||
(glam::Mat3A::from_diagonal(glam::Vec3::splat(placement.scale))
|
||||
let mut prop_mesh_count=0;
|
||||
//declare all prop models to Loader
|
||||
let prop_models=bsp.static_props().map(|prop|{
|
||||
//get or create mesh_id
|
||||
let mesh_id=acquire_mesh_id(prop.model());
|
||||
//not the most failsafe code but this is just for the map tool lmao
|
||||
if prop_mesh_count==mesh_id.get(){
|
||||
prop_mesh_count+=1;
|
||||
};
|
||||
let placement=prop.as_prop_placement();
|
||||
model::Model{
|
||||
mesh:mesh_id,
|
||||
attributes:TEMP_TOUCH_ME_ATTRIBUTE,
|
||||
transform:integer::Planar64Affine3::new(
|
||||
integer::mat3::try_from_f32_array_2d((
|
||||
glam::Mat3A::from_diagonal(glam::Vec3::splat(placement.scale))
|
||||
//TODO: figure this out
|
||||
*glam::Mat3A::from_quat(glam::Quat::from_array(placement.rotation.into())))
|
||||
.to_cols_array_2d(),
|
||||
)
|
||||
.unwrap(),
|
||||
valve_transform(placement.origin.into()),
|
||||
),
|
||||
color: glam::Vec4::ONE,
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
*glam::Mat3A::from_quat(glam::Quat::from_array(placement.rotation.into()))
|
||||
).to_cols_array_2d()).unwrap(),
|
||||
valve_transform(placement.origin.into()),
|
||||
),
|
||||
color:glam::Vec4::ONE,
|
||||
}
|
||||
}).collect();
|
||||
|
||||
//TODO: make the main map one single mesh with a bunch of different physics groups and graphics groups
|
||||
//TODO: make the main map one single mesh with a bunch of different physics groups and graphics groups
|
||||
|
||||
//the generated MeshIds in here will collide with the Loader Mesh Ids
|
||||
//but I can't think of a good workaround other than just remapping one later.
|
||||
let world_meshes: Vec<model::Mesh> = bsp
|
||||
.models()
|
||||
.map(|world_model| {
|
||||
//non-deduplicated
|
||||
let mut spam_pos = Vec::new();
|
||||
let mut spam_tex = Vec::new();
|
||||
let mut spam_normal = Vec::new();
|
||||
let mut spam_vertices = Vec::new();
|
||||
let mut graphics_groups = Vec::new();
|
||||
let mut physics_group = model::IndexedPhysicsGroup::default();
|
||||
let polygon_groups = world_model
|
||||
.faces()
|
||||
.enumerate()
|
||||
.map(|(polygon_group_id, face)| {
|
||||
let polygon_group_id = model::PolygonGroupId::new(polygon_group_id as u32);
|
||||
let face_texture = face.texture();
|
||||
let face_texture_data = face_texture.texture_data();
|
||||
//this would be better as a 4x2 matrix
|
||||
let texture_transform_u =
|
||||
glam::Vec4::from_array(face_texture.texture_transforms_u)
|
||||
/ (face_texture_data.width as f32);
|
||||
let texture_transform_v =
|
||||
glam::Vec4::from_array(face_texture.texture_transforms_v)
|
||||
/ (face_texture_data.height as f32);
|
||||
//the generated MeshIds in here will collide with the Loader Mesh Ids
|
||||
//but I can't think of a good workaround other than just remapping one later.
|
||||
let world_meshes:Vec<model::Mesh>=bsp.models().map(|world_model|{
|
||||
//non-deduplicated
|
||||
let mut spam_pos=Vec::new();
|
||||
let mut spam_tex=Vec::new();
|
||||
let mut spam_normal=Vec::new();
|
||||
let mut spam_vertices=Vec::new();
|
||||
let mut graphics_groups=Vec::new();
|
||||
let mut physics_group=model::IndexedPhysicsGroup::default();
|
||||
let polygon_groups=world_model.faces().enumerate().map(|(polygon_group_id,face)|{
|
||||
let polygon_group_id=model::PolygonGroupId::new(polygon_group_id as u32);
|
||||
let face_texture=face.texture();
|
||||
let face_texture_data=face_texture.texture_data();
|
||||
//this would be better as a 4x2 matrix
|
||||
let texture_transform_u=glam::Vec4::from_array(face_texture.texture_transforms_u)/(face_texture_data.width as f32);
|
||||
let texture_transform_v=glam::Vec4::from_array(face_texture.texture_transforms_v)/(face_texture_data.height as f32);
|
||||
|
||||
//this automatically figures out what the texture is trying to do and creates
|
||||
//a render config for it, and then returns the id to that render config
|
||||
let render_id = acquire_render_config_id(Some(face_texture_data.name()));
|
||||
//this automatically figures out what the texture is trying to do and creates
|
||||
//a render config for it, and then returns the id to that render config
|
||||
let render_id=acquire_render_config_id(Some(face_texture_data.name()));
|
||||
|
||||
//normal
|
||||
let normal = face.normal();
|
||||
let normal_idx = spam_normal.len() as u32;
|
||||
spam_normal.push(valve_transform(normal.into()));
|
||||
let mut polygon_iter = face.vertex_positions().map(|vertex_position| {
|
||||
//world_model.origin seems to always be 0,0,0
|
||||
let vertex_xyz = (world_model.origin + vertex_position).into();
|
||||
let pos_idx = spam_pos.len();
|
||||
spam_pos.push(valve_transform(vertex_xyz));
|
||||
//normal
|
||||
let normal=face.normal();
|
||||
let normal_idx=spam_normal.len() as u32;
|
||||
spam_normal.push(valve_transform(normal.into()));
|
||||
let mut polygon_iter=face.vertex_positions().map(|vertex_position|{
|
||||
//world_model.origin seems to always be 0,0,0
|
||||
let vertex_xyz=(world_model.origin+vertex_position).into();
|
||||
let pos_idx=spam_pos.len();
|
||||
spam_pos.push(valve_transform(vertex_xyz));
|
||||
|
||||
//calculate texture coordinates
|
||||
let pos = glam::Vec3::from_array(vertex_xyz).extend(1.0);
|
||||
let tex =
|
||||
glam::vec2(texture_transform_u.dot(pos), texture_transform_v.dot(pos));
|
||||
let tex_idx = spam_tex.len() as u32;
|
||||
spam_tex.push(tex);
|
||||
//calculate texture coordinates
|
||||
let pos=glam::Vec3::from_array(vertex_xyz).extend(1.0);
|
||||
let tex=glam::vec2(texture_transform_u.dot(pos),texture_transform_v.dot(pos));
|
||||
let tex_idx=spam_tex.len() as u32;
|
||||
spam_tex.push(tex);
|
||||
|
||||
let vertex_id = model::VertexId::new(spam_vertices.len() as u32);
|
||||
spam_vertices.push(model::IndexedVertex {
|
||||
pos: model::PositionId::new(pos_idx as u32),
|
||||
tex: model::TextureCoordinateId::new(tex_idx as u32),
|
||||
normal: model::NormalId::new(normal_idx),
|
||||
color: model::ColorId::new(0),
|
||||
});
|
||||
vertex_id
|
||||
});
|
||||
let polygon_list = std::iter::from_fn(move || {
|
||||
match (
|
||||
polygon_iter.next(),
|
||||
polygon_iter.next(),
|
||||
polygon_iter.next(),
|
||||
) {
|
||||
(Some(v1), Some(v2), Some(v3)) => Some(vec![v1, v2, v3]),
|
||||
//ignore extra vertices, not sure what to do in this case, failing the whole conversion could be appropriate
|
||||
_ => None,
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
if face.is_visible() {
|
||||
//TODO: deduplicate graphics groups by render id
|
||||
graphics_groups.push(model::IndexedGraphicsGroup {
|
||||
render: render_id,
|
||||
groups: vec![polygon_group_id],
|
||||
})
|
||||
}
|
||||
physics_group.groups.push(polygon_group_id);
|
||||
model::PolygonGroup::PolygonList(model::PolygonList::new(polygon_list))
|
||||
})
|
||||
.collect();
|
||||
model::Mesh {
|
||||
unique_pos: spam_pos,
|
||||
unique_tex: spam_tex,
|
||||
unique_normal: spam_normal,
|
||||
unique_color: vec![glam::Vec4::ONE],
|
||||
unique_vertices: spam_vertices,
|
||||
polygon_groups,
|
||||
graphics_groups,
|
||||
physics_groups: vec![physics_group],
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
let vertex_id=model::VertexId::new(spam_vertices.len() as u32);
|
||||
spam_vertices.push(model::IndexedVertex{
|
||||
pos:model::PositionId::new(pos_idx as u32),
|
||||
tex:model::TextureCoordinateId::new(tex_idx as u32),
|
||||
normal:model::NormalId::new(normal_idx),
|
||||
color:model::ColorId::new(0),
|
||||
});
|
||||
vertex_id
|
||||
});
|
||||
let polygon_list=std::iter::from_fn(move||{
|
||||
match (polygon_iter.next(),polygon_iter.next(),polygon_iter.next()){
|
||||
(Some(v1),Some(v2),Some(v3))=>Some(vec![v1,v2,v3]),
|
||||
//ignore extra vertices, not sure what to do in this case, failing the whole conversion could be appropriate
|
||||
_=>None,
|
||||
}
|
||||
}).collect();
|
||||
if face.is_visible(){
|
||||
//TODO: deduplicate graphics groups by render id
|
||||
graphics_groups.push(model::IndexedGraphicsGroup{
|
||||
render:render_id,
|
||||
groups:vec![polygon_group_id],
|
||||
})
|
||||
}
|
||||
physics_group.groups.push(polygon_group_id);
|
||||
model::PolygonGroup::PolygonList(model::PolygonList::new(polygon_list))
|
||||
}).collect();
|
||||
model::Mesh{
|
||||
unique_pos:spam_pos,
|
||||
unique_tex:spam_tex,
|
||||
unique_normal:spam_normal,
|
||||
unique_color:vec![glam::Vec4::ONE],
|
||||
unique_vertices:spam_vertices,
|
||||
polygon_groups,
|
||||
graphics_groups,
|
||||
physics_groups:vec![physics_group],
|
||||
}
|
||||
}).collect();
|
||||
|
||||
let world_models:Vec<model::Model>=
|
||||
let world_models:Vec<model::Model>=
|
||||
//one instance of the main world mesh
|
||||
std::iter::once((
|
||||
//world_model
|
||||
@ -181,35 +158,35 @@ where
|
||||
}
|
||||
}).collect();
|
||||
|
||||
PartialMap1 {
|
||||
attributes: unique_attributes,
|
||||
world_meshes,
|
||||
prop_models,
|
||||
world_models,
|
||||
modes: strafesnet_common::gameplay_modes::Modes::new(Vec::new()),
|
||||
}
|
||||
PartialMap1{
|
||||
attributes:unique_attributes,
|
||||
world_meshes,
|
||||
prop_models,
|
||||
world_models,
|
||||
modes:strafesnet_common::gameplay_modes::Modes::new(Vec::new()),
|
||||
}
|
||||
}
|
||||
|
||||
//partially constructed map types
|
||||
pub struct PartialMap1 {
|
||||
attributes: Vec<strafesnet_common::gameplay_attributes::CollisionAttributes>,
|
||||
prop_models: Vec<model::Model>,
|
||||
world_meshes: Vec<model::Mesh>,
|
||||
world_models: Vec<model::Model>,
|
||||
modes: strafesnet_common::gameplay_modes::Modes,
|
||||
pub struct PartialMap1{
|
||||
attributes:Vec<strafesnet_common::gameplay_attributes::CollisionAttributes>,
|
||||
prop_models:Vec<model::Model>,
|
||||
world_meshes:Vec<model::Mesh>,
|
||||
world_models:Vec<model::Model>,
|
||||
modes:strafesnet_common::gameplay_modes::Modes,
|
||||
}
|
||||
impl PartialMap1 {
|
||||
pub fn add_prop_meshes<AcquireRenderConfigId>(
|
||||
self,
|
||||
prop_meshes: impl IntoIterator<Item = (model::MeshId, crate::data::ModelData)>,
|
||||
mut acquire_render_config_id: AcquireRenderConfigId,
|
||||
) -> PartialMap2
|
||||
where
|
||||
AcquireRenderConfigId: FnMut(Option<&str>) -> model::RenderConfigId,
|
||||
{
|
||||
PartialMap2 {
|
||||
attributes: self.attributes,
|
||||
prop_meshes: prop_meshes.into_iter().filter_map(|(mesh_id,model_data)|
|
||||
impl PartialMap1{
|
||||
pub fn add_prop_meshes<AcquireRenderConfigId>(
|
||||
self,
|
||||
prop_meshes:impl IntoIterator<Item=(model::MeshId,crate::data::ModelData)>,
|
||||
mut acquire_render_config_id:AcquireRenderConfigId,
|
||||
)->PartialMap2
|
||||
where
|
||||
AcquireRenderConfigId:FnMut(Option<&str>)->model::RenderConfigId,
|
||||
{
|
||||
PartialMap2{
|
||||
attributes:self.attributes,
|
||||
prop_meshes:prop_meshes.into_iter().filter_map(|(mesh_id,model_data)|
|
||||
//this will generate new render ids and texture ids
|
||||
match convert_mesh(model_data,&mut acquire_render_config_id){
|
||||
Ok(mesh)=>Some((mesh_id,mesh)),
|
||||
@ -219,175 +196,138 @@ impl PartialMap1 {
|
||||
}
|
||||
}
|
||||
).collect(),
|
||||
prop_models: self.prop_models,
|
||||
world_meshes: self.world_meshes,
|
||||
world_models: self.world_models,
|
||||
modes: self.modes,
|
||||
}
|
||||
}
|
||||
prop_models:self.prop_models,
|
||||
world_meshes:self.world_meshes,
|
||||
world_models:self.world_models,
|
||||
modes:self.modes,
|
||||
}
|
||||
}
|
||||
}
|
||||
pub struct PartialMap2 {
|
||||
attributes: Vec<strafesnet_common::gameplay_attributes::CollisionAttributes>,
|
||||
prop_meshes: Vec<(model::MeshId, model::Mesh)>,
|
||||
prop_models: Vec<model::Model>,
|
||||
world_meshes: Vec<model::Mesh>,
|
||||
world_models: Vec<model::Model>,
|
||||
modes: strafesnet_common::gameplay_modes::Modes,
|
||||
pub struct PartialMap2{
|
||||
attributes:Vec<strafesnet_common::gameplay_attributes::CollisionAttributes>,
|
||||
prop_meshes:Vec<(model::MeshId,model::Mesh)>,
|
||||
prop_models:Vec<model::Model>,
|
||||
world_meshes:Vec<model::Mesh>,
|
||||
world_models:Vec<model::Model>,
|
||||
modes:strafesnet_common::gameplay_modes::Modes,
|
||||
}
|
||||
impl PartialMap2 {
|
||||
pub fn add_render_configs_and_textures(
|
||||
mut self,
|
||||
render_configs: impl IntoIterator<Item = (model::RenderConfigId, model::RenderConfig)>,
|
||||
textures: impl IntoIterator<Item = (model::TextureId, Vec<u8>)>,
|
||||
) -> map::CompleteMap {
|
||||
//merge mesh and model lists, flatten and remap all ids
|
||||
let mesh_id_offset = self.world_meshes.len();
|
||||
println!("prop_meshes.len()={}", self.prop_meshes.len());
|
||||
let (mut prop_meshes, prop_mesh_id_map): (
|
||||
Vec<model::Mesh>,
|
||||
std::collections::HashMap<model::MeshId, model::MeshId>,
|
||||
) = self
|
||||
.prop_meshes
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
.map(|(new_mesh_id, (old_mesh_id, mesh))| {
|
||||
(
|
||||
mesh,
|
||||
(
|
||||
old_mesh_id,
|
||||
model::MeshId::new((mesh_id_offset + new_mesh_id) as u32),
|
||||
),
|
||||
)
|
||||
})
|
||||
.unzip();
|
||||
self.world_meshes.append(&mut prop_meshes);
|
||||
//there is no modes or runtime behaviour with references to the model ids currently
|
||||
//so just relentlessly cull them if the mesh is missing
|
||||
self.world_models
|
||||
.extend(self.prop_models.into_iter().filter_map(|mut model| {
|
||||
prop_mesh_id_map.get(&model.mesh).map(|&new_mesh_id| {
|
||||
model.mesh = new_mesh_id;
|
||||
model
|
||||
})
|
||||
}));
|
||||
//let mut models=Vec::new();
|
||||
let (textures, texture_id_map): (
|
||||
Vec<Vec<u8>>,
|
||||
std::collections::HashMap<model::TextureId, model::TextureId>,
|
||||
) = textures
|
||||
.into_iter()
|
||||
//.filter_map(f) cull unused textures
|
||||
.enumerate()
|
||||
.map(|(new_texture_id, (old_texture_id, texture))| {
|
||||
(
|
||||
texture,
|
||||
(old_texture_id, model::TextureId::new(new_texture_id as u32)),
|
||||
)
|
||||
})
|
||||
.unzip();
|
||||
let render_configs = render_configs
|
||||
.into_iter()
|
||||
.map(|(render_config_id, mut render_config)| {
|
||||
//this may generate duplicate no-texture render configs but idc
|
||||
render_config.texture = render_config
|
||||
.texture
|
||||
.and_then(|texture_id| texture_id_map.get(&texture_id).copied());
|
||||
render_config
|
||||
})
|
||||
.collect();
|
||||
map::CompleteMap {
|
||||
modes: self.modes,
|
||||
attributes: self.attributes,
|
||||
meshes: self.world_meshes,
|
||||
models: self.world_models,
|
||||
textures,
|
||||
render_configs,
|
||||
}
|
||||
}
|
||||
impl PartialMap2{
|
||||
pub fn add_render_configs_and_textures(
|
||||
mut self,
|
||||
render_configs:impl IntoIterator<Item=(model::RenderConfigId,model::RenderConfig)>,
|
||||
textures:impl IntoIterator<Item=(model::TextureId,Vec<u8>)>,
|
||||
)->map::CompleteMap{
|
||||
//merge mesh and model lists, flatten and remap all ids
|
||||
let mesh_id_offset=self.world_meshes.len();
|
||||
println!("prop_meshes.len()={}",self.prop_meshes.len());
|
||||
let (mut prop_meshes,prop_mesh_id_map):(Vec<model::Mesh>,std::collections::HashMap<model::MeshId,model::MeshId>)
|
||||
=self.prop_meshes.into_iter().enumerate().map(|(new_mesh_id,(old_mesh_id,mesh))|{
|
||||
(mesh,(old_mesh_id,model::MeshId::new((mesh_id_offset+new_mesh_id) as u32)))
|
||||
}).unzip();
|
||||
self.world_meshes.append(&mut prop_meshes);
|
||||
//there is no modes or runtime behaviour with references to the model ids currently
|
||||
//so just relentlessly cull them if the mesh is missing
|
||||
self.world_models.extend(self.prop_models.into_iter().filter_map(|mut model|
|
||||
prop_mesh_id_map.get(&model.mesh).map(|&new_mesh_id|{
|
||||
model.mesh=new_mesh_id;
|
||||
model
|
||||
})
|
||||
));
|
||||
//let mut models=Vec::new();
|
||||
let (textures,texture_id_map):(Vec<Vec<u8>>,std::collections::HashMap<model::TextureId,model::TextureId>)
|
||||
=textures.into_iter()
|
||||
//.filter_map(f) cull unused textures
|
||||
.enumerate().map(|(new_texture_id,(old_texture_id,texture))|{
|
||||
(texture,(old_texture_id,model::TextureId::new(new_texture_id as u32)))
|
||||
}).unzip();
|
||||
let render_configs=render_configs.into_iter().map(|(_render_config_id,mut render_config)|{
|
||||
//this may generate duplicate no-texture render configs but idc
|
||||
render_config.texture=render_config.texture.and_then(|texture_id|
|
||||
texture_id_map.get(&texture_id).copied()
|
||||
);
|
||||
render_config
|
||||
}).collect();
|
||||
map::CompleteMap{
|
||||
modes:self.modes,
|
||||
attributes:self.attributes,
|
||||
meshes:self.world_meshes,
|
||||
models:self.world_models,
|
||||
textures,
|
||||
render_configs,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn convert_mesh<AcquireRenderConfigId>(
|
||||
model_data: crate::data::ModelData,
|
||||
acquire_render_config_id: &mut AcquireRenderConfigId,
|
||||
) -> Result<model::Mesh, vmdl::ModelError>
|
||||
model_data:crate::data::ModelData,
|
||||
acquire_render_config_id:&mut AcquireRenderConfigId,
|
||||
)->Result<model::Mesh,vmdl::ModelError>
|
||||
where
|
||||
AcquireRenderConfigId: FnMut(Option<&str>) -> model::RenderConfigId,
|
||||
AcquireRenderConfigId:FnMut(Option<&str>)->model::RenderConfigId,
|
||||
{
|
||||
let model = model_data.read_model()?;
|
||||
let texture_paths = model.texture_directories();
|
||||
if texture_paths.len() != 1 {
|
||||
println!("WARNING: multiple texture paths");
|
||||
}
|
||||
let skin = model.skin_tables().nth(0).unwrap();
|
||||
let model=model_data.read_model()?;
|
||||
let texture_paths=model.texture_directories();
|
||||
if texture_paths.len()!=1{
|
||||
println!("WARNING: multiple texture paths");
|
||||
}
|
||||
let skin=model.skin_tables().nth(0).unwrap();
|
||||
|
||||
let mut spam_pos = Vec::with_capacity(model.vertices().len());
|
||||
let mut spam_normal = Vec::with_capacity(model.vertices().len());
|
||||
let mut spam_tex = Vec::with_capacity(model.vertices().len());
|
||||
let mut spam_vertices = Vec::with_capacity(model.vertices().len());
|
||||
for (i, vertex) in model.vertices().iter().enumerate() {
|
||||
spam_pos.push(valve_transform(vertex.position.into()));
|
||||
spam_normal.push(valve_transform(vertex.normal.into()));
|
||||
spam_tex.push(glam::Vec2::from_array(vertex.texture_coordinates));
|
||||
spam_vertices.push(model::IndexedVertex {
|
||||
pos: model::PositionId::new(i as u32),
|
||||
tex: model::TextureCoordinateId::new(i as u32),
|
||||
normal: model::NormalId::new(i as u32),
|
||||
color: model::ColorId::new(0),
|
||||
});
|
||||
}
|
||||
let mut graphics_groups = Vec::new();
|
||||
let mut physics_groups = Vec::new();
|
||||
let polygon_groups = model
|
||||
.meshes()
|
||||
.enumerate()
|
||||
.map(|(polygon_group_id, mesh)| {
|
||||
let polygon_group_id = model::PolygonGroupId::new(polygon_group_id as u32);
|
||||
let mut spam_pos=Vec::with_capacity(model.vertices().len());
|
||||
let mut spam_normal=Vec::with_capacity(model.vertices().len());
|
||||
let mut spam_tex=Vec::with_capacity(model.vertices().len());
|
||||
let mut spam_vertices=Vec::with_capacity(model.vertices().len());
|
||||
for (i,vertex) in model.vertices().iter().enumerate(){
|
||||
spam_pos.push(valve_transform(vertex.position.into()));
|
||||
spam_normal.push(valve_transform(vertex.normal.into()));
|
||||
spam_tex.push(glam::Vec2::from_array(vertex.texture_coordinates));
|
||||
spam_vertices.push(model::IndexedVertex{
|
||||
pos:model::PositionId::new(i as u32),
|
||||
tex:model::TextureCoordinateId::new(i as u32),
|
||||
normal:model::NormalId::new(i as u32),
|
||||
color:model::ColorId::new(0),
|
||||
});
|
||||
}
|
||||
let mut graphics_groups=Vec::new();
|
||||
let mut physics_groups=Vec::new();
|
||||
let polygon_groups=model.meshes().enumerate().map(|(polygon_group_id,mesh)|{
|
||||
let polygon_group_id=model::PolygonGroupId::new(polygon_group_id as u32);
|
||||
|
||||
let render_id = if let (Some(texture_path), Some(texture_name)) =
|
||||
(texture_paths.get(0), skin.texture(mesh.material_index()))
|
||||
{
|
||||
let mut path = std::path::PathBuf::from(texture_path.as_str());
|
||||
path.push(texture_name);
|
||||
acquire_render_config_id(path.as_os_str().to_str())
|
||||
} else {
|
||||
acquire_render_config_id(None)
|
||||
};
|
||||
let render_id=if let (Some(texture_path),Some(texture_name))=(texture_paths.get(0),skin.texture(mesh.material_index())){
|
||||
let mut path=std::path::PathBuf::from(texture_path.as_str());
|
||||
path.push(texture_name);
|
||||
acquire_render_config_id(path.as_os_str().to_str())
|
||||
}else{
|
||||
acquire_render_config_id(None)
|
||||
};
|
||||
|
||||
graphics_groups.push(model::IndexedGraphicsGroup {
|
||||
render: render_id,
|
||||
groups: vec![polygon_group_id],
|
||||
});
|
||||
physics_groups.push(model::IndexedPhysicsGroup {
|
||||
groups: vec![polygon_group_id],
|
||||
});
|
||||
model::PolygonGroup::PolygonList(model::PolygonList::new(
|
||||
//looking at the code, it would seem that the strips are pre-deindexed into triangle lists when calling this function
|
||||
mesh.vertex_strip_indices()
|
||||
.flat_map(|mut strip| {
|
||||
std::iter::from_fn(move || {
|
||||
match (strip.next(), strip.next(), strip.next()) {
|
||||
(Some(v1), Some(v2), Some(v3)) => Some(
|
||||
[v1, v2, v3]
|
||||
.map(|vertex_id| model::VertexId::new(vertex_id as u32))
|
||||
.to_vec(),
|
||||
),
|
||||
//ignore extra vertices, not sure what to do in this case, failing the whole conversion could be appropriate
|
||||
_ => None,
|
||||
}
|
||||
})
|
||||
})
|
||||
.collect(),
|
||||
))
|
||||
})
|
||||
.collect();
|
||||
Ok(model::Mesh {
|
||||
unique_pos: spam_pos,
|
||||
unique_normal: spam_normal,
|
||||
unique_tex: spam_tex,
|
||||
unique_color: vec![glam::Vec4::ONE],
|
||||
unique_vertices: spam_vertices,
|
||||
polygon_groups,
|
||||
graphics_groups,
|
||||
physics_groups,
|
||||
})
|
||||
graphics_groups.push(model::IndexedGraphicsGroup{
|
||||
render:render_id,
|
||||
groups:vec![polygon_group_id],
|
||||
});
|
||||
physics_groups.push(model::IndexedPhysicsGroup{
|
||||
groups:vec![polygon_group_id],
|
||||
});
|
||||
model::PolygonGroup::PolygonList(model::PolygonList::new(
|
||||
//looking at the code, it would seem that the strips are pre-deindexed into triangle lists when calling this function
|
||||
mesh.vertex_strip_indices().flat_map(|mut strip|
|
||||
std::iter::from_fn(move||{
|
||||
match (strip.next(),strip.next(),strip.next()){
|
||||
(Some(v1),Some(v2),Some(v3))=>Some([v1,v2,v3].map(|vertex_id|model::VertexId::new(vertex_id as u32)).to_vec()),
|
||||
//ignore extra vertices, not sure what to do in this case, failing the whole conversion could be appropriate
|
||||
_=>None,
|
||||
}
|
||||
})
|
||||
).collect()
|
||||
))
|
||||
}).collect();
|
||||
Ok(model::Mesh{
|
||||
unique_pos:spam_pos,
|
||||
unique_normal:spam_normal,
|
||||
unique_tex:spam_tex,
|
||||
unique_color:vec![glam::Vec4::ONE],
|
||||
unique_vertices:spam_vertices,
|
||||
polygon_groups,
|
||||
graphics_groups,
|
||||
physics_groups,
|
||||
})
|
||||
}
|
||||
|
@ -1,60 +1,60 @@
|
||||
pub struct Bsp(vbsp::Bsp);
|
||||
impl Bsp {
|
||||
pub const fn new(value: vbsp::Bsp) -> Self {
|
||||
Self(value)
|
||||
}
|
||||
impl Bsp{
|
||||
pub const fn new(value:vbsp::Bsp)->Self{
|
||||
Self(value)
|
||||
}
|
||||
}
|
||||
impl AsRef<vbsp::Bsp> for Bsp {
|
||||
fn as_ref(&self) -> &vbsp::Bsp {
|
||||
&self.0
|
||||
}
|
||||
impl AsRef<vbsp::Bsp> for Bsp{
|
||||
fn as_ref(&self)->&vbsp::Bsp{
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
pub struct MdlData(Vec<u8>);
|
||||
impl MdlData {
|
||||
pub const fn new(value: Vec<u8>) -> Self {
|
||||
Self(value)
|
||||
}
|
||||
impl MdlData{
|
||||
pub const fn new(value:Vec<u8>)->Self{
|
||||
Self(value)
|
||||
}
|
||||
}
|
||||
impl AsRef<[u8]> for MdlData {
|
||||
fn as_ref(&self) -> &[u8] {
|
||||
self.0.as_ref()
|
||||
}
|
||||
impl AsRef<[u8]> for MdlData{
|
||||
fn as_ref(&self)->&[u8]{
|
||||
self.0.as_ref()
|
||||
}
|
||||
}
|
||||
pub struct VtxData(Vec<u8>);
|
||||
impl VtxData {
|
||||
pub const fn new(value: Vec<u8>) -> Self {
|
||||
Self(value)
|
||||
}
|
||||
impl VtxData{
|
||||
pub const fn new(value:Vec<u8>)->Self{
|
||||
Self(value)
|
||||
}
|
||||
}
|
||||
impl AsRef<[u8]> for VtxData {
|
||||
fn as_ref(&self) -> &[u8] {
|
||||
self.0.as_ref()
|
||||
}
|
||||
impl AsRef<[u8]> for VtxData{
|
||||
fn as_ref(&self)->&[u8]{
|
||||
self.0.as_ref()
|
||||
}
|
||||
}
|
||||
pub struct VvdData(Vec<u8>);
|
||||
impl VvdData {
|
||||
pub const fn new(value: Vec<u8>) -> Self {
|
||||
Self(value)
|
||||
}
|
||||
impl VvdData{
|
||||
pub const fn new(value:Vec<u8>)->Self{
|
||||
Self(value)
|
||||
}
|
||||
}
|
||||
impl AsRef<[u8]> for VvdData {
|
||||
fn as_ref(&self) -> &[u8] {
|
||||
self.0.as_ref()
|
||||
}
|
||||
impl AsRef<[u8]> for VvdData{
|
||||
fn as_ref(&self)->&[u8]{
|
||||
self.0.as_ref()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ModelData {
|
||||
pub mdl: MdlData,
|
||||
pub vtx: VtxData,
|
||||
pub vvd: VvdData,
|
||||
}
|
||||
impl ModelData {
|
||||
pub fn read_model(&self) -> Result<vmdl::Model, vmdl::ModelError> {
|
||||
Ok(vmdl::Model::from_parts(
|
||||
vmdl::mdl::Mdl::read(self.mdl.as_ref())?,
|
||||
vmdl::vtx::Vtx::read(self.vtx.as_ref())?,
|
||||
vmdl::vvd::Vvd::read(self.vvd.as_ref())?,
|
||||
))
|
||||
}
|
||||
pub struct ModelData{
|
||||
pub mdl:MdlData,
|
||||
pub vtx:VtxData,
|
||||
pub vvd:VvdData,
|
||||
}
|
||||
impl ModelData{
|
||||
pub fn read_model(&self)->Result<vmdl::Model,vmdl::ModelError>{
|
||||
Ok(vmdl::Model::from_parts(
|
||||
vmdl::mdl::Mdl::read(self.mdl.as_ref())?,
|
||||
vmdl::vtx::Vtx::read(self.vtx.as_ref())?,
|
||||
vmdl::vvd::Vvd::read(self.vvd.as_ref())?,
|
||||
))
|
||||
}
|
||||
}
|
@ -4,36 +4,34 @@ pub mod data;
|
||||
pub use data::Bsp;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum ReadError {
|
||||
Bsp(vbsp::BspError),
|
||||
Io(std::io::Error),
|
||||
pub enum ReadError{
|
||||
Bsp(vbsp::BspError),
|
||||
Io(std::io::Error),
|
||||
}
|
||||
impl std::fmt::Display for ReadError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{self:?}")
|
||||
}
|
||||
impl std::fmt::Display for ReadError{
|
||||
fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{
|
||||
write!(f,"{self:?}")
|
||||
}
|
||||
}
|
||||
impl std::error::Error for ReadError {}
|
||||
impl std::error::Error for ReadError{}
|
||||
|
||||
pub fn read<R: std::io::Read>(mut input: R) -> Result<Bsp, ReadError> {
|
||||
let mut s = Vec::new();
|
||||
pub fn read<R:std::io::Read>(mut input:R)->Result<Bsp,ReadError>{
|
||||
let mut s=Vec::new();
|
||||
|
||||
//TODO: mmap
|
||||
input.read_to_end(&mut s).map_err(ReadError::Io)?;
|
||||
//TODO: mmap
|
||||
input.read_to_end(&mut s).map_err(ReadError::Io)?;
|
||||
|
||||
vbsp::Bsp::read(s.as_slice())
|
||||
.map(Bsp::new)
|
||||
.map_err(ReadError::Bsp)
|
||||
vbsp::Bsp::read(s.as_slice()).map(Bsp::new).map_err(ReadError::Bsp)
|
||||
}
|
||||
|
||||
pub fn convert<AcquireRenderConfigId, AcquireMeshId>(
|
||||
bsp: &Bsp,
|
||||
acquire_render_config_id: AcquireRenderConfigId,
|
||||
acquire_mesh_id: AcquireMeshId,
|
||||
) -> bsp::PartialMap1
|
||||
pub fn convert<AcquireRenderConfigId,AcquireMeshId>(
|
||||
bsp:&Bsp,
|
||||
acquire_render_config_id:AcquireRenderConfigId,
|
||||
acquire_mesh_id:AcquireMeshId
|
||||
)->bsp::PartialMap1
|
||||
where
|
||||
AcquireRenderConfigId: FnMut(Option<&str>) -> strafesnet_common::model::RenderConfigId,
|
||||
AcquireMeshId: FnMut(&str) -> strafesnet_common::model::MeshId,
|
||||
AcquireRenderConfigId:FnMut(Option<&str>)->strafesnet_common::model::RenderConfigId,
|
||||
AcquireMeshId:FnMut(&str)->strafesnet_common::model::MeshId,
|
||||
{
|
||||
bsp::convert_bsp(bsp.as_ref(), acquire_render_config_id, acquire_mesh_id)
|
||||
}
|
||||
bsp::convert_bsp(bsp.as_ref(),acquire_render_config_id,acquire_mesh_id)
|
||||
}
|
@ -1,59 +1,56 @@
|
||||
use crate::integer::{vec3, Planar64Vec3};
|
||||
use crate::integer::{vec3,Planar64Vec3};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Aabb {
|
||||
min: Planar64Vec3,
|
||||
max: Planar64Vec3,
|
||||
pub struct Aabb{
|
||||
min:Planar64Vec3,
|
||||
max:Planar64Vec3,
|
||||
}
|
||||
|
||||
impl Default for Aabb {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
min: vec3::MAX,
|
||||
max: vec3::MIN,
|
||||
}
|
||||
}
|
||||
impl Default for Aabb{
|
||||
fn default()->Self{
|
||||
Self{min:vec3::MAX,max:vec3::MIN}
|
||||
}
|
||||
}
|
||||
|
||||
impl Aabb {
|
||||
pub const fn new(min: Planar64Vec3, max: Planar64Vec3) -> Self {
|
||||
Self { min, max }
|
||||
}
|
||||
pub const fn max(&self) -> Planar64Vec3 {
|
||||
self.max
|
||||
}
|
||||
pub const fn min(&self) -> Planar64Vec3 {
|
||||
self.min
|
||||
}
|
||||
pub fn grow(&mut self, point: Planar64Vec3) {
|
||||
self.min = self.min.min(point);
|
||||
self.max = self.max.max(point);
|
||||
}
|
||||
pub fn join(&mut self, aabb: &Aabb) {
|
||||
self.min = self.min.min(aabb.min);
|
||||
self.max = self.max.max(aabb.max);
|
||||
}
|
||||
pub fn inflate(&mut self, hs: Planar64Vec3) {
|
||||
self.min -= hs;
|
||||
self.max += hs;
|
||||
}
|
||||
pub fn intersects(&self, aabb: &Aabb) -> bool {
|
||||
let bvec = self.min.lt(aabb.max) & aabb.min.lt(self.max);
|
||||
bvec.all()
|
||||
}
|
||||
pub fn size(&self) -> Planar64Vec3 {
|
||||
self.max - self.min
|
||||
}
|
||||
pub fn center(&self) -> Planar64Vec3 {
|
||||
self.min + (self.max - self.min) >> 1
|
||||
}
|
||||
//probably use floats for area & volume because we don't care about precision
|
||||
// pub fn area_weight(&self)->f32{
|
||||
// let d=self.max-self.min;
|
||||
// d.x*d.y+d.y*d.z+d.z*d.x
|
||||
// }
|
||||
// pub fn volume(&self)->f32{
|
||||
// let d=self.max-self.min;
|
||||
// d.x*d.y*d.z
|
||||
// }
|
||||
impl Aabb{
|
||||
pub const fn new(min:Planar64Vec3,max:Planar64Vec3)->Self{
|
||||
Self{min,max}
|
||||
}
|
||||
pub const fn max(&self)->Planar64Vec3{
|
||||
self.max
|
||||
}
|
||||
pub const fn min(&self)->Planar64Vec3{
|
||||
self.min
|
||||
}
|
||||
pub fn grow(&mut self,point:Planar64Vec3){
|
||||
self.min=self.min.min(point);
|
||||
self.max=self.max.max(point);
|
||||
}
|
||||
pub fn join(&mut self,aabb:&Aabb){
|
||||
self.min=self.min.min(aabb.min);
|
||||
self.max=self.max.max(aabb.max);
|
||||
}
|
||||
pub fn inflate(&mut self,hs:Planar64Vec3){
|
||||
self.min-=hs;
|
||||
self.max+=hs;
|
||||
}
|
||||
pub fn intersects(&self,aabb:&Aabb)->bool{
|
||||
let bvec=self.min.lt(aabb.max)&aabb.min.lt(self.max);
|
||||
bvec.all()
|
||||
}
|
||||
pub fn size(&self)->Planar64Vec3{
|
||||
self.max-self.min
|
||||
}
|
||||
pub fn center(&self)->Planar64Vec3{
|
||||
self.min+(self.max-self.min)>>1
|
||||
}
|
||||
//probably use floats for area & volume because we don't care about precision
|
||||
// pub fn area_weight(&self)->f32{
|
||||
// let d=self.max-self.min;
|
||||
// d.x*d.y+d.y*d.z+d.z*d.x
|
||||
// }
|
||||
// pub fn volume(&self)->f32{
|
||||
// let d=self.max-self.min;
|
||||
// d.x*d.y*d.z
|
||||
// }
|
||||
}
|
||||
|
@ -10,217 +10,185 @@ use crate::aabb::Aabb;
|
||||
//sort the centerpoints on each axis (3 lists)
|
||||
//bv is put into octant based on whether it is upper or lower in each list
|
||||
|
||||
pub enum RecursiveContent<R, T> {
|
||||
Branch(Vec<R>),
|
||||
Leaf(T),
|
||||
pub enum RecursiveContent<R,T>{
|
||||
Branch(Vec<R>),
|
||||
Leaf(T),
|
||||
}
|
||||
impl<R, T> Default for RecursiveContent<R, T> {
|
||||
fn default() -> Self {
|
||||
Self::Branch(Vec::new())
|
||||
}
|
||||
impl<R,T> Default for RecursiveContent<R,T>{
|
||||
fn default()->Self{
|
||||
Self::Branch(Vec::new())
|
||||
}
|
||||
}
|
||||
pub struct BvhNode<T> {
|
||||
content: RecursiveContent<BvhNode<T>, T>,
|
||||
aabb: Aabb,
|
||||
pub struct BvhNode<T>{
|
||||
content:RecursiveContent<BvhNode<T>,T>,
|
||||
aabb:Aabb,
|
||||
}
|
||||
impl<T> Default for BvhNode<T> {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
content: Default::default(),
|
||||
aabb: Aabb::default(),
|
||||
}
|
||||
}
|
||||
impl<T> Default for BvhNode<T>{
|
||||
fn default()->Self{
|
||||
Self{
|
||||
content:Default::default(),
|
||||
aabb:Aabb::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
pub struct BvhWeightNode<W, T> {
|
||||
content: RecursiveContent<BvhWeightNode<W, T>, T>,
|
||||
weight: W,
|
||||
aabb: Aabb,
|
||||
pub struct BvhWeightNode<W,T>{
|
||||
content:RecursiveContent<BvhWeightNode<W,T>,T>,
|
||||
weight:W,
|
||||
aabb:Aabb,
|
||||
}
|
||||
|
||||
impl<T> BvhNode<T> {
|
||||
pub fn the_tester<F: FnMut(&T)>(&self, aabb: &Aabb, f: &mut F) {
|
||||
match &self.content {
|
||||
RecursiveContent::Leaf(model) => f(model),
|
||||
RecursiveContent::Branch(children) => {
|
||||
for child in children {
|
||||
//this test could be moved outside the match statement
|
||||
//but that would test the root node aabb
|
||||
//you're probably not going to spend a lot of time outside the map,
|
||||
//so the test is extra work for nothing
|
||||
if aabb.intersects(&child.aabb) {
|
||||
child.the_tester(aabb, f);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
pub fn into_visitor<F: FnMut(T)>(self, f: &mut F) {
|
||||
match self.content {
|
||||
RecursiveContent::Leaf(model) => f(model),
|
||||
RecursiveContent::Branch(children) => {
|
||||
for child in children {
|
||||
child.into_visitor(f)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
pub fn weigh_contents<W: Copy + std::iter::Sum<W>, F: Fn(&T) -> W>(
|
||||
self,
|
||||
f: &F,
|
||||
) -> BvhWeightNode<W, T> {
|
||||
match self.content {
|
||||
RecursiveContent::Leaf(model) => BvhWeightNode {
|
||||
weight: f(&model),
|
||||
content: RecursiveContent::Leaf(model),
|
||||
aabb: self.aabb,
|
||||
},
|
||||
RecursiveContent::Branch(children) => {
|
||||
let branch: Vec<BvhWeightNode<W, T>> = children
|
||||
.into_iter()
|
||||
.map(|child| child.weigh_contents(f))
|
||||
.collect();
|
||||
BvhWeightNode {
|
||||
weight: branch.iter().map(|node| node.weight).sum(),
|
||||
content: RecursiveContent::Branch(branch),
|
||||
aabb: self.aabb,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
impl<T> BvhNode<T>{
|
||||
pub fn the_tester<F:FnMut(&T)>(&self,aabb:&Aabb,f:&mut F){
|
||||
match &self.content{
|
||||
RecursiveContent::Leaf(model)=>f(model),
|
||||
RecursiveContent::Branch(children)=>for child in children{
|
||||
//this test could be moved outside the match statement
|
||||
//but that would test the root node aabb
|
||||
//you're probably not going to spend a lot of time outside the map,
|
||||
//so the test is extra work for nothing
|
||||
if aabb.intersects(&child.aabb){
|
||||
child.the_tester(aabb,f);
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
pub fn into_visitor<F:FnMut(T)>(self,f:&mut F){
|
||||
match self.content{
|
||||
RecursiveContent::Leaf(model)=>f(model),
|
||||
RecursiveContent::Branch(children)=>for child in children{
|
||||
child.into_visitor(f)
|
||||
},
|
||||
}
|
||||
}
|
||||
pub fn weigh_contents<W:Copy+std::iter::Sum<W>,F:Fn(&T)->W>(self,f:&F)->BvhWeightNode<W,T>{
|
||||
match self.content{
|
||||
RecursiveContent::Leaf(model)=>BvhWeightNode{
|
||||
weight:f(&model),
|
||||
content:RecursiveContent::Leaf(model),
|
||||
aabb:self.aabb,
|
||||
},
|
||||
RecursiveContent::Branch(children)=>{
|
||||
let branch:Vec<BvhWeightNode<W,T>>=children.into_iter().map(|child|
|
||||
child.weigh_contents(f)
|
||||
).collect();
|
||||
BvhWeightNode{
|
||||
weight:branch.iter().map(|node|node.weight).sum(),
|
||||
content:RecursiveContent::Branch(branch),
|
||||
aabb:self.aabb,
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<W, T> BvhWeightNode<W, T> {
|
||||
pub const fn weight(&self) -> &W {
|
||||
&self.weight
|
||||
}
|
||||
pub const fn aabb(&self) -> &Aabb {
|
||||
&self.aabb
|
||||
}
|
||||
pub fn into_content(self) -> RecursiveContent<BvhWeightNode<W, T>, T> {
|
||||
self.content
|
||||
}
|
||||
pub fn into_visitor<F: FnMut(T)>(self, f: &mut F) {
|
||||
match self.content {
|
||||
RecursiveContent::Leaf(model) => f(model),
|
||||
RecursiveContent::Branch(children) => {
|
||||
for child in children {
|
||||
child.into_visitor(f)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
impl <W,T> BvhWeightNode<W,T>{
|
||||
pub const fn weight(&self)->&W{
|
||||
&self.weight
|
||||
}
|
||||
pub const fn aabb(&self)->&Aabb{
|
||||
&self.aabb
|
||||
}
|
||||
pub fn into_content(self)->RecursiveContent<BvhWeightNode<W,T>,T>{
|
||||
self.content
|
||||
}
|
||||
pub fn into_visitor<F:FnMut(T)>(self,f:&mut F){
|
||||
match self.content{
|
||||
RecursiveContent::Leaf(model)=>f(model),
|
||||
RecursiveContent::Branch(children)=>for child in children{
|
||||
child.into_visitor(f)
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn generate_bvh<T>(boxen: Vec<(T, Aabb)>) -> BvhNode<T> {
|
||||
generate_bvh_node(boxen, false)
|
||||
pub fn generate_bvh<T>(boxen:Vec<(T,Aabb)>)->BvhNode<T>{
|
||||
generate_bvh_node(boxen,false)
|
||||
}
|
||||
|
||||
fn generate_bvh_node<T>(boxen: Vec<(T, Aabb)>, force: bool) -> BvhNode<T> {
|
||||
let n = boxen.len();
|
||||
if force || n < 20 {
|
||||
let mut aabb = Aabb::default();
|
||||
let nodes = boxen
|
||||
.into_iter()
|
||||
.map(|b| {
|
||||
aabb.join(&b.1);
|
||||
BvhNode {
|
||||
content: RecursiveContent::Leaf(b.0),
|
||||
aabb: b.1,
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
BvhNode {
|
||||
content: RecursiveContent::Branch(nodes),
|
||||
aabb,
|
||||
}
|
||||
} else {
|
||||
let mut sort_x = Vec::with_capacity(n);
|
||||
let mut sort_y = Vec::with_capacity(n);
|
||||
let mut sort_z = Vec::with_capacity(n);
|
||||
for (i, (_, aabb)) in boxen.iter().enumerate() {
|
||||
let center = aabb.center();
|
||||
sort_x.push((i, center.x));
|
||||
sort_y.push((i, center.y));
|
||||
sort_z.push((i, center.z));
|
||||
}
|
||||
sort_x.sort_by(|tup0, tup1| tup0.1.cmp(&tup1.1));
|
||||
sort_y.sort_by(|tup0, tup1| tup0.1.cmp(&tup1.1));
|
||||
sort_z.sort_by(|tup0, tup1| tup0.1.cmp(&tup1.1));
|
||||
let h = n / 2;
|
||||
let median_x = sort_x[h].1;
|
||||
let median_y = sort_y[h].1;
|
||||
let median_z = sort_z[h].1;
|
||||
//locate a run of values equal to the median
|
||||
//partition point gives the first index for which the predicate evaluates to false
|
||||
let first_index_eq_median_x = sort_x.partition_point(|&(_, x)| x < median_x);
|
||||
let first_index_eq_median_y = sort_y.partition_point(|&(_, y)| y < median_y);
|
||||
let first_index_eq_median_z = sort_z.partition_point(|&(_, z)| z < median_z);
|
||||
let first_index_gt_median_x = sort_x.partition_point(|&(_, x)| x <= median_x);
|
||||
let first_index_gt_median_y = sort_y.partition_point(|&(_, y)| y <= median_y);
|
||||
let first_index_gt_median_z = sort_z.partition_point(|&(_, z)| z <= median_z);
|
||||
//pick which side median value copies go into such that both sides are as balanced as possible based on distance from n/2
|
||||
let partition_point_x =
|
||||
if n.abs_diff(2 * first_index_eq_median_x) < n.abs_diff(2 * first_index_gt_median_x) {
|
||||
first_index_eq_median_x
|
||||
} else {
|
||||
first_index_gt_median_x
|
||||
};
|
||||
let partition_point_y =
|
||||
if n.abs_diff(2 * first_index_eq_median_y) < n.abs_diff(2 * first_index_gt_median_y) {
|
||||
first_index_eq_median_y
|
||||
} else {
|
||||
first_index_gt_median_y
|
||||
};
|
||||
let partition_point_z =
|
||||
if n.abs_diff(2 * first_index_eq_median_z) < n.abs_diff(2 * first_index_gt_median_z) {
|
||||
first_index_eq_median_z
|
||||
} else {
|
||||
first_index_gt_median_z
|
||||
};
|
||||
//this ids which octant the boxen is put in
|
||||
let mut octant = vec![0; n];
|
||||
for &(i, _) in &sort_x[partition_point_x..] {
|
||||
octant[i] += 1 << 0;
|
||||
}
|
||||
for &(i, _) in &sort_y[partition_point_y..] {
|
||||
octant[i] += 1 << 1;
|
||||
}
|
||||
for &(i, _) in &sort_z[partition_point_z..] {
|
||||
octant[i] += 1 << 2;
|
||||
}
|
||||
//generate lists for unique octant values
|
||||
let mut list_list = Vec::with_capacity(8);
|
||||
let mut octant_list = Vec::with_capacity(8);
|
||||
for (i, (data, aabb)) in boxen.into_iter().enumerate() {
|
||||
let octant_id = octant[i];
|
||||
let list_id = if let Some(list_id) = octant_list.iter().position(|&id| id == octant_id)
|
||||
{
|
||||
list_id
|
||||
} else {
|
||||
let list_id = list_list.len();
|
||||
octant_list.push(octant_id);
|
||||
list_list.push(Vec::new());
|
||||
list_id
|
||||
};
|
||||
list_list[list_id].push((data, aabb));
|
||||
}
|
||||
let mut aabb = Aabb::default();
|
||||
if list_list.len() == 1 {
|
||||
generate_bvh_node(list_list.remove(0), true)
|
||||
} else {
|
||||
BvhNode {
|
||||
content: RecursiveContent::Branch(
|
||||
list_list
|
||||
.into_iter()
|
||||
.map(|b| {
|
||||
let node = generate_bvh_node(b, false);
|
||||
aabb.join(&node.aabb);
|
||||
node
|
||||
})
|
||||
.collect(),
|
||||
),
|
||||
aabb,
|
||||
}
|
||||
}
|
||||
}
|
||||
fn generate_bvh_node<T>(boxen:Vec<(T,Aabb)>,force:bool)->BvhNode<T>{
|
||||
let n=boxen.len();
|
||||
if force||n<20{
|
||||
let mut aabb=Aabb::default();
|
||||
let nodes=boxen.into_iter().map(|b|{
|
||||
aabb.join(&b.1);
|
||||
BvhNode{
|
||||
content:RecursiveContent::Leaf(b.0),
|
||||
aabb:b.1,
|
||||
}
|
||||
}).collect();
|
||||
BvhNode{
|
||||
content:RecursiveContent::Branch(nodes),
|
||||
aabb,
|
||||
}
|
||||
}else{
|
||||
let mut sort_x=Vec::with_capacity(n);
|
||||
let mut sort_y=Vec::with_capacity(n);
|
||||
let mut sort_z=Vec::with_capacity(n);
|
||||
for (i,(_,aabb)) in boxen.iter().enumerate(){
|
||||
let center=aabb.center();
|
||||
sort_x.push((i,center.x));
|
||||
sort_y.push((i,center.y));
|
||||
sort_z.push((i,center.z));
|
||||
}
|
||||
sort_x.sort_by(|tup0,tup1|tup0.1.cmp(&tup1.1));
|
||||
sort_y.sort_by(|tup0,tup1|tup0.1.cmp(&tup1.1));
|
||||
sort_z.sort_by(|tup0,tup1|tup0.1.cmp(&tup1.1));
|
||||
let h=n/2;
|
||||
let median_x=sort_x[h].1;
|
||||
let median_y=sort_y[h].1;
|
||||
let median_z=sort_z[h].1;
|
||||
//locate a run of values equal to the median
|
||||
//partition point gives the first index for which the predicate evaluates to false
|
||||
let first_index_eq_median_x=sort_x.partition_point(|&(_,x)|x<median_x);
|
||||
let first_index_eq_median_y=sort_y.partition_point(|&(_,y)|y<median_y);
|
||||
let first_index_eq_median_z=sort_z.partition_point(|&(_,z)|z<median_z);
|
||||
let first_index_gt_median_x=sort_x.partition_point(|&(_,x)|x<=median_x);
|
||||
let first_index_gt_median_y=sort_y.partition_point(|&(_,y)|y<=median_y);
|
||||
let first_index_gt_median_z=sort_z.partition_point(|&(_,z)|z<=median_z);
|
||||
//pick which side median value copies go into such that both sides are as balanced as possible based on distance from n/2
|
||||
let partition_point_x=if n.abs_diff(2*first_index_eq_median_x)<n.abs_diff(2*first_index_gt_median_x){first_index_eq_median_x}else{first_index_gt_median_x};
|
||||
let partition_point_y=if n.abs_diff(2*first_index_eq_median_y)<n.abs_diff(2*first_index_gt_median_y){first_index_eq_median_y}else{first_index_gt_median_y};
|
||||
let partition_point_z=if n.abs_diff(2*first_index_eq_median_z)<n.abs_diff(2*first_index_gt_median_z){first_index_eq_median_z}else{first_index_gt_median_z};
|
||||
//this ids which octant the boxen is put in
|
||||
let mut octant=vec![0;n];
|
||||
for &(i,_) in &sort_x[partition_point_x..]{
|
||||
octant[i]+=1<<0;
|
||||
}
|
||||
for &(i,_) in &sort_y[partition_point_y..]{
|
||||
octant[i]+=1<<1;
|
||||
}
|
||||
for &(i,_) in &sort_z[partition_point_z..]{
|
||||
octant[i]+=1<<2;
|
||||
}
|
||||
//generate lists for unique octant values
|
||||
let mut list_list=Vec::with_capacity(8);
|
||||
let mut octant_list=Vec::with_capacity(8);
|
||||
for (i,(data,aabb)) in boxen.into_iter().enumerate(){
|
||||
let octant_id=octant[i];
|
||||
let list_id=if let Some(list_id)=octant_list.iter().position(|&id|id==octant_id){
|
||||
list_id
|
||||
}else{
|
||||
let list_id=list_list.len();
|
||||
octant_list.push(octant_id);
|
||||
list_list.push(Vec::new());
|
||||
list_id
|
||||
};
|
||||
list_list[list_id].push((data,aabb));
|
||||
}
|
||||
let mut aabb=Aabb::default();
|
||||
if list_list.len()==1{
|
||||
generate_bvh_node(list_list.remove(0),true)
|
||||
}else{
|
||||
BvhNode{
|
||||
content:RecursiveContent::Branch(
|
||||
list_list.into_iter().map(|b|{
|
||||
let node=generate_bvh_node(b,false);
|
||||
aabb.join(&node.aabb);
|
||||
node
|
||||
}).collect()
|
||||
),
|
||||
aabb,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,25 +1,25 @@
|
||||
bitflags::bitflags! {
|
||||
#[derive(Clone,Copy,Debug,Default)]
|
||||
pub struct Controls:u32{
|
||||
const MoveForward=1<<0;
|
||||
const MoveLeft=1<<1;
|
||||
const MoveBackward=1<<2;
|
||||
const MoveRight=1<<3;
|
||||
const MoveUp=1<<4;
|
||||
const MoveDown=1<<5;
|
||||
const LookUp=1<<6;
|
||||
const LookLeft=1<<7;
|
||||
const LookDown=1<<8;
|
||||
const LookRight=1<<9;
|
||||
const Jump=1<<10;
|
||||
const Crouch=1<<11;
|
||||
const Sprint=1<<12;
|
||||
const Zoom=1<<13;
|
||||
const Use=1<<14;//Interact with object
|
||||
const PrimaryAction=1<<15;//LBM/Shoot/Melee
|
||||
const SecondaryAction=1<<16;//RMB/ADS/Block
|
||||
bitflags::bitflags!{
|
||||
#[derive(Clone,Copy,Debug,Default)]
|
||||
pub struct Controls:u32{
|
||||
const MoveForward=1<<0;
|
||||
const MoveLeft=1<<1;
|
||||
const MoveBackward=1<<2;
|
||||
const MoveRight=1<<3;
|
||||
const MoveUp=1<<4;
|
||||
const MoveDown=1<<5;
|
||||
const LookUp=1<<6;
|
||||
const LookLeft=1<<7;
|
||||
const LookDown=1<<8;
|
||||
const LookRight=1<<9;
|
||||
const Jump=1<<10;
|
||||
const Crouch=1<<11;
|
||||
const Sprint=1<<12;
|
||||
const Zoom=1<<13;
|
||||
const Use=1<<14;//Interact with object
|
||||
const PrimaryAction=1<<15;//LBM/Shoot/Melee
|
||||
const SecondaryAction=1<<16;//RMB/ADS/Block
|
||||
|
||||
const WASD=Self::MoveForward.union(Self::MoveLeft).union(Self::MoveBackward).union(Self::MoveRight).bits();
|
||||
const WASDQE=Self::MoveForward.union(Self::MoveLeft).union(Self::MoveBackward).union(Self::MoveRight).union(Self::MoveUp).union(Self::MoveDown).bits();
|
||||
}
|
||||
const WASD=Self::MoveForward.union(Self::MoveLeft).union(Self::MoveBackward).union(Self::MoveRight).bits();
|
||||
const WASDQE=Self::MoveForward.union(Self::MoveLeft).union(Self::MoveBackward).union(Self::MoveRight).union(Self::MoveUp).union(Self::MoveDown).bits();
|
||||
}
|
||||
}
|
||||
|
@ -1,106 +1,88 @@
|
||||
use crate::integer::{AbsoluteTime, Planar64, Planar64Vec3};
|
||||
use crate::model;
|
||||
use crate::integer::{AbsoluteTime,Planar64,Planar64Vec3};
|
||||
|
||||
//you have this effect while in contact
|
||||
#[derive(Clone, Hash, Eq, PartialEq)]
|
||||
pub struct ContactingLadder {
|
||||
pub sticky: bool,
|
||||
#[derive(Clone,Hash,Eq,PartialEq)]
|
||||
pub struct ContactingLadder{
|
||||
pub sticky:bool
|
||||
}
|
||||
#[derive(Clone, Hash, Eq, PartialEq)]
|
||||
pub enum ContactingBehaviour {
|
||||
Surf,
|
||||
Ladder(ContactingLadder),
|
||||
NoJump,
|
||||
Cling, //usable as a zipline, or other weird and wonderful things
|
||||
Elastic(u32), //[1/2^32,1] 0=None (elasticity+1)/2^32
|
||||
#[derive(Clone,Hash,Eq,PartialEq)]
|
||||
pub enum ContactingBehaviour{
|
||||
Surf,
|
||||
Ladder(ContactingLadder),
|
||||
NoJump,
|
||||
Cling,//usable as a zipline, or other weird and wonderful things
|
||||
Elastic(u32),//[1/2^32,1] 0=None (elasticity+1)/2^32
|
||||
}
|
||||
//you have this effect while intersecting
|
||||
#[derive(Clone, Hash, Eq, PartialEq)]
|
||||
pub struct IntersectingWater {
|
||||
pub viscosity: Planar64,
|
||||
pub density: Planar64,
|
||||
pub velocity: Planar64Vec3,
|
||||
#[derive(Clone,Hash,Eq,PartialEq)]
|
||||
pub struct IntersectingWater{
|
||||
pub viscosity:Planar64,
|
||||
pub density:Planar64,
|
||||
pub velocity:Planar64Vec3,
|
||||
}
|
||||
//All models can be given these attributes
|
||||
#[derive(Clone, Hash, Eq, PartialEq)]
|
||||
pub struct Accelerator {
|
||||
pub acceleration: Planar64Vec3,
|
||||
#[derive(Clone,Hash,Eq,PartialEq)]
|
||||
pub struct Accelerator{
|
||||
pub acceleration:Planar64Vec3
|
||||
}
|
||||
#[derive(Clone, Hash, Eq, PartialEq)]
|
||||
pub enum Booster {
|
||||
//Affine(crate::integer::Planar64Affine3),//capable of SetVelocity,DotVelocity,normal booster,bouncy part,redirect velocity, and much more
|
||||
Velocity(Planar64Vec3), //straight up boost velocity adds to your current velocity
|
||||
Energy {
|
||||
direction: Planar64Vec3,
|
||||
energy: Planar64,
|
||||
}, //increase energy in direction
|
||||
AirTime(AbsoluteTime), //increase airtime, invariant across mass and gravity changes
|
||||
Height(Planar64), //increase height, invariant across mass and gravity changes
|
||||
#[derive(Clone,Hash,Eq,PartialEq)]
|
||||
pub enum Booster{
|
||||
//Affine(crate::integer::Planar64Affine3),//capable of SetVelocity,DotVelocity,normal booster,bouncy part,redirect velocity, and much more
|
||||
Velocity(Planar64Vec3),//straight up boost velocity adds to your current velocity
|
||||
Energy{direction:Planar64Vec3,energy:Planar64},//increase energy in direction
|
||||
AirTime(AbsoluteTime),//increase airtime, invariant across mass and gravity changes
|
||||
Height(Planar64),//increase height, invariant across mass and gravity changes
|
||||
}
|
||||
impl Booster {
|
||||
pub fn boost(&self, velocity: Planar64Vec3) -> Planar64Vec3 {
|
||||
match self {
|
||||
&Booster::Velocity(boost_velocity) => velocity + boost_velocity,
|
||||
&Booster::Energy { .. } => {
|
||||
todo!()
|
||||
//let d=direction.dot(velocity);
|
||||
//TODO: think about negative
|
||||
//velocity+direction.with_length((d*d+energy).sqrt()-d)
|
||||
}
|
||||
Booster::AirTime(_) => todo!(),
|
||||
Booster::Height(_) => todo!(),
|
||||
}
|
||||
}
|
||||
impl Booster{
|
||||
pub fn boost(&self,velocity:Planar64Vec3)->Planar64Vec3{
|
||||
match self{
|
||||
&Booster::Velocity(boost_velocity)=>velocity+boost_velocity,
|
||||
&Booster::Energy{..}=>{
|
||||
todo!()
|
||||
//let d=direction.dot(velocity);
|
||||
//TODO: think about negative
|
||||
//velocity+direction.with_length((d*d+energy).sqrt()-d)
|
||||
},
|
||||
Booster::AirTime(_)=>todo!(),
|
||||
Booster::Height(_)=>todo!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
#[derive(Clone, Hash, Eq, PartialEq)]
|
||||
pub enum TrajectoryChoice {
|
||||
HighArcLongDuration, //underhand lob at target: less horizontal speed and more air time
|
||||
LowArcShortDuration, //overhand throw at target: more horizontal speed and less air time
|
||||
#[derive(Clone,Hash,Eq,PartialEq)]
|
||||
pub enum TrajectoryChoice{
|
||||
HighArcLongDuration,//underhand lob at target: less horizontal speed and more air time
|
||||
LowArcShortDuration,//overhand throw at target: more horizontal speed and less air time
|
||||
}
|
||||
#[derive(Clone, Hash, Eq, PartialEq)]
|
||||
pub enum SetTrajectory {
|
||||
//Speed-type SetTrajectory
|
||||
AirTime(AbsoluteTime), //air time (relative to gravity direction) is invariant across mass and gravity changes
|
||||
Height(Planar64), //boost height (relative to gravity direction) is invariant across mass and gravity changes
|
||||
DotVelocity {
|
||||
direction: Planar64Vec3,
|
||||
dot: Planar64,
|
||||
}, //set your velocity in a specific direction without touching other directions
|
||||
//Velocity-type SetTrajectory
|
||||
TargetPointTime {
|
||||
//launch on a trajectory that will land at a target point in a set amount of time
|
||||
target_point: Planar64Vec3,
|
||||
time: AbsoluteTime, //short time = fast and direct, long time = launch high in the air, negative time = wrong way
|
||||
},
|
||||
TargetPointSpeed {
|
||||
//launch at a fixed speed and land at a target point
|
||||
target_point: Planar64Vec3,
|
||||
speed: Planar64, //if speed is too low this will fail to reach the target. The closest-passing trajectory will be chosen instead
|
||||
trajectory_choice: TrajectoryChoice,
|
||||
},
|
||||
Velocity(Planar64Vec3), //SetVelocity
|
||||
#[derive(Clone,Hash,Eq,PartialEq)]
|
||||
pub enum SetTrajectory{
|
||||
//Speed-type SetTrajectory
|
||||
AirTime(AbsoluteTime),//air time (relative to gravity direction) is invariant across mass and gravity changes
|
||||
Height(Planar64),//boost height (relative to gravity direction) is invariant across mass and gravity changes
|
||||
DotVelocity{direction:Planar64Vec3,dot:Planar64},//set your velocity in a specific direction without touching other directions
|
||||
//Velocity-type SetTrajectory
|
||||
TargetPointTime{//launch on a trajectory that will land at a target point in a set amount of time
|
||||
target_point:Planar64Vec3,
|
||||
time:AbsoluteTime,//short time = fast and direct, long time = launch high in the air, negative time = wrong way
|
||||
},
|
||||
TargetPointSpeed{//launch at a fixed speed and land at a target point
|
||||
target_point:Planar64Vec3,
|
||||
speed:Planar64,//if speed is too low this will fail to reach the target. The closest-passing trajectory will be chosen instead
|
||||
trajectory_choice:TrajectoryChoice,
|
||||
},
|
||||
Velocity(Planar64Vec3),//SetVelocity
|
||||
}
|
||||
impl SetTrajectory {
|
||||
pub const fn is_absolute(&self) -> bool {
|
||||
match self {
|
||||
SetTrajectory::AirTime(_)
|
||||
| SetTrajectory::Height(_)
|
||||
| SetTrajectory::DotVelocity {
|
||||
direction: _,
|
||||
dot: _,
|
||||
} => false,
|
||||
SetTrajectory::TargetPointTime {
|
||||
target_point: _,
|
||||
time: _,
|
||||
}
|
||||
| SetTrajectory::TargetPointSpeed {
|
||||
target_point: _,
|
||||
speed: _,
|
||||
trajectory_choice: _,
|
||||
}
|
||||
| SetTrajectory::Velocity(_) => true,
|
||||
}
|
||||
}
|
||||
impl SetTrajectory{
|
||||
pub const fn is_absolute(&self)->bool{
|
||||
match self{
|
||||
SetTrajectory::AirTime(_)
|
||||
|SetTrajectory::Height(_)
|
||||
|SetTrajectory::DotVelocity{direction:_,dot:_}=>false,
|
||||
SetTrajectory::TargetPointTime{target_point:_,time:_}
|
||||
|SetTrajectory::TargetPointSpeed{target_point:_,speed:_,trajectory_choice:_}
|
||||
|SetTrajectory::Velocity(_)=>true,
|
||||
}
|
||||
}
|
||||
}
|
||||
// enum TrapCondition{
|
||||
// FasterThan(Planar64),
|
||||
@ -108,85 +90,85 @@ impl SetTrajectory {
|
||||
// InRange(Planar64,Planar64),
|
||||
// OutsideRange(Planar64,Planar64),
|
||||
// }
|
||||
#[derive(Clone, Hash, Eq, PartialEq)]
|
||||
pub struct Wormhole {
|
||||
//destination does not need to be another wormhole
|
||||
//this defines a one way portal to a destination model transform
|
||||
//two of these can create a two way wormhole
|
||||
pub destination_model: model::ModelId,
|
||||
//(position,angles)*=origin.transform.inverse()*destination.transform
|
||||
#[derive(Clone,Hash,Eq,PartialEq)]
|
||||
pub struct Wormhole{
|
||||
//destination does not need to be another wormhole
|
||||
//this defines a one way portal to a destination model transform
|
||||
//two of these can create a two way wormhole
|
||||
pub destination_model:model::ModelId,
|
||||
//(position,angles)*=origin.transform.inverse()*destination.transform
|
||||
}
|
||||
//attributes listed in order of handling
|
||||
#[derive(Default, Clone, Hash, Eq, PartialEq)]
|
||||
pub struct GeneralAttributes {
|
||||
pub booster: Option<Booster>,
|
||||
pub trajectory: Option<SetTrajectory>,
|
||||
pub wormhole: Option<Wormhole>,
|
||||
pub accelerator: Option<Accelerator>,
|
||||
#[derive(Default,Clone,Hash,Eq,PartialEq)]
|
||||
pub struct GeneralAttributes{
|
||||
pub booster:Option<Booster>,
|
||||
pub trajectory:Option<SetTrajectory>,
|
||||
pub wormhole:Option<Wormhole>,
|
||||
pub accelerator:Option<Accelerator>,
|
||||
}
|
||||
impl GeneralAttributes {
|
||||
pub const fn any(&self) -> bool {
|
||||
self.booster.is_some()
|
||||
|| self.trajectory.is_some()
|
||||
|| self.wormhole.is_some()
|
||||
|| self.accelerator.is_some()
|
||||
}
|
||||
pub fn is_wrcp(&self) -> bool {
|
||||
self.trajectory.as_ref().map_or(false, |t| t.is_absolute())
|
||||
/*
|
||||
&&match &self.teleport_behaviour{
|
||||
Some(TeleportBehaviour::StageElement(
|
||||
StageElement{
|
||||
mode_id,
|
||||
stage_id:_,
|
||||
force:true,
|
||||
behaviour:StageElementBehaviour::Trigger|StageElementBehaviour::Teleport
|
||||
}
|
||||
))=>current_mode_id==*mode_id,
|
||||
_=>false,
|
||||
}
|
||||
*/
|
||||
}
|
||||
impl GeneralAttributes{
|
||||
pub const fn any(&self)->bool{
|
||||
self.booster.is_some()
|
||||
||self.trajectory.is_some()
|
||||
||self.wormhole.is_some()
|
||||
||self.accelerator.is_some()
|
||||
}
|
||||
pub fn is_wrcp(&self)->bool{
|
||||
self.trajectory.as_ref().map_or(false,|t|t.is_absolute())
|
||||
/*
|
||||
&&match &self.teleport_behaviour{
|
||||
Some(TeleportBehaviour::StageElement(
|
||||
StageElement{
|
||||
mode_id,
|
||||
stage_id:_,
|
||||
force:true,
|
||||
behaviour:StageElementBehaviour::Trigger|StageElementBehaviour::Teleport
|
||||
}
|
||||
))=>current_mode_id==*mode_id,
|
||||
_=>false,
|
||||
}
|
||||
*/
|
||||
}
|
||||
}
|
||||
#[derive(Default, Clone, Hash, Eq, PartialEq)]
|
||||
pub struct ContactingAttributes {
|
||||
//friction?
|
||||
pub contact_behaviour: Option<ContactingBehaviour>,
|
||||
#[derive(Default,Clone,Hash,Eq,PartialEq)]
|
||||
pub struct ContactingAttributes{
|
||||
//friction?
|
||||
pub contact_behaviour:Option<ContactingBehaviour>,
|
||||
}
|
||||
impl ContactingAttributes {
|
||||
pub const fn any(&self) -> bool {
|
||||
self.contact_behaviour.is_some()
|
||||
}
|
||||
impl ContactingAttributes{
|
||||
pub const fn any(&self)->bool{
|
||||
self.contact_behaviour.is_some()
|
||||
}
|
||||
}
|
||||
#[derive(Default, Clone, Hash, Eq, PartialEq)]
|
||||
pub struct IntersectingAttributes {
|
||||
pub water: Option<IntersectingWater>,
|
||||
#[derive(Default,Clone,Hash,Eq,PartialEq)]
|
||||
pub struct IntersectingAttributes{
|
||||
pub water:Option<IntersectingWater>,
|
||||
}
|
||||
impl IntersectingAttributes {
|
||||
pub const fn any(&self) -> bool {
|
||||
self.water.is_some()
|
||||
}
|
||||
impl IntersectingAttributes{
|
||||
pub const fn any(&self)->bool{
|
||||
self.water.is_some()
|
||||
}
|
||||
}
|
||||
#[derive(Clone, Copy, id::Id, Hash, Eq, PartialEq)]
|
||||
#[derive(Clone,Copy,id::Id,Hash,Eq,PartialEq)]
|
||||
pub struct CollisionAttributesId(u32);
|
||||
#[derive(Clone, Default, Hash, Eq, PartialEq)]
|
||||
pub struct ContactAttributes {
|
||||
pub contacting: ContactingAttributes,
|
||||
pub general: GeneralAttributes,
|
||||
#[derive(Clone,Default,Hash,Eq,PartialEq)]
|
||||
pub struct ContactAttributes{
|
||||
pub contacting:ContactingAttributes,
|
||||
pub general:GeneralAttributes,
|
||||
}
|
||||
#[derive(Clone, Default, Hash, Eq, PartialEq)]
|
||||
pub struct IntersectAttributes {
|
||||
pub intersecting: IntersectingAttributes,
|
||||
pub general: GeneralAttributes,
|
||||
#[derive(Clone,Default,Hash,Eq,PartialEq)]
|
||||
pub struct IntersectAttributes{
|
||||
pub intersecting:IntersectingAttributes,
|
||||
pub general:GeneralAttributes,
|
||||
}
|
||||
#[derive(Clone, Hash, Eq, PartialEq)]
|
||||
pub enum CollisionAttributes {
|
||||
Decoration, //visual only
|
||||
Contact(ContactAttributes), //track whether you are contacting the object
|
||||
Intersect(IntersectAttributes), //track whether you are intersecting the object
|
||||
#[derive(Clone,Hash,Eq,PartialEq)]
|
||||
pub enum CollisionAttributes{
|
||||
Decoration,//visual only
|
||||
Contact(ContactAttributes),//track whether you are contacting the object
|
||||
Intersect(IntersectAttributes),//track whether you are intersecting the object
|
||||
}
|
||||
impl CollisionAttributes {
|
||||
pub fn contact_default() -> Self {
|
||||
Self::Contact(ContactAttributes::default())
|
||||
}
|
||||
impl CollisionAttributes{
|
||||
pub fn contact_default()->Self{
|
||||
Self::Contact(ContactAttributes::default())
|
||||
}
|
||||
}
|
||||
|
@ -1,358 +1,332 @@
|
||||
use crate::gameplay_style;
|
||||
use std::collections::{HashSet,HashMap};
|
||||
use crate::model::ModelId;
|
||||
use crate::gameplay_style;
|
||||
use crate::updatable::Updatable;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct StageElement {
|
||||
stage_id: StageId, //which stage spawn to send to
|
||||
force: bool, //allow setting to lower spawn id i.e. 7->3
|
||||
behaviour: StageElementBehaviour,
|
||||
jump_limit: Option<u8>,
|
||||
pub struct StageElement{
|
||||
stage_id:StageId,//which stage spawn to send to
|
||||
force:bool,//allow setting to lower spawn id i.e. 7->3
|
||||
behaviour:StageElementBehaviour,
|
||||
jump_limit:Option<u8>,
|
||||
}
|
||||
impl StageElement {
|
||||
#[inline]
|
||||
pub const fn new(
|
||||
stage_id: StageId,
|
||||
force: bool,
|
||||
behaviour: StageElementBehaviour,
|
||||
jump_limit: Option<u8>,
|
||||
) -> Self {
|
||||
Self {
|
||||
stage_id,
|
||||
force,
|
||||
behaviour,
|
||||
jump_limit,
|
||||
}
|
||||
}
|
||||
#[inline]
|
||||
pub const fn stage_id(&self) -> StageId {
|
||||
self.stage_id
|
||||
}
|
||||
#[inline]
|
||||
pub const fn force(&self) -> bool {
|
||||
self.force
|
||||
}
|
||||
#[inline]
|
||||
pub const fn behaviour(&self) -> StageElementBehaviour {
|
||||
self.behaviour
|
||||
}
|
||||
#[inline]
|
||||
pub const fn jump_limit(&self) -> Option<u8> {
|
||||
self.jump_limit
|
||||
}
|
||||
impl StageElement{
|
||||
#[inline]
|
||||
pub const fn new(stage_id:StageId,force:bool,behaviour:StageElementBehaviour,jump_limit:Option<u8>)->Self{
|
||||
Self{
|
||||
stage_id,
|
||||
force,
|
||||
behaviour,
|
||||
jump_limit,
|
||||
}
|
||||
}
|
||||
#[inline]
|
||||
pub const fn stage_id(&self)->StageId{
|
||||
self.stage_id
|
||||
}
|
||||
#[inline]
|
||||
pub const fn force(&self)->bool{
|
||||
self.force
|
||||
}
|
||||
#[inline]
|
||||
pub const fn behaviour(&self)->StageElementBehaviour{
|
||||
self.behaviour
|
||||
}
|
||||
#[inline]
|
||||
pub const fn jump_limit(&self)->Option<u8>{
|
||||
self.jump_limit
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Hash, Eq, PartialEq)]
|
||||
pub enum StageElementBehaviour {
|
||||
SpawnAt, //must be standing on top to get effect. except cancollide false
|
||||
Trigger,
|
||||
Teleport,
|
||||
Platform,
|
||||
//Check(point) acts like a trigger if you haven't hit all the checkpoints on previous stages yet.
|
||||
//Note that all stage elements act like this, this is just the isolated behaviour.
|
||||
Check,
|
||||
Checkpoint, //this is a combined behaviour for Ordered & Unordered in case a model is used multiple times or for both.
|
||||
#[derive(Clone,Copy,Hash,Eq,PartialEq)]
|
||||
pub enum StageElementBehaviour{
|
||||
SpawnAt,//must be standing on top to get effect. except cancollide false
|
||||
Trigger,
|
||||
Teleport,
|
||||
Platform,
|
||||
//Check(point) acts like a trigger if you haven't hit all the checkpoints on previous stages yet.
|
||||
//Note that all stage elements act like this, this is just the isolated behaviour.
|
||||
Check,
|
||||
Checkpoint,//this is a combined behaviour for Ordered & Unordered in case a model is used multiple times or for both.
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Hash, id::Id, Eq, PartialEq)]
|
||||
#[derive(Clone,Copy,Debug,Hash,id::Id,Eq,PartialEq)]
|
||||
pub struct CheckpointId(u32);
|
||||
impl CheckpointId {
|
||||
pub const FIRST: Self = Self(0);
|
||||
impl CheckpointId{
|
||||
pub const FIRST:Self=Self(0);
|
||||
}
|
||||
#[derive(Clone, Copy, Debug, Hash, id::Id, Eq, PartialEq, Ord, PartialOrd)]
|
||||
#[derive(Clone,Copy,Debug,Hash,id::Id,Eq,PartialEq,Ord,PartialOrd)]
|
||||
pub struct StageId(u32);
|
||||
impl StageId {
|
||||
pub const FIRST: Self = Self(0);
|
||||
impl StageId{
|
||||
pub const FIRST:Self=Self(0);
|
||||
}
|
||||
#[derive(Clone)]
|
||||
pub struct Stage {
|
||||
spawn: ModelId,
|
||||
//open world support lol
|
||||
ordered_checkpoints_count: u32,
|
||||
unordered_checkpoints_count: u32,
|
||||
//currently loaded checkpoint models
|
||||
ordered_checkpoints: HashMap<CheckpointId, ModelId>,
|
||||
unordered_checkpoints: HashSet<ModelId>,
|
||||
pub struct Stage{
|
||||
spawn:ModelId,
|
||||
//open world support lol
|
||||
ordered_checkpoints_count:u32,
|
||||
unordered_checkpoints_count:u32,
|
||||
//currently loaded checkpoint models
|
||||
ordered_checkpoints:HashMap<CheckpointId,ModelId>,
|
||||
unordered_checkpoints:HashSet<ModelId>,
|
||||
}
|
||||
impl Stage {
|
||||
pub fn new(
|
||||
spawn: ModelId,
|
||||
ordered_checkpoints_count: u32,
|
||||
unordered_checkpoints_count: u32,
|
||||
ordered_checkpoints: HashMap<CheckpointId, ModelId>,
|
||||
unordered_checkpoints: HashSet<ModelId>,
|
||||
) -> Self {
|
||||
Self {
|
||||
spawn,
|
||||
ordered_checkpoints_count,
|
||||
unordered_checkpoints_count,
|
||||
ordered_checkpoints,
|
||||
unordered_checkpoints,
|
||||
}
|
||||
}
|
||||
pub fn empty(spawn: ModelId) -> Self {
|
||||
Self {
|
||||
spawn,
|
||||
ordered_checkpoints_count: 0,
|
||||
unordered_checkpoints_count: 0,
|
||||
ordered_checkpoints: HashMap::new(),
|
||||
unordered_checkpoints: HashSet::new(),
|
||||
}
|
||||
}
|
||||
#[inline]
|
||||
pub const fn spawn(&self) -> ModelId {
|
||||
self.spawn
|
||||
}
|
||||
#[inline]
|
||||
pub const fn ordered_checkpoints_count(&self) -> u32 {
|
||||
self.ordered_checkpoints_count
|
||||
}
|
||||
#[inline]
|
||||
pub const fn unordered_checkpoints_count(&self) -> u32 {
|
||||
self.unordered_checkpoints_count
|
||||
}
|
||||
pub fn into_inner(self) -> (HashMap<CheckpointId, ModelId>, HashSet<ModelId>) {
|
||||
(self.ordered_checkpoints, self.unordered_checkpoints)
|
||||
}
|
||||
/// Returns true if the stage has no checkpoints.
|
||||
#[inline]
|
||||
pub const fn is_empty(&self) -> bool {
|
||||
self.is_complete(0, 0)
|
||||
}
|
||||
#[inline]
|
||||
pub const fn is_complete(
|
||||
&self,
|
||||
ordered_checkpoints_count: u32,
|
||||
unordered_checkpoints_count: u32,
|
||||
) -> bool {
|
||||
self.ordered_checkpoints_count == ordered_checkpoints_count
|
||||
&& self.unordered_checkpoints_count == unordered_checkpoints_count
|
||||
}
|
||||
#[inline]
|
||||
pub fn is_next_ordered_checkpoint(
|
||||
&self,
|
||||
next_ordered_checkpoint_id: CheckpointId,
|
||||
model_id: ModelId,
|
||||
) -> bool {
|
||||
self.ordered_checkpoints
|
||||
.get(&next_ordered_checkpoint_id)
|
||||
.is_some_and(|&next_checkpoint| model_id == next_checkpoint)
|
||||
}
|
||||
#[inline]
|
||||
pub fn is_unordered_checkpoint(&self, model_id: ModelId) -> bool {
|
||||
self.unordered_checkpoints.contains(&model_id)
|
||||
}
|
||||
impl Stage{
|
||||
pub fn new(
|
||||
spawn:ModelId,
|
||||
ordered_checkpoints_count:u32,
|
||||
unordered_checkpoints_count:u32,
|
||||
ordered_checkpoints:HashMap<CheckpointId,ModelId>,
|
||||
unordered_checkpoints:HashSet<ModelId>,
|
||||
)->Self{
|
||||
Self{
|
||||
spawn,
|
||||
ordered_checkpoints_count,
|
||||
unordered_checkpoints_count,
|
||||
ordered_checkpoints,
|
||||
unordered_checkpoints,
|
||||
}
|
||||
}
|
||||
pub fn empty(spawn:ModelId)->Self{
|
||||
Self{
|
||||
spawn,
|
||||
ordered_checkpoints_count:0,
|
||||
unordered_checkpoints_count:0,
|
||||
ordered_checkpoints:HashMap::new(),
|
||||
unordered_checkpoints:HashSet::new(),
|
||||
}
|
||||
}
|
||||
#[inline]
|
||||
pub const fn spawn(&self)->ModelId{
|
||||
self.spawn
|
||||
}
|
||||
#[inline]
|
||||
pub const fn ordered_checkpoints_count(&self)->u32{
|
||||
self.ordered_checkpoints_count
|
||||
}
|
||||
#[inline]
|
||||
pub const fn unordered_checkpoints_count(&self)->u32{
|
||||
self.unordered_checkpoints_count
|
||||
}
|
||||
pub fn into_inner(self)->(HashMap<CheckpointId,ModelId>,HashSet<ModelId>){
|
||||
(self.ordered_checkpoints,self.unordered_checkpoints)
|
||||
}
|
||||
/// Returns true if the stage has no checkpoints.
|
||||
#[inline]
|
||||
pub const fn is_empty(&self)->bool{
|
||||
self.is_complete(0,0)
|
||||
}
|
||||
#[inline]
|
||||
pub const fn is_complete(&self,ordered_checkpoints_count:u32,unordered_checkpoints_count:u32)->bool{
|
||||
self.ordered_checkpoints_count==ordered_checkpoints_count&&self.unordered_checkpoints_count==unordered_checkpoints_count
|
||||
}
|
||||
#[inline]
|
||||
pub fn is_next_ordered_checkpoint(&self,next_ordered_checkpoint_id:CheckpointId,model_id:ModelId)->bool{
|
||||
self.ordered_checkpoints.get(&next_ordered_checkpoint_id).is_some_and(|&next_checkpoint|model_id==next_checkpoint)
|
||||
}
|
||||
#[inline]
|
||||
pub fn is_unordered_checkpoint(&self,model_id:ModelId)->bool{
|
||||
self.unordered_checkpoints.contains(&model_id)
|
||||
}
|
||||
}
|
||||
#[derive(Default)]
|
||||
pub struct StageUpdate {
|
||||
//other behaviour models of this stage can have
|
||||
ordered_checkpoints: HashMap<CheckpointId, ModelId>,
|
||||
unordered_checkpoints: HashSet<ModelId>,
|
||||
pub struct StageUpdate{
|
||||
//other behaviour models of this stage can have
|
||||
ordered_checkpoints:HashMap<CheckpointId,ModelId>,
|
||||
unordered_checkpoints:HashSet<ModelId>,
|
||||
}
|
||||
impl Updatable<StageUpdate> for Stage {
|
||||
fn update(&mut self, update: StageUpdate) {
|
||||
self.ordered_checkpoints.extend(update.ordered_checkpoints);
|
||||
self.unordered_checkpoints
|
||||
.extend(update.unordered_checkpoints);
|
||||
}
|
||||
impl Updatable<StageUpdate> for Stage{
|
||||
fn update(&mut self,update:StageUpdate){
|
||||
self.ordered_checkpoints.extend(update.ordered_checkpoints);
|
||||
self.unordered_checkpoints.extend(update.unordered_checkpoints);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Hash, Eq, PartialEq)]
|
||||
pub enum Zone {
|
||||
Start,
|
||||
Finish,
|
||||
Anticheat,
|
||||
#[derive(Clone,Copy,Hash,Eq,PartialEq)]
|
||||
pub enum Zone{
|
||||
Start,
|
||||
Finish,
|
||||
Anticheat,
|
||||
}
|
||||
#[derive(Clone, Copy, Debug, Hash, id::Id, Eq, PartialEq, Ord, PartialOrd)]
|
||||
#[derive(Clone,Copy,Debug,Hash,id::Id,Eq,PartialEq,Ord,PartialOrd)]
|
||||
pub struct ModeId(u32);
|
||||
impl ModeId {
|
||||
pub const MAIN: Self = Self(0);
|
||||
pub const BONUS: Self = Self(1);
|
||||
impl ModeId{
|
||||
pub const MAIN:Self=Self(0);
|
||||
pub const BONUS:Self=Self(1);
|
||||
}
|
||||
#[derive(Clone)]
|
||||
pub struct Mode {
|
||||
style: gameplay_style::StyleModifiers,
|
||||
start: ModelId, //when you press reset you go here
|
||||
zones: HashMap<ModelId, Zone>,
|
||||
stages: Vec<Stage>, //when you load the map you go to stages[0].spawn
|
||||
//mutually exlusive stage element behaviour
|
||||
elements: HashMap<ModelId, StageElement>,
|
||||
pub struct Mode{
|
||||
style:gameplay_style::StyleModifiers,
|
||||
start:ModelId,//when you press reset you go here
|
||||
zones:HashMap<ModelId,Zone>,
|
||||
stages:Vec<Stage>,//when you load the map you go to stages[0].spawn
|
||||
//mutually exlusive stage element behaviour
|
||||
elements:HashMap<ModelId,StageElement>,
|
||||
}
|
||||
impl Mode {
|
||||
pub fn new(
|
||||
style: gameplay_style::StyleModifiers,
|
||||
start: ModelId,
|
||||
zones: HashMap<ModelId, Zone>,
|
||||
stages: Vec<Stage>,
|
||||
elements: HashMap<ModelId, StageElement>,
|
||||
) -> Self {
|
||||
Self {
|
||||
style,
|
||||
start,
|
||||
zones,
|
||||
stages,
|
||||
elements,
|
||||
}
|
||||
}
|
||||
pub fn empty(style: gameplay_style::StyleModifiers, start: ModelId) -> Self {
|
||||
Self {
|
||||
style,
|
||||
start,
|
||||
zones: HashMap::new(),
|
||||
stages: Vec::new(),
|
||||
elements: HashMap::new(),
|
||||
}
|
||||
}
|
||||
pub fn into_inner(
|
||||
self,
|
||||
) -> (
|
||||
gameplay_style::StyleModifiers,
|
||||
ModelId,
|
||||
HashMap<ModelId, Zone>,
|
||||
Vec<Stage>,
|
||||
HashMap<ModelId, StageElement>,
|
||||
) {
|
||||
(
|
||||
self.style,
|
||||
self.start,
|
||||
self.zones,
|
||||
self.stages,
|
||||
self.elements,
|
||||
)
|
||||
}
|
||||
pub const fn get_start(&self) -> ModelId {
|
||||
self.start
|
||||
}
|
||||
pub const fn get_style(&self) -> &gameplay_style::StyleModifiers {
|
||||
&self.style
|
||||
}
|
||||
pub fn push_stage(&mut self, stage: Stage) {
|
||||
self.stages.push(stage)
|
||||
}
|
||||
pub fn get_stage_mut(&mut self, stage: StageId) -> Option<&mut Stage> {
|
||||
self.stages.get_mut(stage.0 as usize)
|
||||
}
|
||||
pub fn get_spawn_model_id(&self, stage: StageId) -> Option<ModelId> {
|
||||
self.stages.get(stage.0 as usize).map(|s| s.spawn)
|
||||
}
|
||||
pub fn get_zone(&self, model_id: ModelId) -> Option<&Zone> {
|
||||
self.zones.get(&model_id)
|
||||
}
|
||||
pub fn get_stage(&self, stage_id: StageId) -> Option<&Stage> {
|
||||
self.stages.get(stage_id.0 as usize)
|
||||
}
|
||||
pub fn get_element(&self, model_id: ModelId) -> Option<&StageElement> {
|
||||
self.elements.get(&model_id)
|
||||
}
|
||||
//TODO: put this in the SNF
|
||||
pub fn denormalize_data(&mut self) {
|
||||
//expand and index normalized data
|
||||
self.zones.insert(self.start, Zone::Start);
|
||||
for (stage_id, stage) in self.stages.iter().enumerate() {
|
||||
self.elements.insert(
|
||||
stage.spawn,
|
||||
StageElement {
|
||||
stage_id: StageId(stage_id as u32),
|
||||
force: false,
|
||||
behaviour: StageElementBehaviour::SpawnAt,
|
||||
jump_limit: None,
|
||||
},
|
||||
);
|
||||
for (_, &model) in &stage.ordered_checkpoints {
|
||||
self.elements.insert(
|
||||
model,
|
||||
StageElement {
|
||||
stage_id: StageId(stage_id as u32),
|
||||
force: false,
|
||||
behaviour: StageElementBehaviour::Checkpoint,
|
||||
jump_limit: None,
|
||||
},
|
||||
);
|
||||
}
|
||||
for &model in &stage.unordered_checkpoints {
|
||||
self.elements.insert(
|
||||
model,
|
||||
StageElement {
|
||||
stage_id: StageId(stage_id as u32),
|
||||
force: false,
|
||||
behaviour: StageElementBehaviour::Checkpoint,
|
||||
jump_limit: None,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
impl Mode{
|
||||
pub fn new(
|
||||
style:gameplay_style::StyleModifiers,
|
||||
start:ModelId,
|
||||
zones:HashMap<ModelId,Zone>,
|
||||
stages:Vec<Stage>,
|
||||
elements:HashMap<ModelId,StageElement>,
|
||||
)->Self{
|
||||
Self{
|
||||
style,
|
||||
start,
|
||||
zones,
|
||||
stages,
|
||||
elements,
|
||||
}
|
||||
}
|
||||
pub fn empty(style:gameplay_style::StyleModifiers,start:ModelId)->Self{
|
||||
Self{
|
||||
style,
|
||||
start,
|
||||
zones:HashMap::new(),
|
||||
stages:Vec::new(),
|
||||
elements:HashMap::new(),
|
||||
}
|
||||
}
|
||||
pub fn into_inner(self)->(
|
||||
gameplay_style::StyleModifiers,
|
||||
ModelId,
|
||||
HashMap<ModelId,Zone>,
|
||||
Vec<Stage>,
|
||||
HashMap<ModelId,StageElement>,
|
||||
){
|
||||
(
|
||||
self.style,
|
||||
self.start,
|
||||
self.zones,
|
||||
self.stages,
|
||||
self.elements,
|
||||
)
|
||||
}
|
||||
pub const fn get_start(&self)->ModelId{
|
||||
self.start
|
||||
}
|
||||
pub const fn get_style(&self)->&gameplay_style::StyleModifiers{
|
||||
&self.style
|
||||
}
|
||||
pub fn push_stage(&mut self,stage:Stage){
|
||||
self.stages.push(stage)
|
||||
}
|
||||
pub fn get_stage_mut(&mut self,stage:StageId)->Option<&mut Stage>{
|
||||
self.stages.get_mut(stage.0 as usize)
|
||||
}
|
||||
pub fn get_spawn_model_id(&self,stage:StageId)->Option<ModelId>{
|
||||
self.stages.get(stage.0 as usize).map(|s|s.spawn)
|
||||
}
|
||||
pub fn get_zone(&self,model_id:ModelId)->Option<&Zone>{
|
||||
self.zones.get(&model_id)
|
||||
}
|
||||
pub fn get_stage(&self,stage_id:StageId)->Option<&Stage>{
|
||||
self.stages.get(stage_id.0 as usize)
|
||||
}
|
||||
pub fn get_element(&self,model_id:ModelId)->Option<&StageElement>{
|
||||
self.elements.get(&model_id)
|
||||
}
|
||||
//TODO: put this in the SNF
|
||||
pub fn denormalize_data(&mut self){
|
||||
//expand and index normalized data
|
||||
self.zones.insert(self.start,Zone::Start);
|
||||
for (stage_id,stage) in self.stages.iter().enumerate(){
|
||||
self.elements.insert(stage.spawn,StageElement{
|
||||
stage_id:StageId(stage_id as u32),
|
||||
force:false,
|
||||
behaviour:StageElementBehaviour::SpawnAt,
|
||||
jump_limit:None,
|
||||
});
|
||||
for (_,&model) in &stage.ordered_checkpoints{
|
||||
self.elements.insert(model,StageElement{
|
||||
stage_id:StageId(stage_id as u32),
|
||||
force:false,
|
||||
behaviour:StageElementBehaviour::Checkpoint,
|
||||
jump_limit:None,
|
||||
});
|
||||
}
|
||||
for &model in &stage.unordered_checkpoints{
|
||||
self.elements.insert(model,StageElement{
|
||||
stage_id:StageId(stage_id as u32),
|
||||
force:false,
|
||||
behaviour:StageElementBehaviour::Checkpoint,
|
||||
jump_limit:None,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
//this would be nice as a macro
|
||||
#[derive(Default)]
|
||||
pub struct ModeUpdate {
|
||||
zones: HashMap<ModelId, Zone>,
|
||||
stages: HashMap<StageId, StageUpdate>,
|
||||
//mutually exlusive stage element behaviour
|
||||
elements: HashMap<ModelId, StageElement>,
|
||||
pub struct ModeUpdate{
|
||||
zones:HashMap<ModelId,Zone>,
|
||||
stages:HashMap<StageId,StageUpdate>,
|
||||
//mutually exlusive stage element behaviour
|
||||
elements:HashMap<ModelId,StageElement>,
|
||||
}
|
||||
impl Updatable<ModeUpdate> for Mode {
|
||||
fn update(&mut self, update: ModeUpdate) {
|
||||
self.zones.extend(update.zones);
|
||||
for (stage, stage_update) in update.stages {
|
||||
if let Some(stage) = self.stages.get_mut(stage.0 as usize) {
|
||||
stage.update(stage_update);
|
||||
}
|
||||
}
|
||||
self.elements.extend(update.elements);
|
||||
}
|
||||
impl Updatable<ModeUpdate> for Mode{
|
||||
fn update(&mut self,update:ModeUpdate){
|
||||
self.zones.extend(update.zones);
|
||||
for (stage,stage_update) in update.stages{
|
||||
if let Some(stage)=self.stages.get_mut(stage.0 as usize){
|
||||
stage.update(stage_update);
|
||||
}
|
||||
}
|
||||
self.elements.extend(update.elements);
|
||||
}
|
||||
}
|
||||
impl ModeUpdate {
|
||||
pub fn zone(model_id: ModelId, zone: Zone) -> Self {
|
||||
let mut mu = Self::default();
|
||||
mu.zones.insert(model_id, zone);
|
||||
mu
|
||||
}
|
||||
pub fn stage(stage_id: StageId, stage_update: StageUpdate) -> Self {
|
||||
let mut mu = Self::default();
|
||||
mu.stages.insert(stage_id, stage_update);
|
||||
mu
|
||||
}
|
||||
pub fn element(model_id: ModelId, element: StageElement) -> Self {
|
||||
let mut mu = Self::default();
|
||||
mu.elements.insert(model_id, element);
|
||||
mu
|
||||
}
|
||||
pub fn map_stage_element_ids<F: Fn(StageId) -> StageId>(&mut self, f: F) {
|
||||
for (_, stage_element) in self.elements.iter_mut() {
|
||||
stage_element.stage_id = f(stage_element.stage_id);
|
||||
}
|
||||
}
|
||||
impl ModeUpdate{
|
||||
pub fn zone(model_id:ModelId,zone:Zone)->Self{
|
||||
let mut mu=Self::default();
|
||||
mu.zones.insert(model_id,zone);
|
||||
mu
|
||||
}
|
||||
pub fn stage(stage_id:StageId,stage_update:StageUpdate)->Self{
|
||||
let mut mu=Self::default();
|
||||
mu.stages.insert(stage_id,stage_update);
|
||||
mu
|
||||
}
|
||||
pub fn element(model_id:ModelId,element:StageElement)->Self{
|
||||
let mut mu=Self::default();
|
||||
mu.elements.insert(model_id,element);
|
||||
mu
|
||||
}
|
||||
pub fn map_stage_element_ids<F:Fn(StageId)->StageId>(&mut self,f:F){
|
||||
for (_,stage_element) in self.elements.iter_mut(){
|
||||
stage_element.stage_id=f(stage_element.stage_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Clone)]
|
||||
pub struct Modes {
|
||||
pub modes: Vec<Mode>,
|
||||
#[derive(Default,Clone)]
|
||||
pub struct Modes{
|
||||
pub modes:Vec<Mode>,
|
||||
}
|
||||
impl Modes {
|
||||
pub const fn new(modes: Vec<Mode>) -> Self {
|
||||
Self { modes }
|
||||
}
|
||||
pub fn into_inner(self) -> Vec<Mode> {
|
||||
self.modes
|
||||
}
|
||||
pub fn push_mode(&mut self, mode: Mode) {
|
||||
self.modes.push(mode)
|
||||
}
|
||||
pub fn get_mode(&self, mode: ModeId) -> Option<&Mode> {
|
||||
self.modes.get(mode.0 as usize)
|
||||
}
|
||||
impl Modes{
|
||||
pub const fn new(modes:Vec<Mode>)->Self{
|
||||
Self{
|
||||
modes,
|
||||
}
|
||||
}
|
||||
pub fn into_inner(self)->Vec<Mode>{
|
||||
self.modes
|
||||
}
|
||||
pub fn push_mode(&mut self,mode:Mode){
|
||||
self.modes.push(mode)
|
||||
}
|
||||
pub fn get_mode(&self,mode:ModeId)->Option<&Mode>{
|
||||
self.modes.get(mode.0 as usize)
|
||||
}
|
||||
}
|
||||
pub struct ModesUpdate {
|
||||
modes: HashMap<ModeId, ModeUpdate>,
|
||||
pub struct ModesUpdate{
|
||||
modes:HashMap<ModeId,ModeUpdate>,
|
||||
}
|
||||
impl Updatable<ModesUpdate> for Modes {
|
||||
fn update(&mut self, update: ModesUpdate) {
|
||||
for (mode, mode_update) in update.modes {
|
||||
if let Some(mode) = self.modes.get_mut(mode.0 as usize) {
|
||||
mode.update(mode_update);
|
||||
}
|
||||
}
|
||||
}
|
||||
impl Updatable<ModesUpdate> for Modes{
|
||||
fn update(&mut self,update:ModesUpdate){
|
||||
for (mode,mode_update) in update.modes{
|
||||
if let Some(mode)=self.modes.get_mut(mode.0 as usize){
|
||||
mode.update(mode_update);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -1,66 +1,82 @@
|
||||
use crate::integer::Time;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct TimedInstruction<I, T> {
|
||||
pub time: Time<T>,
|
||||
pub instruction: I,
|
||||
#[derive(Clone,Debug)]
|
||||
pub struct TimedInstruction<I,T>{
|
||||
pub time:Time<T>,
|
||||
pub instruction:I,
|
||||
}
|
||||
impl<I,T> TimedInstruction<I,T>{
|
||||
#[inline]
|
||||
pub fn set_time<TimeInner>(self,new_time:Time<TimeInner>)->TimedInstruction<I,TimeInner>{
|
||||
TimedInstruction{
|
||||
time:new_time,
|
||||
instruction:self.instruction,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait InstructionEmitter {
|
||||
type Instruction;
|
||||
type TimeInner;
|
||||
fn next_instruction(
|
||||
&self,
|
||||
time_limit: Time<Self::TimeInner>,
|
||||
) -> Option<TimedInstruction<Self::Instruction, Self::TimeInner>>;
|
||||
/// Ensure all emitted instructions are processed before consuming external instructions
|
||||
pub trait InstructionEmitter<I>{
|
||||
type TimeInner;
|
||||
fn next_instruction(&self,time_limit:Time<Self::TimeInner>)->Option<TimedInstruction<I,Self::TimeInner>>;
|
||||
}
|
||||
pub trait InstructionConsumer {
|
||||
type Instruction;
|
||||
type TimeInner;
|
||||
fn process_instruction(
|
||||
&mut self,
|
||||
instruction: TimedInstruction<Self::Instruction, Self::TimeInner>,
|
||||
);
|
||||
/// Apply an atomic state update
|
||||
pub trait InstructionConsumer<I>{
|
||||
type TimeInner;
|
||||
fn process_instruction(&mut self,instruction:TimedInstruction<I,Self::TimeInner>);
|
||||
}
|
||||
/// If the object produces its own instructions, allow exhaustively feeding them back in
|
||||
pub trait InstructionFeedback<I,T>:InstructionEmitter<I,TimeInner=T>+InstructionConsumer<I,TimeInner=T>
|
||||
where
|
||||
Time<T>:Copy,
|
||||
{
|
||||
#[inline]
|
||||
fn process_exhaustive(&mut self,time_limit:Time<T>){
|
||||
while let Some(instruction)=self.next_instruction(time_limit){
|
||||
self.process_instruction(instruction);
|
||||
}
|
||||
}
|
||||
}
|
||||
impl<I,T,X> InstructionFeedback<I,T> for X
|
||||
where
|
||||
Time<T>:Copy,
|
||||
X:InstructionEmitter<I,TimeInner=T>+InstructionConsumer<I,TimeInner=T>,
|
||||
{}
|
||||
|
||||
//PROPER PRIVATE FIELDS!!!
|
||||
pub struct InstructionCollector<I, T> {
|
||||
time: Time<T>,
|
||||
instruction: Option<I>,
|
||||
pub struct InstructionCollector<I,T>{
|
||||
time:Time<T>,
|
||||
instruction:Option<I>,
|
||||
}
|
||||
impl<I, T> InstructionCollector<I, T>
|
||||
where
|
||||
Time<T>: Copy + PartialOrd,
|
||||
impl<I,T> InstructionCollector<I,T>
|
||||
where Time<T>:Copy+PartialOrd,
|
||||
{
|
||||
pub const fn new(time: Time<T>) -> Self {
|
||||
Self {
|
||||
time,
|
||||
instruction: None,
|
||||
}
|
||||
}
|
||||
#[inline]
|
||||
pub const fn time(&self) -> Time<T> {
|
||||
self.time
|
||||
}
|
||||
pub fn collect(&mut self, instruction: Option<TimedInstruction<I, T>>) {
|
||||
match instruction {
|
||||
Some(unwrap_instruction) => {
|
||||
if unwrap_instruction.time < self.time {
|
||||
self.time = unwrap_instruction.time;
|
||||
self.instruction = Some(unwrap_instruction.instruction);
|
||||
}
|
||||
}
|
||||
None => (),
|
||||
}
|
||||
}
|
||||
pub fn instruction(self) -> Option<TimedInstruction<I, T>> {
|
||||
//STEAL INSTRUCTION AND DESTROY INSTRUCTIONCOLLECTOR
|
||||
match self.instruction {
|
||||
Some(instruction) => Some(TimedInstruction {
|
||||
time: self.time,
|
||||
instruction,
|
||||
}),
|
||||
None => None,
|
||||
}
|
||||
}
|
||||
#[inline]
|
||||
pub const fn new(time:Time<T>)->Self{
|
||||
Self{
|
||||
time,
|
||||
instruction:None
|
||||
}
|
||||
}
|
||||
#[inline]
|
||||
pub const fn time(&self)->Time<T>{
|
||||
self.time
|
||||
}
|
||||
#[inline]
|
||||
pub fn collect(&mut self,instruction:Option<TimedInstruction<I,T>>){
|
||||
if let Some(ins)=instruction{
|
||||
if ins.time<self.time{
|
||||
self.time=ins.time;
|
||||
self.instruction=Some(ins.instruction);
|
||||
}
|
||||
}
|
||||
}
|
||||
#[inline]
|
||||
pub fn take(self)->Option<TimedInstruction<I,T>>{
|
||||
//STEAL INSTRUCTION AND DESTROY INSTRUCTIONCOLLECTOR
|
||||
self.instruction.map(|instruction|TimedInstruction{
|
||||
time:self.time,
|
||||
instruction
|
||||
})
|
||||
}
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -1,16 +1,16 @@
|
||||
pub mod aabb;
|
||||
pub mod bvh;
|
||||
pub mod controls_bitflag;
|
||||
pub mod map;
|
||||
pub mod run;
|
||||
pub mod aabb;
|
||||
pub mod model;
|
||||
pub mod mouse;
|
||||
pub mod timer;
|
||||
pub mod integer;
|
||||
pub mod physics;
|
||||
pub mod session;
|
||||
pub mod updatable;
|
||||
pub mod instruction;
|
||||
pub mod gameplay_attributes;
|
||||
pub mod gameplay_modes;
|
||||
pub mod gameplay_style;
|
||||
pub mod instruction;
|
||||
pub mod integer;
|
||||
pub mod map;
|
||||
pub mod model;
|
||||
pub mod mouse;
|
||||
pub mod physics;
|
||||
pub mod run;
|
||||
pub mod session;
|
||||
pub mod timer;
|
||||
pub mod updatable;
|
||||
pub mod controls_bitflag;
|
||||
|
@ -1,14 +1,14 @@
|
||||
use crate::gameplay_attributes;
|
||||
use crate::gameplay_modes;
|
||||
use crate::model;
|
||||
use crate::gameplay_modes;
|
||||
use crate::gameplay_attributes;
|
||||
//this is a temporary struct to try to get the code running again
|
||||
//TODO: use snf::map::Region to update the data in physics and graphics instead of this
|
||||
pub struct CompleteMap {
|
||||
pub modes: gameplay_modes::Modes,
|
||||
pub attributes: Vec<gameplay_attributes::CollisionAttributes>,
|
||||
pub meshes: Vec<model::Mesh>,
|
||||
pub models: Vec<model::Model>,
|
||||
//RenderPattern
|
||||
pub textures: Vec<Vec<u8>>,
|
||||
pub render_configs: Vec<model::RenderConfig>,
|
||||
pub struct CompleteMap{
|
||||
pub modes:gameplay_modes::Modes,
|
||||
pub attributes:Vec<gameplay_attributes::CollisionAttributes>,
|
||||
pub meshes:Vec<model::Mesh>,
|
||||
pub models:Vec<model::Model>,
|
||||
//RenderPattern
|
||||
pub textures:Vec<Vec<u8>>,
|
||||
pub render_configs:Vec<model::RenderConfig>,
|
||||
}
|
||||
|
@ -1,56 +1,51 @@
|
||||
use crate::integer::{Planar64Vec3,Planar64Affine3};
|
||||
use crate::gameplay_attributes;
|
||||
use crate::integer::{Planar64Affine3, Planar64Vec3};
|
||||
|
||||
pub type TextureCoordinate = glam::Vec2;
|
||||
pub type Color4 = glam::Vec4;
|
||||
#[derive(Clone, Copy, Hash, id::Id, PartialEq, Eq)]
|
||||
pub type TextureCoordinate=glam::Vec2;
|
||||
pub type Color4=glam::Vec4;
|
||||
#[derive(Clone,Copy,Hash,id::Id,PartialEq,Eq)]
|
||||
pub struct PositionId(u32);
|
||||
#[derive(Clone, Copy, Hash, id::Id, PartialEq, Eq)]
|
||||
#[derive(Clone,Copy,Hash,id::Id,PartialEq,Eq)]
|
||||
pub struct TextureCoordinateId(u32);
|
||||
#[derive(Clone, Copy, Hash, id::Id, PartialEq, Eq)]
|
||||
#[derive(Clone,Copy,Hash,id::Id,PartialEq,Eq)]
|
||||
pub struct NormalId(u32);
|
||||
#[derive(Clone, Copy, Hash, id::Id, PartialEq, Eq)]
|
||||
#[derive(Clone,Copy,Hash,id::Id,PartialEq,Eq)]
|
||||
pub struct ColorId(u32);
|
||||
#[derive(Clone, Hash, PartialEq, Eq)]
|
||||
pub struct IndexedVertex {
|
||||
pub pos: PositionId,
|
||||
pub tex: TextureCoordinateId,
|
||||
pub normal: NormalId,
|
||||
pub color: ColorId,
|
||||
#[derive(Clone,Hash,PartialEq,Eq)]
|
||||
pub struct IndexedVertex{
|
||||
pub pos:PositionId,
|
||||
pub tex:TextureCoordinateId,
|
||||
pub normal:NormalId,
|
||||
pub color:ColorId,
|
||||
}
|
||||
#[derive(Clone, Copy, Hash, id::Id, PartialEq, Eq)]
|
||||
#[derive(Clone,Copy,Hash,id::Id,PartialEq,Eq)]
|
||||
pub struct VertexId(u32);
|
||||
pub type IndexedVertexList = Vec<VertexId>;
|
||||
pub trait PolygonIter {
|
||||
fn polys(&self) -> impl Iterator<Item = &[VertexId]>;
|
||||
pub type IndexedVertexList=Vec<VertexId>;
|
||||
pub trait PolygonIter{
|
||||
fn polys(&self)->impl Iterator<Item=&[VertexId]>;
|
||||
}
|
||||
pub trait MapVertexId {
|
||||
fn map_vertex_id<F: Fn(VertexId) -> VertexId>(self, f: F) -> Self;
|
||||
pub trait MapVertexId{
|
||||
fn map_vertex_id<F:Fn(VertexId)->VertexId>(self,f:F)->Self;
|
||||
}
|
||||
#[derive(Clone)]
|
||||
pub struct PolygonList(Vec<IndexedVertexList>);
|
||||
impl PolygonList {
|
||||
pub const fn new(list: Vec<IndexedVertexList>) -> Self {
|
||||
Self(list)
|
||||
}
|
||||
pub fn extend<T: IntoIterator<Item = IndexedVertexList>>(&mut self, iter: T) {
|
||||
self.0.extend(iter);
|
||||
}
|
||||
impl PolygonList{
|
||||
pub const fn new(list:Vec<IndexedVertexList>)->Self{
|
||||
Self(list)
|
||||
}
|
||||
pub fn extend<T:IntoIterator<Item=IndexedVertexList>>(&mut self,iter:T){
|
||||
self.0.extend(iter);
|
||||
}
|
||||
}
|
||||
impl PolygonIter for PolygonList {
|
||||
fn polys(&self) -> impl Iterator<Item = &[VertexId]> {
|
||||
self.0.iter().map(|poly| poly.as_slice())
|
||||
}
|
||||
impl PolygonIter for PolygonList{
|
||||
fn polys(&self)->impl Iterator<Item=&[VertexId]>{
|
||||
self.0.iter().map(|poly|poly.as_slice())
|
||||
}
|
||||
}
|
||||
impl MapVertexId for PolygonList {
|
||||
fn map_vertex_id<F: Fn(VertexId) -> VertexId>(self, f: F) -> Self {
|
||||
Self(
|
||||
self.0
|
||||
.into_iter()
|
||||
.map(|ivl| ivl.into_iter().map(&f).collect())
|
||||
.collect(),
|
||||
)
|
||||
}
|
||||
impl MapVertexId for PolygonList{
|
||||
fn map_vertex_id<F:Fn(VertexId)->VertexId>(self,f:F)->Self{
|
||||
Self(self.0.into_iter().map(|ivl|ivl.into_iter().map(&f).collect()).collect())
|
||||
}
|
||||
}
|
||||
// pub struct TriangleStrip(IndexedVertexList);
|
||||
// impl PolygonIter for TriangleStrip{
|
||||
@ -58,81 +53,81 @@ impl MapVertexId for PolygonList {
|
||||
// self.0.vertices.windows(3).enumerate().map(|(i,s)|if i&0!=0{return s.iter().rev()}else{return s.iter()})
|
||||
// }
|
||||
// }
|
||||
#[derive(Clone, Copy, Hash, id::Id, PartialEq, Eq)]
|
||||
#[derive(Clone,Copy,Hash,id::Id,PartialEq,Eq)]
|
||||
pub struct PolygonGroupId(u32);
|
||||
#[derive(Clone)]
|
||||
pub enum PolygonGroup {
|
||||
PolygonList(PolygonList),
|
||||
//TriangleStrip(TriangleStrip),
|
||||
pub enum PolygonGroup{
|
||||
PolygonList(PolygonList),
|
||||
//TriangleStrip(TriangleStrip),
|
||||
}
|
||||
impl PolygonIter for PolygonGroup {
|
||||
fn polys(&self) -> impl Iterator<Item = &[VertexId]> {
|
||||
match self {
|
||||
PolygonGroup::PolygonList(list) => list.polys(),
|
||||
//PolygonGroup::TriangleStrip(strip)=>strip.polys(),
|
||||
}
|
||||
}
|
||||
impl PolygonIter for PolygonGroup{
|
||||
fn polys(&self)->impl Iterator<Item=&[VertexId]>{
|
||||
match self{
|
||||
PolygonGroup::PolygonList(list)=>list.polys(),
|
||||
//PolygonGroup::TriangleStrip(strip)=>strip.polys(),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl MapVertexId for PolygonGroup {
|
||||
fn map_vertex_id<F: Fn(VertexId) -> VertexId>(self, f: F) -> Self {
|
||||
match self {
|
||||
PolygonGroup::PolygonList(polys) => Self::PolygonList(polys.map_vertex_id(f)),
|
||||
}
|
||||
}
|
||||
impl MapVertexId for PolygonGroup{
|
||||
fn map_vertex_id<F:Fn(VertexId)->VertexId>(self,f:F)->Self{
|
||||
match self{
|
||||
PolygonGroup::PolygonList(polys)=>Self::PolygonList(polys.map_vertex_id(f)),
|
||||
}
|
||||
}
|
||||
}
|
||||
/// Ah yes, a group of things to render at the same time
|
||||
#[derive(Clone, Copy, Debug, Hash, id::Id, Eq, PartialEq)]
|
||||
#[derive(Clone,Copy,Debug,Hash,id::Id,Eq,PartialEq)]
|
||||
pub struct TextureId(u32);
|
||||
#[derive(Clone, Copy, Hash, id::Id, Eq, PartialEq)]
|
||||
#[derive(Clone,Copy,Hash,id::Id,Eq,PartialEq)]
|
||||
pub struct RenderConfigId(u32);
|
||||
#[derive(Clone, Copy, Default)]
|
||||
pub struct RenderConfig {
|
||||
pub texture: Option<TextureId>,
|
||||
#[derive(Clone,Copy,Default)]
|
||||
pub struct RenderConfig{
|
||||
pub texture:Option<TextureId>,
|
||||
}
|
||||
impl RenderConfig {
|
||||
pub const fn texture(texture: TextureId) -> Self {
|
||||
Self {
|
||||
texture: Some(texture),
|
||||
}
|
||||
}
|
||||
impl RenderConfig{
|
||||
pub const fn texture(texture:TextureId)->Self{
|
||||
Self{
|
||||
texture:Some(texture),
|
||||
}
|
||||
}
|
||||
}
|
||||
#[derive(Clone)]
|
||||
pub struct IndexedGraphicsGroup {
|
||||
//Render pattern material/texture/shader/flat color
|
||||
pub render: RenderConfigId,
|
||||
pub groups: Vec<PolygonGroupId>,
|
||||
pub struct IndexedGraphicsGroup{
|
||||
//Render pattern material/texture/shader/flat color
|
||||
pub render:RenderConfigId,
|
||||
pub groups:Vec<PolygonGroupId>,
|
||||
}
|
||||
#[derive(Clone, Default)]
|
||||
pub struct IndexedPhysicsGroup {
|
||||
//the polygons in this group are guaranteed to make a closed convex shape
|
||||
pub groups: Vec<PolygonGroupId>,
|
||||
#[derive(Clone,Default)]
|
||||
pub struct IndexedPhysicsGroup{
|
||||
//the polygons in this group are guaranteed to make a closed convex shape
|
||||
pub groups:Vec<PolygonGroupId>,
|
||||
}
|
||||
//This is a superset of PhysicsModel and GraphicsModel
|
||||
#[derive(Clone, Copy, Debug, Hash, id::Id, Eq, PartialEq)]
|
||||
#[derive(Clone,Copy,Debug,Hash,id::Id,Eq,PartialEq)]
|
||||
pub struct MeshId(u32);
|
||||
#[derive(Clone)]
|
||||
pub struct Mesh {
|
||||
pub unique_pos: Vec<Planar64Vec3>, //Unit32Vec3
|
||||
pub unique_normal: Vec<Planar64Vec3>, //Unit32Vec3
|
||||
pub unique_tex: Vec<TextureCoordinate>,
|
||||
pub unique_color: Vec<Color4>,
|
||||
pub unique_vertices: Vec<IndexedVertex>,
|
||||
//polygon groups are constant texture AND convexity slices
|
||||
//note that this may need to be changed to be a list of individual faces
|
||||
//for submeshes to work since face ids need to be consistent across submeshes
|
||||
//so face == polygon_groups[face_id]
|
||||
pub polygon_groups: Vec<PolygonGroup>,
|
||||
//graphics indexed (by texture)
|
||||
pub graphics_groups: Vec<IndexedGraphicsGroup>,
|
||||
//physics indexed (by convexity)
|
||||
pub physics_groups: Vec<IndexedPhysicsGroup>,
|
||||
pub struct Mesh{
|
||||
pub unique_pos:Vec<Planar64Vec3>,//Unit32Vec3
|
||||
pub unique_normal:Vec<Planar64Vec3>,//Unit32Vec3
|
||||
pub unique_tex:Vec<TextureCoordinate>,
|
||||
pub unique_color:Vec<Color4>,
|
||||
pub unique_vertices:Vec<IndexedVertex>,
|
||||
//polygon groups are constant texture AND convexity slices
|
||||
//note that this may need to be changed to be a list of individual faces
|
||||
//for submeshes to work since face ids need to be consistent across submeshes
|
||||
//so face == polygon_groups[face_id]
|
||||
pub polygon_groups:Vec<PolygonGroup>,
|
||||
//graphics indexed (by texture)
|
||||
pub graphics_groups:Vec<IndexedGraphicsGroup>,
|
||||
//physics indexed (by convexity)
|
||||
pub physics_groups:Vec<IndexedPhysicsGroup>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Hash, id::Id, Eq, PartialEq)]
|
||||
#[derive(Debug,Clone,Copy,Hash,id::Id,Eq,PartialEq)]
|
||||
pub struct ModelId(u32);
|
||||
pub struct Model {
|
||||
pub mesh: MeshId,
|
||||
pub attributes: gameplay_attributes::CollisionAttributesId,
|
||||
pub color: Color4, //transparency is in here
|
||||
pub transform: Planar64Affine3,
|
||||
pub struct Model{
|
||||
pub mesh:MeshId,
|
||||
pub attributes:gameplay_attributes::CollisionAttributesId,
|
||||
pub color:Color4,//transparency is in here
|
||||
pub transform:Planar64Affine3,
|
||||
}
|
||||
|
@ -1,29 +1,28 @@
|
||||
use crate::integer::Time;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct MouseState<T> {
|
||||
pub pos: glam::IVec2,
|
||||
pub time: Time<T>,
|
||||
#[derive(Clone,Debug)]
|
||||
pub struct MouseState<T>{
|
||||
pub pos:glam::IVec2,
|
||||
pub time:Time<T>,
|
||||
}
|
||||
impl<T> Default for MouseState<T> {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
time: Time::ZERO,
|
||||
pos: glam::IVec2::ZERO,
|
||||
}
|
||||
}
|
||||
impl<T> Default for MouseState<T>{
|
||||
fn default()->Self{
|
||||
Self{
|
||||
time:Time::ZERO,
|
||||
pos:glam::IVec2::ZERO,
|
||||
}
|
||||
}
|
||||
}
|
||||
impl<T> MouseState<T>
|
||||
where
|
||||
Time<T>: Copy,
|
||||
where Time<T>:Copy,
|
||||
{
|
||||
pub fn lerp(&self, target: &MouseState<T>, time: Time<T>) -> glam::IVec2 {
|
||||
let m0 = self.pos.as_i64vec2();
|
||||
let m1 = target.pos.as_i64vec2();
|
||||
//these are deltas
|
||||
let t1t = (target.time - time).nanos();
|
||||
let tt0 = (time - self.time).nanos();
|
||||
let dt = (target.time - self.time).nanos();
|
||||
((m0 * t1t + m1 * tt0) / dt).as_ivec2()
|
||||
}
|
||||
pub fn lerp(&self,target:&MouseState<T>,time:Time<T>)->glam::IVec2{
|
||||
let m0=self.pos.as_i64vec2();
|
||||
let m1=target.pos.as_i64vec2();
|
||||
//these are deltas
|
||||
let t1t=(target.time-time).nanos();
|
||||
let tt0=(time-self.time).nanos();
|
||||
let dt=(target.time-self.time).nanos();
|
||||
((m0*t1t+m1*tt0)/dt).as_ivec2()
|
||||
}
|
||||
}
|
||||
|
@ -1,37 +1,56 @@
|
||||
#[derive(Clone, Copy, Hash, Eq, PartialEq, PartialOrd, Debug)]
|
||||
pub enum TimeInner {}
|
||||
pub type Time = crate::integer::Time<TimeInner>;
|
||||
use crate::mouse::MouseState;
|
||||
use crate::gameplay_modes::{ModeId,StageId};
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum Instruction {
|
||||
ReplaceMouse(
|
||||
crate::mouse::MouseState<TimeInner>,
|
||||
crate::mouse::MouseState<TimeInner>,
|
||||
),
|
||||
SetNextMouse(crate::mouse::MouseState<TimeInner>),
|
||||
SetMoveRight(bool),
|
||||
SetMoveUp(bool),
|
||||
SetMoveBack(bool),
|
||||
SetMoveLeft(bool),
|
||||
SetMoveDown(bool),
|
||||
SetMoveForward(bool),
|
||||
SetJump(bool),
|
||||
SetZoom(bool),
|
||||
/// Reset: fully replace the physics state.
|
||||
/// This forgets all inputs and settings which need to be reapplied.
|
||||
Reset,
|
||||
/// Restart: Teleport to the start zone.
|
||||
Restart,
|
||||
/// Spawn: Teleport to a specific mode's spawn
|
||||
/// Sets current mode & spawn
|
||||
Spawn(
|
||||
crate::gameplay_modes::ModeId,
|
||||
crate::gameplay_modes::StageId,
|
||||
),
|
||||
Idle,
|
||||
//Idle: there were no input events, but the simulation is safe to advance to this timestep
|
||||
//for interpolation / networking / playback reasons, most playback heads will always want
|
||||
//to be 1 instruction ahead to generate the next state for interpolation.
|
||||
PracticeFly,
|
||||
SetSensitivity(crate::integer::Ratio64Vec2),
|
||||
#[derive(Clone,Copy,Hash,Eq,PartialEq,PartialOrd,Debug)]
|
||||
pub enum TimeInner{}
|
||||
pub type Time=crate::integer::Time<TimeInner>;
|
||||
|
||||
#[derive(Clone,Debug)]
|
||||
pub enum Instruction{
|
||||
Mouse(MouseInstruction),
|
||||
SetControl(SetControlInstruction),
|
||||
Mode(ModeInstruction),
|
||||
Misc(MiscInstruction),
|
||||
/// Idle: there were no input events, but the simulation is safe to advance to this timestep
|
||||
Idle,
|
||||
}
|
||||
impl Instruction{
|
||||
pub const IDLE:Self=Self::Idle;
|
||||
}
|
||||
#[derive(Clone,Debug)]
|
||||
pub enum MouseInstruction{
|
||||
/// Replace the entire interpolation state to avoid dividing by zero when replacing twice
|
||||
ReplaceMouse{
|
||||
m0:MouseState<TimeInner>,
|
||||
m1:MouseState<TimeInner>,
|
||||
},
|
||||
SetNextMouse(MouseState<TimeInner>),
|
||||
}
|
||||
#[derive(Clone,Debug)]
|
||||
pub enum SetControlInstruction{
|
||||
SetMoveRight(bool),
|
||||
SetMoveUp(bool),
|
||||
SetMoveBack(bool),
|
||||
SetMoveLeft(bool),
|
||||
SetMoveDown(bool),
|
||||
SetMoveForward(bool),
|
||||
SetJump(bool),
|
||||
SetZoom(bool),
|
||||
}
|
||||
#[derive(Clone,Debug)]
|
||||
pub enum ModeInstruction{
|
||||
/// Reset: fully replace the physics state.
|
||||
/// This forgets all inputs and settings which need to be reapplied.
|
||||
Reset,
|
||||
/// Restart: Teleport to the start zone.
|
||||
/// This runs when you press R or teleport to a bonus
|
||||
Restart(ModeId),
|
||||
/// Spawn: Teleport to a specific mode's spawn
|
||||
/// This runs when the map loads to put you at the map lobby
|
||||
Spawn(ModeId,StageId),
|
||||
}
|
||||
#[derive(Clone,Debug)]
|
||||
pub enum MiscInstruction{
|
||||
PracticeFly,
|
||||
SetSensitivity(crate::integer::Ratio64Vec2),
|
||||
}
|
||||
|
@ -1,117 +1,119 @@
|
||||
use crate::timer::{Paused, Realtime, TimerFixed, Unpaused};
|
||||
use crate::timer::{TimerFixed,Realtime,Paused,Unpaused};
|
||||
|
||||
use crate::physics::{Time as PhysicsTime, TimeInner as PhysicsTimeInner};
|
||||
use crate::physics::{TimeInner as PhysicsTimeInner,Time as PhysicsTime};
|
||||
|
||||
#[derive(Clone, Copy, Hash, Eq, PartialEq, PartialOrd, Debug)]
|
||||
pub enum TimeInner {}
|
||||
pub type Time = crate::integer::Time<TimeInner>;
|
||||
#[derive(Clone,Copy,Hash,Eq,PartialEq,PartialOrd,Debug)]
|
||||
pub enum TimeInner{}
|
||||
pub type Time=crate::integer::Time<TimeInner>;
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub enum FlagReason {
|
||||
Anticheat,
|
||||
StyleChange,
|
||||
Clock,
|
||||
Pause,
|
||||
Flying,
|
||||
Gravity,
|
||||
Timescale,
|
||||
TimeTravel,
|
||||
Teleport,
|
||||
#[derive(Clone,Copy,Debug)]
|
||||
pub enum FlagReason{
|
||||
Anticheat,
|
||||
StyleChange,
|
||||
Clock,
|
||||
Pause,
|
||||
Flying,
|
||||
Gravity,
|
||||
Timescale,
|
||||
TimeTravel,
|
||||
Teleport,
|
||||
}
|
||||
impl ToString for FlagReason {
|
||||
fn to_string(&self) -> String {
|
||||
self.as_ref().to_owned()
|
||||
}
|
||||
impl ToString for FlagReason{
|
||||
fn to_string(&self)->String{
|
||||
self.as_ref().to_owned()
|
||||
}
|
||||
}
|
||||
impl AsRef<str> for FlagReason {
|
||||
fn as_ref(&self) -> &str {
|
||||
match self {
|
||||
FlagReason::Anticheat => "Passed through anticheat zone.",
|
||||
FlagReason::StyleChange => "Changed style.",
|
||||
FlagReason::Clock => "Incorrect clock. (This can be caused by internet hiccups)",
|
||||
FlagReason::Pause => "Pausing is not allowed in this style.",
|
||||
FlagReason::Flying => "Flying is not allowed in this style.",
|
||||
FlagReason::Gravity => "Gravity modification is not allowed in this style.",
|
||||
FlagReason::Timescale => "Timescale is not allowed in this style.",
|
||||
FlagReason::TimeTravel => "Time travel is not allowed in this style.",
|
||||
FlagReason::Teleport => "Illegal teleport.",
|
||||
}
|
||||
}
|
||||
impl AsRef<str> for FlagReason{
|
||||
fn as_ref(&self)->&str{
|
||||
match self{
|
||||
FlagReason::Anticheat=>"Passed through anticheat zone.",
|
||||
FlagReason::StyleChange=>"Changed style.",
|
||||
FlagReason::Clock=>"Incorrect clock. (This can be caused by internet hiccups)",
|
||||
FlagReason::Pause=>"Pausing is not allowed in this style.",
|
||||
FlagReason::Flying=>"Flying is not allowed in this style.",
|
||||
FlagReason::Gravity=>"Gravity modification is not allowed in this style.",
|
||||
FlagReason::Timescale=>"Timescale is not allowed in this style.",
|
||||
FlagReason::TimeTravel=>"Time travel is not allowed in this style.",
|
||||
FlagReason::Teleport=>"Illegal teleport.",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
NotStarted,
|
||||
AlreadyStarted,
|
||||
AlreadyFinished,
|
||||
pub enum Error{
|
||||
NotStarted,
|
||||
AlreadyStarted,
|
||||
AlreadyFinished,
|
||||
}
|
||||
impl std::fmt::Display for Error {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{self:?}")
|
||||
}
|
||||
impl std::fmt::Display for Error{
|
||||
fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{
|
||||
write!(f,"{self:?}")
|
||||
}
|
||||
}
|
||||
impl std::error::Error for Error {}
|
||||
impl std::error::Error for Error{}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
enum RunState {
|
||||
Created,
|
||||
Started {
|
||||
timer: TimerFixed<Realtime<PhysicsTimeInner, TimeInner>, Unpaused>,
|
||||
},
|
||||
Finished {
|
||||
timer: TimerFixed<Realtime<PhysicsTimeInner, TimeInner>, Paused>,
|
||||
},
|
||||
#[derive(Clone,Copy,Debug)]
|
||||
enum RunState{
|
||||
Created,
|
||||
Started{timer:TimerFixed<Realtime<PhysicsTimeInner,TimeInner>,Unpaused>},
|
||||
Finished{timer:TimerFixed<Realtime<PhysicsTimeInner,TimeInner>,Paused>},
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct Run {
|
||||
state: RunState,
|
||||
flagged: Option<FlagReason>,
|
||||
#[derive(Clone,Copy,Debug)]
|
||||
pub struct Run{
|
||||
state:RunState,
|
||||
flagged:Option<FlagReason>,
|
||||
}
|
||||
|
||||
impl Run {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
state: RunState::Created,
|
||||
flagged: None,
|
||||
}
|
||||
}
|
||||
pub fn time(&self, time: PhysicsTime) -> Time {
|
||||
match &self.state {
|
||||
RunState::Created => Time::ZERO,
|
||||
RunState::Started { timer } => timer.time(time),
|
||||
RunState::Finished { timer } => timer.time(time),
|
||||
}
|
||||
}
|
||||
pub fn start(&mut self, time: PhysicsTime) -> Result<(), Error> {
|
||||
match &self.state {
|
||||
RunState::Created => {
|
||||
self.state = RunState::Started {
|
||||
timer: TimerFixed::new(time, Time::ZERO),
|
||||
};
|
||||
Ok(())
|
||||
}
|
||||
RunState::Started { .. } => Err(Error::AlreadyStarted),
|
||||
RunState::Finished { .. } => Err(Error::AlreadyFinished),
|
||||
}
|
||||
}
|
||||
pub fn finish(&mut self, time: PhysicsTime) -> Result<(), Error> {
|
||||
//this uses Copy
|
||||
match &self.state {
|
||||
RunState::Created => Err(Error::NotStarted),
|
||||
RunState::Started { timer } => {
|
||||
self.state = RunState::Finished {
|
||||
timer: timer.into_paused(time),
|
||||
};
|
||||
Ok(())
|
||||
}
|
||||
RunState::Finished { .. } => Err(Error::AlreadyFinished),
|
||||
}
|
||||
}
|
||||
pub fn flag(&mut self, flag_reason: FlagReason) {
|
||||
//don't replace the first reason the run was flagged
|
||||
if self.flagged.is_none() {
|
||||
self.flagged = Some(flag_reason);
|
||||
}
|
||||
}
|
||||
impl Run{
|
||||
pub fn new()->Self{
|
||||
Self{
|
||||
state:RunState::Created,
|
||||
flagged:None,
|
||||
}
|
||||
}
|
||||
pub fn time(&self,time:PhysicsTime)->Time{
|
||||
match &self.state{
|
||||
RunState::Created=>Time::ZERO,
|
||||
RunState::Started{timer}=>timer.time(time),
|
||||
RunState::Finished{timer}=>timer.time(),
|
||||
}
|
||||
}
|
||||
pub fn start(&mut self,time:PhysicsTime)->Result<(),Error>{
|
||||
match &self.state{
|
||||
RunState::Created=>{
|
||||
self.state=RunState::Started{
|
||||
timer:TimerFixed::new(time,Time::ZERO),
|
||||
};
|
||||
Ok(())
|
||||
},
|
||||
RunState::Started{..}=>Err(Error::AlreadyStarted),
|
||||
RunState::Finished{..}=>Err(Error::AlreadyFinished),
|
||||
}
|
||||
}
|
||||
pub fn finish(&mut self,time:PhysicsTime)->Result<(),Error>{
|
||||
//this uses Copy
|
||||
match &self.state{
|
||||
RunState::Created=>Err(Error::NotStarted),
|
||||
RunState::Started{timer}=>{
|
||||
self.state=RunState::Finished{
|
||||
timer:timer.into_paused(time),
|
||||
};
|
||||
Ok(())
|
||||
},
|
||||
RunState::Finished{..}=>Err(Error::AlreadyFinished),
|
||||
}
|
||||
}
|
||||
pub fn flag(&mut self,flag_reason:FlagReason){
|
||||
//don't replace the first reason the run was flagged
|
||||
if self.flagged.is_none(){
|
||||
self.flagged=Some(flag_reason);
|
||||
}
|
||||
}
|
||||
pub fn get_finish_time(&self)->Option<Time>{
|
||||
match &self.state{
|
||||
RunState::Finished{timer}=>Some(timer.time()),
|
||||
_=>None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,3 +1,3 @@
|
||||
#[derive(Clone, Copy, Hash, Eq, PartialEq, PartialOrd, Debug)]
|
||||
pub enum TimeInner {}
|
||||
pub type Time = crate::integer::Time<TimeInner>;
|
||||
#[derive(Clone,Copy,Hash,Eq,PartialEq,PartialOrd,Debug)]
|
||||
pub enum TimeInner{}
|
||||
pub type Time=crate::integer::Time<TimeInner>;
|
||||
|
@ -1,372 +1,363 @@
|
||||
use crate::integer::{Ratio64, Time};
|
||||
use crate::integer::{Time,Ratio64};
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
#[derive(Clone,Copy,Debug)]
|
||||
pub struct Paused;
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
#[derive(Clone,Copy,Debug)]
|
||||
pub struct Unpaused;
|
||||
|
||||
pub trait PauseState: Copy + std::fmt::Debug {
|
||||
const IS_PAUSED: bool;
|
||||
fn new() -> Self;
|
||||
pub trait PauseState:Copy+std::fmt::Debug{
|
||||
const IS_PAUSED:bool;
|
||||
fn new()->Self;
|
||||
}
|
||||
impl PauseState for Paused {
|
||||
const IS_PAUSED: bool = true;
|
||||
fn new() -> Self {
|
||||
Self
|
||||
}
|
||||
impl PauseState for Paused{
|
||||
const IS_PAUSED:bool=true;
|
||||
fn new()->Self{
|
||||
Self
|
||||
}
|
||||
}
|
||||
impl PauseState for Unpaused {
|
||||
const IS_PAUSED: bool = false;
|
||||
fn new() -> Self {
|
||||
Self
|
||||
}
|
||||
impl PauseState for Unpaused{
|
||||
const IS_PAUSED:bool=false;
|
||||
fn new()->Self{
|
||||
Self
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Hash, Eq, PartialEq, PartialOrd, Debug)]
|
||||
enum Inner {}
|
||||
type InnerTime = Time<Inner>;
|
||||
#[derive(Clone,Copy,Hash,Eq,PartialEq,PartialOrd,Debug)]
|
||||
pub enum Inner{}
|
||||
type InnerTime=Time<Inner>;
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct Realtime<In, Out> {
|
||||
offset: InnerTime,
|
||||
_in: core::marker::PhantomData<In>,
|
||||
_out: core::marker::PhantomData<Out>,
|
||||
#[derive(Clone,Copy,Debug)]
|
||||
pub struct Realtime<In,Out>{
|
||||
offset:InnerTime,
|
||||
_in:core::marker::PhantomData<In>,
|
||||
_out:core::marker::PhantomData<Out>,
|
||||
}
|
||||
impl<In, Out> Realtime<In, Out> {
|
||||
pub const fn new(offset: InnerTime) -> Self {
|
||||
Self {
|
||||
offset,
|
||||
_in: core::marker::PhantomData,
|
||||
_out: core::marker::PhantomData,
|
||||
}
|
||||
}
|
||||
impl<In,Out> Realtime<In,Out>{
|
||||
pub const fn new(offset:InnerTime)->Self{
|
||||
Self{
|
||||
offset,
|
||||
_in:core::marker::PhantomData,
|
||||
_out:core::marker::PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct Scaled<In, Out> {
|
||||
scale: Ratio64,
|
||||
offset: InnerTime,
|
||||
_in: core::marker::PhantomData<In>,
|
||||
_out: core::marker::PhantomData<Out>,
|
||||
#[derive(Clone,Copy,Debug)]
|
||||
pub struct Scaled<In,Out>{
|
||||
scale:Ratio64,
|
||||
offset:InnerTime,
|
||||
_in:core::marker::PhantomData<In>,
|
||||
_out:core::marker::PhantomData<Out>,
|
||||
}
|
||||
impl<In, Out> Scaled<In, Out>
|
||||
where
|
||||
Time<In>: Copy,
|
||||
impl<In,Out> Scaled<In,Out>
|
||||
where Time<In>:Copy,
|
||||
{
|
||||
pub const fn new(scale: Ratio64, offset: InnerTime) -> Self {
|
||||
Self {
|
||||
scale,
|
||||
offset,
|
||||
_in: core::marker::PhantomData,
|
||||
_out: core::marker::PhantomData,
|
||||
}
|
||||
}
|
||||
const fn with_scale(scale: Ratio64) -> Self {
|
||||
Self::new(scale, InnerTime::ZERO)
|
||||
}
|
||||
const fn scale(&self, time: Time<In>) -> InnerTime {
|
||||
InnerTime::raw(self.scale.mul_int(time.get()))
|
||||
}
|
||||
const fn get_scale(&self) -> Ratio64 {
|
||||
self.scale
|
||||
}
|
||||
fn set_scale(&mut self, time: Time<In>, new_scale: Ratio64) {
|
||||
let new_time = self.get_time(time);
|
||||
self.scale = new_scale;
|
||||
self.set_time(time, new_time);
|
||||
}
|
||||
pub const fn new(scale:Ratio64,offset:InnerTime)->Self{
|
||||
Self{
|
||||
scale,
|
||||
offset,
|
||||
_in:core::marker::PhantomData,
|
||||
_out:core::marker::PhantomData,
|
||||
}
|
||||
}
|
||||
const fn with_scale(scale:Ratio64)->Self{
|
||||
Self::new(scale,InnerTime::ZERO)
|
||||
}
|
||||
const fn scale(&self,time:Time<In>)->InnerTime{
|
||||
InnerTime::raw(self.scale.mul_int(time.get()))
|
||||
}
|
||||
const fn get_scale(&self)->Ratio64{
|
||||
self.scale
|
||||
}
|
||||
fn set_scale(&mut self,time:Time<In>,new_scale:Ratio64){
|
||||
let new_time=self.get_time(time);
|
||||
self.scale=new_scale;
|
||||
self.set_time(time,new_time);
|
||||
}
|
||||
}
|
||||
|
||||
pub trait TimerState {
|
||||
type In;
|
||||
type Out;
|
||||
fn identity() -> Self;
|
||||
fn get_time(&self, time: Time<Self::In>) -> Time<Self::Out>;
|
||||
fn set_time(&mut self, time: Time<Self::In>, new_time: Time<Self::Out>);
|
||||
fn get_offset(&self) -> InnerTime;
|
||||
fn set_offset(&mut self, offset: InnerTime);
|
||||
pub trait TimerState{
|
||||
type In;
|
||||
type Out;
|
||||
fn identity()->Self;
|
||||
fn get_time(&self,time:Time<Self::In>)->Time<Self::Out>;
|
||||
fn set_time(&mut self,time:Time<Self::In>,new_time:Time<Self::Out>);
|
||||
fn get_offset(&self)->InnerTime;
|
||||
fn set_offset(&mut self,offset:InnerTime);
|
||||
}
|
||||
impl<In, Out> TimerState for Realtime<In, Out> {
|
||||
type In = In;
|
||||
type Out = Out;
|
||||
fn identity() -> Self {
|
||||
Self::new(InnerTime::ZERO)
|
||||
}
|
||||
fn get_time(&self, time: Time<In>) -> Time<Out> {
|
||||
time.coerce() + self.offset.coerce()
|
||||
}
|
||||
fn set_time(&mut self, time: Time<In>, new_time: Time<Out>) {
|
||||
self.offset = new_time.coerce() - time.coerce();
|
||||
}
|
||||
fn get_offset(&self) -> InnerTime {
|
||||
self.offset
|
||||
}
|
||||
fn set_offset(&mut self, offset: InnerTime) {
|
||||
self.offset = offset;
|
||||
}
|
||||
impl<In,Out> TimerState for Realtime<In,Out>{
|
||||
type In=In;
|
||||
type Out=Out;
|
||||
fn identity()->Self{
|
||||
Self::new(InnerTime::ZERO)
|
||||
}
|
||||
fn get_time(&self,time:Time<In>)->Time<Out>{
|
||||
time.coerce()+self.offset.coerce()
|
||||
}
|
||||
fn set_time(&mut self,time:Time<In>,new_time:Time<Out>){
|
||||
self.offset=new_time.coerce()-time.coerce();
|
||||
}
|
||||
fn get_offset(&self)->InnerTime{
|
||||
self.offset
|
||||
}
|
||||
fn set_offset(&mut self,offset:InnerTime){
|
||||
self.offset=offset;
|
||||
}
|
||||
}
|
||||
impl<In, Out> TimerState for Scaled<In, Out>
|
||||
where
|
||||
Time<In>: Copy,
|
||||
impl<In,Out> TimerState for Scaled<In,Out>
|
||||
where Time<In>:Copy,
|
||||
{
|
||||
type In = In;
|
||||
type Out = Out;
|
||||
fn identity() -> Self {
|
||||
Self::new(Ratio64::ONE, InnerTime::ZERO)
|
||||
}
|
||||
fn get_time(&self, time: Time<In>) -> Time<Out> {
|
||||
(self.scale(time) + self.offset).coerce()
|
||||
}
|
||||
fn set_time(&mut self, time: Time<In>, new_time: Time<Out>) {
|
||||
self.offset = new_time.coerce() - self.scale(time);
|
||||
}
|
||||
fn get_offset(&self) -> InnerTime {
|
||||
self.offset
|
||||
}
|
||||
fn set_offset(&mut self, offset: InnerTime) {
|
||||
self.offset = offset;
|
||||
}
|
||||
type In=In;
|
||||
type Out=Out;
|
||||
fn identity()->Self{
|
||||
Self::new(Ratio64::ONE,InnerTime::ZERO)
|
||||
}
|
||||
fn get_time(&self,time:Time<In>)->Time<Out>{
|
||||
(self.scale(time)+self.offset).coerce()
|
||||
}
|
||||
fn set_time(&mut self,time:Time<In>,new_time:Time<Out>){
|
||||
self.offset=new_time.coerce()-self.scale(time);
|
||||
}
|
||||
fn get_offset(&self)->InnerTime{
|
||||
self.offset
|
||||
}
|
||||
fn set_offset(&mut self,offset:InnerTime){
|
||||
self.offset=offset;
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct TimerFixed<T: TimerState, P: PauseState> {
|
||||
state: T,
|
||||
_paused: P,
|
||||
#[derive(Clone,Copy,Debug)]
|
||||
pub struct TimerFixed<T:TimerState,P:PauseState>{
|
||||
state:T,
|
||||
_paused:P,
|
||||
}
|
||||
|
||||
//scaled timer methods are generic across PauseState
|
||||
impl<P: PauseState, In, Out> TimerFixed<Scaled<In, Out>, P>
|
||||
where
|
||||
Time<In>: Copy,
|
||||
impl<P:PauseState,In,Out> TimerFixed<Scaled<In,Out>,P>
|
||||
where Time<In>:Copy,
|
||||
{
|
||||
pub fn scaled(time: Time<In>, new_time: Time<Out>, scale: Ratio64) -> Self {
|
||||
let mut timer = Self {
|
||||
state: Scaled::with_scale(scale),
|
||||
_paused: P::new(),
|
||||
};
|
||||
timer.set_time(time, new_time);
|
||||
timer
|
||||
}
|
||||
pub const fn get_scale(&self) -> Ratio64 {
|
||||
self.state.get_scale()
|
||||
}
|
||||
pub fn set_scale(&mut self, time: Time<In>, new_scale: Ratio64) {
|
||||
self.state.set_scale(time, new_scale)
|
||||
}
|
||||
pub fn scaled(time:Time<In>,new_time:Time<Out>,scale:Ratio64)->Self{
|
||||
let mut timer=Self{
|
||||
state:Scaled::with_scale(scale),
|
||||
_paused:P::new(),
|
||||
};
|
||||
timer.set_time(time,new_time);
|
||||
timer
|
||||
}
|
||||
pub const fn get_scale(&self)->Ratio64{
|
||||
self.state.get_scale()
|
||||
}
|
||||
pub fn set_scale(&mut self,time:Time<In>,new_scale:Ratio64){
|
||||
self.state.set_scale(time,new_scale)
|
||||
}
|
||||
}
|
||||
|
||||
//pause and unpause is generic across TimerState
|
||||
impl<T: TimerState> TimerFixed<T, Paused>
|
||||
where
|
||||
Time<T::In>: Copy,
|
||||
impl<T:TimerState> TimerFixed<T,Paused>
|
||||
where Time<T::In>:Copy,
|
||||
{
|
||||
pub fn into_unpaused(self, time: Time<T::In>) -> TimerFixed<T, Unpaused> {
|
||||
let new_time = self.time(time);
|
||||
let mut timer = TimerFixed {
|
||||
state: self.state,
|
||||
_paused: Unpaused,
|
||||
};
|
||||
timer.set_time(time, new_time);
|
||||
timer
|
||||
}
|
||||
pub fn into_unpaused(self,time:Time<T::In>)->TimerFixed<T,Unpaused>{
|
||||
let new_time=self.time();
|
||||
let mut timer=TimerFixed{
|
||||
state:self.state,
|
||||
_paused:Unpaused,
|
||||
};
|
||||
timer.set_time(time,new_time);
|
||||
timer
|
||||
}
|
||||
pub fn time(&self)->Time<T::Out>{
|
||||
self.state.get_offset().coerce()
|
||||
}
|
||||
}
|
||||
impl<T: TimerState> TimerFixed<T, Unpaused>
|
||||
where
|
||||
Time<T::In>: Copy,
|
||||
impl<T:TimerState> TimerFixed<T,Unpaused>
|
||||
where Time<T::In>:Copy,
|
||||
{
|
||||
pub fn into_paused(self, time: Time<T::In>) -> TimerFixed<T, Paused> {
|
||||
let new_time = self.time(time);
|
||||
let mut timer = TimerFixed {
|
||||
state: self.state,
|
||||
_paused: Paused,
|
||||
};
|
||||
timer.set_time(time, new_time);
|
||||
timer
|
||||
}
|
||||
pub fn into_paused(self,time:Time<T::In>)->TimerFixed<T,Paused>{
|
||||
let new_time=self.time(time);
|
||||
let mut timer=TimerFixed{
|
||||
state:self.state,
|
||||
_paused:Paused,
|
||||
};
|
||||
timer.set_time(time,new_time);
|
||||
timer
|
||||
}
|
||||
pub fn time(&self,time:Time<T::In>)->Time<T::Out>{
|
||||
self.state.get_time(time)
|
||||
}
|
||||
}
|
||||
|
||||
//the new constructor and time queries are generic across both
|
||||
impl<T: TimerState, P: PauseState> TimerFixed<T, P> {
|
||||
pub fn new(time: Time<T::In>, new_time: Time<T::Out>) -> Self {
|
||||
let mut timer = Self {
|
||||
state: T::identity(),
|
||||
_paused: P::new(),
|
||||
};
|
||||
timer.set_time(time, new_time);
|
||||
timer
|
||||
}
|
||||
pub fn from_state(state: T) -> Self {
|
||||
Self {
|
||||
state,
|
||||
_paused: P::new(),
|
||||
}
|
||||
}
|
||||
pub fn into_state(self) -> T {
|
||||
self.state
|
||||
}
|
||||
pub fn time(&self, time: Time<T::In>) -> Time<T::Out> {
|
||||
match P::IS_PAUSED {
|
||||
true => self.state.get_offset().coerce(),
|
||||
false => self.state.get_time(time),
|
||||
}
|
||||
}
|
||||
pub fn set_time(&mut self, time: Time<T::In>, new_time: Time<T::Out>) {
|
||||
match P::IS_PAUSED {
|
||||
true => self.state.set_offset(new_time.coerce()),
|
||||
false => self.state.set_time(time, new_time),
|
||||
}
|
||||
}
|
||||
impl<T:TimerState,P:PauseState> TimerFixed<T,P>{
|
||||
pub fn new(time:Time<T::In>,new_time:Time<T::Out>)->Self{
|
||||
let mut timer=Self{
|
||||
state:T::identity(),
|
||||
_paused:P::new(),
|
||||
};
|
||||
timer.set_time(time,new_time);
|
||||
timer
|
||||
}
|
||||
pub fn from_state(state:T)->Self{
|
||||
Self{
|
||||
state,
|
||||
_paused:P::new(),
|
||||
}
|
||||
}
|
||||
pub fn into_state(self)->T{
|
||||
self.state
|
||||
}
|
||||
pub fn set_time(&mut self,time:Time<T::In>,new_time:Time<T::Out>){
|
||||
match P::IS_PAUSED{
|
||||
true=>self.state.set_offset(new_time.coerce()),
|
||||
false=>self.state.set_time(time,new_time),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
AlreadyPaused,
|
||||
AlreadyUnpaused,
|
||||
pub enum Error{
|
||||
AlreadyPaused,
|
||||
AlreadyUnpaused,
|
||||
}
|
||||
impl std::fmt::Display for Error {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{self:?}")
|
||||
}
|
||||
impl std::fmt::Display for Error{
|
||||
fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{
|
||||
write!(f,"{self:?}")
|
||||
}
|
||||
}
|
||||
impl std::error::Error for Error {}
|
||||
impl std::error::Error for Error{}
|
||||
|
||||
//wrapper type which holds type state internally
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum Timer<T: TimerState> {
|
||||
Paused(TimerFixed<T, Paused>),
|
||||
Unpaused(TimerFixed<T, Unpaused>),
|
||||
#[derive(Clone,Debug)]
|
||||
pub enum Timer<T:TimerState>{
|
||||
Paused(TimerFixed<T,Paused>),
|
||||
Unpaused(TimerFixed<T,Unpaused>),
|
||||
}
|
||||
impl<T: TimerState> Timer<T>
|
||||
where
|
||||
T: Copy,
|
||||
Time<T::In>: Copy,
|
||||
impl<T:TimerState> Timer<T>
|
||||
where
|
||||
T:Copy,
|
||||
Time<T::In>:Copy,
|
||||
{
|
||||
pub fn from_state(state: T, paused: bool) -> Self {
|
||||
match paused {
|
||||
true => Self::Paused(TimerFixed::from_state(state)),
|
||||
false => Self::Unpaused(TimerFixed::from_state(state)),
|
||||
}
|
||||
}
|
||||
pub fn into_state(self) -> (T, bool) {
|
||||
match self {
|
||||
Self::Paused(timer) => (timer.into_state(), true),
|
||||
Self::Unpaused(timer) => (timer.into_state(), false),
|
||||
}
|
||||
}
|
||||
pub fn paused(time: Time<T::In>, new_time: Time<T::Out>) -> Self {
|
||||
Self::Paused(TimerFixed::new(time, new_time))
|
||||
}
|
||||
pub fn unpaused(time: Time<T::In>, new_time: Time<T::Out>) -> Self {
|
||||
Self::Unpaused(TimerFixed::new(time, new_time))
|
||||
}
|
||||
pub fn time(&self, time: Time<T::In>) -> Time<T::Out> {
|
||||
match self {
|
||||
Self::Paused(timer) => timer.time(time),
|
||||
Self::Unpaused(timer) => timer.time(time),
|
||||
}
|
||||
}
|
||||
pub fn set_time(&mut self, time: Time<T::In>, new_time: Time<T::Out>) {
|
||||
match self {
|
||||
Self::Paused(timer) => timer.set_time(time, new_time),
|
||||
Self::Unpaused(timer) => timer.set_time(time, new_time),
|
||||
}
|
||||
}
|
||||
pub fn pause(&mut self, time: Time<T::In>) -> Result<(), Error> {
|
||||
*self = match *self {
|
||||
Self::Paused(_) => return Err(Error::AlreadyPaused),
|
||||
Self::Unpaused(timer) => Self::Paused(timer.into_paused(time)),
|
||||
};
|
||||
Ok(())
|
||||
}
|
||||
pub fn unpause(&mut self, time: Time<T::In>) -> Result<(), Error> {
|
||||
*self = match *self {
|
||||
Self::Paused(timer) => Self::Unpaused(timer.into_unpaused(time)),
|
||||
Self::Unpaused(_) => return Err(Error::AlreadyUnpaused),
|
||||
};
|
||||
Ok(())
|
||||
}
|
||||
pub fn is_paused(&self) -> bool {
|
||||
match self {
|
||||
Self::Paused(_) => true,
|
||||
Self::Unpaused(_) => false,
|
||||
}
|
||||
}
|
||||
pub fn set_paused(&mut self, time: Time<T::In>, paused: bool) -> Result<(), Error> {
|
||||
match paused {
|
||||
true => self.pause(time),
|
||||
false => self.unpause(time),
|
||||
}
|
||||
}
|
||||
pub fn from_state(state:T,paused:bool)->Self{
|
||||
match paused{
|
||||
true=>Self::Paused(TimerFixed::from_state(state)),
|
||||
false=>Self::Unpaused(TimerFixed::from_state(state)),
|
||||
}
|
||||
}
|
||||
pub fn into_state(self)->(T,bool){
|
||||
match self{
|
||||
Self::Paused(timer)=>(timer.into_state(),true),
|
||||
Self::Unpaused(timer)=>(timer.into_state(),false),
|
||||
}
|
||||
}
|
||||
pub fn paused(time:Time<T::In>,new_time:Time<T::Out>)->Self{
|
||||
Self::Paused(TimerFixed::new(time,new_time))
|
||||
}
|
||||
pub fn unpaused(time:Time<T::In>,new_time:Time<T::Out>)->Self{
|
||||
Self::Unpaused(TimerFixed::new(time,new_time))
|
||||
}
|
||||
pub fn time(&self,time:Time<T::In>)->Time<T::Out>{
|
||||
match self{
|
||||
Self::Paused(timer)=>timer.time(),
|
||||
Self::Unpaused(timer)=>timer.time(time),
|
||||
}
|
||||
}
|
||||
pub fn set_time(&mut self,time:Time<T::In>,new_time:Time<T::Out>){
|
||||
match self{
|
||||
Self::Paused(timer)=>timer.set_time(time,new_time),
|
||||
Self::Unpaused(timer)=>timer.set_time(time,new_time),
|
||||
}
|
||||
}
|
||||
pub fn pause(&mut self,time:Time<T::In>)->Result<(),Error>{
|
||||
*self=match *self{
|
||||
Self::Paused(_)=>return Err(Error::AlreadyPaused),
|
||||
Self::Unpaused(timer)=>Self::Paused(timer.into_paused(time)),
|
||||
};
|
||||
Ok(())
|
||||
}
|
||||
pub fn unpause(&mut self,time:Time<T::In>)->Result<(),Error>{
|
||||
*self=match *self{
|
||||
Self::Paused(timer)=>Self::Unpaused(timer.into_unpaused(time)),
|
||||
Self::Unpaused(_)=>return Err(Error::AlreadyUnpaused),
|
||||
};
|
||||
Ok(())
|
||||
}
|
||||
pub fn is_paused(&self)->bool{
|
||||
match self{
|
||||
Self::Paused(_)=>true,
|
||||
Self::Unpaused(_)=>false,
|
||||
}
|
||||
}
|
||||
pub fn set_paused(&mut self,time:Time<T::In>,paused:bool)->Result<(),Error>{
|
||||
match paused{
|
||||
true=>self.pause(time),
|
||||
false=>self.unpause(time),
|
||||
}
|
||||
}
|
||||
}
|
||||
//scaled timer methods are generic across PauseState
|
||||
impl<In, Out> Timer<Scaled<In, Out>>
|
||||
where
|
||||
Time<In>: Copy,
|
||||
impl<In,Out> Timer<Scaled<In,Out>>
|
||||
where Time<In>:Copy,
|
||||
{
|
||||
pub const fn get_scale(&self) -> Ratio64 {
|
||||
match self {
|
||||
Self::Paused(timer) => timer.get_scale(),
|
||||
Self::Unpaused(timer) => timer.get_scale(),
|
||||
}
|
||||
}
|
||||
pub fn set_scale(&mut self, time: Time<In>, new_scale: Ratio64) {
|
||||
match self {
|
||||
Self::Paused(timer) => timer.set_scale(time, new_scale),
|
||||
Self::Unpaused(timer) => timer.set_scale(time, new_scale),
|
||||
}
|
||||
}
|
||||
pub const fn get_scale(&self)->Ratio64{
|
||||
match self{
|
||||
Self::Paused(timer)=>timer.get_scale(),
|
||||
Self::Unpaused(timer)=>timer.get_scale(),
|
||||
}
|
||||
}
|
||||
pub fn set_scale(&mut self,time:Time<In>,new_scale:Ratio64){
|
||||
match self{
|
||||
Self::Paused(timer)=>timer.set_scale(time,new_scale),
|
||||
Self::Unpaused(timer)=>timer.set_scale(time,new_scale),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
macro_rules! sec {
|
||||
($s: expr) => {
|
||||
Time::from_secs($s)
|
||||
};
|
||||
}
|
||||
mod test{
|
||||
use super::*;
|
||||
macro_rules! sec {
|
||||
($s: expr) => {
|
||||
Time::from_secs($s)
|
||||
};
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Hash, Eq, PartialEq, PartialOrd, Debug)]
|
||||
enum Parent {}
|
||||
#[derive(Clone, Copy, Hash, Eq, PartialEq, PartialOrd, Debug)]
|
||||
enum Calculated {}
|
||||
#[test]
|
||||
fn test_timerfixed_scaled() {
|
||||
//create a paused timer that reads 0s
|
||||
let timer = TimerFixed::<Scaled<Parent, Calculated>, Paused>::from_state(Scaled::new(
|
||||
0.5f32.try_into().unwrap(),
|
||||
sec!(0),
|
||||
));
|
||||
//the paused timer at 1 second should read 0s
|
||||
assert_eq!(timer.time(sec!(1)), sec!(0));
|
||||
#[derive(Clone,Copy,Hash,Eq,PartialEq,PartialOrd,Debug)]
|
||||
enum Parent{}
|
||||
#[derive(Clone,Copy,Hash,Eq,PartialEq,PartialOrd,Debug)]
|
||||
enum Calculated{}
|
||||
#[test]
|
||||
fn test_timerfixed_scaled(){
|
||||
//create a paused timer that reads 0s
|
||||
let timer=TimerFixed::<Scaled<Parent,Calculated>,Paused>::from_state(Scaled::new(0.5f32.try_into().unwrap(),sec!(0)));
|
||||
//the paused timer at 1 second should read 0s
|
||||
assert_eq!(timer.time(),sec!(0));
|
||||
|
||||
//unpause it after one second
|
||||
let timer = timer.into_unpaused(sec!(1));
|
||||
//the timer at 6 seconds should read 2.5s
|
||||
assert_eq!(timer.time(sec!(6)), Time::from_millis(2500));
|
||||
//unpause it after one second
|
||||
let timer=timer.into_unpaused(sec!(1));
|
||||
//the timer at 6 seconds should read 2.5s
|
||||
assert_eq!(timer.time(sec!(6)),Time::from_millis(2500));
|
||||
|
||||
//pause the timer after 11 seconds
|
||||
let timer = timer.into_paused(sec!(11));
|
||||
//the paused timer at 20 seconds should read 5s
|
||||
assert_eq!(timer.time(sec!(20)), sec!(5));
|
||||
}
|
||||
#[test]
|
||||
fn test_timer() -> Result<(), Error> {
|
||||
//create a paused timer that reads 0s
|
||||
let mut timer = Timer::<Realtime<Parent, Calculated>>::paused(sec!(0), sec!(0));
|
||||
//the paused timer at 1 second should read 0s
|
||||
assert_eq!(timer.time(sec!(1)), sec!(0));
|
||||
//pause the timer after 11 seconds
|
||||
let timer=timer.into_paused(sec!(11));
|
||||
//the paused timer at 20 seconds should read 5s
|
||||
assert_eq!(timer.time(),sec!(5));
|
||||
}
|
||||
#[test]
|
||||
fn test_timer()->Result<(),Error>{
|
||||
//create a paused timer that reads 0s
|
||||
let mut timer=Timer::<Realtime<Parent,Calculated>>::paused(sec!(0),sec!(0));
|
||||
//the paused timer at 1 second should read 0s
|
||||
assert_eq!(timer.time(sec!(1)),sec!(0));
|
||||
|
||||
//unpause it after one second
|
||||
timer.unpause(sec!(1))?;
|
||||
//the timer at 6 seconds should read 5s
|
||||
assert_eq!(timer.time(sec!(6)), sec!(5));
|
||||
//unpause it after one second
|
||||
timer.unpause(sec!(1))?;
|
||||
//the timer at 6 seconds should read 5s
|
||||
assert_eq!(timer.time(sec!(6)),sec!(5));
|
||||
|
||||
//pause the timer after 11 seconds
|
||||
timer.pause(sec!(11))?;
|
||||
//the paused timer at 20 seconds should read 10s
|
||||
assert_eq!(timer.time(sec!(20)), sec!(10));
|
||||
//pause the timer after 11 seconds
|
||||
timer.pause(sec!(11))?;
|
||||
//the paused timer at 20 seconds should read 10s
|
||||
assert_eq!(timer.time(sec!(20)),sec!(10));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
@ -1,56 +1,57 @@
|
||||
pub trait Updatable<Updater> {
|
||||
fn update(&mut self, update: Updater);
|
||||
// This whole thing should be a drive macro
|
||||
|
||||
pub trait Updatable<Updater>{
|
||||
fn update(&mut self,update:Updater);
|
||||
}
|
||||
#[derive(Clone, Copy, Hash, Eq, PartialEq)]
|
||||
#[derive(Clone,Copy,Hash,Eq,PartialEq)]
|
||||
struct InnerId(u32);
|
||||
#[derive(Clone)]
|
||||
struct Inner {
|
||||
id: InnerId,
|
||||
enabled: bool,
|
||||
struct Inner{
|
||||
id:InnerId,
|
||||
enabled:bool,
|
||||
}
|
||||
#[derive(Clone, Copy, Hash, Eq, PartialEq)]
|
||||
#[derive(Clone,Copy,Hash,Eq,PartialEq)]
|
||||
struct OuterId(u32);
|
||||
struct Outer {
|
||||
id: OuterId,
|
||||
inners: std::collections::HashMap<InnerId, Inner>,
|
||||
struct Outer{
|
||||
id:OuterId,
|
||||
inners:std::collections::HashMap<InnerId,Inner>,
|
||||
}
|
||||
|
||||
enum Update<I, U> {
|
||||
Insert(I),
|
||||
Update(U),
|
||||
Remove,
|
||||
enum Update<I,U>{
|
||||
Insert(I),
|
||||
Update(U),
|
||||
Remove
|
||||
}
|
||||
|
||||
struct InnerUpdate {
|
||||
//#[updatable(Update)]
|
||||
enabled: Option<bool>,
|
||||
struct InnerUpdate{
|
||||
//#[updatable(Update)]
|
||||
enabled:Option<bool>,
|
||||
}
|
||||
struct OuterUpdate {
|
||||
//#[updatable(Insert,Update,Remove)]
|
||||
inners: std::collections::HashMap<InnerId, Update<Inner, InnerUpdate>>,
|
||||
//#[updatable(Update)]
|
||||
//inners:std::collections::HashMap<InnerId,InnerUpdate>,
|
||||
struct OuterUpdate{
|
||||
//#[updatable(Insert,Update,Remove)]
|
||||
inners:std::collections::HashMap<InnerId,Update<Inner,InnerUpdate>>,
|
||||
//#[updatable(Update)]
|
||||
//inners:std::collections::HashMap<InnerId,InnerUpdate>,
|
||||
}
|
||||
impl Updatable<InnerUpdate> for Inner {
|
||||
fn update(&mut self, update: InnerUpdate) {
|
||||
if let Some(enabled) = update.enabled {
|
||||
self.enabled = enabled;
|
||||
}
|
||||
}
|
||||
impl Updatable<InnerUpdate> for Inner{
|
||||
fn update(&mut self,update:InnerUpdate){
|
||||
if let Some(enabled)=update.enabled{
|
||||
self.enabled=enabled;
|
||||
}
|
||||
}
|
||||
}
|
||||
impl Updatable<OuterUpdate> for Outer {
|
||||
fn update(&mut self, update: OuterUpdate) {
|
||||
for (id, up) in update.inners {
|
||||
match up {
|
||||
Update::Insert(new_inner) => self.inners.insert(id, new_inner),
|
||||
Update::Update(inner_update) => self.inners.get_mut(&id).map(|inner| {
|
||||
let old = inner.clone();
|
||||
inner.update(inner_update);
|
||||
old
|
||||
}),
|
||||
Update::Remove => self.inners.remove(&id),
|
||||
};
|
||||
}
|
||||
}
|
||||
impl Updatable<OuterUpdate> for Outer{
|
||||
fn update(&mut self,update:OuterUpdate){
|
||||
for (id,up) in update.inners{
|
||||
match up{
|
||||
Update::Insert(new_inner)=>self.inners.insert(id,new_inner),
|
||||
Update::Update(inner_update)=>self.inners.get_mut(&id).map(|inner|{
|
||||
let old=inner.clone();
|
||||
inner.update(inner_update);
|
||||
old
|
||||
}),
|
||||
Update::Remove=>self.inners.remove(&id),
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
//*/
|
||||
|
@ -12,8 +12,8 @@ authors = ["Rhys Lloyd <krakow20@gmail.com>"]
|
||||
[features]
|
||||
default = ["legacy"]
|
||||
legacy = ["dep:url","dep:vbsp"]
|
||||
#roblox = ["dep:lazy-regex"]
|
||||
#source = ["dep:vbsp"]
|
||||
roblox = []
|
||||
source = ["dep:vbsp"]
|
||||
|
||||
[dependencies]
|
||||
strafesnet_common = { path = "../common", registry = "strafesnet" }
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -1,7 +1,7 @@
|
||||
pub mod fixed;
|
||||
pub mod types;
|
||||
|
||||
#[cfg(feature = "zeroes")]
|
||||
#[cfg(feature="zeroes")]
|
||||
pub mod zeroes;
|
||||
|
||||
#[cfg(test)]
|
||||
|
@ -1,230 +1,218 @@
|
||||
use crate::types::I256F256;
|
||||
use crate::types::I32F32;
|
||||
use crate::types::I256F256;
|
||||
|
||||
#[test]
|
||||
fn you_can_add_numbers() {
|
||||
let a = I256F256::from((3i128 * 2).pow(4));
|
||||
assert_eq!(a + a, I256F256::from((3i128 * 2).pow(4) * 2));
|
||||
fn you_can_add_numbers(){
|
||||
let a=I256F256::from((3i128*2).pow(4));
|
||||
assert_eq!(a+a,I256F256::from((3i128*2).pow(4)*2));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn to_f32() {
|
||||
let a = I256F256::from(1) >> 2;
|
||||
let f: f32 = a.into();
|
||||
assert_eq!(f, 0.25f32);
|
||||
let f: f32 = (-a).into();
|
||||
assert_eq!(f, -0.25f32);
|
||||
let a = I256F256::from(0);
|
||||
let f: f32 = (-a).into();
|
||||
assert_eq!(f, 0f32);
|
||||
let a = I256F256::from(237946589723468975i64) << 16;
|
||||
let f: f32 = a.into();
|
||||
assert_eq!(f, 237946589723468975f32 * 2.0f32.powi(16));
|
||||
fn to_f32(){
|
||||
let a=I256F256::from(1)>>2;
|
||||
let f:f32=a.into();
|
||||
assert_eq!(f,0.25f32);
|
||||
let f:f32=(-a).into();
|
||||
assert_eq!(f,-0.25f32);
|
||||
let a=I256F256::from(0);
|
||||
let f:f32=(-a).into();
|
||||
assert_eq!(f,0f32);
|
||||
let a=I256F256::from(237946589723468975i64)<<16;
|
||||
let f:f32=a.into();
|
||||
assert_eq!(f,237946589723468975f32*2.0f32.powi(16));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn to_f64() {
|
||||
let a = I256F256::from(1) >> 2;
|
||||
let f: f64 = a.into();
|
||||
assert_eq!(f, 0.25f64);
|
||||
let f: f64 = (-a).into();
|
||||
assert_eq!(f, -0.25f64);
|
||||
let a = I256F256::from(0);
|
||||
let f: f64 = (-a).into();
|
||||
assert_eq!(f, 0f64);
|
||||
let a = I256F256::from(237946589723468975i64) << 16;
|
||||
let f: f64 = a.into();
|
||||
assert_eq!(f, 237946589723468975f64 * 2.0f64.powi(16));
|
||||
fn to_f64(){
|
||||
let a=I256F256::from(1)>>2;
|
||||
let f:f64=a.into();
|
||||
assert_eq!(f,0.25f64);
|
||||
let f:f64=(-a).into();
|
||||
assert_eq!(f,-0.25f64);
|
||||
let a=I256F256::from(0);
|
||||
let f:f64=(-a).into();
|
||||
assert_eq!(f,0f64);
|
||||
let a=I256F256::from(237946589723468975i64)<<16;
|
||||
let f:f64=a.into();
|
||||
assert_eq!(f,237946589723468975f64*2.0f64.powi(16));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn from_f32() {
|
||||
let a = I256F256::from(1) >> 2;
|
||||
let b: Result<I256F256, _> = 0.25f32.try_into();
|
||||
assert_eq!(b, Ok(a));
|
||||
let a = I256F256::from(-1) >> 2;
|
||||
let b: Result<I256F256, _> = (-0.25f32).try_into();
|
||||
assert_eq!(b, Ok(a));
|
||||
let a = I256F256::from(0);
|
||||
let b: Result<I256F256, _> = 0.try_into();
|
||||
assert_eq!(b, Ok(a));
|
||||
let a = I256F256::from(0b101011110101001010101010000000000000000000000000000i64) << 16;
|
||||
let b: Result<I256F256, _> = (0b101011110101001010101010000000000000000000000000000u64 as f32
|
||||
* 2.0f32.powi(16))
|
||||
.try_into();
|
||||
assert_eq!(b, Ok(a));
|
||||
//I32F32::MAX into f32 is truncated into this value
|
||||
let a = I32F32::raw(0b111111111111111111111111000000000000000000000000000000000000000i64);
|
||||
let b: Result<I32F32, _> = Into::<f32>::into(I32F32::MAX).try_into();
|
||||
assert_eq!(b, Ok(a));
|
||||
//I32F32::MIN hits a special case since it's not representable as a positive signed integer
|
||||
//TODO: don't return an overflow because this is technically possible
|
||||
let a = I32F32::MIN;
|
||||
let b: Result<I32F32, _> = Into::<f32>::into(I32F32::MIN).try_into();
|
||||
assert_eq!(b, Err(crate::fixed::FixedFromFloatError::Overflow));
|
||||
//16 is within the 24 bits of float precision
|
||||
let b: Result<I32F32, _> = Into::<f32>::into(-I32F32::MIN.fix_2()).try_into();
|
||||
assert_eq!(b, Err(crate::fixed::FixedFromFloatError::Overflow));
|
||||
let b: Result<I32F32, _> = f32::MIN_POSITIVE.try_into();
|
||||
assert_eq!(b, Err(crate::fixed::FixedFromFloatError::Underflow));
|
||||
//test many cases
|
||||
for i in 0..64 {
|
||||
let a = crate::fixed::Fixed::<2, 64>::raw_digit(
|
||||
0b111111111111111111111111000000000000000000000000000000000000000i64,
|
||||
) << i;
|
||||
let f: f32 = a.into();
|
||||
let b: Result<crate::fixed::Fixed<2, 64>, _> = f.try_into();
|
||||
assert_eq!(b, Ok(a));
|
||||
}
|
||||
fn from_f32(){
|
||||
let a=I256F256::from(1)>>2;
|
||||
let b:Result<I256F256,_>=0.25f32.try_into();
|
||||
assert_eq!(b,Ok(a));
|
||||
let a=I256F256::from(-1)>>2;
|
||||
let b:Result<I256F256,_>=(-0.25f32).try_into();
|
||||
assert_eq!(b,Ok(a));
|
||||
let a=I256F256::from(0);
|
||||
let b:Result<I256F256,_>=0.try_into();
|
||||
assert_eq!(b,Ok(a));
|
||||
let a=I256F256::from(0b101011110101001010101010000000000000000000000000000i64)<<16;
|
||||
let b:Result<I256F256,_>=(0b101011110101001010101010000000000000000000000000000u64 as f32*2.0f32.powi(16)).try_into();
|
||||
assert_eq!(b,Ok(a));
|
||||
//I32F32::MAX into f32 is truncated into this value
|
||||
let a=I32F32::raw(0b111111111111111111111111000000000000000000000000000000000000000i64);
|
||||
let b:Result<I32F32,_>=Into::<f32>::into(I32F32::MAX).try_into();
|
||||
assert_eq!(b,Ok(a));
|
||||
//I32F32::MIN hits a special case since it's not representable as a positive signed integer
|
||||
//TODO: don't return an overflow because this is technically possible
|
||||
let _a=I32F32::MIN;
|
||||
let b:Result<I32F32,_>=Into::<f32>::into(I32F32::MIN).try_into();
|
||||
assert_eq!(b,Err(crate::fixed::FixedFromFloatError::Overflow));
|
||||
//16 is within the 24 bits of float precision
|
||||
let b:Result<I32F32,_>=Into::<f32>::into(-I32F32::MIN.fix_2()).try_into();
|
||||
assert_eq!(b,Err(crate::fixed::FixedFromFloatError::Overflow));
|
||||
let b:Result<I32F32,_>=f32::MIN_POSITIVE.try_into();
|
||||
assert_eq!(b,Err(crate::fixed::FixedFromFloatError::Underflow));
|
||||
//test many cases
|
||||
for i in 0..64{
|
||||
let a=crate::fixed::Fixed::<2,64>::raw_digit(0b111111111111111111111111000000000000000000000000000000000000000i64)<<i;
|
||||
let f:f32=a.into();
|
||||
let b:Result<crate::fixed::Fixed<2,64>,_>=f.try_into();
|
||||
assert_eq!(b,Ok(a));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn from_f64() {
|
||||
let a = I256F256::from(1) >> 2;
|
||||
let b: Result<I256F256, _> = 0.25f64.try_into();
|
||||
assert_eq!(b, Ok(a));
|
||||
let a = I256F256::from(-1) >> 2;
|
||||
let b: Result<I256F256, _> = (-0.25f64).try_into();
|
||||
assert_eq!(b, Ok(a));
|
||||
let a = I256F256::from(0);
|
||||
let b: Result<I256F256, _> = 0.try_into();
|
||||
assert_eq!(b, Ok(a));
|
||||
let a = I256F256::from(0b101011110101001010101010000000000000000000000000000i64) << 16;
|
||||
let b: Result<I256F256, _> = (0b101011110101001010101010000000000000000000000000000u64 as f64
|
||||
* 2.0f64.powi(16))
|
||||
.try_into();
|
||||
assert_eq!(b, Ok(a));
|
||||
fn from_f64(){
|
||||
let a=I256F256::from(1)>>2;
|
||||
let b:Result<I256F256,_>=0.25f64.try_into();
|
||||
assert_eq!(b,Ok(a));
|
||||
let a=I256F256::from(-1)>>2;
|
||||
let b:Result<I256F256,_>=(-0.25f64).try_into();
|
||||
assert_eq!(b,Ok(a));
|
||||
let a=I256F256::from(0);
|
||||
let b:Result<I256F256,_>=0.try_into();
|
||||
assert_eq!(b,Ok(a));
|
||||
let a=I256F256::from(0b101011110101001010101010000000000000000000000000000i64)<<16;
|
||||
let b:Result<I256F256,_>=(0b101011110101001010101010000000000000000000000000000u64 as f64*2.0f64.powi(16)).try_into();
|
||||
assert_eq!(b,Ok(a));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn you_can_shr_numbers() {
|
||||
let a = I32F32::from(4);
|
||||
assert_eq!(a >> 1, I32F32::from(2));
|
||||
fn you_can_shr_numbers(){
|
||||
let a=I32F32::from(4);
|
||||
assert_eq!(a>>1,I32F32::from(2));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_wide_mul() {
|
||||
let a = I32F32::ONE;
|
||||
let aa = a.wide_mul_1_1(a);
|
||||
assert_eq!(aa, crate::types::I64F64::ONE);
|
||||
fn test_wide_mul(){
|
||||
let a=I32F32::ONE;
|
||||
let aa=a.wide_mul_1_1(a);
|
||||
assert_eq!(aa,crate::types::I64F64::ONE);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_wide_div() {
|
||||
let a = I32F32::ONE * 4;
|
||||
let b = I32F32::ONE * 2;
|
||||
let wide_a = a.wide_mul_1_1(I32F32::ONE);
|
||||
let wide_b = b.wide_mul_1_1(I32F32::ONE);
|
||||
let ab = a.wide_div_1_1(b);
|
||||
assert_eq!(ab, crate::types::I64F64::ONE * 2);
|
||||
let wab = wide_a.wide_div_2_1(b);
|
||||
assert_eq!(wab, crate::fixed::Fixed::<3, 96>::ONE * 2);
|
||||
let awb = a.wide_div_1_2(wide_b);
|
||||
assert_eq!(awb, crate::fixed::Fixed::<3, 96>::ONE * 2);
|
||||
fn test_wide_div(){
|
||||
let a=I32F32::ONE*4;
|
||||
let b=I32F32::ONE*2;
|
||||
let wide_a=a.wide_mul_1_1(I32F32::ONE);
|
||||
let wide_b=b.wide_mul_1_1(I32F32::ONE);
|
||||
let ab=a.wide_div_1_1(b);
|
||||
assert_eq!(ab,crate::types::I64F64::ONE*2);
|
||||
let wab=wide_a.wide_div_2_1(b);
|
||||
assert_eq!(wab,crate::fixed::Fixed::<3,96>::ONE*2);
|
||||
let awb=a.wide_div_1_2(wide_b);
|
||||
assert_eq!(awb,crate::fixed::Fixed::<3,96>::ONE*2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_wide_mul_repeated() {
|
||||
let a = I32F32::from(2);
|
||||
let b = I32F32::from(3);
|
||||
let a=I32F32::from(2);
|
||||
let b=I32F32::from(3);
|
||||
|
||||
let w1 = a.wide_mul_1_1(b);
|
||||
let w2 = w1.wide_mul_2_2(w1);
|
||||
let w3 = w2.wide_mul_4_4(w2);
|
||||
let w1=a.wide_mul_1_1(b);
|
||||
let w2=w1.wide_mul_2_2(w1);
|
||||
let w3=w2.wide_mul_4_4(w2);
|
||||
|
||||
assert_eq!(w3, I256F256::from((3i128 * 2).pow(4)));
|
||||
assert_eq!(w3,I256F256::from((3i128*2).pow(4)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_bint() {
|
||||
let a = I32F32::ONE;
|
||||
assert_eq!(a * 2, I32F32::from(2));
|
||||
fn test_bint(){
|
||||
let a=I32F32::ONE;
|
||||
assert_eq!(a*2,I32F32::from(2));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_fix() {
|
||||
assert_eq!(I32F32::ONE.fix_8(), I256F256::ONE);
|
||||
assert_eq!(I32F32::ONE, I256F256::ONE.fix_1());
|
||||
assert_eq!(I32F32::NEG_ONE.fix_8(), I256F256::NEG_ONE);
|
||||
assert_eq!(I32F32::NEG_ONE, I256F256::NEG_ONE.fix_1());
|
||||
fn test_fix(){
|
||||
assert_eq!(I32F32::ONE.fix_8(),I256F256::ONE);
|
||||
assert_eq!(I32F32::ONE,I256F256::ONE.fix_1());
|
||||
assert_eq!(I32F32::NEG_ONE.fix_8(),I256F256::NEG_ONE);
|
||||
assert_eq!(I32F32::NEG_ONE,I256F256::NEG_ONE.fix_1());
|
||||
}
|
||||
#[test]
|
||||
fn test_sqrt() {
|
||||
let a = I32F32::ONE * 4;
|
||||
assert_eq!(a.sqrt(), I32F32::from(2));
|
||||
fn test_sqrt(){
|
||||
let a=I32F32::ONE*4;
|
||||
assert_eq!(a.sqrt(),I32F32::from(2));
|
||||
}
|
||||
#[test]
|
||||
fn test_sqrt_zero() {
|
||||
let a = I32F32::ZERO;
|
||||
assert_eq!(a.sqrt(), I32F32::ZERO);
|
||||
fn test_sqrt_zero(){
|
||||
let a=I32F32::ZERO;
|
||||
assert_eq!(a.sqrt(),I32F32::ZERO);
|
||||
}
|
||||
#[test]
|
||||
fn test_sqrt_low() {
|
||||
let a = I32F32::HALF;
|
||||
let b = a.fixed_mul(a);
|
||||
assert_eq!(b.sqrt(), a);
|
||||
fn test_sqrt_low(){
|
||||
let a=I32F32::HALF;
|
||||
let b=a.fixed_mul(a);
|
||||
assert_eq!(b.sqrt(),a);
|
||||
}
|
||||
fn find_equiv_sqrt_via_f64(n: I32F32) -> I32F32 {
|
||||
//GIMME THEM BITS BOY
|
||||
let &[bits] = n.to_bits().to_bits().digits();
|
||||
let ibits = bits as i64;
|
||||
let f = (ibits as f64) / ((1u64 << 32) as f64);
|
||||
let f_ans = f.sqrt();
|
||||
let i = (f_ans * ((1u64 << 32) as f64)) as i64;
|
||||
let r = I32F32::from_bits(bnum::BInt::<1>::from(i));
|
||||
//mimic the behaviour of the algorithm,
|
||||
//return the result if it truncates to the exact answer
|
||||
if (r + I32F32::EPSILON).wide_mul_1_1(r + I32F32::EPSILON) == n.wide_mul_1_1(I32F32::ONE) {
|
||||
return r + I32F32::EPSILON;
|
||||
}
|
||||
if (r - I32F32::EPSILON).wide_mul_1_1(r - I32F32::EPSILON) == n.wide_mul_1_1(I32F32::ONE) {
|
||||
return r - I32F32::EPSILON;
|
||||
}
|
||||
return r;
|
||||
fn find_equiv_sqrt_via_f64(n:I32F32)->I32F32{
|
||||
//GIMME THEM BITS BOY
|
||||
let &[bits]=n.to_bits().to_bits().digits();
|
||||
let ibits=bits as i64;
|
||||
let f=(ibits as f64)/((1u64<<32) as f64);
|
||||
let f_ans=f.sqrt();
|
||||
let i=(f_ans*((1u64<<32) as f64)) as i64;
|
||||
let r=I32F32::from_bits(bnum::BInt::<1>::from(i));
|
||||
//mimic the behaviour of the algorithm,
|
||||
//return the result if it truncates to the exact answer
|
||||
if (r+I32F32::EPSILON).wide_mul_1_1(r+I32F32::EPSILON)==n.wide_mul_1_1(I32F32::ONE){
|
||||
return r+I32F32::EPSILON;
|
||||
}
|
||||
if (r-I32F32::EPSILON).wide_mul_1_1(r-I32F32::EPSILON)==n.wide_mul_1_1(I32F32::ONE){
|
||||
return r-I32F32::EPSILON;
|
||||
}
|
||||
return r;
|
||||
}
|
||||
fn test_exact(n: I32F32) {
|
||||
assert_eq!(n.sqrt(), find_equiv_sqrt_via_f64(n));
|
||||
fn test_exact(n:I32F32){
|
||||
assert_eq!(n.sqrt(),find_equiv_sqrt_via_f64(n));
|
||||
}
|
||||
#[test]
|
||||
fn test_sqrt_exact() {
|
||||
//43
|
||||
for i in 0..((i64::MAX as f32).ln() as u32) {
|
||||
let n = I32F32::from_bits(bnum::BInt::<1>::from((i as f32).exp() as i64));
|
||||
test_exact(n);
|
||||
}
|
||||
fn test_sqrt_exact(){
|
||||
//43
|
||||
for i in 0..((i64::MAX as f32).ln() as u32){
|
||||
let n=I32F32::from_bits(bnum::BInt::<1>::from((i as f32).exp() as i64));
|
||||
test_exact(n);
|
||||
}
|
||||
}
|
||||
#[test]
|
||||
fn test_sqrt_max() {
|
||||
let a = I32F32::MAX;
|
||||
test_exact(a);
|
||||
fn test_sqrt_max(){
|
||||
let a=I32F32::MAX;
|
||||
test_exact(a);
|
||||
}
|
||||
#[test]
|
||||
#[cfg(all(feature = "zeroes", not(feature = "deferred-division")))]
|
||||
fn test_zeroes_normal() {
|
||||
// (x-1)*(x+1)
|
||||
// x^2-1
|
||||
let zeroes = I32F32::zeroes2(I32F32::NEG_ONE, I32F32::ZERO, I32F32::ONE);
|
||||
assert_eq!(
|
||||
zeroes,
|
||||
arrayvec::ArrayVec::from_iter([I32F32::NEG_ONE, I32F32::ONE])
|
||||
);
|
||||
let zeroes = I32F32::zeroes2(I32F32::NEG_ONE * 3, I32F32::ONE * 2, I32F32::ONE);
|
||||
assert_eq!(
|
||||
zeroes,
|
||||
arrayvec::ArrayVec::from_iter([I32F32::NEG_ONE * 3, I32F32::ONE])
|
||||
);
|
||||
#[cfg(all(feature="zeroes",not(feature="deferred-division")))]
|
||||
fn test_zeroes_normal(){
|
||||
// (x-1)*(x+1)
|
||||
// x^2-1
|
||||
let zeroes=I32F32::zeroes2(I32F32::NEG_ONE,I32F32::ZERO,I32F32::ONE);
|
||||
assert_eq!(zeroes,arrayvec::ArrayVec::from_iter([I32F32::NEG_ONE,I32F32::ONE]));
|
||||
let zeroes=I32F32::zeroes2(I32F32::NEG_ONE*3,I32F32::ONE*2,I32F32::ONE);
|
||||
assert_eq!(zeroes,arrayvec::ArrayVec::from_iter([I32F32::NEG_ONE*3,I32F32::ONE]));
|
||||
}
|
||||
#[test]
|
||||
#[cfg(all(feature = "zeroes", feature = "deferred-division"))]
|
||||
fn test_zeroes_deferred_division() {
|
||||
// (x-1)*(x+1)
|
||||
// x^2-1
|
||||
let zeroes = I32F32::zeroes2(I32F32::NEG_ONE, I32F32::ZERO, I32F32::ONE);
|
||||
assert_eq!(
|
||||
zeroes,
|
||||
arrayvec::ArrayVec::from_iter([
|
||||
ratio_ops::ratio::Ratio::new(I32F32::ONE * 2, I32F32::NEG_ONE * 2),
|
||||
ratio_ops::ratio::Ratio::new(I32F32::ONE * 2, I32F32::ONE * 2),
|
||||
])
|
||||
);
|
||||
#[cfg(all(feature="zeroes",feature="deferred-division"))]
|
||||
fn test_zeroes_deferred_division(){
|
||||
// (x-1)*(x+1)
|
||||
// x^2-1
|
||||
let zeroes=I32F32::zeroes2(I32F32::NEG_ONE,I32F32::ZERO,I32F32::ONE);
|
||||
assert_eq!(
|
||||
zeroes,
|
||||
arrayvec::ArrayVec::from_iter([
|
||||
ratio_ops::ratio::Ratio::new(I32F32::ONE*2,I32F32::NEG_ONE*2),
|
||||
ratio_ops::ratio::Ratio::new(I32F32::ONE*2,I32F32::ONE*2),
|
||||
])
|
||||
);
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
pub type I32F32 = crate::fixed::Fixed<1, 32>;
|
||||
pub type I64F64 = crate::fixed::Fixed<2, 64>;
|
||||
pub type I128F128 = crate::fixed::Fixed<4, 128>;
|
||||
pub type I256F256 = crate::fixed::Fixed<8, 256>;
|
||||
pub type I32F32=crate::fixed::Fixed<1,32>;
|
||||
pub type I64F64=crate::fixed::Fixed<2,64>;
|
||||
pub type I128F128=crate::fixed::Fixed<4,128>;
|
||||
pub type I256F256=crate::fixed::Fixed<8,256>;
|
||||
|
@ -2,63 +2,45 @@ use crate::fixed::Fixed;
|
||||
|
||||
use arrayvec::ArrayVec;
|
||||
use std::cmp::Ordering;
|
||||
macro_rules! impl_zeroes {
|
||||
($n:expr) => {
|
||||
impl Fixed<$n, { $n * 32 }> {
|
||||
#[inline]
|
||||
pub fn zeroes2(
|
||||
a0: Self,
|
||||
a1: Self,
|
||||
a2: Self,
|
||||
) -> ArrayVec<<Self as core::ops::Div>::Output, 2> {
|
||||
let a2pos = match a2.cmp(&Self::ZERO) {
|
||||
Ordering::Greater => true,
|
||||
Ordering::Equal => {
|
||||
return ArrayVec::from_iter(Self::zeroes1(a0, a1).into_iter())
|
||||
}
|
||||
Ordering::Less => false,
|
||||
};
|
||||
let radicand = a1 * a1 - a2 * a0 * 4;
|
||||
match radicand.cmp(&<Self as core::ops::Mul>::Output::ZERO) {
|
||||
Ordering::Greater => {
|
||||
paste::item! {
|
||||
let planar_radicand=radicand.sqrt().[<fix_ $n>]();
|
||||
}
|
||||
//sort roots ascending and avoid taking the difference of large numbers
|
||||
let zeroes = match (a2pos, Self::ZERO < a1) {
|
||||
(true, true) => [
|
||||
(-a1 - planar_radicand) / (a2 * 2),
|
||||
(a0 * 2) / (-a1 - planar_radicand),
|
||||
],
|
||||
(true, false) => [
|
||||
(a0 * 2) / (-a1 + planar_radicand),
|
||||
(-a1 + planar_radicand) / (a2 * 2),
|
||||
],
|
||||
(false, true) => [
|
||||
(a0 * 2) / (-a1 - planar_radicand),
|
||||
(-a1 - planar_radicand) / (a2 * 2),
|
||||
],
|
||||
(false, false) => [
|
||||
(-a1 + planar_radicand) / (a2 * 2),
|
||||
(a0 * 2) / (-a1 + planar_radicand),
|
||||
],
|
||||
};
|
||||
ArrayVec::from_iter(zeroes)
|
||||
}
|
||||
Ordering::Equal => ArrayVec::from_iter([(a1) / (a2 * -2)]),
|
||||
Ordering::Less => ArrayVec::new_const(),
|
||||
}
|
||||
}
|
||||
#[inline]
|
||||
pub fn zeroes1(a0: Self, a1: Self) -> ArrayVec<<Self as core::ops::Div>::Output, 1> {
|
||||
if a1 == Self::ZERO {
|
||||
ArrayVec::new_const()
|
||||
} else {
|
||||
ArrayVec::from_iter([(-a0) / (a1)])
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
macro_rules! impl_zeroes{
|
||||
($n:expr)=>{
|
||||
impl Fixed<$n,{$n*32}>{
|
||||
#[inline]
|
||||
pub fn zeroes2(a0:Self,a1:Self,a2:Self)->ArrayVec<<Self as core::ops::Div>::Output,2>{
|
||||
let a2pos=match a2.cmp(&Self::ZERO){
|
||||
Ordering::Greater=>true,
|
||||
Ordering::Equal=>return ArrayVec::from_iter(Self::zeroes1(a0,a1).into_iter()),
|
||||
Ordering::Less=>false,
|
||||
};
|
||||
let radicand=a1*a1-a2*a0*4;
|
||||
match radicand.cmp(&<Self as core::ops::Mul>::Output::ZERO){
|
||||
Ordering::Greater=>{
|
||||
paste::item!{
|
||||
let planar_radicand=radicand.sqrt().[<fix_ $n>]();
|
||||
}
|
||||
//sort roots ascending and avoid taking the difference of large numbers
|
||||
let zeroes=match (a2pos,Self::ZERO<a1){
|
||||
(true, true )=>[(-a1-planar_radicand)/(a2*2),(a0*2)/(-a1-planar_radicand)],
|
||||
(true, false)=>[(a0*2)/(-a1+planar_radicand),(-a1+planar_radicand)/(a2*2)],
|
||||
(false,true )=>[(a0*2)/(-a1-planar_radicand),(-a1-planar_radicand)/(a2*2)],
|
||||
(false,false)=>[(-a1+planar_radicand)/(a2*2),(a0*2)/(-a1+planar_radicand)],
|
||||
};
|
||||
ArrayVec::from_iter(zeroes)
|
||||
},
|
||||
Ordering::Equal=>ArrayVec::from_iter([(a1)/(a2*-2)]),
|
||||
Ordering::Less=>ArrayVec::new_const(),
|
||||
}
|
||||
}
|
||||
#[inline]
|
||||
pub fn zeroes1(a0:Self,a1:Self)->ArrayVec<<Self as core::ops::Div>::Output,1>{
|
||||
if a1==Self::ZERO{
|
||||
ArrayVec::new_const()
|
||||
}else{
|
||||
ArrayVec::from_iter([(-a0)/(a1)])
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
impl_zeroes!(1);
|
||||
impl_zeroes!(2);
|
||||
|
@ -1,9 +1,9 @@
|
||||
mod macros;
|
||||
pub mod matrix;
|
||||
pub mod types;
|
||||
pub mod vector;
|
||||
pub mod matrix;
|
||||
|
||||
#[cfg(feature = "named-fields")]
|
||||
#[cfg(feature="named-fields")]
|
||||
mod named;
|
||||
|
||||
#[cfg(test)]
|
||||
|
@ -1,328 +1,79 @@
|
||||
#[doc(hidden)]
|
||||
#[macro_export(local_inner_macros)]
|
||||
macro_rules! impl_fixed_wide_vector_not_const_generic {
|
||||
(
|
||||
(
|
||||
(),
|
||||
$n:expr
|
||||
) => {
|
||||
impl<const N: usize> Vector<N, fixed_wide::fixed::Fixed<$n, { $n * 32 }>> {
|
||||
#[inline]
|
||||
pub fn length(
|
||||
self,
|
||||
) -> <fixed_wide::fixed::Fixed<$n, { $n * 32 }> as core::ops::Mul>::Output {
|
||||
self.length_squared().sqrt_unchecked()
|
||||
}
|
||||
#[inline]
|
||||
pub fn with_length<U, V>(
|
||||
self,
|
||||
length: U,
|
||||
) -> <Vector<N, V> as core::ops::Div<
|
||||
<fixed_wide::fixed::Fixed<$n, { $n * 32 }> as core::ops::Mul>::Output,
|
||||
>>::Output
|
||||
where
|
||||
fixed_wide::fixed::Fixed<$n, { $n * 32 }>: core::ops::Mul<U, Output = V>,
|
||||
U: Copy,
|
||||
V: core::ops::Div<
|
||||
<fixed_wide::fixed::Fixed<$n, { $n * 32 }> as core::ops::Mul>::Output,
|
||||
>,
|
||||
{
|
||||
self * length / self.length()
|
||||
}
|
||||
}
|
||||
};
|
||||
impl<const N:usize> Vector<N,fixed_wide::fixed::Fixed<$n,{$n*32}>>{
|
||||
#[inline]
|
||||
pub fn length(self)-><fixed_wide::fixed::Fixed::<$n,{$n*32}> as core::ops::Mul>::Output{
|
||||
self.length_squared().sqrt_unchecked()
|
||||
}
|
||||
#[inline]
|
||||
pub fn with_length<U,V>(self,length:U)-><Vector<N,V> as core::ops::Div<<fixed_wide::fixed::Fixed::<$n,{$n*32}> as core::ops::Mul>::Output>>::Output
|
||||
where
|
||||
fixed_wide::fixed::Fixed<$n,{$n*32}>:core::ops::Mul<U,Output=V>,
|
||||
U:Copy,
|
||||
V:core::ops::Div<<fixed_wide::fixed::Fixed::<$n,{$n*32}> as core::ops::Mul>::Output>,
|
||||
{
|
||||
self*length/self.length()
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
#[macro_export(local_inner_macros)]
|
||||
macro_rules! macro_4 {
|
||||
( $macro: ident, $any:tt ) => {
|
||||
$crate::macro_repeated!($macro, $any, 1, 2, 3, 4);
|
||||
};
|
||||
( $macro: ident, $any:tt ) => {
|
||||
$crate::macro_repeated!($macro,$any,1,2,3,4);
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
#[macro_export(local_inner_macros)]
|
||||
macro_rules! impl_fixed_wide_vector {
|
||||
() => {
|
||||
$crate::macro_4!(impl_fixed_wide_vector_not_const_generic, ());
|
||||
// I LOVE NOT BEING ABLE TO USE CONST GENERICS
|
||||
$crate::macro_repeated!(
|
||||
impl_fix_not_const_generic,
|
||||
(),
|
||||
(1, 1),
|
||||
(2, 1),
|
||||
(3, 1),
|
||||
(4, 1),
|
||||
(5, 1),
|
||||
(6, 1),
|
||||
(7, 1),
|
||||
(8, 1),
|
||||
(9, 1),
|
||||
(10, 1),
|
||||
(11, 1),
|
||||
(12, 1),
|
||||
(13, 1),
|
||||
(14, 1),
|
||||
(15, 1),
|
||||
(16, 1),
|
||||
(1, 2),
|
||||
(2, 2),
|
||||
(3, 2),
|
||||
(4, 2),
|
||||
(5, 2),
|
||||
(6, 2),
|
||||
(7, 2),
|
||||
(8, 2),
|
||||
(9, 2),
|
||||
(10, 2),
|
||||
(11, 2),
|
||||
(12, 2),
|
||||
(13, 2),
|
||||
(14, 2),
|
||||
(15, 2),
|
||||
(16, 2),
|
||||
(1, 3),
|
||||
(2, 3),
|
||||
(3, 3),
|
||||
(4, 3),
|
||||
(5, 3),
|
||||
(6, 3),
|
||||
(7, 3),
|
||||
(8, 3),
|
||||
(9, 3),
|
||||
(10, 3),
|
||||
(11, 3),
|
||||
(12, 3),
|
||||
(13, 3),
|
||||
(14, 3),
|
||||
(15, 3),
|
||||
(16, 3),
|
||||
(1, 4),
|
||||
(2, 4),
|
||||
(3, 4),
|
||||
(4, 4),
|
||||
(5, 4),
|
||||
(6, 4),
|
||||
(7, 4),
|
||||
(8, 4),
|
||||
(9, 4),
|
||||
(10, 4),
|
||||
(11, 4),
|
||||
(12, 4),
|
||||
(13, 4),
|
||||
(14, 4),
|
||||
(15, 4),
|
||||
(16, 4),
|
||||
(1, 5),
|
||||
(2, 5),
|
||||
(3, 5),
|
||||
(4, 5),
|
||||
(5, 5),
|
||||
(6, 5),
|
||||
(7, 5),
|
||||
(8, 5),
|
||||
(9, 5),
|
||||
(10, 5),
|
||||
(11, 5),
|
||||
(12, 5),
|
||||
(13, 5),
|
||||
(14, 5),
|
||||
(15, 5),
|
||||
(16, 5),
|
||||
(1, 6),
|
||||
(2, 6),
|
||||
(3, 6),
|
||||
(4, 6),
|
||||
(5, 6),
|
||||
(6, 6),
|
||||
(7, 6),
|
||||
(8, 6),
|
||||
(9, 6),
|
||||
(10, 6),
|
||||
(11, 6),
|
||||
(12, 6),
|
||||
(13, 6),
|
||||
(14, 6),
|
||||
(15, 6),
|
||||
(16, 6),
|
||||
(1, 7),
|
||||
(2, 7),
|
||||
(3, 7),
|
||||
(4, 7),
|
||||
(5, 7),
|
||||
(6, 7),
|
||||
(7, 7),
|
||||
(8, 7),
|
||||
(9, 7),
|
||||
(10, 7),
|
||||
(11, 7),
|
||||
(12, 7),
|
||||
(13, 7),
|
||||
(14, 7),
|
||||
(15, 7),
|
||||
(16, 7),
|
||||
(1, 8),
|
||||
(2, 8),
|
||||
(3, 8),
|
||||
(4, 8),
|
||||
(5, 8),
|
||||
(6, 8),
|
||||
(7, 8),
|
||||
(8, 8),
|
||||
(9, 8),
|
||||
(10, 8),
|
||||
(11, 8),
|
||||
(12, 8),
|
||||
(13, 8),
|
||||
(14, 8),
|
||||
(15, 8),
|
||||
(16, 8),
|
||||
(1, 9),
|
||||
(2, 9),
|
||||
(3, 9),
|
||||
(4, 9),
|
||||
(5, 9),
|
||||
(6, 9),
|
||||
(7, 9),
|
||||
(8, 9),
|
||||
(9, 9),
|
||||
(10, 9),
|
||||
(11, 9),
|
||||
(12, 9),
|
||||
(13, 9),
|
||||
(14, 9),
|
||||
(15, 9),
|
||||
(16, 9),
|
||||
(1, 10),
|
||||
(2, 10),
|
||||
(3, 10),
|
||||
(4, 10),
|
||||
(5, 10),
|
||||
(6, 10),
|
||||
(7, 10),
|
||||
(8, 10),
|
||||
(9, 10),
|
||||
(10, 10),
|
||||
(11, 10),
|
||||
(12, 10),
|
||||
(13, 10),
|
||||
(14, 10),
|
||||
(15, 10),
|
||||
(16, 10),
|
||||
(1, 11),
|
||||
(2, 11),
|
||||
(3, 11),
|
||||
(4, 11),
|
||||
(5, 11),
|
||||
(6, 11),
|
||||
(7, 11),
|
||||
(8, 11),
|
||||
(9, 11),
|
||||
(10, 11),
|
||||
(11, 11),
|
||||
(12, 11),
|
||||
(13, 11),
|
||||
(14, 11),
|
||||
(15, 11),
|
||||
(16, 11),
|
||||
(1, 12),
|
||||
(2, 12),
|
||||
(3, 12),
|
||||
(4, 12),
|
||||
(5, 12),
|
||||
(6, 12),
|
||||
(7, 12),
|
||||
(8, 12),
|
||||
(9, 12),
|
||||
(10, 12),
|
||||
(11, 12),
|
||||
(12, 12),
|
||||
(13, 12),
|
||||
(14, 12),
|
||||
(15, 12),
|
||||
(16, 12),
|
||||
(1, 13),
|
||||
(2, 13),
|
||||
(3, 13),
|
||||
(4, 13),
|
||||
(5, 13),
|
||||
(6, 13),
|
||||
(7, 13),
|
||||
(8, 13),
|
||||
(9, 13),
|
||||
(10, 13),
|
||||
(11, 13),
|
||||
(12, 13),
|
||||
(13, 13),
|
||||
(14, 13),
|
||||
(15, 13),
|
||||
(16, 13),
|
||||
(1, 14),
|
||||
(2, 14),
|
||||
(3, 14),
|
||||
(4, 14),
|
||||
(5, 14),
|
||||
(6, 14),
|
||||
(7, 14),
|
||||
(8, 14),
|
||||
(9, 14),
|
||||
(10, 14),
|
||||
(11, 14),
|
||||
(12, 14),
|
||||
(13, 14),
|
||||
(14, 14),
|
||||
(15, 14),
|
||||
(16, 14),
|
||||
(1, 15),
|
||||
(2, 15),
|
||||
(3, 15),
|
||||
(4, 15),
|
||||
(5, 15),
|
||||
(6, 15),
|
||||
(7, 15),
|
||||
(8, 15),
|
||||
(9, 15),
|
||||
(10, 15),
|
||||
(11, 15),
|
||||
(12, 15),
|
||||
(13, 15),
|
||||
(14, 15),
|
||||
(15, 15),
|
||||
(16, 15),
|
||||
(1, 16),
|
||||
(2, 16),
|
||||
(3, 16),
|
||||
(4, 16),
|
||||
(5, 16),
|
||||
(6, 16),
|
||||
(7, 16),
|
||||
(8, 16),
|
||||
(9, 16),
|
||||
(10, 16),
|
||||
(11, 16),
|
||||
(12, 16),
|
||||
(13, 16),
|
||||
(14, 16),
|
||||
(15, 16),
|
||||
(16, 16)
|
||||
);
|
||||
};
|
||||
() => {
|
||||
$crate::macro_4!(impl_fixed_wide_vector_not_const_generic,());
|
||||
// I LOVE NOT BEING ABLE TO USE CONST GENERICS
|
||||
$crate::macro_repeated!(
|
||||
impl_fix_not_const_generic,(),
|
||||
(1,1),(2,1),(3,1),(4,1),(5,1),(6,1),(7,1),(8,1),(9,1),(10,1),(11,1),(12,1),(13,1),(14,1),(15,1),(16,1),
|
||||
(1,2),(2,2),(3,2),(4,2),(5,2),(6,2),(7,2),(8,2),(9,2),(10,2),(11,2),(12,2),(13,2),(14,2),(15,2),(16,2),
|
||||
(1,3),(2,3),(3,3),(4,3),(5,3),(6,3),(7,3),(8,3),(9,3),(10,3),(11,3),(12,3),(13,3),(14,3),(15,3),(16,3),
|
||||
(1,4),(2,4),(3,4),(4,4),(5,4),(6,4),(7,4),(8,4),(9,4),(10,4),(11,4),(12,4),(13,4),(14,4),(15,4),(16,4),
|
||||
(1,5),(2,5),(3,5),(4,5),(5,5),(6,5),(7,5),(8,5),(9,5),(10,5),(11,5),(12,5),(13,5),(14,5),(15,5),(16,5),
|
||||
(1,6),(2,6),(3,6),(4,6),(5,6),(6,6),(7,6),(8,6),(9,6),(10,6),(11,6),(12,6),(13,6),(14,6),(15,6),(16,6),
|
||||
(1,7),(2,7),(3,7),(4,7),(5,7),(6,7),(7,7),(8,7),(9,7),(10,7),(11,7),(12,7),(13,7),(14,7),(15,7),(16,7),
|
||||
(1,8),(2,8),(3,8),(4,8),(5,8),(6,8),(7,8),(8,8),(9,8),(10,8),(11,8),(12,8),(13,8),(14,8),(15,8),(16,8),
|
||||
(1,9),(2,9),(3,9),(4,9),(5,9),(6,9),(7,9),(8,9),(9,9),(10,9),(11,9),(12,9),(13,9),(14,9),(15,9),(16,9),
|
||||
(1,10),(2,10),(3,10),(4,10),(5,10),(6,10),(7,10),(8,10),(9,10),(10,10),(11,10),(12,10),(13,10),(14,10),(15,10),(16,10),
|
||||
(1,11),(2,11),(3,11),(4,11),(5,11),(6,11),(7,11),(8,11),(9,11),(10,11),(11,11),(12,11),(13,11),(14,11),(15,11),(16,11),
|
||||
(1,12),(2,12),(3,12),(4,12),(5,12),(6,12),(7,12),(8,12),(9,12),(10,12),(11,12),(12,12),(13,12),(14,12),(15,12),(16,12),
|
||||
(1,13),(2,13),(3,13),(4,13),(5,13),(6,13),(7,13),(8,13),(9,13),(10,13),(11,13),(12,13),(13,13),(14,13),(15,13),(16,13),
|
||||
(1,14),(2,14),(3,14),(4,14),(5,14),(6,14),(7,14),(8,14),(9,14),(10,14),(11,14),(12,14),(13,14),(14,14),(15,14),(16,14),
|
||||
(1,15),(2,15),(3,15),(4,15),(5,15),(6,15),(7,15),(8,15),(9,15),(10,15),(11,15),(12,15),(13,15),(14,15),(15,15),(16,15),
|
||||
(1,16),(2,16),(3,16),(4,16),(5,16),(6,16),(7,16),(8,16),(9,16),(10,16),(11,16),(12,16),(13,16),(14,16),(15,16),(16,16)
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
#[macro_export(local_inner_macros)]
|
||||
macro_rules! impl_fix_not_const_generic {
|
||||
(
|
||||
macro_rules! impl_fix_not_const_generic{
|
||||
(
|
||||
(),
|
||||
($lhs:expr,$rhs:expr)
|
||||
) => {
|
||||
impl<const N: usize> Vector<N, fixed_wide::fixed::Fixed<$lhs, { $lhs * 32 }>> {
|
||||
paste::item! {
|
||||
#[inline]
|
||||
pub fn [<fix_ $rhs>](self)->Vector<N,fixed_wide::fixed::Fixed<$rhs,{$rhs*32}>>{
|
||||
self.map(|t|t.[<fix_ $rhs>]())
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
)=>{
|
||||
impl<const N:usize> Vector<N,fixed_wide::fixed::Fixed<$lhs,{$lhs*32}>>
|
||||
{
|
||||
paste::item!{
|
||||
#[inline]
|
||||
pub fn [<fix_ $rhs>](self)->Vector<N,fixed_wide::fixed::Fixed<$rhs,{$rhs*32}>>{
|
||||
self.map(|t|t.[<fix_ $rhs>]())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,217 +1,219 @@
|
||||
#[doc(hidden)]
|
||||
#[macro_export(local_inner_macros)]
|
||||
macro_rules! impl_matrix {
|
||||
() => {
|
||||
impl<const X: usize, const Y: usize, T> Matrix<X, Y, T> {
|
||||
#[inline(always)]
|
||||
pub const fn new(array: [[T; Y]; X]) -> Self {
|
||||
Self { array }
|
||||
}
|
||||
#[inline(always)]
|
||||
pub fn to_array(self) -> [[T; Y]; X] {
|
||||
self.array
|
||||
}
|
||||
#[inline]
|
||||
pub fn from_cols(cols: [Vector<Y, T>; X]) -> Self {
|
||||
Matrix::new(cols.map(|col| col.array))
|
||||
}
|
||||
#[inline]
|
||||
pub fn map<F, U>(self, f: F) -> Matrix<X, Y, U>
|
||||
where
|
||||
F: Fn(T) -> U,
|
||||
{
|
||||
Matrix::new(self.array.map(|inner| inner.map(&f)))
|
||||
}
|
||||
#[inline]
|
||||
pub fn transpose(self) -> Matrix<Y, X, T> {
|
||||
//how did I think of this
|
||||
let mut array_of_iterators = self.array.map(|axis| axis.into_iter());
|
||||
Matrix::new(core::array::from_fn(|_| {
|
||||
array_of_iterators
|
||||
.each_mut()
|
||||
.map(|iter| iter.next().unwrap())
|
||||
}))
|
||||
}
|
||||
#[inline]
|
||||
// old (list of rows) MatY<VecX>.MatX<VecZ> = MatY<VecZ>
|
||||
// new (list of columns) MatX<VecY>.MatZ<VecX> = MatZ<VecY>
|
||||
pub fn dot<const Z: usize, U, V>(self, rhs: Matrix<Z, X, U>) -> Matrix<Z, Y, V>
|
||||
where
|
||||
T: core::ops::Mul<U, Output = V> + Copy,
|
||||
V: core::iter::Sum,
|
||||
U: Copy,
|
||||
{
|
||||
let mut array_of_iterators = self.array.map(|axis| axis.into_iter().cycle());
|
||||
Matrix {
|
||||
array: rhs.array.map(|rhs_axis| {
|
||||
core::array::from_fn(|_| {
|
||||
array_of_iterators
|
||||
.iter_mut()
|
||||
.zip(rhs_axis.iter())
|
||||
.map(|(lhs_iter, &rhs_value)| lhs_iter.next().unwrap() * rhs_value)
|
||||
.sum()
|
||||
})
|
||||
}),
|
||||
}
|
||||
}
|
||||
#[inline]
|
||||
// MatX<VecY>.VecY = VecX
|
||||
pub fn transform_vector<U, V>(self, rhs: Vector<X, U>) -> Vector<Y, V>
|
||||
where
|
||||
T: core::ops::Mul<U, Output = V>,
|
||||
V: core::iter::Sum,
|
||||
U: Copy,
|
||||
{
|
||||
let mut array_of_iterators = self.array.map(|axis| axis.into_iter());
|
||||
Vector::new(core::array::from_fn(|_| {
|
||||
array_of_iterators
|
||||
.iter_mut()
|
||||
.zip(rhs.array.iter())
|
||||
.map(|(lhs_iter, &rhs_value)| lhs_iter.next().unwrap() * rhs_value)
|
||||
.sum()
|
||||
}))
|
||||
}
|
||||
}
|
||||
impl<const X: usize, const Y: usize, T> Matrix<X, Y, T>
|
||||
where
|
||||
T: Copy,
|
||||
{
|
||||
#[inline(always)]
|
||||
pub const fn from_value(value: T) -> Self {
|
||||
Self::new([[value; Y]; X])
|
||||
}
|
||||
}
|
||||
() => {
|
||||
impl<const X:usize,const Y:usize,T> Matrix<X,Y,T>{
|
||||
#[inline(always)]
|
||||
pub const fn new(array:[[T;Y];X])->Self{
|
||||
Self{array}
|
||||
}
|
||||
#[inline(always)]
|
||||
pub fn to_array(self)->[[T;Y];X]{
|
||||
self.array
|
||||
}
|
||||
#[inline]
|
||||
pub fn from_cols(cols:[Vector<Y,T>;X])->Self
|
||||
{
|
||||
Matrix::new(
|
||||
cols.map(|col|col.array),
|
||||
)
|
||||
}
|
||||
#[inline]
|
||||
pub fn map<F,U>(self,f:F)->Matrix<X,Y,U>
|
||||
where
|
||||
F:Fn(T)->U
|
||||
{
|
||||
Matrix::new(
|
||||
self.array.map(|inner|inner.map(&f)),
|
||||
)
|
||||
}
|
||||
#[inline]
|
||||
pub fn transpose(self)->Matrix<Y,X,T>{
|
||||
//how did I think of this
|
||||
let mut array_of_iterators=self.array.map(|axis|axis.into_iter());
|
||||
Matrix::new(
|
||||
core::array::from_fn(|_|
|
||||
array_of_iterators.each_mut().map(|iter|
|
||||
iter.next().unwrap()
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
#[inline]
|
||||
// old (list of rows) MatY<VecX>.MatX<VecZ> = MatY<VecZ>
|
||||
// new (list of columns) MatX<VecY>.MatZ<VecX> = MatZ<VecY>
|
||||
pub fn dot<const Z:usize,U,V>(self,rhs:Matrix<Z,X,U>)->Matrix<Z,Y,V>
|
||||
where
|
||||
T:core::ops::Mul<U,Output=V>+Copy,
|
||||
V:core::iter::Sum,
|
||||
U:Copy,
|
||||
{
|
||||
let mut array_of_iterators=self.array.map(|axis|axis.into_iter().cycle());
|
||||
Matrix{
|
||||
array:rhs.array.map(|rhs_axis|
|
||||
core::array::from_fn(|_|
|
||||
array_of_iterators
|
||||
.iter_mut()
|
||||
.zip(rhs_axis.iter())
|
||||
.map(|(lhs_iter,&rhs_value)|
|
||||
lhs_iter.next().unwrap()*rhs_value
|
||||
).sum()
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
#[inline]
|
||||
// MatX<VecY>.VecY = VecX
|
||||
pub fn transform_vector<U,V>(self,rhs:Vector<X,U>)->Vector<Y,V>
|
||||
where
|
||||
T:core::ops::Mul<U,Output=V>,
|
||||
V:core::iter::Sum,
|
||||
U:Copy,
|
||||
{
|
||||
let mut array_of_iterators=self.array.map(|axis|axis.into_iter());
|
||||
Vector::new(
|
||||
core::array::from_fn(|_|
|
||||
array_of_iterators
|
||||
.iter_mut()
|
||||
.zip(rhs.array.iter())
|
||||
.map(|(lhs_iter,&rhs_value)|
|
||||
lhs_iter.next().unwrap()*rhs_value
|
||||
).sum()
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
impl<const X:usize,const Y:usize,T> Matrix<X,Y,T>
|
||||
where
|
||||
T:Copy
|
||||
{
|
||||
#[inline(always)]
|
||||
pub const fn from_value(value:T)->Self{
|
||||
Self::new([[value;Y];X])
|
||||
}
|
||||
}
|
||||
|
||||
impl<const X: usize, const Y: usize, T: Default> Default for Matrix<X, Y, T> {
|
||||
#[inline]
|
||||
fn default() -> Self {
|
||||
Self::new(core::array::from_fn(|_| {
|
||||
core::array::from_fn(|_| Default::default())
|
||||
}))
|
||||
}
|
||||
}
|
||||
impl<const X:usize,const Y:usize,T:Default> Default for Matrix<X,Y,T>{
|
||||
#[inline]
|
||||
fn default()->Self{
|
||||
Self::new(
|
||||
core::array::from_fn(|_|core::array::from_fn(|_|Default::default()))
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<const X: usize, const Y: usize, T: core::fmt::Display> core::fmt::Display
|
||||
for Matrix<X, Y, T>
|
||||
{
|
||||
#[inline]
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter) -> Result<(), core::fmt::Error> {
|
||||
for col in &self.array[0..X] {
|
||||
core::write!(f, "\n")?;
|
||||
for elem in &col[0..Y - 1] {
|
||||
core::write!(f, "{}, ", elem)?;
|
||||
}
|
||||
// assume we will be using matrices of size 1x1 or greater
|
||||
core::write!(f, "{}", col.last().unwrap())?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
impl<const X:usize,const Y:usize,T:core::fmt::Display> core::fmt::Display for Matrix<X,Y,T>{
|
||||
#[inline]
|
||||
fn fmt(&self,f:&mut core::fmt::Formatter)->Result<(),core::fmt::Error>{
|
||||
for col in &self.array[0..X]{
|
||||
core::write!(f,"\n")?;
|
||||
for elem in &col[0..Y-1]{
|
||||
core::write!(f,"{}, ",elem)?;
|
||||
}
|
||||
// assume we will be using matrices of size 1x1 or greater
|
||||
core::write!(f,"{}",col.last().unwrap())?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<const X: usize, const Y: usize, const Z: usize, T, U, V>
|
||||
core::ops::Mul<Matrix<Z, X, U>> for Matrix<X, Y, T>
|
||||
where
|
||||
T: core::ops::Mul<U, Output = V> + Copy,
|
||||
V: core::iter::Sum,
|
||||
U: Copy,
|
||||
{
|
||||
type Output = Matrix<Z, Y, V>;
|
||||
#[inline]
|
||||
fn mul(self, rhs: Matrix<Z, X, U>) -> Self::Output {
|
||||
self.dot(rhs)
|
||||
}
|
||||
}
|
||||
impl<const X: usize, const Y: usize, T, U, V> core::ops::Mul<Vector<X, U>>
|
||||
for Matrix<X, Y, T>
|
||||
where
|
||||
T: core::ops::Mul<U, Output = V>,
|
||||
V: core::iter::Sum,
|
||||
U: Copy,
|
||||
{
|
||||
type Output = Vector<Y, V>;
|
||||
#[inline]
|
||||
fn mul(self, rhs: Vector<X, U>) -> Self::Output {
|
||||
self.transform_vector(rhs)
|
||||
}
|
||||
}
|
||||
#[cfg(feature = "deferred-division")]
|
||||
$crate::impl_matrix_deferred_division!();
|
||||
};
|
||||
impl<const X:usize,const Y:usize,const Z:usize,T,U,V> core::ops::Mul<Matrix<Z,X,U>> for Matrix<X,Y,T>
|
||||
where
|
||||
T:core::ops::Mul<U,Output=V>+Copy,
|
||||
V:core::iter::Sum,
|
||||
U:Copy,
|
||||
{
|
||||
type Output=Matrix<Z,Y,V>;
|
||||
#[inline]
|
||||
fn mul(self,rhs:Matrix<Z,X,U>)->Self::Output{
|
||||
self.dot(rhs)
|
||||
}
|
||||
}
|
||||
impl<const X:usize,const Y:usize,T,U,V> core::ops::Mul<Vector<X,U>> for Matrix<X,Y,T>
|
||||
where
|
||||
T:core::ops::Mul<U,Output=V>,
|
||||
V:core::iter::Sum,
|
||||
U:Copy,
|
||||
{
|
||||
type Output=Vector<Y,V>;
|
||||
#[inline]
|
||||
fn mul(self,rhs:Vector<X,U>)->Self::Output{
|
||||
self.transform_vector(rhs)
|
||||
}
|
||||
}
|
||||
#[cfg(feature="deferred-division")]
|
||||
$crate::impl_matrix_deferred_division!();
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
#[macro_export(local_inner_macros)]
|
||||
macro_rules! impl_matrix_deferred_division {
|
||||
() => {
|
||||
impl<
|
||||
const X: usize,
|
||||
const Y: usize,
|
||||
T: ratio_ops::ratio::Divide<U, Output = V>,
|
||||
U: Copy,
|
||||
V,
|
||||
> ratio_ops::ratio::Divide<U> for Matrix<X, Y, T>
|
||||
{
|
||||
type Output = Matrix<X, Y, V>;
|
||||
#[inline]
|
||||
fn divide(self, rhs: U) -> Self::Output {
|
||||
self.map(|t| t.divide(rhs))
|
||||
}
|
||||
}
|
||||
impl<const X: usize, const Y: usize, T, U> core::ops::Div<U> for Matrix<X, Y, T> {
|
||||
type Output = ratio_ops::ratio::Ratio<Matrix<X, Y, T>, U>;
|
||||
#[inline]
|
||||
fn div(self, rhs: U) -> Self::Output {
|
||||
ratio_ops::ratio::Ratio::new(self, rhs)
|
||||
}
|
||||
}
|
||||
};
|
||||
() => {
|
||||
impl<const X:usize,const Y:usize,T:ratio_ops::ratio::Divide<U,Output=V>,U:Copy,V> ratio_ops::ratio::Divide<U> for Matrix<X,Y,T>{
|
||||
type Output=Matrix<X,Y,V>;
|
||||
#[inline]
|
||||
fn divide(self,rhs:U)->Self::Output{
|
||||
self.map(|t|t.divide(rhs))
|
||||
}
|
||||
}
|
||||
impl<const X:usize,const Y:usize,T,U> core::ops::Div<U> for Matrix<X,Y,T>{
|
||||
type Output=ratio_ops::ratio::Ratio<Matrix<X,Y,T>,U>;
|
||||
#[inline]
|
||||
fn div(self,rhs:U)->Self::Output{
|
||||
ratio_ops::ratio::Ratio::new(self,rhs)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
#[macro_export(local_inner_macros)]
|
||||
macro_rules! impl_matrix_extend {
|
||||
( $x: expr, $y: expr ) => {
|
||||
impl<T> Matrix<$x, $y, T> {
|
||||
#[inline]
|
||||
pub fn extend_column(self, value: Vector<$y, T>) -> Matrix<{ $x + 1 }, $y, T> {
|
||||
let mut iter = self.array.into_iter().chain(core::iter::once(value.array));
|
||||
Matrix::new(core::array::from_fn(|_| iter.next().unwrap()))
|
||||
}
|
||||
#[inline]
|
||||
pub fn extend_row(self, value: Vector<$x, T>) -> Matrix<$x, { $y + 1 }, T> {
|
||||
let mut iter_rows = value.array.into_iter();
|
||||
Matrix::new(self.array.map(|axis| {
|
||||
let mut elements_iter = axis
|
||||
.into_iter()
|
||||
.chain(core::iter::once(iter_rows.next().unwrap()));
|
||||
core::array::from_fn(|_| elements_iter.next().unwrap())
|
||||
}))
|
||||
}
|
||||
}
|
||||
};
|
||||
( $x: expr, $y: expr ) => {
|
||||
impl<T> Matrix<$x,$y,T>{
|
||||
#[inline]
|
||||
pub fn extend_column(self,value:Vector<$y,T>)->Matrix<{$x+1},$y,T>{
|
||||
let mut iter=self.array.into_iter().chain(core::iter::once(value.array));
|
||||
Matrix::new(
|
||||
core::array::from_fn(|_|iter.next().unwrap()),
|
||||
)
|
||||
}
|
||||
#[inline]
|
||||
pub fn extend_row(self,value:Vector<$x,T>)->Matrix<$x,{$y+1},T>{
|
||||
let mut iter_rows=value.array.into_iter();
|
||||
Matrix::new(
|
||||
self.array.map(|axis|{
|
||||
let mut elements_iter=axis.into_iter().chain(core::iter::once(iter_rows.next().unwrap()));
|
||||
core::array::from_fn(|_|elements_iter.next().unwrap())
|
||||
})
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
#[macro_export(local_inner_macros)]
|
||||
macro_rules! impl_matrix_named_fields_shape {
|
||||
(
|
||||
(
|
||||
($struct_outer:ident, $size_outer: expr),
|
||||
($size_inner: expr)
|
||||
) => {
|
||||
impl<T> core::ops::Deref for Matrix<$size_outer, $size_inner, T> {
|
||||
type Target = $struct_outer<Vector<$size_inner, T>>;
|
||||
#[inline]
|
||||
fn deref(&self) -> &Self::Target {
|
||||
unsafe { core::mem::transmute(&self.array) }
|
||||
}
|
||||
}
|
||||
impl<T> core::ops::DerefMut for Matrix<$size_outer, $size_inner, T> {
|
||||
#[inline]
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
unsafe { core::mem::transmute(&mut self.array) }
|
||||
}
|
||||
}
|
||||
};
|
||||
impl<T> core::ops::Deref for Matrix<$size_outer,$size_inner,T>{
|
||||
type Target=$struct_outer<Vector<$size_inner,T>>;
|
||||
#[inline]
|
||||
fn deref(&self)->&Self::Target{
|
||||
unsafe{core::mem::transmute(&self.array)}
|
||||
}
|
||||
}
|
||||
impl<T> core::ops::DerefMut for Matrix<$size_outer,$size_inner,T>{
|
||||
#[inline]
|
||||
fn deref_mut(&mut self)->&mut Self::Target{
|
||||
unsafe{core::mem::transmute(&mut self.array)}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
@ -239,44 +241,32 @@ macro_rules! impl_matrix_named_fields {
|
||||
#[doc(hidden)]
|
||||
#[macro_export(local_inner_macros)]
|
||||
macro_rules! impl_matrix_3x3 {
|
||||
() => {
|
||||
impl<T, T2, T3> Matrix<3, 3, T>
|
||||
where
|
||||
//cross
|
||||
T: core::ops::Mul<T, Output = T2> + Copy,
|
||||
T2: core::ops::Sub,
|
||||
//dot
|
||||
T: core::ops::Mul<<T2 as core::ops::Sub>::Output, Output = T3>,
|
||||
T3: core::iter::Sum,
|
||||
{
|
||||
pub fn det(self) -> T3 {
|
||||
self.x_axis.dot(self.y_axis.cross(self.z_axis))
|
||||
}
|
||||
}
|
||||
impl<T, T2> Matrix<3, 3, T>
|
||||
where
|
||||
T: core::ops::Mul<T, Output = T2> + Copy,
|
||||
T2: core::ops::Sub,
|
||||
{
|
||||
pub fn adjugate(self) -> Matrix<3, 3, <T2 as core::ops::Sub>::Output> {
|
||||
Matrix::new([
|
||||
[
|
||||
self.y_axis.y * self.z_axis.z - self.y_axis.z * self.z_axis.y,
|
||||
self.x_axis.z * self.z_axis.y - self.x_axis.y * self.z_axis.z,
|
||||
self.x_axis.y * self.y_axis.z - self.x_axis.z * self.y_axis.y,
|
||||
],
|
||||
[
|
||||
self.y_axis.z * self.z_axis.x - self.y_axis.x * self.z_axis.z,
|
||||
self.x_axis.x * self.z_axis.z - self.x_axis.z * self.z_axis.x,
|
||||
self.x_axis.z * self.y_axis.x - self.x_axis.x * self.y_axis.z,
|
||||
],
|
||||
[
|
||||
self.y_axis.x * self.z_axis.y - self.y_axis.y * self.z_axis.x,
|
||||
self.x_axis.y * self.z_axis.x - self.x_axis.x * self.z_axis.y,
|
||||
self.x_axis.x * self.y_axis.y - self.x_axis.y * self.y_axis.x,
|
||||
],
|
||||
])
|
||||
}
|
||||
}
|
||||
};
|
||||
()=>{
|
||||
impl<T,T2,T3> Matrix<3,3,T>
|
||||
where
|
||||
//cross
|
||||
T:core::ops::Mul<T,Output=T2>+Copy,
|
||||
T2:core::ops::Sub,
|
||||
//dot
|
||||
T:core::ops::Mul<<T2 as core::ops::Sub>::Output,Output=T3>,
|
||||
T3:core::iter::Sum,
|
||||
{
|
||||
pub fn det(self)->T3{
|
||||
self.x_axis.dot(self.y_axis.cross(self.z_axis))
|
||||
}
|
||||
}
|
||||
impl<T,T2> Matrix<3,3,T>
|
||||
where
|
||||
T:core::ops::Mul<T,Output=T2>+Copy,
|
||||
T2:core::ops::Sub,
|
||||
{
|
||||
pub fn adjugate(self)->Matrix<3,3,<T2 as core::ops::Sub>::Output>{
|
||||
Matrix::new([
|
||||
[self.y_axis.y*self.z_axis.z-self.y_axis.z*self.z_axis.y,self.x_axis.z*self.z_axis.y-self.x_axis.y*self.z_axis.z,self.x_axis.y*self.y_axis.z-self.x_axis.z*self.y_axis.y],
|
||||
[self.y_axis.z*self.z_axis.x-self.y_axis.x*self.z_axis.z,self.x_axis.x*self.z_axis.z-self.x_axis.z*self.z_axis.x,self.x_axis.z*self.y_axis.x-self.x_axis.x*self.y_axis.z],
|
||||
[self.y_axis.x*self.z_axis.y-self.y_axis.y*self.z_axis.x,self.x_axis.y*self.z_axis.x-self.x_axis.x*self.z_axis.y,self.x_axis.x*self.y_axis.y-self.x_axis.y*self.y_axis.x],
|
||||
])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,8 +1,8 @@
|
||||
pub mod common;
|
||||
pub mod matrix;
|
||||
pub mod vector;
|
||||
pub mod matrix;
|
||||
|
||||
#[cfg(feature = "fixed-wide")]
|
||||
#[cfg(feature="fixed-wide")]
|
||||
pub mod fixed_wide;
|
||||
|
||||
#[doc(hidden)]
|
||||
|
@ -1,368 +1,357 @@
|
||||
#[doc(hidden)]
|
||||
#[macro_export(local_inner_macros)]
|
||||
macro_rules! impl_vector {
|
||||
() => {
|
||||
impl<const N: usize, T> Vector<N, T> {
|
||||
#[inline(always)]
|
||||
pub const fn new(array: [T; N]) -> Self {
|
||||
Self { array }
|
||||
}
|
||||
#[inline(always)]
|
||||
pub fn to_array(self) -> [T; N] {
|
||||
self.array
|
||||
}
|
||||
#[inline]
|
||||
pub fn map<F, U>(self, f: F) -> Vector<N, U>
|
||||
where
|
||||
F: Fn(T) -> U,
|
||||
{
|
||||
Vector::new(self.array.map(f))
|
||||
}
|
||||
#[inline]
|
||||
pub fn map_zip<F, U, V>(self, other: Vector<N, U>, f: F) -> Vector<N, V>
|
||||
where
|
||||
F: Fn((T, U)) -> V,
|
||||
{
|
||||
let mut iter = self.array.into_iter().zip(other.array);
|
||||
Vector::new(core::array::from_fn(|_| f(iter.next().unwrap())))
|
||||
}
|
||||
}
|
||||
impl<const N: usize, T: Copy> Vector<N, T> {
|
||||
#[inline(always)]
|
||||
pub const fn from_value(value: T) -> Self {
|
||||
Self::new([value; N])
|
||||
}
|
||||
}
|
||||
() => {
|
||||
impl<const N:usize,T> Vector<N,T>{
|
||||
#[inline(always)]
|
||||
pub const fn new(array:[T;N])->Self{
|
||||
Self{array}
|
||||
}
|
||||
#[inline(always)]
|
||||
pub fn to_array(self)->[T;N]{
|
||||
self.array
|
||||
}
|
||||
#[inline]
|
||||
pub fn map<F,U>(self,f:F)->Vector<N,U>
|
||||
where
|
||||
F:Fn(T)->U
|
||||
{
|
||||
Vector::new(
|
||||
self.array.map(f)
|
||||
)
|
||||
}
|
||||
#[inline]
|
||||
pub fn map_zip<F,U,V>(self,other:Vector<N,U>,f:F)->Vector<N,V>
|
||||
where
|
||||
F:Fn((T,U))->V,
|
||||
{
|
||||
let mut iter=self.array.into_iter().zip(other.array);
|
||||
Vector::new(
|
||||
core::array::from_fn(|_|f(iter.next().unwrap())),
|
||||
)
|
||||
}
|
||||
}
|
||||
impl<const N:usize,T:Copy> Vector<N,T>{
|
||||
#[inline(always)]
|
||||
pub const fn from_value(value:T)->Self{
|
||||
Self::new([value;N])
|
||||
}
|
||||
}
|
||||
|
||||
impl<const N: usize, T: Default> Default for Vector<N, T> {
|
||||
#[inline]
|
||||
fn default() -> Self {
|
||||
Self::new(core::array::from_fn(|_| Default::default()))
|
||||
}
|
||||
}
|
||||
impl<const N:usize,T:Default> Default for Vector<N,T>{
|
||||
#[inline]
|
||||
fn default()->Self{
|
||||
Self::new(
|
||||
core::array::from_fn(|_|Default::default())
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<const N: usize, T: core::fmt::Display> core::fmt::Display for Vector<N, T> {
|
||||
#[inline]
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter) -> Result<(), core::fmt::Error> {
|
||||
for elem in &self.array[0..N - 1] {
|
||||
core::write!(f, "{}, ", elem)?;
|
||||
}
|
||||
// assume we will be using vectors of length 1 or greater
|
||||
core::write!(f, "{}", self.array.last().unwrap())
|
||||
}
|
||||
}
|
||||
impl<const N:usize,T:core::fmt::Display> core::fmt::Display for Vector<N,T>{
|
||||
#[inline]
|
||||
fn fmt(&self,f:&mut core::fmt::Formatter)->Result<(),core::fmt::Error>{
|
||||
for elem in &self.array[0..N-1]{
|
||||
core::write!(f,"{}, ",elem)?;
|
||||
}
|
||||
// assume we will be using vectors of length 1 or greater
|
||||
core::write!(f,"{}",self.array.last().unwrap())
|
||||
}
|
||||
}
|
||||
|
||||
impl<const N: usize, T: Ord> Vector<N, T> {
|
||||
#[inline]
|
||||
pub fn min(self, rhs: Self) -> Self {
|
||||
self.map_zip(rhs, |(a, b)| a.min(b))
|
||||
}
|
||||
#[inline]
|
||||
pub fn max(self, rhs: Self) -> Self {
|
||||
self.map_zip(rhs, |(a, b)| a.max(b))
|
||||
}
|
||||
#[inline]
|
||||
pub fn cmp(self, rhs: Self) -> Vector<N, core::cmp::Ordering> {
|
||||
self.map_zip(rhs, |(a, b)| a.cmp(&b))
|
||||
}
|
||||
#[inline]
|
||||
pub fn lt(self, rhs: Self) -> Vector<N, bool> {
|
||||
self.map_zip(rhs, |(a, b)| a.lt(&b))
|
||||
}
|
||||
#[inline]
|
||||
pub fn gt(self, rhs: Self) -> Vector<N, bool> {
|
||||
self.map_zip(rhs, |(a, b)| a.gt(&b))
|
||||
}
|
||||
#[inline]
|
||||
pub fn ge(self, rhs: Self) -> Vector<N, bool> {
|
||||
self.map_zip(rhs, |(a, b)| a.ge(&b))
|
||||
}
|
||||
#[inline]
|
||||
pub fn le(self, rhs: Self) -> Vector<N, bool> {
|
||||
self.map_zip(rhs, |(a, b)| a.le(&b))
|
||||
}
|
||||
}
|
||||
impl<const N:usize,T:Ord> Vector<N,T>{
|
||||
#[inline]
|
||||
pub fn min(self,rhs:Self)->Self{
|
||||
self.map_zip(rhs,|(a,b)|a.min(b))
|
||||
}
|
||||
#[inline]
|
||||
pub fn max(self,rhs:Self)->Self{
|
||||
self.map_zip(rhs,|(a,b)|a.max(b))
|
||||
}
|
||||
#[inline]
|
||||
pub fn cmp(self,rhs:Self)->Vector<N,core::cmp::Ordering>{
|
||||
self.map_zip(rhs,|(a,b)|a.cmp(&b))
|
||||
}
|
||||
#[inline]
|
||||
pub fn lt(self,rhs:Self)->Vector<N,bool>{
|
||||
self.map_zip(rhs,|(a,b)|a.lt(&b))
|
||||
}
|
||||
#[inline]
|
||||
pub fn gt(self,rhs:Self)->Vector<N,bool>{
|
||||
self.map_zip(rhs,|(a,b)|a.gt(&b))
|
||||
}
|
||||
#[inline]
|
||||
pub fn ge(self,rhs:Self)->Vector<N,bool>{
|
||||
self.map_zip(rhs,|(a,b)|a.ge(&b))
|
||||
}
|
||||
#[inline]
|
||||
pub fn le(self,rhs:Self)->Vector<N,bool>{
|
||||
self.map_zip(rhs,|(a,b)|a.le(&b))
|
||||
}
|
||||
}
|
||||
|
||||
impl<const N: usize> Vector<N, bool> {
|
||||
#[inline]
|
||||
pub fn all(&self) -> bool {
|
||||
self.array == [true; N]
|
||||
}
|
||||
#[inline]
|
||||
pub fn any(&self) -> bool {
|
||||
self.array != [false; N]
|
||||
}
|
||||
}
|
||||
impl<const N:usize> Vector<N,bool>{
|
||||
#[inline]
|
||||
pub fn all(&self)->bool{
|
||||
self.array==[true;N]
|
||||
}
|
||||
#[inline]
|
||||
pub fn any(&self)->bool{
|
||||
self.array!=[false;N]
|
||||
}
|
||||
}
|
||||
|
||||
impl<const N: usize, T: core::ops::Neg<Output = V>, V> core::ops::Neg for Vector<N, T> {
|
||||
type Output = Vector<N, V>;
|
||||
#[inline]
|
||||
fn neg(self) -> Self::Output {
|
||||
Vector::new(self.array.map(|t| -t))
|
||||
}
|
||||
}
|
||||
impl<const N:usize,T:core::ops::Neg<Output=V>,V> core::ops::Neg for Vector<N,T>{
|
||||
type Output=Vector<N,V>;
|
||||
#[inline]
|
||||
fn neg(self)->Self::Output{
|
||||
Vector::new(
|
||||
self.array.map(|t|-t)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<const N: usize, T> Vector<N, T> {
|
||||
#[inline]
|
||||
pub fn dot<U, V>(self, rhs: Vector<N, U>) -> V
|
||||
where
|
||||
T: core::ops::Mul<U, Output = V>,
|
||||
V: core::iter::Sum,
|
||||
{
|
||||
self.array
|
||||
.into_iter()
|
||||
.zip(rhs.array)
|
||||
.map(|(a, b)| a * b)
|
||||
.sum()
|
||||
}
|
||||
}
|
||||
impl<const N:usize,T> Vector<N,T>
|
||||
{
|
||||
#[inline]
|
||||
pub fn dot<U,V>(self,rhs:Vector<N,U>)->V
|
||||
where
|
||||
T:core::ops::Mul<U,Output=V>,
|
||||
V:core::iter::Sum,
|
||||
{
|
||||
self.array.into_iter().zip(rhs.array).map(|(a,b)|a*b).sum()
|
||||
}
|
||||
}
|
||||
|
||||
impl<const N: usize, T, V> Vector<N, T>
|
||||
where
|
||||
T: core::ops::Mul<Output = V> + Copy,
|
||||
V: core::iter::Sum,
|
||||
{
|
||||
#[inline]
|
||||
pub fn length_squared(self) -> V {
|
||||
self.array.into_iter().map(|t| t * t).sum()
|
||||
}
|
||||
}
|
||||
impl<const N:usize,T,V> Vector<N,T>
|
||||
where
|
||||
T:core::ops::Mul<Output=V>+Copy,
|
||||
V:core::iter::Sum,
|
||||
{
|
||||
#[inline]
|
||||
pub fn length_squared(self)->V{
|
||||
self.array.into_iter().map(|t|t*t).sum()
|
||||
}
|
||||
}
|
||||
|
||||
// Impl arithmetic operators
|
||||
$crate::impl_vector_assign_operator!(AddAssign, add_assign);
|
||||
$crate::impl_vector_operator!(Add, add);
|
||||
$crate::impl_vector_assign_operator!(SubAssign, sub_assign);
|
||||
$crate::impl_vector_operator!(Sub, sub);
|
||||
$crate::impl_vector_assign_operator!(RemAssign, rem_assign);
|
||||
$crate::impl_vector_operator!(Rem, rem);
|
||||
// Impl arithmetic operators
|
||||
$crate::impl_vector_assign_operator!(AddAssign, add_assign );
|
||||
$crate::impl_vector_operator!(Add, add );
|
||||
$crate::impl_vector_assign_operator!(SubAssign, sub_assign );
|
||||
$crate::impl_vector_operator!(Sub, sub );
|
||||
$crate::impl_vector_assign_operator!(RemAssign, rem_assign );
|
||||
$crate::impl_vector_operator!(Rem, rem );
|
||||
|
||||
// mul and div are special, usually you multiply by a scalar
|
||||
// and implementing both vec*vec and vec*scalar is conflicting implementations Q_Q
|
||||
$crate::impl_vector_assign_operator_scalar!(MulAssign, mul_assign);
|
||||
$crate::impl_vector_operator_scalar!(Mul, mul);
|
||||
$crate::impl_vector_assign_operator_scalar!(DivAssign, div_assign);
|
||||
#[cfg(not(feature = "deferred-division"))]
|
||||
$crate::impl_vector_operator_scalar!(Div, div);
|
||||
#[cfg(feature = "deferred-division")]
|
||||
$crate::impl_vector_deferred_division!();
|
||||
// mul and div are special, usually you multiply by a scalar
|
||||
// and implementing both vec*vec and vec*scalar is conflicting implementations Q_Q
|
||||
$crate::impl_vector_assign_operator_scalar!(MulAssign, mul_assign );
|
||||
$crate::impl_vector_operator_scalar!(Mul, mul );
|
||||
$crate::impl_vector_assign_operator_scalar!(DivAssign, div_assign );
|
||||
#[cfg(not(feature="deferred-division"))]
|
||||
$crate::impl_vector_operator_scalar!(Div, div );
|
||||
#[cfg(feature="deferred-division")]
|
||||
$crate::impl_vector_deferred_division!();
|
||||
|
||||
// Impl bitwise operators
|
||||
$crate::impl_vector_assign_operator!(BitAndAssign, bitand_assign);
|
||||
$crate::impl_vector_operator!(BitAnd, bitand);
|
||||
$crate::impl_vector_assign_operator!(BitOrAssign, bitor_assign);
|
||||
$crate::impl_vector_operator!(BitOr, bitor);
|
||||
$crate::impl_vector_assign_operator!(BitXorAssign, bitxor_assign);
|
||||
$crate::impl_vector_operator!(BitXor, bitxor);
|
||||
// Impl bitwise operators
|
||||
$crate::impl_vector_assign_operator!(BitAndAssign, bitand_assign );
|
||||
$crate::impl_vector_operator!(BitAnd, bitand );
|
||||
$crate::impl_vector_assign_operator!(BitOrAssign, bitor_assign );
|
||||
$crate::impl_vector_operator!(BitOr, bitor );
|
||||
$crate::impl_vector_assign_operator!(BitXorAssign, bitxor_assign );
|
||||
$crate::impl_vector_operator!(BitXor, bitxor );
|
||||
|
||||
// Impl shift operators
|
||||
$crate::impl_vector_shift_assign_operator!(ShlAssign, shl_assign);
|
||||
$crate::impl_vector_shift_operator!(Shl, shl);
|
||||
$crate::impl_vector_shift_assign_operator!(ShrAssign, shr_assign);
|
||||
$crate::impl_vector_shift_operator!(Shr, shr);
|
||||
// Impl shift operators
|
||||
$crate::impl_vector_shift_assign_operator!(ShlAssign, shl_assign);
|
||||
$crate::impl_vector_shift_operator!(Shl, shl);
|
||||
$crate::impl_vector_shift_assign_operator!(ShrAssign, shr_assign);
|
||||
$crate::impl_vector_shift_operator!(Shr, shr);
|
||||
|
||||
// dedicated methods for this type
|
||||
#[cfg(feature = "fixed-wide")]
|
||||
$crate::impl_fixed_wide_vector!();
|
||||
};
|
||||
// dedicated methods for this type
|
||||
#[cfg(feature="fixed-wide")]
|
||||
$crate::impl_fixed_wide_vector!();
|
||||
}
|
||||
}
|
||||
#[doc(hidden)]
|
||||
#[macro_export(local_inner_macros)]
|
||||
macro_rules! impl_vector_deferred_division {
|
||||
() => {
|
||||
impl<const N: usize, T: ratio_ops::ratio::Divide<U, Output = V>, U: Copy, V>
|
||||
ratio_ops::ratio::Divide<U> for Vector<N, T>
|
||||
{
|
||||
type Output = Vector<N, V>;
|
||||
#[inline]
|
||||
fn divide(self, rhs: U) -> Self::Output {
|
||||
self.map(|t| t.divide(rhs))
|
||||
}
|
||||
}
|
||||
impl<const N: usize, T, U> core::ops::Div<U> for Vector<N, T> {
|
||||
type Output = ratio_ops::ratio::Ratio<Vector<N, T>, U>;
|
||||
#[inline]
|
||||
fn div(self, rhs: U) -> Self::Output {
|
||||
ratio_ops::ratio::Ratio::new(self, rhs)
|
||||
}
|
||||
}
|
||||
};
|
||||
() => {
|
||||
impl<const N:usize,T:ratio_ops::ratio::Divide<U,Output=V>,U:Copy,V> ratio_ops::ratio::Divide<U> for Vector<N,T>{
|
||||
type Output=Vector<N,V>;
|
||||
#[inline]
|
||||
fn divide(self,rhs:U)->Self::Output{
|
||||
self.map(|t|t.divide(rhs))
|
||||
}
|
||||
}
|
||||
impl<const N:usize,T,U> core::ops::Div<U> for Vector<N,T>{
|
||||
type Output=ratio_ops::ratio::Ratio<Vector<N,T>,U>;
|
||||
#[inline]
|
||||
fn div(self,rhs:U)->Self::Output{
|
||||
ratio_ops::ratio::Ratio::new(self,rhs)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#[doc(hidden)]
|
||||
#[macro_export(local_inner_macros)]
|
||||
macro_rules! impl_vector_operator_scalar {
|
||||
($trait: ident, $method: ident ) => {
|
||||
impl<const N: usize, T: core::ops::$trait<U, Output = V>, U: Copy, V> core::ops::$trait<U>
|
||||
for Vector<N, T>
|
||||
{
|
||||
type Output = Vector<N, V>;
|
||||
#[inline]
|
||||
fn $method(self, rhs: U) -> Self::Output {
|
||||
self.map(|t| t.$method(rhs))
|
||||
}
|
||||
}
|
||||
};
|
||||
($trait: ident, $method: ident ) => {
|
||||
impl<const N:usize,T:core::ops::$trait<U,Output=V>,U:Copy,V> core::ops::$trait<U> for Vector<N,T>{
|
||||
type Output=Vector<N,V>;
|
||||
#[inline]
|
||||
fn $method(self,rhs:U)->Self::Output{
|
||||
self.map(|t|t.$method(rhs))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#[doc(hidden)]
|
||||
#[macro_export(local_inner_macros)]
|
||||
macro_rules! impl_vector_operator {
|
||||
($trait: ident, $method: ident ) => {
|
||||
impl<const N: usize, T: core::ops::$trait<U, Output = V>, U, V>
|
||||
core::ops::$trait<Vector<N, U>> for Vector<N, T>
|
||||
{
|
||||
type Output = Vector<N, V>;
|
||||
#[inline]
|
||||
fn $method(self, rhs: Vector<N, U>) -> Self::Output {
|
||||
self.map_zip(rhs, |(a, b)| a.$method(b))
|
||||
}
|
||||
}
|
||||
impl<const N: usize, T: core::ops::$trait<i64, Output = T>> core::ops::$trait<i64>
|
||||
for Vector<N, T>
|
||||
{
|
||||
type Output = Self;
|
||||
#[inline]
|
||||
fn $method(self, rhs: i64) -> Self::Output {
|
||||
self.map(|t| t.$method(rhs))
|
||||
}
|
||||
}
|
||||
};
|
||||
($trait: ident, $method: ident ) => {
|
||||
impl<const N:usize,T:core::ops::$trait<U,Output=V>,U,V> core::ops::$trait<Vector<N,U>> for Vector<N,T>{
|
||||
type Output=Vector<N,V>;
|
||||
#[inline]
|
||||
fn $method(self,rhs:Vector<N,U>)->Self::Output{
|
||||
self.map_zip(rhs,|(a,b)|a.$method(b))
|
||||
}
|
||||
}
|
||||
impl<const N:usize,T:core::ops::$trait<i64,Output=T>> core::ops::$trait<i64> for Vector<N,T>{
|
||||
type Output=Self;
|
||||
#[inline]
|
||||
fn $method(self,rhs:i64)->Self::Output{
|
||||
self.map(|t|t.$method(rhs))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#[doc(hidden)]
|
||||
#[macro_export(local_inner_macros)]
|
||||
macro_rules! impl_vector_assign_operator_scalar {
|
||||
($trait: ident, $method: ident ) => {
|
||||
impl<const N: usize, T: core::ops::$trait<U>, U: Copy> core::ops::$trait<U>
|
||||
for Vector<N, T>
|
||||
{
|
||||
#[inline]
|
||||
fn $method(&mut self, rhs: U) {
|
||||
self.array.iter_mut().for_each(|t| t.$method(rhs))
|
||||
}
|
||||
}
|
||||
};
|
||||
($trait: ident, $method: ident ) => {
|
||||
impl<const N:usize,T:core::ops::$trait<U>,U:Copy> core::ops::$trait<U> for Vector<N,T>{
|
||||
#[inline]
|
||||
fn $method(&mut self,rhs:U){
|
||||
self.array.iter_mut()
|
||||
.for_each(|t|t.$method(rhs))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#[doc(hidden)]
|
||||
#[macro_export(local_inner_macros)]
|
||||
macro_rules! impl_vector_assign_operator {
|
||||
($trait: ident, $method: ident ) => {
|
||||
impl<const N: usize, T: core::ops::$trait<U>, U> core::ops::$trait<Vector<N, U>>
|
||||
for Vector<N, T>
|
||||
{
|
||||
#[inline]
|
||||
fn $method(&mut self, rhs: Vector<N, U>) {
|
||||
self.array
|
||||
.iter_mut()
|
||||
.zip(rhs.array)
|
||||
.for_each(|(a, b)| a.$method(b))
|
||||
}
|
||||
}
|
||||
impl<const N: usize, T: core::ops::$trait<i64>> core::ops::$trait<i64> for Vector<N, T> {
|
||||
#[inline]
|
||||
fn $method(&mut self, rhs: i64) {
|
||||
self.array.iter_mut().for_each(|t| t.$method(rhs))
|
||||
}
|
||||
}
|
||||
};
|
||||
($trait: ident, $method: ident ) => {
|
||||
impl<const N:usize,T:core::ops::$trait<U>,U> core::ops::$trait<Vector<N,U>> for Vector<N,T>{
|
||||
#[inline]
|
||||
fn $method(&mut self,rhs:Vector<N,U>){
|
||||
self.array.iter_mut().zip(rhs.array)
|
||||
.for_each(|(a,b)|a.$method(b))
|
||||
}
|
||||
}
|
||||
impl<const N:usize,T:core::ops::$trait<i64>> core::ops::$trait<i64> for Vector<N,T>{
|
||||
#[inline]
|
||||
fn $method(&mut self,rhs:i64){
|
||||
self.array.iter_mut()
|
||||
.for_each(|t|t.$method(rhs))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#[doc(hidden)]
|
||||
#[macro_export(local_inner_macros)]
|
||||
macro_rules! impl_vector_shift_operator {
|
||||
($trait: ident, $method: ident ) => {
|
||||
impl<const N: usize, T: core::ops::$trait<U, Output = V>, U, V>
|
||||
core::ops::$trait<Vector<N, U>> for Vector<N, T>
|
||||
{
|
||||
type Output = Vector<N, V>;
|
||||
#[inline]
|
||||
fn $method(self, rhs: Vector<N, U>) -> Self::Output {
|
||||
self.map_zip(rhs, |(a, b)| a.$method(b))
|
||||
}
|
||||
}
|
||||
impl<const N: usize, T: core::ops::$trait<u32, Output = V>, V> core::ops::$trait<u32>
|
||||
for Vector<N, T>
|
||||
{
|
||||
type Output = Vector<N, V>;
|
||||
#[inline]
|
||||
fn $method(self, rhs: u32) -> Self::Output {
|
||||
self.map(|t| t.$method(rhs))
|
||||
}
|
||||
}
|
||||
};
|
||||
($trait: ident, $method: ident ) => {
|
||||
impl<const N:usize,T:core::ops::$trait<U,Output=V>,U,V> core::ops::$trait<Vector<N,U>> for Vector<N,T>{
|
||||
type Output=Vector<N,V>;
|
||||
#[inline]
|
||||
fn $method(self,rhs:Vector<N,U>)->Self::Output{
|
||||
self.map_zip(rhs,|(a,b)|a.$method(b))
|
||||
}
|
||||
}
|
||||
impl<const N:usize,T:core::ops::$trait<u32,Output=V>,V> core::ops::$trait<u32> for Vector<N,T>{
|
||||
type Output=Vector<N,V>;
|
||||
#[inline]
|
||||
fn $method(self,rhs:u32)->Self::Output{
|
||||
self.map(|t|t.$method(rhs))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#[doc(hidden)]
|
||||
#[macro_export(local_inner_macros)]
|
||||
macro_rules! impl_vector_shift_assign_operator {
|
||||
($trait: ident, $method: ident ) => {
|
||||
impl<const N: usize, T: core::ops::$trait<U>, U> core::ops::$trait<Vector<N, U>>
|
||||
for Vector<N, T>
|
||||
{
|
||||
#[inline]
|
||||
fn $method(&mut self, rhs: Vector<N, U>) {
|
||||
self.array
|
||||
.iter_mut()
|
||||
.zip(rhs.array)
|
||||
.for_each(|(a, b)| a.$method(b))
|
||||
}
|
||||
}
|
||||
impl<const N: usize, T: core::ops::$trait<u32>> core::ops::$trait<u32> for Vector<N, T> {
|
||||
#[inline]
|
||||
fn $method(&mut self, rhs: u32) {
|
||||
self.array.iter_mut().for_each(|t| t.$method(rhs))
|
||||
}
|
||||
}
|
||||
};
|
||||
($trait: ident, $method: ident ) => {
|
||||
impl<const N:usize,T:core::ops::$trait<U>,U> core::ops::$trait<Vector<N,U>> for Vector<N,T>{
|
||||
#[inline]
|
||||
fn $method(&mut self,rhs:Vector<N,U>){
|
||||
self.array.iter_mut().zip(rhs.array)
|
||||
.for_each(|(a,b)|a.$method(b))
|
||||
}
|
||||
}
|
||||
impl<const N:usize,T:core::ops::$trait<u32>> core::ops::$trait<u32> for Vector<N,T>{
|
||||
#[inline]
|
||||
fn $method(&mut self,rhs:u32){
|
||||
self.array.iter_mut()
|
||||
.for_each(|t|t.$method(rhs))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
#[macro_export(local_inner_macros)]
|
||||
macro_rules! impl_vector_extend {
|
||||
( $size: expr ) => {
|
||||
impl<T> Vector<$size, T> {
|
||||
#[inline]
|
||||
pub fn extend(self, value: T) -> Vector<{ $size + 1 }, T> {
|
||||
let mut iter = self.array.into_iter().chain(core::iter::once(value));
|
||||
Vector::new(core::array::from_fn(|_| iter.next().unwrap()))
|
||||
}
|
||||
}
|
||||
};
|
||||
( $size: expr ) => {
|
||||
impl<T> Vector<$size,T>{
|
||||
#[inline]
|
||||
pub fn extend(self,value:T)->Vector<{$size+1},T>{
|
||||
let mut iter=self.array.into_iter().chain(core::iter::once(value));
|
||||
Vector::new(
|
||||
core::array::from_fn(|_|iter.next().unwrap()),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
#[macro_export(local_inner_macros)]
|
||||
macro_rules! impl_vector_named_fields {
|
||||
( $struct:ident, $size: expr ) => {
|
||||
impl<T> core::ops::Deref for Vector<$size, T> {
|
||||
type Target = $struct<T>;
|
||||
#[inline]
|
||||
fn deref(&self) -> &Self::Target {
|
||||
unsafe { core::mem::transmute(&self.array) }
|
||||
}
|
||||
}
|
||||
impl<T> core::ops::DerefMut for Vector<$size, T> {
|
||||
#[inline]
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
unsafe { core::mem::transmute(&mut self.array) }
|
||||
}
|
||||
}
|
||||
};
|
||||
( $struct:ident, $size: expr ) => {
|
||||
impl<T> core::ops::Deref for Vector<$size,T>{
|
||||
type Target=$struct<T>;
|
||||
#[inline]
|
||||
fn deref(&self)->&Self::Target{
|
||||
unsafe{core::mem::transmute(&self.array)}
|
||||
}
|
||||
}
|
||||
impl<T> core::ops::DerefMut for Vector<$size,T>{
|
||||
#[inline]
|
||||
fn deref_mut(&mut self)->&mut Self::Target{
|
||||
unsafe{core::mem::transmute(&mut self.array)}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
#[macro_export(local_inner_macros)]
|
||||
macro_rules! impl_vector_3 {
|
||||
() => {
|
||||
impl<T> Vector<3, T> {
|
||||
#[inline]
|
||||
pub fn cross<U, V>(self, rhs: Vector<3, U>) -> Vector<3, <V as core::ops::Sub>::Output>
|
||||
where
|
||||
T: core::ops::Mul<U, Output = V> + Copy,
|
||||
U: Copy,
|
||||
V: core::ops::Sub,
|
||||
{
|
||||
Vector::new([
|
||||
self.y * rhs.z - self.z * rhs.y,
|
||||
self.z * rhs.x - self.x * rhs.z,
|
||||
self.x * rhs.y - self.y * rhs.x,
|
||||
])
|
||||
}
|
||||
}
|
||||
};
|
||||
()=>{
|
||||
impl<T> Vector<3,T>
|
||||
{
|
||||
#[inline]
|
||||
pub fn cross<U,V>(self,rhs:Vector<3,U>)->Vector<3,<V as core::ops::Sub>::Output>
|
||||
where
|
||||
T:core::ops::Mul<U,Output=V>+Copy,
|
||||
U:Copy,
|
||||
V:core::ops::Sub,
|
||||
{
|
||||
Vector::new([
|
||||
self.y*rhs.z-self.z*rhs.y,
|
||||
self.z*rhs.x-self.x*rhs.z,
|
||||
self.x*rhs.y-self.y*rhs.x,
|
||||
])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,17 +1,17 @@
|
||||
use crate::vector::Vector;
|
||||
|
||||
#[derive(Clone, Copy, Debug, Hash, Eq, PartialEq)]
|
||||
pub struct Matrix<const X: usize, const Y: usize, T> {
|
||||
pub(crate) array: [[T; Y]; X],
|
||||
#[derive(Clone,Copy,Debug,Hash,Eq,PartialEq)]
|
||||
pub struct Matrix<const X:usize,const Y:usize,T>{
|
||||
pub(crate) array:[[T;Y];X],
|
||||
}
|
||||
|
||||
crate::impl_matrix!();
|
||||
|
||||
crate::impl_matrix_extend!(2, 2);
|
||||
crate::impl_matrix_extend!(2, 3);
|
||||
crate::impl_matrix_extend!(3, 2);
|
||||
crate::impl_matrix_extend!(3, 3);
|
||||
crate::impl_matrix_extend!(2,2);
|
||||
crate::impl_matrix_extend!(2,3);
|
||||
crate::impl_matrix_extend!(3,2);
|
||||
crate::impl_matrix_extend!(3,3);
|
||||
|
||||
//Special case 3x3 matrix operations because I cba to write macros for the arbitrary cases
|
||||
#[cfg(feature = "named-fields")]
|
||||
#[cfg(feature="named-fields")]
|
||||
crate::impl_matrix_3x3!();
|
||||
|
@ -1,23 +1,23 @@
|
||||
use crate::matrix::Matrix;
|
||||
use crate::vector::Vector;
|
||||
use crate::matrix::Matrix;
|
||||
|
||||
#[repr(C)]
|
||||
pub struct Vector2<T> {
|
||||
pub x: T,
|
||||
pub y: T,
|
||||
pub x: T,
|
||||
pub y: T,
|
||||
}
|
||||
#[repr(C)]
|
||||
pub struct Vector3<T> {
|
||||
pub x: T,
|
||||
pub y: T,
|
||||
pub z: T,
|
||||
pub x: T,
|
||||
pub y: T,
|
||||
pub z: T,
|
||||
}
|
||||
#[repr(C)]
|
||||
pub struct Vector4<T> {
|
||||
pub x: T,
|
||||
pub y: T,
|
||||
pub z: T,
|
||||
pub w: T,
|
||||
pub x: T,
|
||||
pub y: T,
|
||||
pub z: T,
|
||||
pub w: T,
|
||||
}
|
||||
|
||||
crate::impl_vector_named_fields!(Vector2, 2);
|
||||
@ -26,26 +26,34 @@ crate::impl_vector_named_fields!(Vector4, 4);
|
||||
|
||||
#[repr(C)]
|
||||
pub struct Matrix2<T> {
|
||||
pub x_axis: T,
|
||||
pub y_axis: T,
|
||||
pub x_axis: T,
|
||||
pub y_axis: T,
|
||||
}
|
||||
#[repr(C)]
|
||||
pub struct Matrix3<T> {
|
||||
pub x_axis: T,
|
||||
pub y_axis: T,
|
||||
pub z_axis: T,
|
||||
pub x_axis: T,
|
||||
pub y_axis: T,
|
||||
pub z_axis: T,
|
||||
}
|
||||
#[repr(C)]
|
||||
pub struct Matrix4<T> {
|
||||
pub x_axis: T,
|
||||
pub y_axis: T,
|
||||
pub z_axis: T,
|
||||
pub w_axis: T,
|
||||
pub x_axis: T,
|
||||
pub y_axis: T,
|
||||
pub z_axis: T,
|
||||
pub w_axis: T,
|
||||
}
|
||||
|
||||
crate::impl_matrix_named_fields!(
|
||||
//outer struct
|
||||
((Matrix2, 2), (Matrix3, 3), (Matrix4, 4)),
|
||||
//inner struct
|
||||
((2), (3), (4))
|
||||
//outer struct
|
||||
(
|
||||
(Matrix2, 2),
|
||||
(Matrix3, 3),
|
||||
(Matrix4, 4)
|
||||
),
|
||||
//inner struct
|
||||
(
|
||||
(2),
|
||||
(3),
|
||||
(4)
|
||||
)
|
||||
);
|
||||
|
@ -1,131 +1,96 @@
|
||||
use crate::types::{Matrix3, Matrix3x2, Matrix3x4, Matrix4x2, Vector3};
|
||||
use crate::types::{Matrix3,Matrix3x2,Matrix3x4,Matrix4x2,Vector3};
|
||||
|
||||
type Planar64 = fixed_wide::types::I32F32;
|
||||
type Planar64Wide1 = fixed_wide::types::I64F64;
|
||||
type Planar64=fixed_wide::types::I32F32;
|
||||
type Planar64Wide1=fixed_wide::types::I64F64;
|
||||
//type Planar64Wide2=fixed_wide::types::I128F128;
|
||||
type Planar64Wide3 = fixed_wide::types::I256F256;
|
||||
type Planar64Wide3=fixed_wide::types::I256F256;
|
||||
|
||||
#[test]
|
||||
fn wide_vec3() {
|
||||
let v = Vector3::from_value(Planar64::from(3));
|
||||
let v1 = v * v.x;
|
||||
let v2 = v1 * v1.y;
|
||||
let v3 = v2 * v2.z;
|
||||
fn wide_vec3(){
|
||||
let v=Vector3::from_value(Planar64::from(3));
|
||||
let v1=v*v.x;
|
||||
let v2=v1*v1.y;
|
||||
let v3=v2*v2.z;
|
||||
|
||||
assert_eq!(
|
||||
v3.array,
|
||||
Vector3::from_value(Planar64Wide3::from(3i128.pow(8))).array
|
||||
);
|
||||
assert_eq!(v3.array,Vector3::from_value(Planar64Wide3::from(3i128.pow(8))).array);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn wide_vec3_dot() {
|
||||
let v = Vector3::from_value(Planar64::from(3));
|
||||
let v1 = v * v.x;
|
||||
let v2 = v1 * v1.y;
|
||||
let v3 = v2.dot(v2);
|
||||
fn wide_vec3_dot(){
|
||||
let v=Vector3::from_value(Planar64::from(3));
|
||||
let v1=v*v.x;
|
||||
let v2=v1*v1.y;
|
||||
let v3=v2.dot(v2);
|
||||
|
||||
assert_eq!(v3, Planar64Wide3::from(3i128.pow(8) * 3));
|
||||
assert_eq!(v3,Planar64Wide3::from(3i128.pow(8)*3));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn wide_vec3_length_squared() {
|
||||
let v = Vector3::from_value(Planar64::from(3));
|
||||
let v1 = v * v.x;
|
||||
let v2 = v1 * v1.y;
|
||||
let v3 = v2.length_squared();
|
||||
fn wide_vec3_length_squared(){
|
||||
let v=Vector3::from_value(Planar64::from(3));
|
||||
let v1=v*v.x;
|
||||
let v2=v1*v1.y;
|
||||
let v3=v2.length_squared();
|
||||
|
||||
assert_eq!(v3, Planar64Wide3::from(3i128.pow(8) * 3));
|
||||
assert_eq!(v3,Planar64Wide3::from(3i128.pow(8)*3));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn wide_matrix_dot() {
|
||||
let lhs = Matrix3x4::new([
|
||||
[
|
||||
Planar64::from(1),
|
||||
Planar64::from(2),
|
||||
Planar64::from(3),
|
||||
Planar64::from(4),
|
||||
],
|
||||
[
|
||||
Planar64::from(5),
|
||||
Planar64::from(6),
|
||||
Planar64::from(7),
|
||||
Planar64::from(8),
|
||||
],
|
||||
[
|
||||
Planar64::from(9),
|
||||
Planar64::from(10),
|
||||
Planar64::from(11),
|
||||
Planar64::from(12),
|
||||
],
|
||||
])
|
||||
.transpose();
|
||||
let rhs = Matrix4x2::new([
|
||||
[Planar64::from(1), Planar64::from(2)],
|
||||
[Planar64::from(3), Planar64::from(4)],
|
||||
[Planar64::from(5), Planar64::from(6)],
|
||||
[Planar64::from(7), Planar64::from(8)],
|
||||
])
|
||||
.transpose();
|
||||
// Mat3<Vec4>.dot(Mat4<Vec2>) -> Mat3<Vec2>
|
||||
let m_dot = lhs * rhs;
|
||||
//In[1]:= {{1, 2, 3, 4}, {5, 6, 7, 8}, {9, 10, 11, 12}} . {{1, 2}, {3, 4}, {5, 6}, {7, 8}}
|
||||
//Out[1]= {{50, 60}, {114, 140}, {178, 220}}
|
||||
assert_eq!(
|
||||
m_dot.array,
|
||||
Matrix3x2::new([
|
||||
[Planar64Wide1::from(50), Planar64Wide1::from(60)],
|
||||
[Planar64Wide1::from(114), Planar64Wide1::from(140)],
|
||||
[Planar64Wide1::from(178), Planar64Wide1::from(220)],
|
||||
])
|
||||
.transpose()
|
||||
.array
|
||||
);
|
||||
fn wide_matrix_dot(){
|
||||
let lhs=Matrix3x4::new([
|
||||
[Planar64::from(1),Planar64::from(2),Planar64::from(3),Planar64::from(4)],
|
||||
[Planar64::from(5),Planar64::from(6),Planar64::from(7),Planar64::from(8)],
|
||||
[Planar64::from(9),Planar64::from(10),Planar64::from(11),Planar64::from(12)],
|
||||
]).transpose();
|
||||
let rhs=Matrix4x2::new([
|
||||
[Planar64::from(1),Planar64::from(2)],
|
||||
[Planar64::from(3),Planar64::from(4)],
|
||||
[Planar64::from(5),Planar64::from(6)],
|
||||
[Planar64::from(7),Planar64::from(8)],
|
||||
]).transpose();
|
||||
// Mat3<Vec4>.dot(Mat4<Vec2>) -> Mat3<Vec2>
|
||||
let m_dot=lhs*rhs;
|
||||
//In[1]:= {{1, 2, 3, 4}, {5, 6, 7, 8}, {9, 10, 11, 12}} . {{1, 2}, {3, 4}, {5, 6}, {7, 8}}
|
||||
//Out[1]= {{50, 60}, {114, 140}, {178, 220}}
|
||||
assert_eq!(
|
||||
m_dot.array,
|
||||
Matrix3x2::new([
|
||||
[Planar64Wide1::from(50),Planar64Wide1::from(60)],
|
||||
[Planar64Wide1::from(114),Planar64Wide1::from(140)],
|
||||
[Planar64Wide1::from(178),Planar64Wide1::from(220)],
|
||||
]).transpose().array
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "named-fields")]
|
||||
fn wide_matrix_det() {
|
||||
let m = Matrix3::new([
|
||||
[Planar64::from(1), Planar64::from(2), Planar64::from(3)],
|
||||
[Planar64::from(4), Planar64::from(5), Planar64::from(7)],
|
||||
[Planar64::from(6), Planar64::from(8), Planar64::from(9)],
|
||||
]);
|
||||
// In[2]:= Det[{{1, 2, 3}, {4, 5, 7}, {6, 8, 9}}]
|
||||
// Out[2]= 7
|
||||
assert_eq!(m.det(), fixed_wide::fixed::Fixed::<3, 96>::from(7));
|
||||
#[cfg(feature="named-fields")]
|
||||
fn wide_matrix_det(){
|
||||
let m=Matrix3::new([
|
||||
[Planar64::from(1),Planar64::from(2),Planar64::from(3)],
|
||||
[Planar64::from(4),Planar64::from(5),Planar64::from(7)],
|
||||
[Planar64::from(6),Planar64::from(8),Planar64::from(9)],
|
||||
]);
|
||||
// In[2]:= Det[{{1, 2, 3}, {4, 5, 7}, {6, 8, 9}}]
|
||||
// Out[2]= 7
|
||||
assert_eq!(m.det(),fixed_wide::fixed::Fixed::<3,96>::from(7));
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "named-fields")]
|
||||
fn wide_matrix_adjugate() {
|
||||
let m = Matrix3::new([
|
||||
[Planar64::from(1), Planar64::from(2), Planar64::from(3)],
|
||||
[Planar64::from(4), Planar64::from(5), Planar64::from(7)],
|
||||
[Planar64::from(6), Planar64::from(8), Planar64::from(9)],
|
||||
]);
|
||||
// In[6]:= Adjugate[{{1, 2, 3}, {4, 5, 7}, {6, 8, 9}}]
|
||||
// Out[6]= {{-11, 6, -1}, {6, -9, 5}, {2, 4, -3}}
|
||||
assert_eq!(
|
||||
m.adjugate().array,
|
||||
Matrix3::new([
|
||||
[
|
||||
Planar64Wide1::from(-11),
|
||||
Planar64Wide1::from(6),
|
||||
Planar64Wide1::from(-1)
|
||||
],
|
||||
[
|
||||
Planar64Wide1::from(6),
|
||||
Planar64Wide1::from(-9),
|
||||
Planar64Wide1::from(5)
|
||||
],
|
||||
[
|
||||
Planar64Wide1::from(2),
|
||||
Planar64Wide1::from(4),
|
||||
Planar64Wide1::from(-3)
|
||||
],
|
||||
])
|
||||
.array
|
||||
);
|
||||
#[cfg(feature="named-fields")]
|
||||
fn wide_matrix_adjugate(){
|
||||
let m=Matrix3::new([
|
||||
[Planar64::from(1),Planar64::from(2),Planar64::from(3)],
|
||||
[Planar64::from(4),Planar64::from(5),Planar64::from(7)],
|
||||
[Planar64::from(6),Planar64::from(8),Planar64::from(9)],
|
||||
]);
|
||||
// In[6]:= Adjugate[{{1, 2, 3}, {4, 5, 7}, {6, 8, 9}}]
|
||||
// Out[6]= {{-11, 6, -1}, {6, -9, 5}, {2, 4, -3}}
|
||||
assert_eq!(
|
||||
m.adjugate().array,
|
||||
Matrix3::new([
|
||||
[Planar64Wide1::from(-11),Planar64Wide1::from(6),Planar64Wide1::from(-1)],
|
||||
[Planar64Wide1::from(6),Planar64Wide1::from(-9),Planar64Wide1::from(5)],
|
||||
[Planar64Wide1::from(2),Planar64Wide1::from(4),Planar64Wide1::from(-3)],
|
||||
]).array
|
||||
);
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
mod tests;
|
||||
|
||||
#[cfg(feature = "named-fields")]
|
||||
#[cfg(feature="named-fields")]
|
||||
mod named;
|
||||
|
||||
mod fixed_wide;
|
||||
|
@ -1,29 +1,30 @@
|
||||
use crate::types::{Matrix3, Vector3};
|
||||
use crate::types::{Vector3,Matrix3};
|
||||
|
||||
#[test]
|
||||
fn test_vector() {
|
||||
let mut v = Vector3::new([1, 2, 3]);
|
||||
assert_eq!(v.x, 1);
|
||||
assert_eq!(v.y, 2);
|
||||
assert_eq!(v.z, 3);
|
||||
fn test_vector(){
|
||||
let mut v=Vector3::new([1,2,3]);
|
||||
assert_eq!(v.x,1);
|
||||
assert_eq!(v.y,2);
|
||||
assert_eq!(v.z,3);
|
||||
|
||||
v.x = 5;
|
||||
assert_eq!(v.x, 5);
|
||||
v.x=5;
|
||||
assert_eq!(v.x,5);
|
||||
|
||||
v.y *= v.x;
|
||||
assert_eq!(v.y, 10);
|
||||
v.y*=v.x;
|
||||
assert_eq!(v.y,10);
|
||||
}
|
||||
|
||||
|
||||
#[test]
|
||||
fn test_matrix() {
|
||||
let mut v = Matrix3::from_value(2);
|
||||
assert_eq!(v.x_axis.x, 2);
|
||||
assert_eq!(v.y_axis.y, 2);
|
||||
assert_eq!(v.z_axis.z, 2);
|
||||
fn test_matrix(){
|
||||
let mut v=Matrix3::from_value(2);
|
||||
assert_eq!(v.x_axis.x,2);
|
||||
assert_eq!(v.y_axis.y,2);
|
||||
assert_eq!(v.z_axis.z,2);
|
||||
|
||||
v.x_axis.x = 5;
|
||||
assert_eq!(v.x_axis.x, 5);
|
||||
v.x_axis.x=5;
|
||||
assert_eq!(v.x_axis.x,5);
|
||||
|
||||
v.y_axis.z *= v.x_axis.x;
|
||||
assert_eq!(v.y_axis.z, 10);
|
||||
v.y_axis.z*=v.x_axis.x;
|
||||
assert_eq!(v.y_axis.z,10);
|
||||
}
|
||||
|
@ -1,51 +1,59 @@
|
||||
use crate::types::{Matrix2x3, Matrix3x2, Matrix3x4, Matrix4x2, Vector2, Vector3};
|
||||
use crate::types::{Vector2,Vector3,Matrix3x4,Matrix4x2,Matrix3x2,Matrix2x3};
|
||||
|
||||
#[test]
|
||||
fn test_bool() {
|
||||
assert_eq!(Vector3::new([false, false, false]).any(), false);
|
||||
assert_eq!(Vector3::new([false, false, true]).any(), true);
|
||||
assert_eq!(Vector3::new([false, false, true]).all(), false);
|
||||
assert_eq!(Vector3::new([true, true, true]).all(), true);
|
||||
fn test_bool(){
|
||||
assert_eq!(Vector3::new([false,false,false]).any(),false);
|
||||
assert_eq!(Vector3::new([false,false,true]).any(),true);
|
||||
assert_eq!(Vector3::new([false,false,true]).all(),false);
|
||||
assert_eq!(Vector3::new([true,true,true]).all(),true);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_length_squared() {
|
||||
assert_eq!(Vector3::new([1, 2, 3]).length_squared(), 14);
|
||||
fn test_length_squared(){
|
||||
assert_eq!(Vector3::new([1,2,3]).length_squared(),14);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_arithmetic() {
|
||||
let a = Vector3::new([1, 2, 3]);
|
||||
assert_eq!((a + a * 2).array, Vector3::new([1 * 3, 2 * 3, 3 * 3]).array);
|
||||
fn test_arithmetic(){
|
||||
let a=Vector3::new([1,2,3]);
|
||||
assert_eq!((a+a*2).array,Vector3::new([1*3,2*3,3*3]).array);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn matrix_transform_vector() {
|
||||
let m = Matrix2x3::new([[1, 2, 3], [4, 5, 6]]).transpose();
|
||||
let v = Vector3::new([1, 2, 3]);
|
||||
let transformed = m * v;
|
||||
assert_eq!(transformed.array, Vector2::new([14, 32]).array);
|
||||
fn matrix_transform_vector(){
|
||||
let m=Matrix2x3::new([
|
||||
[1,2,3],
|
||||
[4,5,6],
|
||||
]).transpose();
|
||||
let v=Vector3::new([1,2,3]);
|
||||
let transformed=m*v;
|
||||
assert_eq!(transformed.array,Vector2::new([14,32]).array);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn matrix_dot() {
|
||||
// All this code was written row major and I converted the lib to colum major
|
||||
let rhs = Matrix4x2::new([[1.0, 2.0], [3.0, 4.0], [5.0, 6.0], [7.0, 8.0]]).transpose(); // | | |
|
||||
let lhs = Matrix3x4::new([
|
||||
// | | |
|
||||
[1.0, 2.0, 3.0, 4.0], // [ 50.0, 60.0],
|
||||
[5.0, 6.0, 7.0, 8.0], // [114.0,140.0],
|
||||
[9.0, 10.0, 11.0, 12.0], // [178.0,220.0],
|
||||
])
|
||||
.transpose();
|
||||
// Mat3<Vec4>.dot(Mat4<Vec2>) -> Mat3<Vec2>
|
||||
let m_dot = lhs * rhs;
|
||||
//In[1]:= {{1, 2, 3, 4}, {5, 6, 7, 8}, {9, 10, 11, 12}} . {{1, 2}, {3, 4}, {5, 6}, {7, 8}}
|
||||
//Out[1]= {{50, 60}, {114, 140}, {178, 220}}
|
||||
assert_eq!(
|
||||
m_dot.array,
|
||||
Matrix3x2::new([[50.0, 60.0], [114.0, 140.0], [178.0, 220.0],])
|
||||
.transpose()
|
||||
.array
|
||||
);
|
||||
fn matrix_dot(){
|
||||
// All this code was written row major and I converted the lib to colum major
|
||||
let rhs=Matrix4x2::new([
|
||||
[ 1.0, 2.0],
|
||||
[ 3.0, 4.0],
|
||||
[ 5.0, 6.0],
|
||||
[ 7.0, 8.0],
|
||||
]).transpose(); // | | |
|
||||
let lhs=Matrix3x4::new([ // | | |
|
||||
[1.0, 2.0, 3.0, 4.0],// [ 50.0, 60.0],
|
||||
[5.0, 6.0, 7.0, 8.0],// [114.0,140.0],
|
||||
[9.0,10.0,11.0,12.0],// [178.0,220.0],
|
||||
]).transpose();
|
||||
// Mat3<Vec4>.dot(Mat4<Vec2>) -> Mat3<Vec2>
|
||||
let m_dot=lhs*rhs;
|
||||
//In[1]:= {{1, 2, 3, 4}, {5, 6, 7, 8}, {9, 10, 11, 12}} . {{1, 2}, {3, 4}, {5, 6}, {7, 8}}
|
||||
//Out[1]= {{50, 60}, {114, 140}, {178, 220}}
|
||||
assert_eq!(
|
||||
m_dot.array,
|
||||
Matrix3x2::new([
|
||||
[50.0,60.0],
|
||||
[114.0,140.0],
|
||||
[178.0,220.0],
|
||||
]).transpose().array
|
||||
);
|
||||
}
|
||||
|
@ -1,18 +1,18 @@
|
||||
use crate::matrix::Matrix;
|
||||
use crate::vector::Vector;
|
||||
use crate::matrix::Matrix;
|
||||
|
||||
pub type Vector2<T> = Vector<2, T>;
|
||||
pub type Vector3<T> = Vector<3, T>;
|
||||
pub type Vector4<T> = Vector<4, T>;
|
||||
pub type Vector2<T>=Vector<2,T>;
|
||||
pub type Vector3<T>=Vector<3,T>;
|
||||
pub type Vector4<T>=Vector<4,T>;
|
||||
|
||||
pub type Matrix2<T> = Matrix<2, 2, T>;
|
||||
pub type Matrix2x3<T> = Matrix<2, 3, T>;
|
||||
pub type Matrix2x4<T> = Matrix<2, 4, T>;
|
||||
pub type Matrix2<T>=Matrix<2,2,T>;
|
||||
pub type Matrix2x3<T>=Matrix<2,3,T>;
|
||||
pub type Matrix2x4<T>=Matrix<2,4,T>;
|
||||
|
||||
pub type Matrix3x2<T> = Matrix<3, 2, T>;
|
||||
pub type Matrix3<T> = Matrix<3, 3, T>;
|
||||
pub type Matrix3x4<T> = Matrix<3, 4, T>;
|
||||
pub type Matrix3x2<T>=Matrix<3,2,T>;
|
||||
pub type Matrix3<T>=Matrix<3,3,T>;
|
||||
pub type Matrix3x4<T>=Matrix<3,4,T>;
|
||||
|
||||
pub type Matrix4x2<T> = Matrix<4, 2, T>;
|
||||
pub type Matrix4x3<T> = Matrix<4, 3, T>;
|
||||
pub type Matrix4<T> = Matrix<4, 4, T>;
|
||||
pub type Matrix4x2<T>=Matrix<4,2,T>;
|
||||
pub type Matrix4x3<T>=Matrix<4,3,T>;
|
||||
pub type Matrix4<T>=Matrix<4,4,T>;
|
||||
|
@ -3,9 +3,9 @@
|
||||
/// v.x += v.z;
|
||||
/// println!("v.x={}",v.x);
|
||||
|
||||
#[derive(Clone, Copy, Debug, Hash, Eq, PartialEq)]
|
||||
pub struct Vector<const N: usize, T> {
|
||||
pub(crate) array: [T; N],
|
||||
#[derive(Clone,Copy,Debug,Hash,Eq,PartialEq)]
|
||||
pub struct Vector<const N:usize,T>{
|
||||
pub(crate) array:[T;N],
|
||||
}
|
||||
|
||||
crate::impl_vector!();
|
||||
@ -15,5 +15,5 @@ crate::impl_vector_extend!(2);
|
||||
crate::impl_vector_extend!(3);
|
||||
|
||||
//cross product
|
||||
#[cfg(feature = "named-fields")]
|
||||
#[cfg(feature="named-fields")]
|
||||
crate::impl_vector_3!();
|
||||
|
@ -1,92 +1,77 @@
|
||||
#[derive(Clone, Copy, Debug, Hash)]
|
||||
pub struct Ratio<Num, Den> {
|
||||
pub num: Num,
|
||||
pub den: Den,
|
||||
#[derive(Clone,Copy,Debug,Hash)]
|
||||
pub struct Ratio<Num,Den>{
|
||||
pub num:Num,
|
||||
pub den:Den,
|
||||
}
|
||||
impl<Num, Den> Ratio<Num, Den> {
|
||||
#[inline(always)]
|
||||
pub const fn new(num: Num, den: Den) -> Self {
|
||||
Self { num, den }
|
||||
}
|
||||
impl<Num,Den> Ratio<Num,Den>{
|
||||
#[inline(always)]
|
||||
pub const fn new(num:Num,den:Den)->Self{
|
||||
Self{num,den}
|
||||
}
|
||||
}
|
||||
|
||||
/// The actual divide implementation, Div is replaced with a Ratio constructor
|
||||
pub trait Divide<Rhs = Self> {
|
||||
type Output;
|
||||
fn divide(self, rhs: Rhs) -> Self::Output;
|
||||
pub trait Divide<Rhs=Self>{
|
||||
type Output;
|
||||
fn divide(self,rhs:Rhs)->Self::Output;
|
||||
}
|
||||
|
||||
impl<Num, Den> Ratio<Num, Den>
|
||||
where
|
||||
Num: Divide<Den>,
|
||||
impl<Num,Den> Ratio<Num,Den>
|
||||
where
|
||||
Num:Divide<Den>,
|
||||
{
|
||||
#[inline]
|
||||
pub fn divide(self) -> <Num as Divide<Den>>::Output {
|
||||
self.num.divide(self.den)
|
||||
}
|
||||
#[inline]
|
||||
pub fn divide(self)-><Num as Divide<Den>>::Output{
|
||||
self.num.divide(self.den)
|
||||
}
|
||||
}
|
||||
|
||||
//take care to use the ratio methods to avoid nested ratios
|
||||
|
||||
impl<LhsNum, LhsDen> Ratio<LhsNum, LhsDen> {
|
||||
#[inline]
|
||||
pub fn mul_ratio<RhsNum, RhsDen>(
|
||||
self,
|
||||
rhs: Ratio<RhsNum, RhsDen>,
|
||||
) -> Ratio<<LhsNum as core::ops::Mul<RhsNum>>::Output, <LhsDen as core::ops::Mul<RhsDen>>::Output>
|
||||
where
|
||||
LhsNum: core::ops::Mul<RhsNum>,
|
||||
LhsDen: core::ops::Mul<RhsDen>,
|
||||
{
|
||||
Ratio::new(self.num * rhs.num, self.den * rhs.den)
|
||||
}
|
||||
#[inline]
|
||||
pub fn div_ratio<RhsNum, RhsDen>(
|
||||
self,
|
||||
rhs: Ratio<RhsNum, RhsDen>,
|
||||
) -> Ratio<<LhsNum as core::ops::Mul<RhsDen>>::Output, <LhsDen as core::ops::Mul<RhsNum>>::Output>
|
||||
where
|
||||
LhsNum: core::ops::Mul<RhsDen>,
|
||||
LhsDen: core::ops::Mul<RhsNum>,
|
||||
{
|
||||
Ratio::new(self.num * rhs.den, self.den * rhs.num)
|
||||
}
|
||||
impl<LhsNum,LhsDen> Ratio<LhsNum,LhsDen>{
|
||||
#[inline]
|
||||
pub fn mul_ratio<RhsNum,RhsDen>(self,rhs:Ratio<RhsNum,RhsDen>)->Ratio<<LhsNum as core::ops::Mul<RhsNum>>::Output,<LhsDen as core::ops::Mul<RhsDen>>::Output>
|
||||
where
|
||||
LhsNum:core::ops::Mul<RhsNum>,
|
||||
LhsDen:core::ops::Mul<RhsDen>,
|
||||
{
|
||||
Ratio::new(self.num*rhs.num,self.den*rhs.den)
|
||||
}
|
||||
#[inline]
|
||||
pub fn div_ratio<RhsNum,RhsDen>(self,rhs:Ratio<RhsNum,RhsDen>)->Ratio<<LhsNum as core::ops::Mul<RhsDen>>::Output,<LhsDen as core::ops::Mul<RhsNum>>::Output>
|
||||
where
|
||||
LhsNum:core::ops::Mul<RhsDen>,
|
||||
LhsDen:core::ops::Mul<RhsNum>,
|
||||
{
|
||||
Ratio::new(self.num*rhs.den,self.den*rhs.num)
|
||||
}
|
||||
}
|
||||
macro_rules! impl_ratio_method {
|
||||
($trait:ident, $method:ident, $ratio_method:ident) => {
|
||||
impl<LhsNum, LhsDen> Ratio<LhsNum, LhsDen> {
|
||||
#[inline]
|
||||
pub fn $ratio_method<RhsNum, RhsDen, LhsCrossMul, RhsCrossMul>(
|
||||
self,
|
||||
rhs: Ratio<RhsNum, RhsDen>,
|
||||
) -> Ratio<
|
||||
<LhsCrossMul as core::ops::$trait<RhsCrossMul>>::Output,
|
||||
<LhsDen as core::ops::Mul<RhsDen>>::Output,
|
||||
>
|
||||
where
|
||||
LhsNum: core::ops::Mul<RhsDen, Output = LhsCrossMul>,
|
||||
LhsDen: core::ops::Mul<RhsNum, Output = RhsCrossMul>,
|
||||
LhsDen: core::ops::Mul<RhsDen>,
|
||||
LhsDen: Copy,
|
||||
RhsDen: Copy,
|
||||
LhsCrossMul: core::ops::$trait<RhsCrossMul>,
|
||||
{
|
||||
Ratio::new(
|
||||
(self.num * rhs.den).$method(self.den * rhs.num),
|
||||
self.den * rhs.den,
|
||||
)
|
||||
}
|
||||
}
|
||||
};
|
||||
($trait:ident, $method:ident, $ratio_method:ident) => {
|
||||
impl<LhsNum,LhsDen> Ratio<LhsNum,LhsDen>{
|
||||
#[inline]
|
||||
pub fn $ratio_method<RhsNum,RhsDen,LhsCrossMul,RhsCrossMul>(self,rhs:Ratio<RhsNum,RhsDen>)->Ratio<<LhsCrossMul as core::ops::$trait<RhsCrossMul>>::Output,<LhsDen as core::ops::Mul<RhsDen>>::Output>
|
||||
where
|
||||
LhsNum:core::ops::Mul<RhsDen,Output=LhsCrossMul>,
|
||||
LhsDen:core::ops::Mul<RhsNum,Output=RhsCrossMul>,
|
||||
LhsDen:core::ops::Mul<RhsDen>,
|
||||
LhsDen:Copy,
|
||||
RhsDen:Copy,
|
||||
LhsCrossMul:core::ops::$trait<RhsCrossMul>,
|
||||
{
|
||||
Ratio::new((self.num*rhs.den).$method(self.den*rhs.num),self.den*rhs.den)
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
impl_ratio_method!(Add, add, add_ratio);
|
||||
impl_ratio_method!(Sub, sub, sub_ratio);
|
||||
impl_ratio_method!(Rem, rem, rem_ratio);
|
||||
impl_ratio_method!(Add,add,add_ratio);
|
||||
impl_ratio_method!(Sub,sub,sub_ratio);
|
||||
impl_ratio_method!(Rem,rem,rem_ratio);
|
||||
|
||||
/// Comparing two ratios needs to know the parity of the denominators
|
||||
/// For signed integers this can be implemented with is_negative()
|
||||
pub trait Parity {
|
||||
fn parity(&self) -> bool;
|
||||
pub trait Parity{
|
||||
fn parity(&self)->bool;
|
||||
}
|
||||
macro_rules! impl_parity_unsigned{
|
||||
($($type:ty),*)=>{
|
||||
@ -122,196 +107,191 @@ macro_rules! impl_parity_float{
|
||||
};
|
||||
}
|
||||
|
||||
impl_parity_unsigned!(u8, u16, u32, u64, u128, usize);
|
||||
impl_parity_signed!(i8, i16, i32, i64, i128, isize);
|
||||
impl_parity_float!(f32, f64);
|
||||
impl_parity_unsigned!(u8,u16,u32,u64,u128,usize);
|
||||
impl_parity_signed!(i8,i16,i32,i64,i128,isize);
|
||||
impl_parity_float!(f32,f64);
|
||||
|
||||
macro_rules! impl_ratio_ord_method {
|
||||
($method:ident, $ratio_method:ident, $output:ty) => {
|
||||
impl<LhsNum, LhsDen: Parity> Ratio<LhsNum, LhsDen> {
|
||||
#[inline]
|
||||
pub fn $ratio_method<RhsNum, RhsDen: Parity, T>(
|
||||
self,
|
||||
rhs: Ratio<RhsNum, RhsDen>,
|
||||
) -> $output
|
||||
where
|
||||
LhsNum: core::ops::Mul<RhsDen, Output = T>,
|
||||
LhsDen: core::ops::Mul<RhsNum, Output = T>,
|
||||
T: Ord,
|
||||
{
|
||||
match self.den.parity() ^ rhs.den.parity() {
|
||||
true => (self.den * rhs.num).$method(&(self.num * rhs.den)),
|
||||
false => (self.num * rhs.den).$method(&(self.den * rhs.num)),
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
macro_rules! impl_ratio_ord_method{
|
||||
($method:ident, $ratio_method:ident, $output:ty)=>{
|
||||
impl<LhsNum,LhsDen:Parity> Ratio<LhsNum,LhsDen>{
|
||||
#[inline]
|
||||
pub fn $ratio_method<RhsNum,RhsDen:Parity,T>(self,rhs:Ratio<RhsNum,RhsDen>)->$output
|
||||
where
|
||||
LhsNum:core::ops::Mul<RhsDen,Output=T>,
|
||||
LhsDen:core::ops::Mul<RhsNum,Output=T>,
|
||||
T:Ord,
|
||||
{
|
||||
match self.den.parity()^rhs.den.parity(){
|
||||
true=>(self.den*rhs.num).$method(&(self.num*rhs.den)),
|
||||
false=>(self.num*rhs.den).$method(&(self.den*rhs.num)),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
//PartialEq
|
||||
impl_ratio_ord_method!(eq, eq_ratio, bool);
|
||||
impl_ratio_ord_method!(eq,eq_ratio,bool);
|
||||
//PartialOrd
|
||||
impl_ratio_ord_method!(lt, lt_ratio, bool);
|
||||
impl_ratio_ord_method!(gt, gt_ratio, bool);
|
||||
impl_ratio_ord_method!(le, le_ratio, bool);
|
||||
impl_ratio_ord_method!(ge, ge_ratio, bool);
|
||||
impl_ratio_ord_method!(partial_cmp, partial_cmp_ratio, Option<core::cmp::Ordering>);
|
||||
impl_ratio_ord_method!(lt,lt_ratio,bool);
|
||||
impl_ratio_ord_method!(gt,gt_ratio,bool);
|
||||
impl_ratio_ord_method!(le,le_ratio,bool);
|
||||
impl_ratio_ord_method!(ge,ge_ratio,bool);
|
||||
impl_ratio_ord_method!(partial_cmp,partial_cmp_ratio,Option<core::cmp::Ordering>);
|
||||
//Ord
|
||||
impl_ratio_ord_method!(cmp, cmp_ratio, core::cmp::Ordering);
|
||||
impl_ratio_ord_method!(cmp,cmp_ratio,core::cmp::Ordering);
|
||||
|
||||
/* generic rhs mul is not possible!
|
||||
impl<Lhs,RhsNum,RhsDen> core::ops::Mul<Ratio<RhsNum,RhsDen>> for Lhs
|
||||
where
|
||||
Lhs:core::ops::Mul<RhsNum>,
|
||||
where
|
||||
Lhs:core::ops::Mul<RhsNum>,
|
||||
{
|
||||
type Output=Ratio<<Lhs as core::ops::Mul<RhsNum>>::Output,RhsDen>;
|
||||
#[inline]
|
||||
fn mul(self,rhs:Ratio<RhsNum,RhsDen>)->Self::Output{
|
||||
Ratio::new(self*rhs.num,rhs.den)
|
||||
}
|
||||
type Output=Ratio<<Lhs as core::ops::Mul<RhsNum>>::Output,RhsDen>;
|
||||
#[inline]
|
||||
fn mul(self,rhs:Ratio<RhsNum,RhsDen>)->Self::Output{
|
||||
Ratio::new(self*rhs.num,rhs.den)
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
//operators
|
||||
|
||||
impl<LhsNum, LhsDen> core::ops::Neg for Ratio<LhsNum, LhsDen>
|
||||
where
|
||||
LhsNum: core::ops::Neg,
|
||||
impl<LhsNum,LhsDen> core::ops::Neg for Ratio<LhsNum,LhsDen>
|
||||
where
|
||||
LhsNum:core::ops::Neg,
|
||||
{
|
||||
type Output = Ratio<<LhsNum as core::ops::Neg>::Output, LhsDen>;
|
||||
#[inline]
|
||||
fn neg(self) -> Self::Output {
|
||||
Ratio::new(-self.num, self.den)
|
||||
}
|
||||
type Output=Ratio<<LhsNum as core::ops::Neg>::Output,LhsDen>;
|
||||
#[inline]
|
||||
fn neg(self)->Self::Output{
|
||||
Ratio::new(-self.num,self.den)
|
||||
}
|
||||
}
|
||||
impl<LhsNum, LhsDen, Rhs> core::ops::Mul<Rhs> for Ratio<LhsNum, LhsDen>
|
||||
where
|
||||
LhsNum: core::ops::Mul<Rhs>,
|
||||
impl<LhsNum,LhsDen,Rhs> core::ops::Mul<Rhs> for Ratio<LhsNum,LhsDen>
|
||||
where
|
||||
LhsNum:core::ops::Mul<Rhs>,
|
||||
{
|
||||
type Output = Ratio<<LhsNum as core::ops::Mul<Rhs>>::Output, LhsDen>;
|
||||
#[inline]
|
||||
fn mul(self, rhs: Rhs) -> Self::Output {
|
||||
Ratio::new(self.num * rhs, self.den)
|
||||
}
|
||||
type Output=Ratio<<LhsNum as core::ops::Mul<Rhs>>::Output,LhsDen>;
|
||||
#[inline]
|
||||
fn mul(self,rhs:Rhs)->Self::Output{
|
||||
Ratio::new(self.num*rhs,self.den)
|
||||
}
|
||||
}
|
||||
impl<LhsNum, LhsDen, Rhs> core::ops::Div<Rhs> for Ratio<LhsNum, LhsDen>
|
||||
where
|
||||
LhsDen: core::ops::Mul<Rhs>,
|
||||
impl<LhsNum,LhsDen,Rhs> core::ops::Div<Rhs> for Ratio<LhsNum,LhsDen>
|
||||
where
|
||||
LhsDen:core::ops::Mul<Rhs>,
|
||||
{
|
||||
type Output = Ratio<LhsNum, <LhsDen as core::ops::Mul<Rhs>>::Output>;
|
||||
#[inline]
|
||||
fn div(self, rhs: Rhs) -> Self::Output {
|
||||
Ratio::new(self.num, self.den * rhs)
|
||||
}
|
||||
type Output=Ratio<LhsNum,<LhsDen as core::ops::Mul<Rhs>>::Output>;
|
||||
#[inline]
|
||||
fn div(self,rhs:Rhs)->Self::Output{
|
||||
Ratio::new(self.num,self.den*rhs)
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! impl_ratio_operator {
|
||||
($trait:ident, $method:ident) => {
|
||||
impl<LhsNum, LhsDen, Rhs, Intermediate> core::ops::$trait<Rhs> for Ratio<LhsNum, LhsDen>
|
||||
where
|
||||
LhsNum: core::ops::$trait<Intermediate>,
|
||||
LhsDen: Copy,
|
||||
Rhs: core::ops::Mul<LhsDen, Output = Intermediate>,
|
||||
{
|
||||
type Output = Ratio<<LhsNum as core::ops::$trait<Intermediate>>::Output, LhsDen>;
|
||||
#[inline]
|
||||
fn $method(self, rhs: Rhs) -> Self::Output {
|
||||
Ratio::new(self.num.$method(rhs * self.den), self.den)
|
||||
}
|
||||
}
|
||||
};
|
||||
($trait:ident, $method:ident) => {
|
||||
impl<LhsNum,LhsDen,Rhs,Intermediate> core::ops::$trait<Rhs> for Ratio<LhsNum,LhsDen>
|
||||
where
|
||||
LhsNum:core::ops::$trait<Intermediate>,
|
||||
LhsDen:Copy,
|
||||
Rhs:core::ops::Mul<LhsDen,Output=Intermediate>,
|
||||
{
|
||||
type Output=Ratio<<LhsNum as core::ops::$trait<Intermediate>>::Output,LhsDen>;
|
||||
#[inline]
|
||||
fn $method(self,rhs:Rhs)->Self::Output{
|
||||
Ratio::new(self.num.$method(rhs*self.den),self.den)
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
impl_ratio_operator!(Add, add);
|
||||
impl_ratio_operator!(Sub, sub);
|
||||
impl_ratio_operator!(Rem, rem);
|
||||
impl_ratio_operator!(Add,add);
|
||||
impl_ratio_operator!(Sub,sub);
|
||||
impl_ratio_operator!(Rem,rem);
|
||||
|
||||
//assign operators
|
||||
|
||||
impl<LhsNum, LhsDen, Rhs> core::ops::MulAssign<Rhs> for Ratio<LhsNum, LhsDen>
|
||||
where
|
||||
LhsNum: core::ops::MulAssign<Rhs>,
|
||||
impl<LhsNum,LhsDen,Rhs> core::ops::MulAssign<Rhs> for Ratio<LhsNum,LhsDen>
|
||||
where
|
||||
LhsNum:core::ops::MulAssign<Rhs>,
|
||||
{
|
||||
#[inline]
|
||||
fn mul_assign(&mut self, rhs: Rhs) {
|
||||
self.num *= rhs;
|
||||
}
|
||||
#[inline]
|
||||
fn mul_assign(&mut self,rhs:Rhs){
|
||||
self.num*=rhs;
|
||||
}
|
||||
}
|
||||
impl<LhsNum, LhsDen, Rhs> core::ops::DivAssign<Rhs> for Ratio<LhsNum, LhsDen>
|
||||
where
|
||||
LhsDen: core::ops::MulAssign<Rhs>,
|
||||
impl<LhsNum,LhsDen,Rhs> core::ops::DivAssign<Rhs> for Ratio<LhsNum,LhsDen>
|
||||
where
|
||||
LhsDen:core::ops::MulAssign<Rhs>,
|
||||
{
|
||||
#[inline]
|
||||
fn div_assign(&mut self, rhs: Rhs) {
|
||||
self.den *= rhs;
|
||||
}
|
||||
#[inline]
|
||||
fn div_assign(&mut self,rhs:Rhs){
|
||||
self.den*=rhs;
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! impl_ratio_assign_operator {
|
||||
($trait:ident, $method:ident) => {
|
||||
impl<LhsNum, LhsDen, Rhs> core::ops::$trait<Rhs> for Ratio<LhsNum, LhsDen>
|
||||
where
|
||||
LhsNum: core::ops::$trait,
|
||||
LhsDen: Copy,
|
||||
Rhs: core::ops::Mul<LhsDen, Output = LhsNum>,
|
||||
{
|
||||
#[inline]
|
||||
fn $method(&mut self, rhs: Rhs) {
|
||||
self.num.$method(rhs * self.den)
|
||||
}
|
||||
}
|
||||
};
|
||||
($trait:ident, $method:ident) => {
|
||||
impl<LhsNum,LhsDen,Rhs> core::ops::$trait<Rhs> for Ratio<LhsNum,LhsDen>
|
||||
where
|
||||
LhsNum:core::ops::$trait,
|
||||
LhsDen:Copy,
|
||||
Rhs:core::ops::Mul<LhsDen,Output=LhsNum>,
|
||||
{
|
||||
#[inline]
|
||||
fn $method(&mut self,rhs:Rhs){
|
||||
self.num.$method(rhs*self.den)
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
impl_ratio_assign_operator!(AddAssign, add_assign);
|
||||
impl_ratio_assign_operator!(SubAssign, sub_assign);
|
||||
impl_ratio_assign_operator!(RemAssign, rem_assign);
|
||||
impl_ratio_assign_operator!(AddAssign,add_assign);
|
||||
impl_ratio_assign_operator!(SubAssign,sub_assign);
|
||||
impl_ratio_assign_operator!(RemAssign,rem_assign);
|
||||
|
||||
// Only implement PartialEq<Self>
|
||||
// Rust's operators aren't actually that good
|
||||
|
||||
impl<LhsNum, LhsDen, RhsNum, RhsDen, T, U> PartialEq<Ratio<RhsNum, RhsDen>>
|
||||
for Ratio<LhsNum, LhsDen>
|
||||
where
|
||||
LhsNum: Copy,
|
||||
LhsDen: Copy,
|
||||
RhsNum: Copy,
|
||||
RhsDen: Copy,
|
||||
LhsNum: core::ops::Mul<RhsDen, Output = T>,
|
||||
RhsNum: core::ops::Mul<LhsDen, Output = U>,
|
||||
T: PartialEq<U>,
|
||||
impl<LhsNum,LhsDen,RhsNum,RhsDen,T,U> PartialEq<Ratio<RhsNum,RhsDen>> for Ratio<LhsNum,LhsDen>
|
||||
where
|
||||
LhsNum:Copy,
|
||||
LhsDen:Copy,
|
||||
RhsNum:Copy,
|
||||
RhsDen:Copy,
|
||||
LhsNum:core::ops::Mul<RhsDen,Output=T>,
|
||||
RhsNum:core::ops::Mul<LhsDen,Output=U>,
|
||||
T:PartialEq<U>,
|
||||
{
|
||||
#[inline]
|
||||
fn eq(&self, other: &Ratio<RhsNum, RhsDen>) -> bool {
|
||||
(self.num * other.den).eq(&(other.num * self.den))
|
||||
}
|
||||
#[inline]
|
||||
fn eq(&self,other:&Ratio<RhsNum,RhsDen>)->bool{
|
||||
(self.num*other.den).eq(&(other.num*self.den))
|
||||
}
|
||||
}
|
||||
impl<Num, Den> Eq for Ratio<Num, Den> where Self: PartialEq {}
|
||||
impl<Num,Den> Eq for Ratio<Num,Den> where Self:PartialEq{}
|
||||
|
||||
impl<LhsNum, LhsDen, RhsNum, RhsDen, T, U> PartialOrd<Ratio<RhsNum, RhsDen>>
|
||||
for Ratio<LhsNum, LhsDen>
|
||||
where
|
||||
LhsNum: Copy,
|
||||
LhsDen: Copy,
|
||||
RhsNum: Copy,
|
||||
RhsDen: Copy,
|
||||
LhsNum: core::ops::Mul<RhsDen, Output = T>,
|
||||
RhsNum: core::ops::Mul<LhsDen, Output = U>,
|
||||
T: PartialOrd<U>,
|
||||
impl<LhsNum,LhsDen,RhsNum,RhsDen,T,U> PartialOrd<Ratio<RhsNum,RhsDen>> for Ratio<LhsNum,LhsDen>
|
||||
where
|
||||
LhsNum:Copy,
|
||||
LhsDen:Copy,
|
||||
RhsNum:Copy,
|
||||
RhsDen:Copy,
|
||||
LhsNum:core::ops::Mul<RhsDen,Output=T>,
|
||||
RhsNum:core::ops::Mul<LhsDen,Output=U>,
|
||||
T:PartialOrd<U>,
|
||||
{
|
||||
#[inline]
|
||||
fn partial_cmp(&self, other: &Ratio<RhsNum, RhsDen>) -> Option<core::cmp::Ordering> {
|
||||
(self.num * other.den).partial_cmp(&(other.num * self.den))
|
||||
}
|
||||
#[inline]
|
||||
fn partial_cmp(&self,other:&Ratio<RhsNum,RhsDen>)->Option<core::cmp::Ordering>{
|
||||
(self.num*other.den).partial_cmp(&(other.num*self.den))
|
||||
}
|
||||
}
|
||||
impl<Num, Den, T> Ord for Ratio<Num, Den>
|
||||
where
|
||||
Num: Copy,
|
||||
Den: Copy,
|
||||
Num: core::ops::Mul<Den, Output = T>,
|
||||
T: Ord,
|
||||
impl<Num,Den,T> Ord for Ratio<Num,Den>
|
||||
where
|
||||
Num:Copy,
|
||||
Den:Copy,
|
||||
Num:core::ops::Mul<Den,Output=T>,
|
||||
T:Ord,
|
||||
{
|
||||
#[inline]
|
||||
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
|
||||
(self.num * other.den).cmp(&(other.num * self.den))
|
||||
}
|
||||
#[inline]
|
||||
fn cmp(&self,other:&Self)->std::cmp::Ordering{
|
||||
(self.num*other.den).cmp(&(other.num*self.den))
|
||||
}
|
||||
}
|
||||
|
@ -1,55 +1,55 @@
|
||||
use crate::ratio::Ratio;
|
||||
|
||||
macro_rules! test_op {
|
||||
($ratio_op:ident,$op:ident,$a:expr,$b:expr,$c:expr,$d:expr) => {
|
||||
assert_eq!(
|
||||
Ratio::new($a, $b).$ratio_op(Ratio::new($c, $d)),
|
||||
(($a as f32) / ($b as f32)).$op(&(($c as f32) / ($d as f32)))
|
||||
);
|
||||
};
|
||||
macro_rules! test_op{
|
||||
($ratio_op:ident,$op:ident,$a:expr,$b:expr,$c:expr,$d:expr)=>{
|
||||
assert_eq!(
|
||||
Ratio::new($a,$b).$ratio_op(Ratio::new($c,$d)),
|
||||
(($a as f32)/($b as f32)).$op(&(($c as f32)/($d as f32)))
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! test_many_ops {
|
||||
($ratio_op:ident,$op:ident) => {
|
||||
test_op!($ratio_op, $op, 1, 2, 3, 4);
|
||||
test_op!($ratio_op, $op, 1, 2, -3, 4);
|
||||
test_op!($ratio_op, $op, -1, 2, -3, 4);
|
||||
test_op!($ratio_op, $op, -1, -2, -3, 4);
|
||||
test_op!($ratio_op, $op, 2, 1, 6, 3);
|
||||
test_op!($ratio_op, $op, -2, 1, 6, 3);
|
||||
test_op!($ratio_op, $op, 2, -1, -6, 3);
|
||||
test_op!($ratio_op, $op, 2, 1, 6, -3);
|
||||
};
|
||||
macro_rules! test_many_ops{
|
||||
($ratio_op:ident,$op:ident)=>{
|
||||
test_op!($ratio_op,$op,1,2,3,4);
|
||||
test_op!($ratio_op,$op,1,2,-3,4);
|
||||
test_op!($ratio_op,$op,-1,2,-3,4);
|
||||
test_op!($ratio_op,$op,-1,-2,-3,4);
|
||||
test_op!($ratio_op,$op,2,1,6,3);
|
||||
test_op!($ratio_op,$op,-2,1,6,3);
|
||||
test_op!($ratio_op,$op,2,-1,-6,3);
|
||||
test_op!($ratio_op,$op,2,1,6,-3);
|
||||
};
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_lt() {
|
||||
test_many_ops!(lt_ratio, lt);
|
||||
fn test_lt(){
|
||||
test_many_ops!(lt_ratio,lt);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_gt() {
|
||||
test_many_ops!(gt_ratio, gt);
|
||||
fn test_gt(){
|
||||
test_many_ops!(gt_ratio,gt);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_le() {
|
||||
test_many_ops!(le_ratio, le);
|
||||
fn test_le(){
|
||||
test_many_ops!(le_ratio,le);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_ge() {
|
||||
test_many_ops!(ge_ratio, ge);
|
||||
fn test_ge(){
|
||||
test_many_ops!(ge_ratio,ge);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_eq() {
|
||||
test_many_ops!(eq_ratio, eq);
|
||||
fn test_eq(){
|
||||
test_many_ops!(eq_ratio,eq);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_partial_cmp() {
|
||||
test_many_ops!(partial_cmp_ratio, partial_cmp);
|
||||
fn test_partial_cmp(){
|
||||
test_many_ops!(partial_cmp_ratio,partial_cmp);
|
||||
}
|
||||
|
||||
// #[test]
|
||||
|
@ -1,113 +1,107 @@
|
||||
use rbx_dom_weak::WeakDom;
|
||||
use std::io::Read;
|
||||
use rbx_dom_weak::WeakDom;
|
||||
|
||||
mod rbx;
|
||||
mod mesh;
|
||||
mod primitives;
|
||||
mod rbx;
|
||||
|
||||
pub mod data {
|
||||
pub struct RobloxMeshBytes(Vec<u8>);
|
||||
impl RobloxMeshBytes {
|
||||
pub fn new(bytes: Vec<u8>) -> Self {
|
||||
Self(bytes)
|
||||
}
|
||||
pub(crate) fn cursor(self) -> std::io::Cursor<Vec<u8>> {
|
||||
std::io::Cursor::new(self.0)
|
||||
}
|
||||
}
|
||||
pub mod data{
|
||||
pub struct RobloxMeshBytes(Vec<u8>);
|
||||
impl RobloxMeshBytes{
|
||||
pub fn new(bytes:Vec<u8>)->Self{
|
||||
Self(bytes)
|
||||
}
|
||||
pub(crate) fn cursor(self)->std::io::Cursor<Vec<u8>>{
|
||||
std::io::Cursor::new(self.0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Model {
|
||||
dom: WeakDom,
|
||||
pub struct Model{
|
||||
dom:WeakDom,
|
||||
}
|
||||
impl Model {
|
||||
fn new(dom: WeakDom) -> Self {
|
||||
Self { dom }
|
||||
}
|
||||
pub fn into_place(self) -> Place {
|
||||
let Self { mut dom } = self;
|
||||
let context = roblox_emulator::context::Context::from_mut(&mut dom);
|
||||
let services = context.convert_into_place();
|
||||
Place { dom, services }
|
||||
}
|
||||
impl Model{
|
||||
fn new(dom:WeakDom)->Self{
|
||||
Self{dom}
|
||||
}
|
||||
pub fn into_place(self)->Place{
|
||||
let Self{mut dom}=self;
|
||||
let context=roblox_emulator::context::Context::from_mut(&mut dom);
|
||||
let services=context.convert_into_place();
|
||||
Place{dom,services}
|
||||
}
|
||||
}
|
||||
impl AsRef<WeakDom> for Model {
|
||||
fn as_ref(&self) -> &WeakDom {
|
||||
&self.dom
|
||||
}
|
||||
impl AsRef<WeakDom> for Model{
|
||||
fn as_ref(&self)->&WeakDom{
|
||||
&self.dom
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Place {
|
||||
dom: WeakDom,
|
||||
services: roblox_emulator::context::Services,
|
||||
pub struct Place{
|
||||
dom:WeakDom,
|
||||
services:roblox_emulator::context::Services,
|
||||
}
|
||||
impl Place {
|
||||
fn new(dom: WeakDom) -> Option<Self> {
|
||||
let context = roblox_emulator::context::Context::from_ref(&dom);
|
||||
Some(Self {
|
||||
services: context.find_services()?,
|
||||
dom,
|
||||
})
|
||||
}
|
||||
pub fn run_scripts(&mut self) {
|
||||
let Place { dom, services } = self;
|
||||
let runner = roblox_emulator::runner::Runner::new().unwrap();
|
||||
let context = roblox_emulator::context::Context::from_mut(dom);
|
||||
let scripts = context.scripts();
|
||||
let runnable = runner
|
||||
.runnable_context_with_services(context, services)
|
||||
.unwrap();
|
||||
for script in scripts {
|
||||
if let Err(e) = runnable.run_script(script) {
|
||||
println!("runner error: {e}");
|
||||
}
|
||||
}
|
||||
}
|
||||
impl Place{
|
||||
pub fn new(dom:WeakDom)->Option<Self>{
|
||||
let context=roblox_emulator::context::Context::from_ref(&dom);
|
||||
Some(Self{
|
||||
services:context.find_services()?,
|
||||
dom,
|
||||
})
|
||||
}
|
||||
pub fn run_scripts(&mut self){
|
||||
let Place{dom,services}=self;
|
||||
let runner=roblox_emulator::runner::Runner::new().unwrap();
|
||||
let context=roblox_emulator::context::Context::from_mut(dom);
|
||||
let scripts=context.scripts();
|
||||
let runnable=runner.runnable_context_with_services(context,services).unwrap();
|
||||
for script in scripts{
|
||||
if let Err(e)=runnable.run_script(script){
|
||||
println!("runner error: {e}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
impl AsRef<WeakDom> for Place {
|
||||
fn as_ref(&self) -> &WeakDom {
|
||||
&self.dom
|
||||
}
|
||||
impl AsRef<WeakDom> for Place{
|
||||
fn as_ref(&self)->&WeakDom{
|
||||
&self.dom
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum ReadError {
|
||||
RbxBinary(rbx_binary::DecodeError),
|
||||
RbxXml(rbx_xml::DecodeError),
|
||||
Io(std::io::Error),
|
||||
UnknownFileFormat,
|
||||
pub enum ReadError{
|
||||
RbxBinary(rbx_binary::DecodeError),
|
||||
RbxXml(rbx_xml::DecodeError),
|
||||
Io(std::io::Error),
|
||||
UnknownFileFormat,
|
||||
}
|
||||
impl std::fmt::Display for ReadError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{self:?}")
|
||||
}
|
||||
impl std::fmt::Display for ReadError{
|
||||
fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{
|
||||
write!(f,"{self:?}")
|
||||
}
|
||||
}
|
||||
impl std::error::Error for ReadError {}
|
||||
impl std::error::Error for ReadError{}
|
||||
|
||||
pub fn read<R: Read>(input: R) -> Result<Model, ReadError> {
|
||||
let mut buf = std::io::BufReader::new(input);
|
||||
let peek = std::io::BufRead::fill_buf(&mut buf).map_err(ReadError::Io)?;
|
||||
match &peek[0..8] {
|
||||
b"<roblox!" => rbx_binary::from_reader(buf)
|
||||
.map(Model::new)
|
||||
.map_err(ReadError::RbxBinary),
|
||||
b"<roblox " => rbx_xml::from_reader_default(buf)
|
||||
.map(Model::new)
|
||||
.map_err(ReadError::RbxXml),
|
||||
_ => Err(ReadError::UnknownFileFormat),
|
||||
}
|
||||
pub fn read<R:Read>(input:R)->Result<Model,ReadError>{
|
||||
let mut buf=std::io::BufReader::new(input);
|
||||
let peek=std::io::BufRead::fill_buf(&mut buf).map_err(ReadError::Io)?;
|
||||
match &peek[0..8]{
|
||||
b"<roblox!"=>rbx_binary::from_reader(buf).map(Model::new).map_err(ReadError::RbxBinary),
|
||||
b"<roblox "=>rbx_xml::from_reader_default(buf).map(Model::new).map_err(ReadError::RbxXml),
|
||||
_=>Err(ReadError::UnknownFileFormat),
|
||||
}
|
||||
}
|
||||
|
||||
//ConvertError
|
||||
|
||||
pub fn convert<AcquireRenderConfigId, AcquireMeshId>(
|
||||
dom: impl AsRef<WeakDom>,
|
||||
acquire_render_config_id: AcquireRenderConfigId,
|
||||
acquire_mesh_id: AcquireMeshId,
|
||||
) -> rbx::PartialMap1
|
||||
pub fn convert<AcquireRenderConfigId,AcquireMeshId>(
|
||||
dom:impl AsRef<WeakDom>,
|
||||
acquire_render_config_id:AcquireRenderConfigId,
|
||||
acquire_mesh_id:AcquireMeshId
|
||||
)->rbx::PartialMap1
|
||||
where
|
||||
AcquireRenderConfigId: FnMut(Option<&str>) -> strafesnet_common::model::RenderConfigId,
|
||||
AcquireMeshId: FnMut(&str) -> strafesnet_common::model::MeshId,
|
||||
AcquireRenderConfigId:FnMut(Option<&str>)->strafesnet_common::model::RenderConfigId,
|
||||
AcquireMeshId:FnMut(&str)->strafesnet_common::model::MeshId,
|
||||
{
|
||||
rbx::convert(&dom.as_ref(), acquire_render_config_id, acquire_mesh_id)
|
||||
rbx::convert(&dom.as_ref(),acquire_render_config_id,acquire_mesh_id)
|
||||
}
|
||||
|
@ -1,284 +1,211 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use rbx_mesh::mesh::{Vertex2, Vertex2Truncated};
|
||||
use strafesnet_common::{
|
||||
integer::vec3,
|
||||
model::{
|
||||
self, ColorId, IndexedVertex, NormalId, PolygonGroup, PolygonList, PositionId,
|
||||
TextureCoordinateId, VertexId,
|
||||
},
|
||||
};
|
||||
use strafesnet_common::{integer::vec3,model::{self, ColorId, IndexedVertex, NormalId, PolygonGroup, PolygonList, PositionId, TextureCoordinateId, VertexId}};
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
Planar64Vec3(strafesnet_common::integer::Planar64TryFromFloatError),
|
||||
RbxMesh(rbx_mesh::mesh::Error),
|
||||
pub enum Error{
|
||||
Planar64Vec3(strafesnet_common::integer::Planar64TryFromFloatError),
|
||||
RbxMesh(rbx_mesh::mesh::Error)
|
||||
}
|
||||
impl std::fmt::Display for Error {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{self:?}")
|
||||
}
|
||||
impl std::fmt::Display for Error{
|
||||
fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{
|
||||
write!(f,"{self:?}")
|
||||
}
|
||||
}
|
||||
impl std::error::Error for Error {}
|
||||
impl std::error::Error for Error{}
|
||||
|
||||
fn ingest_vertices2<AcquirePosId, AcquireTexId, AcquireNormalId, AcquireColorId, AcquireVertexId>(
|
||||
vertices: Vec<Vertex2>,
|
||||
acquire_pos_id: &mut AcquirePosId,
|
||||
acquire_tex_id: &mut AcquireTexId,
|
||||
acquire_normal_id: &mut AcquireNormalId,
|
||||
acquire_color_id: &mut AcquireColorId,
|
||||
acquire_vertex_id: &mut AcquireVertexId,
|
||||
) -> Result<HashMap<rbx_mesh::mesh::VertexId2, VertexId>, Error>
|
||||
fn ingest_vertices2<
|
||||
AcquirePosId,
|
||||
AcquireTexId,
|
||||
AcquireNormalId,
|
||||
AcquireColorId,
|
||||
AcquireVertexId,
|
||||
>(
|
||||
vertices:Vec<Vertex2>,
|
||||
acquire_pos_id:&mut AcquirePosId,
|
||||
acquire_tex_id:&mut AcquireTexId,
|
||||
acquire_normal_id:&mut AcquireNormalId,
|
||||
acquire_color_id:&mut AcquireColorId,
|
||||
acquire_vertex_id:&mut AcquireVertexId,
|
||||
)->Result<HashMap<rbx_mesh::mesh::VertexId2,VertexId>,Error>
|
||||
where
|
||||
AcquirePosId: FnMut([f32; 3]) -> Result<PositionId, Error>,
|
||||
AcquireTexId: FnMut([f32; 2]) -> TextureCoordinateId,
|
||||
AcquireNormalId: FnMut([f32; 3]) -> Result<NormalId, Error>,
|
||||
AcquireColorId: FnMut([f32; 4]) -> ColorId,
|
||||
AcquireVertexId: FnMut(IndexedVertex) -> VertexId,
|
||||
AcquirePosId:FnMut([f32;3])->Result<PositionId,Error>,
|
||||
AcquireTexId:FnMut([f32;2])->TextureCoordinateId,
|
||||
AcquireNormalId:FnMut([f32;3])->Result<NormalId,Error>,
|
||||
AcquireColorId:FnMut([f32;4])->ColorId,
|
||||
AcquireVertexId:FnMut(IndexedVertex)->VertexId,
|
||||
{
|
||||
//this monster is collecting a map of old_vertices_index -> unique_vertices_index
|
||||
//while also doing the inserting unique entries into lists simultaneously
|
||||
Ok(vertices
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
.map(|(vertex_id, vertex)| {
|
||||
Ok((
|
||||
rbx_mesh::mesh::VertexId2(vertex_id as u32),
|
||||
acquire_vertex_id(IndexedVertex {
|
||||
pos: acquire_pos_id(vertex.pos)?,
|
||||
tex: acquire_tex_id(vertex.tex),
|
||||
normal: acquire_normal_id(vertex.norm)?,
|
||||
color: acquire_color_id(vertex.color.map(|f| f as f32 / 255.0f32)),
|
||||
}),
|
||||
))
|
||||
})
|
||||
.collect::<Result<_, _>>()?)
|
||||
//this monster is collecting a map of old_vertices_index -> unique_vertices_index
|
||||
//while also doing the inserting unique entries into lists simultaneously
|
||||
Ok(vertices.into_iter().enumerate().map(|(vertex_id,vertex)|Ok((
|
||||
rbx_mesh::mesh::VertexId2(vertex_id as u32),
|
||||
acquire_vertex_id(IndexedVertex{
|
||||
pos:acquire_pos_id(vertex.pos)?,
|
||||
tex:acquire_tex_id(vertex.tex),
|
||||
normal:acquire_normal_id(vertex.norm)?,
|
||||
color:acquire_color_id(vertex.color.map(|f|f as f32/255.0f32))
|
||||
}),
|
||||
))).collect::<Result<_,_>>()?)
|
||||
}
|
||||
fn ingest_vertices_truncated2<AcquirePosId, AcquireTexId, AcquireNormalId, AcquireVertexId>(
|
||||
vertices: Vec<Vertex2Truncated>,
|
||||
acquire_pos_id: &mut AcquirePosId,
|
||||
acquire_tex_id: &mut AcquireTexId,
|
||||
acquire_normal_id: &mut AcquireNormalId,
|
||||
static_color_id: ColorId, //pick one color and fill everything with it
|
||||
acquire_vertex_id: &mut AcquireVertexId,
|
||||
) -> Result<HashMap<rbx_mesh::mesh::VertexId2, VertexId>, Error>
|
||||
fn ingest_vertices_truncated2<
|
||||
AcquirePosId,
|
||||
AcquireTexId,
|
||||
AcquireNormalId,
|
||||
AcquireVertexId,
|
||||
>(
|
||||
vertices:Vec<Vertex2Truncated>,
|
||||
acquire_pos_id:&mut AcquirePosId,
|
||||
acquire_tex_id:&mut AcquireTexId,
|
||||
acquire_normal_id:&mut AcquireNormalId,
|
||||
static_color_id:ColorId,//pick one color and fill everything with it
|
||||
acquire_vertex_id:&mut AcquireVertexId,
|
||||
)->Result<HashMap<rbx_mesh::mesh::VertexId2,VertexId>,Error>
|
||||
where
|
||||
AcquirePosId: FnMut([f32; 3]) -> Result<PositionId, Error>,
|
||||
AcquireTexId: FnMut([f32; 2]) -> TextureCoordinateId,
|
||||
AcquireNormalId: FnMut([f32; 3]) -> Result<NormalId, Error>,
|
||||
AcquireVertexId: FnMut(IndexedVertex) -> VertexId,
|
||||
AcquirePosId:FnMut([f32;3])->Result<PositionId,Error>,
|
||||
AcquireTexId:FnMut([f32;2])->TextureCoordinateId,
|
||||
AcquireNormalId:FnMut([f32;3])->Result<NormalId,Error>,
|
||||
AcquireVertexId:FnMut(IndexedVertex)->VertexId,
|
||||
{
|
||||
//this monster is collecting a map of old_vertices_index -> unique_vertices_index
|
||||
//while also doing the inserting unique entries into lists simultaneously
|
||||
Ok(vertices
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
.map(|(vertex_id, vertex)| {
|
||||
Ok((
|
||||
rbx_mesh::mesh::VertexId2(vertex_id as u32),
|
||||
acquire_vertex_id(IndexedVertex {
|
||||
pos: acquire_pos_id(vertex.pos)?,
|
||||
tex: acquire_tex_id(vertex.tex),
|
||||
normal: acquire_normal_id(vertex.norm)?,
|
||||
color: static_color_id,
|
||||
}),
|
||||
))
|
||||
})
|
||||
.collect::<Result<_, _>>()?)
|
||||
//this monster is collecting a map of old_vertices_index -> unique_vertices_index
|
||||
//while also doing the inserting unique entries into lists simultaneously
|
||||
Ok(vertices.into_iter().enumerate().map(|(vertex_id,vertex)|Ok((
|
||||
rbx_mesh::mesh::VertexId2(vertex_id as u32),
|
||||
acquire_vertex_id(IndexedVertex{
|
||||
pos:acquire_pos_id(vertex.pos)?,
|
||||
tex:acquire_tex_id(vertex.tex),
|
||||
normal:acquire_normal_id(vertex.norm)?,
|
||||
color:static_color_id
|
||||
}),
|
||||
))).collect::<Result<_,_>>()?)
|
||||
}
|
||||
|
||||
fn ingest_faces2_lods3(
|
||||
polygon_groups: &mut Vec<PolygonGroup>,
|
||||
vertex_id_map: &HashMap<rbx_mesh::mesh::VertexId2, VertexId>,
|
||||
faces: &Vec<rbx_mesh::mesh::Face2>,
|
||||
lods: &Vec<rbx_mesh::mesh::Lod3>,
|
||||
) {
|
||||
//faces have to be split into polygon groups based on lod
|
||||
polygon_groups.extend(lods.windows(2).map(|lod_pair| {
|
||||
PolygonGroup::PolygonList(PolygonList::new(
|
||||
faces[lod_pair[0].0 as usize..lod_pair[1].0 as usize]
|
||||
.iter()
|
||||
.map(|face| {
|
||||
vec![
|
||||
vertex_id_map[&face.0],
|
||||
vertex_id_map[&face.1],
|
||||
vertex_id_map[&face.2],
|
||||
]
|
||||
})
|
||||
.collect(),
|
||||
))
|
||||
}))
|
||||
polygon_groups:&mut Vec<PolygonGroup>,
|
||||
vertex_id_map:&HashMap<rbx_mesh::mesh::VertexId2,VertexId>,
|
||||
faces:&Vec<rbx_mesh::mesh::Face2>,
|
||||
lods:&Vec<rbx_mesh::mesh::Lod3>
|
||||
){
|
||||
//faces have to be split into polygon groups based on lod
|
||||
polygon_groups.extend(lods.windows(2).map(|lod_pair|
|
||||
PolygonGroup::PolygonList(PolygonList::new(faces[lod_pair[0].0 as usize..lod_pair[1].0 as usize].iter().map(|face|
|
||||
vec![vertex_id_map[&face.0],vertex_id_map[&face.1],vertex_id_map[&face.2]]
|
||||
).collect()))
|
||||
))
|
||||
}
|
||||
|
||||
pub fn convert(roblox_mesh_bytes: crate::data::RobloxMeshBytes) -> Result<model::Mesh, Error> {
|
||||
//generate that mesh boi
|
||||
let mut unique_pos = Vec::new();
|
||||
let mut pos_id_from = HashMap::new();
|
||||
let mut unique_tex = Vec::new();
|
||||
let mut tex_id_from = HashMap::new();
|
||||
let mut unique_normal = Vec::new();
|
||||
let mut normal_id_from = HashMap::new();
|
||||
let mut unique_color = Vec::new();
|
||||
let mut color_id_from = HashMap::new();
|
||||
let mut unique_vertices = Vec::new();
|
||||
let mut vertex_id_from = HashMap::new();
|
||||
let mut polygon_groups = Vec::new();
|
||||
let mut acquire_pos_id = |pos| {
|
||||
let p = vec3::try_from_f32_array(pos).map_err(Error::Planar64Vec3)?;
|
||||
Ok(PositionId::new(*pos_id_from.entry(p).or_insert_with(|| {
|
||||
let pos_id = unique_pos.len();
|
||||
unique_pos.push(p);
|
||||
pos_id
|
||||
}) as u32))
|
||||
};
|
||||
let mut acquire_tex_id = |tex| {
|
||||
let h = bytemuck::cast::<[f32; 2], [u32; 2]>(tex);
|
||||
TextureCoordinateId::new(*tex_id_from.entry(h).or_insert_with(|| {
|
||||
let tex_id = unique_tex.len();
|
||||
unique_tex.push(glam::Vec2::from_array(tex));
|
||||
tex_id
|
||||
}) as u32)
|
||||
};
|
||||
let mut acquire_normal_id = |normal| {
|
||||
let n = vec3::try_from_f32_array(normal).map_err(Error::Planar64Vec3)?;
|
||||
Ok(NormalId::new(*normal_id_from.entry(n).or_insert_with(|| {
|
||||
let normal_id = unique_normal.len();
|
||||
unique_normal.push(n);
|
||||
normal_id
|
||||
}) as u32))
|
||||
};
|
||||
let mut acquire_color_id = |color| {
|
||||
let h = bytemuck::cast::<[f32; 4], [u32; 4]>(color);
|
||||
ColorId::new(*color_id_from.entry(h).or_insert_with(|| {
|
||||
let color_id = unique_color.len();
|
||||
unique_color.push(glam::Vec4::from_array(color));
|
||||
color_id
|
||||
}) as u32)
|
||||
};
|
||||
let mut acquire_vertex_id = |vertex: IndexedVertex| {
|
||||
VertexId::new(*vertex_id_from.entry(vertex.clone()).or_insert_with(|| {
|
||||
let vertex_id = unique_vertices.len();
|
||||
unique_vertices.push(vertex);
|
||||
vertex_id
|
||||
}) as u32)
|
||||
};
|
||||
match rbx_mesh::read_versioned(roblox_mesh_bytes.cursor()).map_err(Error::RbxMesh)? {
|
||||
rbx_mesh::mesh::VersionedMesh::Version1(mesh) => {
|
||||
let color_id = acquire_color_id([1.0f32; 4]);
|
||||
polygon_groups.push(PolygonGroup::PolygonList(PolygonList::new(
|
||||
mesh.vertices
|
||||
.chunks_exact(3)
|
||||
.map(|trip| {
|
||||
let mut ingest_vertex1 = |vertex: &rbx_mesh::mesh::Vertex1| {
|
||||
Ok(acquire_vertex_id(IndexedVertex {
|
||||
pos: acquire_pos_id(vertex.pos)?,
|
||||
tex: acquire_tex_id([vertex.tex[0], vertex.tex[1]]),
|
||||
normal: acquire_normal_id(vertex.norm)?,
|
||||
color: color_id,
|
||||
}))
|
||||
};
|
||||
Ok(vec![
|
||||
ingest_vertex1(&trip[0])?,
|
||||
ingest_vertex1(&trip[1])?,
|
||||
ingest_vertex1(&trip[2])?,
|
||||
])
|
||||
})
|
||||
.collect::<Result<_, _>>()?,
|
||||
)));
|
||||
}
|
||||
rbx_mesh::mesh::VersionedMesh::Version2(mesh) => {
|
||||
let vertex_id_map = match mesh.header.sizeof_vertex {
|
||||
rbx_mesh::mesh::SizeOfVertex2::Truncated => {
|
||||
//pick white and make all the vertices white
|
||||
let color_id = acquire_color_id([1.0f32; 4]);
|
||||
ingest_vertices_truncated2(
|
||||
mesh.vertices_truncated,
|
||||
&mut acquire_pos_id,
|
||||
&mut acquire_tex_id,
|
||||
&mut acquire_normal_id,
|
||||
color_id,
|
||||
&mut acquire_vertex_id,
|
||||
)
|
||||
}
|
||||
rbx_mesh::mesh::SizeOfVertex2::Full => ingest_vertices2(
|
||||
mesh.vertices,
|
||||
&mut acquire_pos_id,
|
||||
&mut acquire_tex_id,
|
||||
&mut acquire_normal_id,
|
||||
&mut acquire_color_id,
|
||||
&mut acquire_vertex_id,
|
||||
),
|
||||
}?;
|
||||
//one big happy group for all the faces
|
||||
polygon_groups.push(PolygonGroup::PolygonList(PolygonList::new(
|
||||
mesh.faces
|
||||
.into_iter()
|
||||
.map(|face| {
|
||||
vec![
|
||||
vertex_id_map[&face.0],
|
||||
vertex_id_map[&face.1],
|
||||
vertex_id_map[&face.2],
|
||||
]
|
||||
})
|
||||
.collect(),
|
||||
)));
|
||||
}
|
||||
rbx_mesh::mesh::VersionedMesh::Version3(mesh) => {
|
||||
let vertex_id_map = match mesh.header.sizeof_vertex {
|
||||
rbx_mesh::mesh::SizeOfVertex2::Truncated => {
|
||||
let color_id = acquire_color_id([1.0f32; 4]);
|
||||
ingest_vertices_truncated2(
|
||||
mesh.vertices_truncated,
|
||||
&mut acquire_pos_id,
|
||||
&mut acquire_tex_id,
|
||||
&mut acquire_normal_id,
|
||||
color_id,
|
||||
&mut acquire_vertex_id,
|
||||
)
|
||||
}
|
||||
rbx_mesh::mesh::SizeOfVertex2::Full => ingest_vertices2(
|
||||
mesh.vertices,
|
||||
&mut acquire_pos_id,
|
||||
&mut acquire_tex_id,
|
||||
&mut acquire_normal_id,
|
||||
&mut acquire_color_id,
|
||||
&mut acquire_vertex_id,
|
||||
),
|
||||
}?;
|
||||
ingest_faces2_lods3(&mut polygon_groups, &vertex_id_map, &mesh.faces, &mesh.lods);
|
||||
}
|
||||
rbx_mesh::mesh::VersionedMesh::Version4(mesh) => {
|
||||
let vertex_id_map = ingest_vertices2(
|
||||
mesh.vertices,
|
||||
&mut acquire_pos_id,
|
||||
&mut acquire_tex_id,
|
||||
&mut acquire_normal_id,
|
||||
&mut acquire_color_id,
|
||||
&mut acquire_vertex_id,
|
||||
)?;
|
||||
ingest_faces2_lods3(&mut polygon_groups, &vertex_id_map, &mesh.faces, &mesh.lods);
|
||||
}
|
||||
rbx_mesh::mesh::VersionedMesh::Version5(mesh) => {
|
||||
let vertex_id_map = ingest_vertices2(
|
||||
mesh.vertices,
|
||||
&mut acquire_pos_id,
|
||||
&mut acquire_tex_id,
|
||||
&mut acquire_normal_id,
|
||||
&mut acquire_color_id,
|
||||
&mut acquire_vertex_id,
|
||||
)?;
|
||||
ingest_faces2_lods3(&mut polygon_groups, &vertex_id_map, &mesh.faces, &mesh.lods);
|
||||
}
|
||||
}
|
||||
Ok(model::Mesh {
|
||||
unique_pos,
|
||||
unique_normal,
|
||||
unique_tex,
|
||||
unique_color,
|
||||
unique_vertices,
|
||||
polygon_groups,
|
||||
//these should probably be moved to the model...
|
||||
graphics_groups: Vec::new(),
|
||||
physics_groups: Vec::new(),
|
||||
})
|
||||
pub fn convert(roblox_mesh_bytes:crate::data::RobloxMeshBytes)->Result<model::Mesh,Error>{
|
||||
//generate that mesh boi
|
||||
let mut unique_pos=Vec::new();
|
||||
let mut pos_id_from=HashMap::new();
|
||||
let mut unique_tex=Vec::new();
|
||||
let mut tex_id_from=HashMap::new();
|
||||
let mut unique_normal=Vec::new();
|
||||
let mut normal_id_from=HashMap::new();
|
||||
let mut unique_color=Vec::new();
|
||||
let mut color_id_from=HashMap::new();
|
||||
let mut unique_vertices=Vec::new();
|
||||
let mut vertex_id_from=HashMap::new();
|
||||
let mut polygon_groups=Vec::new();
|
||||
let mut acquire_pos_id=|pos|{
|
||||
let p=vec3::try_from_f32_array(pos).map_err(Error::Planar64Vec3)?;
|
||||
Ok(PositionId::new(*pos_id_from.entry(p).or_insert_with(||{
|
||||
let pos_id=unique_pos.len();
|
||||
unique_pos.push(p);
|
||||
pos_id
|
||||
}) as u32))
|
||||
};
|
||||
let mut acquire_tex_id=|tex|{
|
||||
let h=bytemuck::cast::<[f32;2],[u32;2]>(tex);
|
||||
TextureCoordinateId::new(*tex_id_from.entry(h).or_insert_with(||{
|
||||
let tex_id=unique_tex.len();
|
||||
unique_tex.push(glam::Vec2::from_array(tex));
|
||||
tex_id
|
||||
}) as u32)
|
||||
};
|
||||
let mut acquire_normal_id=|normal|{
|
||||
let n=vec3::try_from_f32_array(normal).map_err(Error::Planar64Vec3)?;
|
||||
Ok(NormalId::new(*normal_id_from.entry(n).or_insert_with(||{
|
||||
let normal_id=unique_normal.len();
|
||||
unique_normal.push(n);
|
||||
normal_id
|
||||
}) as u32))
|
||||
};
|
||||
let mut acquire_color_id=|color|{
|
||||
let h=bytemuck::cast::<[f32;4],[u32;4]>(color);
|
||||
ColorId::new(*color_id_from.entry(h).or_insert_with(||{
|
||||
let color_id=unique_color.len();
|
||||
unique_color.push(glam::Vec4::from_array(color));
|
||||
color_id
|
||||
}) as u32)
|
||||
};
|
||||
let mut acquire_vertex_id=|vertex:IndexedVertex|{
|
||||
VertexId::new(*vertex_id_from.entry(vertex.clone()).or_insert_with(||{
|
||||
let vertex_id=unique_vertices.len();
|
||||
unique_vertices.push(vertex);
|
||||
vertex_id
|
||||
}) as u32)
|
||||
};
|
||||
match rbx_mesh::read_versioned(roblox_mesh_bytes.cursor()).map_err(Error::RbxMesh)?{
|
||||
rbx_mesh::mesh::VersionedMesh::Version1(mesh)=>{
|
||||
let color_id=acquire_color_id([1.0f32;4]);
|
||||
polygon_groups.push(PolygonGroup::PolygonList(PolygonList::new(mesh.vertices.chunks_exact(3).map(|trip|{
|
||||
let mut ingest_vertex1=|vertex:&rbx_mesh::mesh::Vertex1|Ok(acquire_vertex_id(IndexedVertex{
|
||||
pos:acquire_pos_id(vertex.pos)?,
|
||||
tex:acquire_tex_id([vertex.tex[0],vertex.tex[1]]),
|
||||
normal:acquire_normal_id(vertex.norm)?,
|
||||
color:color_id,
|
||||
}));
|
||||
Ok(vec![ingest_vertex1(&trip[0])?,ingest_vertex1(&trip[1])?,ingest_vertex1(&trip[2])?])
|
||||
}).collect::<Result<_,_>>()?)));
|
||||
},
|
||||
rbx_mesh::mesh::VersionedMesh::Version2(mesh)=>{
|
||||
let vertex_id_map=match mesh.header.sizeof_vertex{
|
||||
rbx_mesh::mesh::SizeOfVertex2::Truncated=>{
|
||||
//pick white and make all the vertices white
|
||||
let color_id=acquire_color_id([1.0f32;4]);
|
||||
ingest_vertices_truncated2(mesh.vertices_truncated,&mut acquire_pos_id,&mut acquire_tex_id,&mut acquire_normal_id,color_id,&mut acquire_vertex_id)
|
||||
},
|
||||
rbx_mesh::mesh::SizeOfVertex2::Full=>ingest_vertices2(mesh.vertices,&mut acquire_pos_id,&mut acquire_tex_id,&mut acquire_normal_id,&mut acquire_color_id,&mut acquire_vertex_id),
|
||||
}?;
|
||||
//one big happy group for all the faces
|
||||
polygon_groups.push(PolygonGroup::PolygonList(PolygonList::new(mesh.faces.into_iter().map(|face|
|
||||
vec![vertex_id_map[&face.0],vertex_id_map[&face.1],vertex_id_map[&face.2]]
|
||||
).collect())));
|
||||
},
|
||||
rbx_mesh::mesh::VersionedMesh::Version3(mesh)=>{
|
||||
let vertex_id_map=match mesh.header.sizeof_vertex{
|
||||
rbx_mesh::mesh::SizeOfVertex2::Truncated=>{
|
||||
let color_id=acquire_color_id([1.0f32;4]);
|
||||
ingest_vertices_truncated2(mesh.vertices_truncated,&mut acquire_pos_id,&mut acquire_tex_id,&mut acquire_normal_id,color_id,&mut acquire_vertex_id)
|
||||
},
|
||||
rbx_mesh::mesh::SizeOfVertex2::Full=>ingest_vertices2(mesh.vertices,&mut acquire_pos_id,&mut acquire_tex_id,&mut acquire_normal_id,&mut acquire_color_id,&mut acquire_vertex_id),
|
||||
}?;
|
||||
ingest_faces2_lods3(&mut polygon_groups,&vertex_id_map,&mesh.faces,&mesh.lods);
|
||||
},
|
||||
rbx_mesh::mesh::VersionedMesh::Version4(mesh)=>{
|
||||
let vertex_id_map=ingest_vertices2(
|
||||
mesh.vertices,&mut acquire_pos_id,&mut acquire_tex_id,&mut acquire_normal_id,&mut acquire_color_id,&mut acquire_vertex_id
|
||||
)?;
|
||||
ingest_faces2_lods3(&mut polygon_groups,&vertex_id_map,&mesh.faces,&mesh.lods);
|
||||
},
|
||||
rbx_mesh::mesh::VersionedMesh::Version5(mesh)=>{
|
||||
let vertex_id_map=ingest_vertices2(
|
||||
mesh.vertices,&mut acquire_pos_id,&mut acquire_tex_id,&mut acquire_normal_id,&mut acquire_color_id,&mut acquire_vertex_id
|
||||
)?;
|
||||
ingest_faces2_lods3(&mut polygon_groups,&vertex_id_map,&mesh.faces,&mesh.lods);
|
||||
},
|
||||
}
|
||||
Ok(model::Mesh{
|
||||
unique_pos,
|
||||
unique_normal,
|
||||
unique_tex,
|
||||
unique_color,
|
||||
unique_vertices,
|
||||
polygon_groups,
|
||||
//these should probably be moved to the model...
|
||||
graphics_groups:Vec::new(),
|
||||
physics_groups:Vec::new(),
|
||||
})
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -1,106 +1,93 @@
|
||||
use rbx_dom_weak::{types::Ref, InstanceBuilder, WeakDom};
|
||||
use rbx_dom_weak::{types::Ref,InstanceBuilder,WeakDom};
|
||||
|
||||
pub fn class_is_a(class: &str, superclass: &str) -> bool {
|
||||
class == superclass
|
||||
|| rbx_reflection_database::get()
|
||||
.classes
|
||||
.get(class)
|
||||
.is_some_and(|descriptor| {
|
||||
descriptor
|
||||
.superclass
|
||||
.as_ref()
|
||||
.is_some_and(|class_super| class_is_a(class_super, superclass))
|
||||
})
|
||||
pub fn class_is_a(class:&str,superclass:&str)->bool{
|
||||
class==superclass
|
||||
||rbx_reflection_database::get().classes.get(class)
|
||||
.is_some_and(|descriptor|
|
||||
descriptor.superclass.as_ref().is_some_and(|class_super|
|
||||
class_is_a(class_super,superclass)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
#[repr(transparent)]
|
||||
pub struct Context {
|
||||
pub(crate) dom: WeakDom,
|
||||
pub struct Context{
|
||||
pub(crate)dom:WeakDom,
|
||||
}
|
||||
|
||||
impl Context {
|
||||
pub const fn new(dom: WeakDom) -> Self {
|
||||
Self { dom }
|
||||
}
|
||||
pub fn script_singleton(
|
||||
source: String,
|
||||
) -> (Context, crate::runner::instance::Instance, Services) {
|
||||
let script = InstanceBuilder::new("Script")
|
||||
.with_property("Source", rbx_types::Variant::String(source));
|
||||
let script_ref = script.referent();
|
||||
let mut context = Self::new(WeakDom::new(
|
||||
InstanceBuilder::new("DataModel").with_child(script),
|
||||
));
|
||||
let services = context.convert_into_place();
|
||||
(
|
||||
context,
|
||||
crate::runner::instance::Instance::new(script_ref),
|
||||
services,
|
||||
)
|
||||
}
|
||||
pub fn from_ref(dom: &WeakDom) -> &Context {
|
||||
unsafe { &*(dom as *const WeakDom as *const Context) }
|
||||
}
|
||||
pub fn from_mut(dom: &mut WeakDom) -> &mut Context {
|
||||
unsafe { &mut *(dom as *mut WeakDom as *mut Context) }
|
||||
}
|
||||
/// Creates an iterator over all items of a particular class.
|
||||
pub fn superclass_iter<'a>(&'a self, superclass: &'a str) -> impl Iterator<Item = Ref> + 'a {
|
||||
self.dom
|
||||
.descendants()
|
||||
.filter(|&instance| class_is_a(instance.class.as_ref(), superclass))
|
||||
.map(|instance| instance.referent())
|
||||
}
|
||||
pub fn scripts(&self) -> Vec<crate::runner::instance::Instance> {
|
||||
self.superclass_iter("LuaSourceContainer")
|
||||
.map(crate::runner::instance::Instance::new)
|
||||
.collect()
|
||||
}
|
||||
impl Context{
|
||||
pub const fn new(dom:WeakDom)->Self{
|
||||
Self{dom}
|
||||
}
|
||||
pub fn script_singleton(source:String)->(Context,crate::runner::instance::Instance,Services){
|
||||
let script=InstanceBuilder::new("Script")
|
||||
.with_property("Source",rbx_types::Variant::String(source));
|
||||
let script_ref=script.referent();
|
||||
let mut context=Self::new(WeakDom::new(
|
||||
InstanceBuilder::new("DataModel")
|
||||
.with_child(script)
|
||||
));
|
||||
let services=context.convert_into_place();
|
||||
(context,crate::runner::instance::Instance::new(script_ref),services)
|
||||
}
|
||||
pub fn from_ref(dom:&WeakDom)->&Context{
|
||||
unsafe{&*(dom as *const WeakDom as *const Context)}
|
||||
}
|
||||
pub fn from_mut(dom:&mut WeakDom)->&mut Context{
|
||||
unsafe{&mut *(dom as *mut WeakDom as *mut Context)}
|
||||
}
|
||||
/// Creates an iterator over all items of a particular class.
|
||||
pub fn superclass_iter<'a>(&'a self,superclass:&'a str)->impl Iterator<Item=Ref>+'a{
|
||||
self.dom.descendants().filter(|&instance|
|
||||
class_is_a(instance.class.as_ref(),superclass)
|
||||
).map(|instance|instance.referent())
|
||||
}
|
||||
pub fn scripts(&self)->Vec<crate::runner::instance::Instance>{
|
||||
self.superclass_iter("LuaSourceContainer").map(crate::runner::instance::Instance::new).collect()
|
||||
}
|
||||
|
||||
pub fn find_services(&self) -> Option<Services> {
|
||||
Some(Services {
|
||||
workspace: *self.dom.root().children().iter().find(|&&r| {
|
||||
self.dom
|
||||
.get_by_ref(r)
|
||||
.is_some_and(|instance| instance.class == "Workspace")
|
||||
})?,
|
||||
game: self.dom.root_ref(),
|
||||
})
|
||||
}
|
||||
pub fn convert_into_place(&mut self) -> Services {
|
||||
//snapshot root instances
|
||||
let children = self.dom.root().children().to_owned();
|
||||
pub fn find_services(&self)->Option<Services>{
|
||||
Some(Services{
|
||||
workspace:*self.dom.root().children().iter().find(|&&r|
|
||||
self.dom.get_by_ref(r).is_some_and(|instance|instance.class=="Workspace")
|
||||
)?,
|
||||
game:self.dom.root_ref(),
|
||||
})
|
||||
}
|
||||
pub fn convert_into_place(&mut self)->Services{
|
||||
//snapshot root instances
|
||||
let children=self.dom.root().children().to_owned();
|
||||
|
||||
//insert services
|
||||
let game = self.dom.root_ref();
|
||||
let terrain_bldr = InstanceBuilder::new("Terrain");
|
||||
let workspace = self.dom.insert(
|
||||
game,
|
||||
InstanceBuilder::new("Workspace")
|
||||
//Set Workspace.Terrain property equal to Terrain
|
||||
.with_property("Terrain", terrain_bldr.referent())
|
||||
.with_child(terrain_bldr),
|
||||
);
|
||||
{
|
||||
//Lowercase and upper case workspace property!
|
||||
let game = self.dom.root_mut();
|
||||
game.properties
|
||||
.insert("workspace".to_owned(), rbx_types::Variant::Ref(workspace));
|
||||
game.properties
|
||||
.insert("Workspace".to_owned(), rbx_types::Variant::Ref(workspace));
|
||||
}
|
||||
self.dom.insert(game, InstanceBuilder::new("Lighting"));
|
||||
//insert services
|
||||
let game=self.dom.root_ref();
|
||||
let terrain_bldr=InstanceBuilder::new("Terrain");
|
||||
let workspace=self.dom.insert(game,
|
||||
InstanceBuilder::new("Workspace")
|
||||
//Set Workspace.Terrain property equal to Terrain
|
||||
.with_property("Terrain",terrain_bldr.referent())
|
||||
.with_child(terrain_bldr)
|
||||
);
|
||||
{
|
||||
//Lowercase and upper case workspace property!
|
||||
let game=self.dom.root_mut();
|
||||
game.properties.insert("workspace".to_owned(),rbx_types::Variant::Ref(workspace));
|
||||
game.properties.insert("Workspace".to_owned(),rbx_types::Variant::Ref(workspace));
|
||||
}
|
||||
self.dom.insert(game,InstanceBuilder::new("Lighting"));
|
||||
|
||||
//transfer original root instances into workspace
|
||||
for instance in children {
|
||||
self.dom.transfer_within(instance, workspace);
|
||||
}
|
||||
//transfer original root instances into workspace
|
||||
for instance in children{
|
||||
self.dom.transfer_within(instance,workspace);
|
||||
}
|
||||
|
||||
Services { game, workspace }
|
||||
}
|
||||
Services{
|
||||
game,
|
||||
workspace,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Services {
|
||||
pub game: Ref,
|
||||
pub workspace: Ref,
|
||||
pub struct Services{
|
||||
pub game:Ref,
|
||||
pub workspace:Ref,
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
pub mod context;
|
||||
pub mod runner;
|
||||
#[cfg(feature = "run-service")]
|
||||
pub mod context;
|
||||
#[cfg(feature="run-service")]
|
||||
pub(crate) mod scheduler;
|
||||
|
||||
#[cfg(test)]
|
||||
|
@ -1,233 +1,174 @@
|
||||
use super::vector3::Vector3;
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct CFrame(pub(crate) glam::Affine3A);
|
||||
#[derive(Clone,Copy)]
|
||||
pub struct CFrame(pub(crate)glam::Affine3A);
|
||||
|
||||
impl CFrame {
|
||||
pub fn new(
|
||||
x: f32,
|
||||
y: f32,
|
||||
z: f32,
|
||||
xx: f32,
|
||||
yx: f32,
|
||||
zx: f32,
|
||||
xy: f32,
|
||||
yy: f32,
|
||||
zy: f32,
|
||||
xz: f32,
|
||||
yz: f32,
|
||||
zz: f32,
|
||||
) -> Self {
|
||||
Self(glam::Affine3A::from_mat3_translation(
|
||||
glam::mat3(
|
||||
glam::vec3(xx, yx, zx),
|
||||
glam::vec3(xy, yy, zy),
|
||||
glam::vec3(xz, yz, zz),
|
||||
),
|
||||
glam::vec3(x, y, z),
|
||||
))
|
||||
}
|
||||
pub fn point(x: f32, y: f32, z: f32) -> Self {
|
||||
Self(glam::Affine3A::from_translation(glam::vec3(x, y, z)))
|
||||
}
|
||||
pub fn angles(x: f32, y: f32, z: f32) -> Self {
|
||||
Self(glam::Affine3A::from_mat3(glam::Mat3::from_euler(
|
||||
glam::EulerRot::YXZ,
|
||||
y,
|
||||
x,
|
||||
z,
|
||||
)))
|
||||
}
|
||||
impl CFrame{
|
||||
pub fn new(
|
||||
x:f32,y:f32,z:f32,
|
||||
xx:f32,yx:f32,zx:f32,
|
||||
xy:f32,yy:f32,zy:f32,
|
||||
xz:f32,yz:f32,zz:f32,
|
||||
)->Self{
|
||||
Self(glam::Affine3A::from_mat3_translation(
|
||||
glam::mat3(
|
||||
glam::vec3(xx,yx,zx),
|
||||
glam::vec3(xy,yy,zy),
|
||||
glam::vec3(xz,yz,zz)
|
||||
),
|
||||
glam::vec3(x,y,z)
|
||||
))
|
||||
}
|
||||
pub fn point(x:f32,y:f32,z:f32)->Self{
|
||||
Self(glam::Affine3A::from_translation(glam::vec3(x,y,z)))
|
||||
}
|
||||
pub fn angles(x:f32,y:f32,z:f32)->Self{
|
||||
Self(glam::Affine3A::from_mat3(glam::Mat3::from_euler(glam::EulerRot::YXZ,y,x,z)))
|
||||
}
|
||||
}
|
||||
|
||||
fn vec3_to_glam(v: glam::Vec3A) -> rbx_types::Vector3 {
|
||||
rbx_types::Vector3::new(v.x, v.y, v.z)
|
||||
fn vec3_to_glam(v:glam::Vec3A)->rbx_types::Vector3{
|
||||
rbx_types::Vector3::new(v.x,v.y,v.z)
|
||||
}
|
||||
fn vec3_from_glam(v: rbx_types::Vector3) -> glam::Vec3A {
|
||||
glam::vec3a(v.x, v.y, v.z)
|
||||
fn vec3_from_glam(v:rbx_types::Vector3)->glam::Vec3A{
|
||||
glam::vec3a(v.x,v.y,v.z)
|
||||
}
|
||||
|
||||
impl Into<rbx_types::CFrame> for CFrame {
|
||||
fn into(self) -> rbx_types::CFrame {
|
||||
rbx_types::CFrame::new(
|
||||
vec3_to_glam(self.0.translation),
|
||||
rbx_types::Matrix3::new(
|
||||
vec3_to_glam(self.0.matrix3.x_axis),
|
||||
vec3_to_glam(self.0.matrix3.y_axis),
|
||||
vec3_to_glam(self.0.matrix3.z_axis),
|
||||
),
|
||||
)
|
||||
}
|
||||
impl Into<rbx_types::CFrame> for CFrame{
|
||||
fn into(self)->rbx_types::CFrame{
|
||||
rbx_types::CFrame::new(
|
||||
vec3_to_glam(self.0.translation),
|
||||
rbx_types::Matrix3::new(
|
||||
vec3_to_glam(self.0.matrix3.x_axis),
|
||||
vec3_to_glam(self.0.matrix3.y_axis),
|
||||
vec3_to_glam(self.0.matrix3.z_axis),
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
impl From<rbx_types::CFrame> for CFrame {
|
||||
fn from(value: rbx_types::CFrame) -> Self {
|
||||
CFrame(glam::Affine3A {
|
||||
matrix3: glam::mat3a(
|
||||
vec3_from_glam(value.orientation.x),
|
||||
vec3_from_glam(value.orientation.y),
|
||||
vec3_from_glam(value.orientation.z),
|
||||
),
|
||||
translation: vec3_from_glam(value.position),
|
||||
})
|
||||
}
|
||||
impl From<rbx_types::CFrame> for CFrame{
|
||||
fn from(value:rbx_types::CFrame)->Self{
|
||||
CFrame(glam::Affine3A{
|
||||
matrix3:glam::mat3a(
|
||||
vec3_from_glam(value.orientation.x),
|
||||
vec3_from_glam(value.orientation.y),
|
||||
vec3_from_glam(value.orientation.z),
|
||||
),
|
||||
translation:vec3_from_glam(value.position)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_globals(lua: &mlua::Lua, globals: &mlua::Table) -> Result<(), mlua::Error> {
|
||||
let cframe_table = lua.create_table()?;
|
||||
pub fn set_globals(lua:&mlua::Lua,globals:&mlua::Table)->Result<(),mlua::Error>{
|
||||
let cframe_table=lua.create_table()?;
|
||||
|
||||
//CFrame.new
|
||||
cframe_table.raw_set(
|
||||
"new",
|
||||
lua.create_function(
|
||||
|_,
|
||||
tuple: (
|
||||
mlua::Value,
|
||||
mlua::Value,
|
||||
Option<f32>,
|
||||
Option<f32>,
|
||||
Option<f32>,
|
||||
Option<f32>,
|
||||
Option<f32>,
|
||||
Option<f32>,
|
||||
Option<f32>,
|
||||
Option<f32>,
|
||||
Option<f32>,
|
||||
Option<f32>,
|
||||
)| match tuple {
|
||||
//CFrame.new(pos)
|
||||
(
|
||||
mlua::Value::UserData(pos),
|
||||
mlua::Value::Nil,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
) => {
|
||||
let pos: Vector3 = pos.take()?;
|
||||
Ok(CFrame::point(pos.0.x, pos.0.y, pos.0.z))
|
||||
}
|
||||
//TODO: CFrame.new(pos,look)
|
||||
(
|
||||
mlua::Value::UserData(pos),
|
||||
mlua::Value::UserData(look),
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
) => {
|
||||
let _pos: Vector3 = pos.take()?;
|
||||
let _look: Vector3 = look.take()?;
|
||||
Err(mlua::Error::runtime("Not yet implemented"))
|
||||
}
|
||||
//CFrame.new(x,y,z)
|
||||
(
|
||||
mlua::Value::Number(x),
|
||||
mlua::Value::Number(y),
|
||||
Some(z),
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
) => Ok(CFrame::point(x as f32, y as f32, z)),
|
||||
//CFrame.new(x,y,z,xx,yx,zx,xy,yy,zy,xz,yz,zz)
|
||||
(
|
||||
mlua::Value::Number(x),
|
||||
mlua::Value::Number(y),
|
||||
Some(z),
|
||||
Some(xx),
|
||||
Some(yx),
|
||||
Some(zx),
|
||||
Some(xy),
|
||||
Some(yy),
|
||||
Some(zy),
|
||||
Some(xz),
|
||||
Some(yz),
|
||||
Some(zz),
|
||||
) => Ok(CFrame::new(
|
||||
x as f32, y as f32, z, xx, yx, zx, xy, yy, zy, xz, yz, zz,
|
||||
)),
|
||||
_ => Err(mlua::Error::runtime("Invalid arguments")),
|
||||
},
|
||||
)?,
|
||||
)?;
|
||||
//CFrame.new
|
||||
cframe_table.raw_set("new",
|
||||
lua.create_function(|_,tuple:(
|
||||
mlua::Value,mlua::Value,Option<f32>,
|
||||
Option<f32>,Option<f32>,Option<f32>,
|
||||
Option<f32>,Option<f32>,Option<f32>,
|
||||
Option<f32>,Option<f32>,Option<f32>,
|
||||
)|match tuple{
|
||||
//CFrame.new(pos)
|
||||
(
|
||||
mlua::Value::UserData(pos),mlua::Value::Nil,None,
|
||||
None,None,None,
|
||||
None,None,None,
|
||||
None,None,None,
|
||||
)=>{
|
||||
let pos:Vector3=pos.take()?;
|
||||
Ok(CFrame::point(pos.0.x,pos.0.y,pos.0.z))
|
||||
},
|
||||
//TODO: CFrame.new(pos,look)
|
||||
(
|
||||
mlua::Value::UserData(pos),mlua::Value::UserData(look),None,
|
||||
None,None,None,
|
||||
None,None,None,
|
||||
None,None,None,
|
||||
)=>{
|
||||
let _pos:Vector3=pos.take()?;
|
||||
let _look:Vector3=look.take()?;
|
||||
Err(mlua::Error::runtime("Not yet implemented"))
|
||||
},
|
||||
//CFrame.new(x,y,z)
|
||||
(
|
||||
mlua::Value::Number(x),mlua::Value::Number(y),Some(z),
|
||||
None,None,None,
|
||||
None,None,None,
|
||||
None,None,None,
|
||||
)=>Ok(CFrame::point(x as f32,y as f32,z)),
|
||||
//CFrame.new(x,y,z,xx,yx,zx,xy,yy,zy,xz,yz,zz)
|
||||
(
|
||||
mlua::Value::Number(x),mlua::Value::Number(y),Some(z),
|
||||
Some(xx),Some(yx),Some(zx),
|
||||
Some(xy),Some(yy),Some(zy),
|
||||
Some(xz),Some(yz),Some(zz),
|
||||
)=>Ok(CFrame::new(x as f32,y as f32,z,
|
||||
xx,yx,zx,
|
||||
xy,yy,zy,
|
||||
xz,yz,zz,
|
||||
)),
|
||||
_=>Err(mlua::Error::runtime("Invalid arguments"))
|
||||
})?
|
||||
)?;
|
||||
|
||||
//CFrame.Angles
|
||||
cframe_table.raw_set(
|
||||
"Angles",
|
||||
lua.create_function(|_, (x, y, z): (f32, f32, f32)| Ok(CFrame::angles(x, y, z)))?,
|
||||
)?;
|
||||
//CFrame.Angles
|
||||
cframe_table.raw_set("Angles",
|
||||
lua.create_function(|_,(x,y,z):(f32,f32,f32)|
|
||||
Ok(CFrame::angles(x,y,z))
|
||||
)?
|
||||
)?;
|
||||
|
||||
globals.set("CFrame", cframe_table)?;
|
||||
globals.set("CFrame",cframe_table)?;
|
||||
|
||||
Ok(())
|
||||
Ok(())
|
||||
}
|
||||
|
||||
impl mlua::UserData for CFrame {
|
||||
fn add_fields<F: mlua::UserDataFields<Self>>(fields: &mut F) {
|
||||
//CFrame.p
|
||||
fields.add_field_method_get("p", |_, this| Ok(Vector3(this.0.translation)));
|
||||
}
|
||||
impl mlua::UserData for CFrame{
|
||||
fn add_fields<F:mlua::UserDataFields<Self>>(fields:&mut F){
|
||||
//CFrame.p
|
||||
fields.add_field_method_get("p",|_,this|Ok(Vector3(this.0.translation)));
|
||||
}
|
||||
|
||||
fn add_methods<M: mlua::UserDataMethods<Self>>(methods: &mut M) {
|
||||
methods.add_method("components", |_, this, ()| {
|
||||
Ok((
|
||||
this.0.translation.x,
|
||||
this.0.translation.y,
|
||||
this.0.translation.z,
|
||||
this.0.matrix3.x_axis.x,
|
||||
this.0.matrix3.y_axis.x,
|
||||
this.0.matrix3.z_axis.x,
|
||||
this.0.matrix3.x_axis.y,
|
||||
this.0.matrix3.y_axis.y,
|
||||
this.0.matrix3.z_axis.y,
|
||||
this.0.matrix3.x_axis.z,
|
||||
this.0.matrix3.y_axis.z,
|
||||
this.0.matrix3.z_axis.z,
|
||||
))
|
||||
});
|
||||
methods.add_method("VectorToWorldSpace", |_, this, v: Vector3| {
|
||||
Ok(Vector3(this.0.transform_vector3a(v.0)))
|
||||
});
|
||||
fn add_methods<M:mlua::UserDataMethods<Self>>(methods:&mut M){
|
||||
methods.add_method("components",|_,this,()|Ok((
|
||||
this.0.translation.x,
|
||||
this.0.translation.y,
|
||||
this.0.translation.z,
|
||||
this.0.matrix3.x_axis.x,
|
||||
this.0.matrix3.y_axis.x,
|
||||
this.0.matrix3.z_axis.x,
|
||||
this.0.matrix3.x_axis.y,
|
||||
this.0.matrix3.y_axis.y,
|
||||
this.0.matrix3.z_axis.y,
|
||||
this.0.matrix3.x_axis.z,
|
||||
this.0.matrix3.y_axis.z,
|
||||
this.0.matrix3.z_axis.z,
|
||||
)));
|
||||
methods.add_method("VectorToWorldSpace",|_,this,v:Vector3|
|
||||
Ok(Vector3(this.0.transform_vector3a(v.0)))
|
||||
);
|
||||
|
||||
//methods.add_meta_method(mlua::MetaMethod::Mul,|_,this,val:&Vector3|Ok(Vector3(this.0.matrix3*val.0+this.0.translation)));
|
||||
methods.add_meta_function(mlua::MetaMethod::Mul, |_, (this, val): (Self, Self)| {
|
||||
Ok(Self(this.0 * val.0))
|
||||
});
|
||||
methods.add_meta_function(mlua::MetaMethod::ToString, |_, this: Self| {
|
||||
Ok(format!(
|
||||
"CFrame.new({},{},{},{},{},{},{},{},{},{},{},{})",
|
||||
this.0.translation.x,
|
||||
this.0.translation.y,
|
||||
this.0.translation.z,
|
||||
this.0.matrix3.x_axis.x,
|
||||
this.0.matrix3.y_axis.x,
|
||||
this.0.matrix3.z_axis.x,
|
||||
this.0.matrix3.x_axis.y,
|
||||
this.0.matrix3.y_axis.y,
|
||||
this.0.matrix3.z_axis.y,
|
||||
this.0.matrix3.x_axis.z,
|
||||
this.0.matrix3.y_axis.z,
|
||||
this.0.matrix3.z_axis.z,
|
||||
))
|
||||
});
|
||||
}
|
||||
//methods.add_meta_method(mlua::MetaMethod::Mul,|_,this,val:&Vector3|Ok(Vector3(this.0.matrix3*val.0+this.0.translation)));
|
||||
methods.add_meta_function(mlua::MetaMethod::Mul,|_,(this,val):(Self,Self)|Ok(Self(this.0*val.0)));
|
||||
methods.add_meta_function(mlua::MetaMethod::ToString,|_,this:Self|
|
||||
Ok(format!("CFrame.new({},{},{},{},{},{},{},{},{},{},{},{})",
|
||||
this.0.translation.x,
|
||||
this.0.translation.y,
|
||||
this.0.translation.z,
|
||||
this.0.matrix3.x_axis.x,
|
||||
this.0.matrix3.y_axis.x,
|
||||
this.0.matrix3.z_axis.x,
|
||||
this.0.matrix3.x_axis.y,
|
||||
this.0.matrix3.y_axis.y,
|
||||
this.0.matrix3.z_axis.y,
|
||||
this.0.matrix3.x_axis.z,
|
||||
this.0.matrix3.y_axis.z,
|
||||
this.0.matrix3.z_axis.z,
|
||||
))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
type_from_lua_userdata!(CFrame);
|
||||
|
@ -1,72 +1,68 @@
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct Color3 {
|
||||
r: f32,
|
||||
g: f32,
|
||||
b: f32,
|
||||
#[derive(Clone,Copy)]
|
||||
pub struct Color3{
|
||||
r:f32,
|
||||
g:f32,
|
||||
b:f32,
|
||||
}
|
||||
impl Color3 {
|
||||
pub const fn new(r: f32, g: f32, b: f32) -> Self {
|
||||
Self { r, g, b }
|
||||
}
|
||||
impl Color3{
|
||||
pub const fn new(r:f32,g:f32,b:f32)->Self{
|
||||
Self{r,g,b}
|
||||
}
|
||||
}
|
||||
impl Into<rbx_types::Color3> for Color3 {
|
||||
fn into(self) -> rbx_types::Color3 {
|
||||
rbx_types::Color3::new(self.r, self.g, self.b)
|
||||
}
|
||||
impl Into<rbx_types::Color3> for Color3{
|
||||
fn into(self)->rbx_types::Color3{
|
||||
rbx_types::Color3::new(self.r,self.g,self.b)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_globals(lua: &mlua::Lua, globals: &mlua::Table) -> Result<(), mlua::Error> {
|
||||
let color3_table = lua.create_table()?;
|
||||
pub fn set_globals(lua:&mlua::Lua,globals:&mlua::Table)->Result<(),mlua::Error>{
|
||||
let color3_table=lua.create_table()?;
|
||||
|
||||
color3_table.raw_set(
|
||||
"new",
|
||||
lua.create_function(|_, (r, g, b): (f32, f32, f32)| Ok(Color3::new(r, g, b)))?,
|
||||
)?;
|
||||
color3_table.raw_set(
|
||||
"fromRGB",
|
||||
lua.create_function(|_, (r, g, b): (u8, u8, u8)| {
|
||||
Ok(Color3::new(
|
||||
r as f32 / 255.0,
|
||||
g as f32 / 255.0,
|
||||
b as f32 / 255.0,
|
||||
))
|
||||
})?,
|
||||
)?;
|
||||
color3_table.raw_set("new",
|
||||
lua.create_function(|_,(r,g,b):(f32,f32,f32)|
|
||||
Ok(Color3::new(r,g,b))
|
||||
)?
|
||||
)?;
|
||||
color3_table.raw_set("fromRGB",
|
||||
lua.create_function(|_,(r,g,b):(u8,u8,u8)|
|
||||
Ok(Color3::new(r as f32/255.0,g as f32/255.0,b as f32/255.0))
|
||||
)?
|
||||
)?;
|
||||
|
||||
globals.set("Color3", color3_table)?;
|
||||
globals.set("Color3",color3_table)?;
|
||||
|
||||
Ok(())
|
||||
Ok(())
|
||||
}
|
||||
fn lerp(lhs: f32, rhs: f32, t: f32) -> f32 {
|
||||
lhs + (rhs - lhs) * t
|
||||
fn lerp(lhs:f32,rhs:f32,t:f32)->f32{
|
||||
lhs+(rhs-lhs)*t
|
||||
}
|
||||
|
||||
impl mlua::UserData for Color3 {
|
||||
fn add_fields<F: mlua::UserDataFields<Self>>(fields: &mut F) {
|
||||
fields.add_field_method_get("r", |_, this| Ok(this.r));
|
||||
fields.add_field_method_set("r", |_, this, val| {
|
||||
this.r = val;
|
||||
Ok(())
|
||||
});
|
||||
fields.add_field_method_get("g", |_, this| Ok(this.g));
|
||||
fields.add_field_method_set("g", |_, this, val| {
|
||||
this.g = val;
|
||||
Ok(())
|
||||
});
|
||||
fields.add_field_method_get("b", |_, this| Ok(this.b));
|
||||
fields.add_field_method_set("b", |_, this, val| {
|
||||
this.b = val;
|
||||
Ok(())
|
||||
});
|
||||
}
|
||||
fn add_methods<M: mlua::UserDataMethods<Self>>(methods: &mut M) {
|
||||
methods.add_method("Lerp", |_, this, (other, t): (Self, f32)| {
|
||||
Ok(Color3::new(
|
||||
lerp(this.r, other.r, t),
|
||||
lerp(this.g, other.g, t),
|
||||
lerp(this.b, other.b, t),
|
||||
))
|
||||
})
|
||||
}
|
||||
impl mlua::UserData for Color3{
|
||||
fn add_fields<F:mlua::UserDataFields<Self>>(fields:&mut F){
|
||||
fields.add_field_method_get("r",|_,this|Ok(this.r));
|
||||
fields.add_field_method_set("r",|_,this,val|{
|
||||
this.r=val;
|
||||
Ok(())
|
||||
});
|
||||
fields.add_field_method_get("g",|_,this|Ok(this.g));
|
||||
fields.add_field_method_set("g",|_,this,val|{
|
||||
this.g=val;
|
||||
Ok(())
|
||||
});
|
||||
fields.add_field_method_get("b",|_,this|Ok(this.b));
|
||||
fields.add_field_method_set("b",|_,this,val|{
|
||||
this.b=val;
|
||||
Ok(())
|
||||
});
|
||||
}
|
||||
fn add_methods<M:mlua::UserDataMethods<Self>>(methods:&mut M){
|
||||
methods.add_method("Lerp",|_,this,(other,t):(Self,f32)|
|
||||
Ok(Color3::new(
|
||||
lerp(this.r,other.r,t),
|
||||
lerp(this.g,other.g,t),
|
||||
lerp(this.b,other.b,t),
|
||||
))
|
||||
)
|
||||
}
|
||||
}
|
||||
type_from_lua_userdata!(Color3);
|
||||
|
@ -1,30 +1,31 @@
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct ColorSequence {}
|
||||
impl ColorSequence {
|
||||
pub const fn new() -> Self {
|
||||
Self {}
|
||||
}
|
||||
#[derive(Clone,Copy)]
|
||||
pub struct ColorSequence{}
|
||||
impl ColorSequence{
|
||||
pub const fn new()->Self{
|
||||
Self{}
|
||||
}
|
||||
}
|
||||
impl Into<rbx_types::ColorSequence> for ColorSequence {
|
||||
fn into(self) -> rbx_types::ColorSequence {
|
||||
rbx_types::ColorSequence {
|
||||
keypoints: Vec::new(),
|
||||
}
|
||||
}
|
||||
impl Into<rbx_types::ColorSequence> for ColorSequence{
|
||||
fn into(self)->rbx_types::ColorSequence{
|
||||
rbx_types::ColorSequence{
|
||||
keypoints:Vec::new()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_globals(lua: &mlua::Lua, globals: &mlua::Table) -> Result<(), mlua::Error> {
|
||||
let number_sequence_table = lua.create_table()?;
|
||||
pub fn set_globals(lua:&mlua::Lua,globals:&mlua::Table)->Result<(),mlua::Error>{
|
||||
let number_sequence_table=lua.create_table()?;
|
||||
|
||||
number_sequence_table.raw_set(
|
||||
"new",
|
||||
lua.create_function(|_, _: mlua::MultiValue| Ok(ColorSequence::new()))?,
|
||||
)?;
|
||||
number_sequence_table.raw_set("new",
|
||||
lua.create_function(|_,_:mlua::MultiValue|
|
||||
Ok(ColorSequence::new())
|
||||
)?
|
||||
)?;
|
||||
|
||||
globals.set("ColorSequence", number_sequence_table)?;
|
||||
globals.set("ColorSequence",number_sequence_table)?;
|
||||
|
||||
Ok(())
|
||||
Ok(())
|
||||
}
|
||||
|
||||
impl mlua::UserData for ColorSequence {}
|
||||
impl mlua::UserData for ColorSequence{}
|
||||
type_from_lua_userdata!(ColorSequence);
|
||||
|
@ -1,67 +1,63 @@
|
||||
use mlua::IntoLua;
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
#[derive(Clone,Copy)]
|
||||
pub struct Enum(u32);
|
||||
#[derive(Clone, Copy)]
|
||||
#[derive(Clone,Copy)]
|
||||
pub struct EnumItems;
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct EnumItem<'a> {
|
||||
ed: &'a rbx_reflection::EnumDescriptor<'a>,
|
||||
#[derive(Clone,Copy)]
|
||||
pub struct EnumItem<'a>{
|
||||
ed:&'a rbx_reflection::EnumDescriptor<'a>,
|
||||
}
|
||||
|
||||
impl Into<rbx_types::Enum> for Enum {
|
||||
fn into(self) -> rbx_types::Enum {
|
||||
rbx_types::Enum::from_u32(self.0)
|
||||
}
|
||||
impl Into<rbx_types::Enum> for Enum{
|
||||
fn into(self)->rbx_types::Enum{
|
||||
rbx_types::Enum::from_u32(self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> EnumItem<'a> {
|
||||
const fn new(ed: &'a rbx_reflection::EnumDescriptor) -> Self {
|
||||
Self { ed }
|
||||
}
|
||||
impl<'a> EnumItem<'a>{
|
||||
const fn new(ed:&'a rbx_reflection::EnumDescriptor)->Self{
|
||||
Self{ed}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_globals(_lua: &mlua::Lua, globals: &mlua::Table) -> Result<(), mlua::Error> {
|
||||
globals.set("Enum", EnumItems)
|
||||
pub fn set_globals(_lua:&mlua::Lua,globals:&mlua::Table)->Result<(),mlua::Error>{
|
||||
globals.set("Enum",EnumItems)
|
||||
}
|
||||
|
||||
impl mlua::UserData for EnumItem<'_> {
|
||||
fn add_fields<F: mlua::UserDataFields<Self>>(_fields: &mut F) {}
|
||||
fn add_methods<M: mlua::UserDataMethods<Self>>(methods: &mut M) {
|
||||
methods.add_meta_function(
|
||||
mlua::MetaMethod::Index,
|
||||
|lua, (this, val): (EnumItem<'_>, mlua::String)| match this
|
||||
.ed
|
||||
.items
|
||||
.get(&*val.to_str()?)
|
||||
{
|
||||
Some(&id) => Enum(id).into_lua(lua),
|
||||
None => mlua::Value::Nil.into_lua(lua),
|
||||
},
|
||||
);
|
||||
}
|
||||
impl mlua::UserData for EnumItem<'_>{
|
||||
fn add_fields<F:mlua::UserDataFields<Self>>(_fields:&mut F){
|
||||
}
|
||||
fn add_methods<M:mlua::UserDataMethods<Self>>(methods:&mut M){
|
||||
methods.add_meta_function(mlua::MetaMethod::Index,|lua,(this,val):(EnumItem<'_>,mlua::String)|{
|
||||
match this.ed.items.get(&*val.to_str()?){
|
||||
Some(&id)=>Enum(id).into_lua(lua),
|
||||
None=>mlua::Value::Nil.into_lua(lua),
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
type_from_lua_userdata_lua_lifetime!(EnumItem);
|
||||
|
||||
impl mlua::UserData for EnumItems {
|
||||
fn add_fields<F: mlua::UserDataFields<Self>>(_fields: &mut F) {}
|
||||
fn add_methods<M: mlua::UserDataMethods<Self>>(methods: &mut M) {
|
||||
methods.add_meta_function(
|
||||
mlua::MetaMethod::Index,
|
||||
|lua, (_, val): (Self, mlua::String)| {
|
||||
let db = rbx_reflection_database::get();
|
||||
match db.enums.get(&*val.to_str()?) {
|
||||
Some(ed) => EnumItem::new(ed).into_lua(lua),
|
||||
None => mlua::Value::Nil.into_lua(lua),
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
impl mlua::UserData for EnumItems{
|
||||
fn add_fields<F:mlua::UserDataFields<Self>>(_fields:&mut F){
|
||||
}
|
||||
fn add_methods<M:mlua::UserDataMethods<Self>>(methods:&mut M){
|
||||
methods.add_meta_function(mlua::MetaMethod::Index,|lua,(_,val):(Self,mlua::String)|{
|
||||
let db=rbx_reflection_database::get();
|
||||
match db.enums.get(&*val.to_str()?){
|
||||
Some(ed)=>EnumItem::new(ed).into_lua(lua),
|
||||
None=>mlua::Value::Nil.into_lua(lua),
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
type_from_lua_userdata!(EnumItems);
|
||||
|
||||
impl mlua::UserData for Enum {
|
||||
fn add_fields<F: mlua::UserDataFields<Self>>(_fields: &mut F) {}
|
||||
fn add_methods<M: mlua::UserDataMethods<Self>>(_methods: &mut M) {}
|
||||
impl mlua::UserData for Enum{
|
||||
fn add_fields<F:mlua::UserDataFields<Self>>(_fields:&mut F){
|
||||
}
|
||||
fn add_methods<M:mlua::UserDataMethods<Self>>(_methods:&mut M){
|
||||
}
|
||||
}
|
||||
type_from_lua_userdata!(Enum);
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -1,32 +1,24 @@
|
||||
macro_rules! type_from_lua_userdata {
|
||||
($asd:ident) => {
|
||||
impl mlua::FromLua for $asd {
|
||||
fn from_lua(value: mlua::Value, _lua: &mlua::Lua) -> Result<Self, mlua::Error> {
|
||||
match value {
|
||||
mlua::Value::UserData(ud) => Ok(*ud.borrow::<Self>()?),
|
||||
other => Err(mlua::Error::runtime(format!(
|
||||
"Expected {} got {:?}",
|
||||
stringify!($asd),
|
||||
other
|
||||
))),
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
macro_rules! type_from_lua_userdata{
|
||||
($ty:ident)=>{
|
||||
impl mlua::FromLua for $ty{
|
||||
fn from_lua(value:mlua::Value,_lua:&mlua::Lua)->Result<Self,mlua::Error>{
|
||||
match value{
|
||||
mlua::Value::UserData(ud)=>Ok(*ud.borrow::<Self>()?),
|
||||
other=>Err(mlua::Error::runtime(format!("Expected {} got {:?}",stringify!($ty),other))),
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
macro_rules! type_from_lua_userdata_lua_lifetime {
|
||||
($asd:ident) => {
|
||||
impl mlua::FromLua for $asd<'static> {
|
||||
fn from_lua(value: mlua::Value, _lua: &mlua::Lua) -> Result<Self, mlua::Error> {
|
||||
match value {
|
||||
mlua::Value::UserData(ud) => Ok(*ud.borrow::<Self>()?),
|
||||
other => Err(mlua::Error::runtime(format!(
|
||||
"Expected {} got {:?}",
|
||||
stringify!($asd),
|
||||
other
|
||||
))),
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
macro_rules! type_from_lua_userdata_lua_lifetime{
|
||||
($ty:ident)=>{
|
||||
impl mlua::FromLua for $ty<'static>{
|
||||
fn from_lua(value:mlua::Value,_lua:&mlua::Lua)->Result<Self,mlua::Error>{
|
||||
match value{
|
||||
mlua::Value::UserData(ud)=>Ok(*ud.borrow::<Self>()?),
|
||||
other=>Err(mlua::Error::runtime(format!("Expected {} got {:?}",stringify!($ty),other))),
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -2,13 +2,13 @@
|
||||
mod macros;
|
||||
mod runner;
|
||||
|
||||
mod cframe;
|
||||
mod color3;
|
||||
mod color_sequence;
|
||||
mod r#enum;
|
||||
pub mod instance;
|
||||
mod number_sequence;
|
||||
mod script_signal;
|
||||
mod color3;
|
||||
mod cframe;
|
||||
mod vector3;
|
||||
pub mod instance;
|
||||
mod script_signal;
|
||||
mod color_sequence;
|
||||
mod number_sequence;
|
||||
|
||||
pub use runner::{Error, Runnable, Runner};
|
||||
pub use runner::{Runner,Runnable,Error};
|
||||
|
@ -1,30 +1,31 @@
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct NumberSequence {}
|
||||
impl NumberSequence {
|
||||
pub const fn new() -> Self {
|
||||
Self {}
|
||||
}
|
||||
#[derive(Clone,Copy)]
|
||||
pub struct NumberSequence{}
|
||||
impl NumberSequence{
|
||||
pub const fn new()->Self{
|
||||
Self{}
|
||||
}
|
||||
}
|
||||
impl Into<rbx_types::NumberSequence> for NumberSequence {
|
||||
fn into(self) -> rbx_types::NumberSequence {
|
||||
rbx_types::NumberSequence {
|
||||
keypoints: Vec::new(),
|
||||
}
|
||||
}
|
||||
impl Into<rbx_types::NumberSequence> for NumberSequence{
|
||||
fn into(self)->rbx_types::NumberSequence{
|
||||
rbx_types::NumberSequence{
|
||||
keypoints:Vec::new()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_globals(lua: &mlua::Lua, globals: &mlua::Table) -> Result<(), mlua::Error> {
|
||||
let number_sequence_table = lua.create_table()?;
|
||||
pub fn set_globals(lua:&mlua::Lua,globals:&mlua::Table)->Result<(),mlua::Error>{
|
||||
let number_sequence_table=lua.create_table()?;
|
||||
|
||||
number_sequence_table.raw_set(
|
||||
"new",
|
||||
lua.create_function(|_, _: mlua::MultiValue| Ok(NumberSequence::new()))?,
|
||||
)?;
|
||||
number_sequence_table.raw_set("new",
|
||||
lua.create_function(|_,_:mlua::MultiValue|
|
||||
Ok(NumberSequence::new())
|
||||
)?
|
||||
)?;
|
||||
|
||||
globals.set("NumberSequence", number_sequence_table)?;
|
||||
globals.set("NumberSequence",number_sequence_table)?;
|
||||
|
||||
Ok(())
|
||||
Ok(())
|
||||
}
|
||||
|
||||
impl mlua::UserData for NumberSequence {}
|
||||
impl mlua::UserData for NumberSequence{}
|
||||
type_from_lua_userdata!(NumberSequence);
|
||||
|
@ -1,171 +1,143 @@
|
||||
use crate::context::Context;
|
||||
#[cfg(feature = "run-service")]
|
||||
#[cfg(feature="run-service")]
|
||||
use crate::scheduler::scheduler_mut;
|
||||
|
||||
pub struct Runner {
|
||||
lua: mlua::Lua,
|
||||
pub struct Runner{
|
||||
lua:mlua::Lua,
|
||||
}
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
Lua { source: String, error: mlua::Error },
|
||||
RustLua(mlua::Error),
|
||||
NoServices,
|
||||
pub enum Error{
|
||||
Lua{
|
||||
source:String,
|
||||
error:mlua::Error
|
||||
},
|
||||
RustLua(mlua::Error),
|
||||
NoServices,
|
||||
}
|
||||
impl std::fmt::Display for Error {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::Lua { source, error } => write!(f, "lua error: source:\n{source}\n{error}"),
|
||||
Self::RustLua(error) => write!(f, "rust-side lua error: {error}"),
|
||||
other => write!(f, "{other:?}"),
|
||||
}
|
||||
}
|
||||
impl std::fmt::Display for Error{
|
||||
fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{
|
||||
match self{
|
||||
Self::Lua{source,error}=>write!(f,"lua error: source:\n{source}\n{error}"),
|
||||
Self::RustLua(error)=>write!(f,"rust-side lua error: {error}"),
|
||||
other=>write!(f,"{other:?}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl std::error::Error for Error {}
|
||||
impl std::error::Error for Error{}
|
||||
|
||||
fn init(lua: &mlua::Lua) -> mlua::Result<()> {
|
||||
lua.sandbox(true)?;
|
||||
fn init(lua:&mlua::Lua)->mlua::Result<()>{
|
||||
lua.sandbox(true)?;
|
||||
|
||||
//global environment
|
||||
let globals = lua.globals();
|
||||
//global environment
|
||||
let globals=lua.globals();
|
||||
|
||||
#[cfg(feature = "run-service")]
|
||||
crate::scheduler::set_globals(lua, &globals)?;
|
||||
super::script_signal::set_globals(lua, &globals)?;
|
||||
super::r#enum::set_globals(lua, &globals)?;
|
||||
super::color3::set_globals(lua, &globals)?;
|
||||
super::vector3::set_globals(lua, &globals)?;
|
||||
super::cframe::set_globals(lua, &globals)?;
|
||||
super::instance::instance::set_globals(lua, &globals)?;
|
||||
super::number_sequence::set_globals(lua, &globals)?;
|
||||
super::color_sequence::set_globals(lua, &globals)?;
|
||||
#[cfg(feature="run-service")]
|
||||
crate::scheduler::set_globals(lua,&globals)?;
|
||||
super::script_signal::set_globals(lua,&globals)?;
|
||||
super::r#enum::set_globals(lua,&globals)?;
|
||||
super::color3::set_globals(lua,&globals)?;
|
||||
super::vector3::set_globals(lua,&globals)?;
|
||||
super::cframe::set_globals(lua,&globals)?;
|
||||
super::instance::instance::set_globals(lua,&globals)?;
|
||||
super::number_sequence::set_globals(lua,&globals)?;
|
||||
super::color_sequence::set_globals(lua,&globals)?;
|
||||
|
||||
Ok(())
|
||||
Ok(())
|
||||
}
|
||||
|
||||
impl Runner {
|
||||
pub fn new() -> Result<Self, Error> {
|
||||
let runner = Self {
|
||||
lua: mlua::Lua::new(),
|
||||
};
|
||||
init(&runner.lua).map_err(Error::RustLua)?;
|
||||
Ok(runner)
|
||||
}
|
||||
pub fn runnable_context<'a>(self, context: &'a mut Context) -> Result<Runnable<'a>, Error> {
|
||||
let services = context.find_services().ok_or(Error::NoServices)?;
|
||||
self.runnable_context_with_services(context, &services)
|
||||
}
|
||||
pub fn runnable_context_with_services<'a>(
|
||||
self,
|
||||
context: &'a mut Context,
|
||||
services: &crate::context::Services,
|
||||
) -> Result<Runnable<'a>, Error> {
|
||||
{
|
||||
let globals = self.lua.globals();
|
||||
globals
|
||||
.set("game", super::instance::Instance::new(services.game))
|
||||
.map_err(Error::RustLua)?;
|
||||
globals
|
||||
.set(
|
||||
"workspace",
|
||||
super::instance::Instance::new(services.workspace),
|
||||
)
|
||||
.map_err(Error::RustLua)?;
|
||||
}
|
||||
//this makes set_app_data shut up about the lifetime
|
||||
self.lua
|
||||
.set_app_data::<&'static mut rbx_dom_weak::WeakDom>(unsafe {
|
||||
core::mem::transmute(&mut context.dom)
|
||||
});
|
||||
#[cfg(feature = "run-service")]
|
||||
self.lua
|
||||
.set_app_data::<crate::scheduler::Scheduler>(crate::scheduler::Scheduler::default());
|
||||
Ok(Runnable {
|
||||
lua: self.lua,
|
||||
_lifetime: &std::marker::PhantomData,
|
||||
})
|
||||
}
|
||||
impl Runner{
|
||||
pub fn new()->Result<Self,Error>{
|
||||
let runner=Self{
|
||||
lua:mlua::Lua::new(),
|
||||
};
|
||||
init(&runner.lua).map_err(Error::RustLua)?;
|
||||
Ok(runner)
|
||||
}
|
||||
pub fn runnable_context<'a>(self,context:&'a mut Context)->Result<Runnable<'a>,Error>{
|
||||
let services=context.find_services().ok_or(Error::NoServices)?;
|
||||
self.runnable_context_with_services(context,&services)
|
||||
}
|
||||
pub fn runnable_context_with_services<'a>(self,context:&'a mut Context,services:&crate::context::Services)->Result<Runnable<'a>,Error>{
|
||||
{
|
||||
let globals=self.lua.globals();
|
||||
globals.set("game",super::instance::Instance::new(services.game)).map_err(Error::RustLua)?;
|
||||
globals.set("workspace",super::instance::Instance::new(services.workspace)).map_err(Error::RustLua)?;
|
||||
}
|
||||
//this makes set_app_data shut up about the lifetime
|
||||
self.lua.set_app_data::<&'static mut rbx_dom_weak::WeakDom>(unsafe{core::mem::transmute(&mut context.dom)});
|
||||
#[cfg(feature="run-service")]
|
||||
self.lua.set_app_data::<crate::scheduler::Scheduler>(crate::scheduler::Scheduler::default());
|
||||
Ok(Runnable{
|
||||
lua:self.lua,
|
||||
_lifetime:&std::marker::PhantomData
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
//Runnable is the same thing but has context set, which it holds the lifetime for.
|
||||
pub struct Runnable<'a> {
|
||||
lua: mlua::Lua,
|
||||
_lifetime: &'a std::marker::PhantomData<()>,
|
||||
pub struct Runnable<'a>{
|
||||
lua:mlua::Lua,
|
||||
_lifetime:&'a std::marker::PhantomData<()>
|
||||
}
|
||||
impl Runnable<'_> {
|
||||
pub fn drop_context(self) -> Runner {
|
||||
self.lua
|
||||
.remove_app_data::<&'static mut rbx_dom_weak::WeakDom>();
|
||||
#[cfg(feature = "run-service")]
|
||||
self.lua.remove_app_data::<crate::scheduler::Scheduler>();
|
||||
Runner { lua: self.lua }
|
||||
}
|
||||
pub fn run_script(&self, script: super::instance::Instance) -> Result<(), Error> {
|
||||
let (name, source) = super::instance::instance::get_name_source(&self.lua, script)
|
||||
.map_err(Error::RustLua)?;
|
||||
self.lua
|
||||
.globals()
|
||||
.raw_set("script", script)
|
||||
.map_err(Error::RustLua)?;
|
||||
let f = self
|
||||
.lua
|
||||
.load(source.as_str())
|
||||
.set_name(name)
|
||||
.into_function()
|
||||
.map_err(Error::RustLua)?;
|
||||
// TODO: set_environment without losing the ability to print from Lua
|
||||
let thread = self.lua.create_thread(f).map_err(Error::RustLua)?;
|
||||
thread
|
||||
.resume::<mlua::MultiValue>(())
|
||||
.map_err(|error| Error::Lua { source, error })?;
|
||||
// wait() is called from inside Lua and goes to a rust function that schedules the thread and then yields
|
||||
// No need to schedule the thread here
|
||||
Ok(())
|
||||
}
|
||||
#[cfg(feature = "run-service")]
|
||||
pub fn has_scheduled_threads(&self) -> Result<bool, mlua::Error> {
|
||||
scheduler_mut(&self.lua, |scheduler| Ok(scheduler.has_scheduled_threads()))
|
||||
}
|
||||
#[cfg(feature = "run-service")]
|
||||
pub fn game_tick(&self) -> Result<(), mlua::Error> {
|
||||
if let Some(threads) = scheduler_mut(&self.lua, |scheduler| Ok(scheduler.tick_threads()))? {
|
||||
for thread in threads {
|
||||
//TODO: return dt and total run time
|
||||
let result = thread
|
||||
.resume::<mlua::MultiValue>((1.0 / 30.0, 0.0))
|
||||
.map_err(|error| Error::Lua {
|
||||
source: "source unavailable".to_owned(),
|
||||
error,
|
||||
});
|
||||
match result {
|
||||
Ok(_) => (),
|
||||
Err(e) => println!("game_tick Error: {e}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
#[cfg(feature = "run-service")]
|
||||
pub fn run_service_step(&self) -> Result<(), mlua::Error> {
|
||||
let render_stepped = super::instance::instance::dom_mut(&self.lua, |dom| {
|
||||
let run_service =
|
||||
super::instance::instance::find_first_child_of_class(dom, dom.root(), "RunService")
|
||||
.ok_or_else(|| mlua::Error::runtime("RunService missing"))?;
|
||||
super::instance::instance::instance_value_store_mut(&self.lua, |instance_value_store| {
|
||||
//unwrap because I trust my find_first_child_of_class function to
|
||||
let mut instance_values = instance_value_store
|
||||
.get_or_create_instance_values(run_service)
|
||||
.ok_or_else(|| mlua::Error::runtime("RunService InstanceValues missing"))?;
|
||||
let render_stepped =
|
||||
instance_values.get_or_create_value(&self.lua, "RenderStepped")?;
|
||||
//let stepped=instance_values.get_or_create_value(&self.lua,"Stepped")?;
|
||||
//let heartbeat=instance_values.get_or_create_value(&self.lua,"Heartbeat")?;
|
||||
Ok(render_stepped)
|
||||
})
|
||||
})?;
|
||||
if let Some(render_stepped) = render_stepped {
|
||||
let signal: &super::script_signal::ScriptSignal = &*render_stepped.borrow()?;
|
||||
signal.fire(&mlua::MultiValue::new());
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
impl Runnable<'_>{
|
||||
pub fn drop_context(self)->Runner{
|
||||
self.lua.remove_app_data::<&'static mut rbx_dom_weak::WeakDom>();
|
||||
#[cfg(feature="run-service")]
|
||||
self.lua.remove_app_data::<crate::scheduler::Scheduler>();
|
||||
Runner{
|
||||
lua:self.lua,
|
||||
}
|
||||
}
|
||||
pub fn run_script(&self,script:super::instance::Instance)->Result<(),Error>{
|
||||
let (name,source)=super::instance::instance::get_name_source(&self.lua,script).map_err(Error::RustLua)?;
|
||||
self.lua.globals().raw_set("script",script).map_err(Error::RustLua)?;
|
||||
let f=self.lua.load(source.as_str())
|
||||
.set_name(name).into_function().map_err(Error::RustLua)?;
|
||||
// TODO: set_environment without losing the ability to print from Lua
|
||||
let thread=self.lua.create_thread(f).map_err(Error::RustLua)?;
|
||||
thread.resume::<mlua::MultiValue>(()).map_err(|error|Error::Lua{source,error})?;
|
||||
// wait() is called from inside Lua and goes to a rust function that schedules the thread and then yields
|
||||
// No need to schedule the thread here
|
||||
Ok(())
|
||||
}
|
||||
#[cfg(feature="run-service")]
|
||||
pub fn has_scheduled_threads(&self)->Result<bool,mlua::Error>{
|
||||
scheduler_mut(&self.lua,|scheduler|
|
||||
Ok(scheduler.has_scheduled_threads())
|
||||
)
|
||||
}
|
||||
#[cfg(feature="run-service")]
|
||||
pub fn game_tick(&self)->Result<(),mlua::Error>{
|
||||
if let Some(threads)=scheduler_mut(&self.lua,|scheduler|Ok(scheduler.tick_threads()))?{
|
||||
for thread in threads{
|
||||
//TODO: return dt and total run time
|
||||
let result=thread.resume::<mlua::MultiValue>((1.0/30.0,0.0))
|
||||
.map_err(|error|Error::Lua{source:"source unavailable".to_owned(),error});
|
||||
match result{
|
||||
Ok(_)=>(),
|
||||
Err(e)=>println!("game_tick Error: {e}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
#[cfg(feature="run-service")]
|
||||
pub fn run_service_step(&self)->Result<(),mlua::Error>{
|
||||
let render_stepped=super::instance::instance::dom_mut(&self.lua,|dom|{
|
||||
let run_service=super::instance::instance::find_first_child_of_class(dom,dom.root(),"RunService").ok_or_else(||mlua::Error::runtime("RunService missing"))?;
|
||||
super::instance::instance::instance_value_store_mut(&self.lua,|instance_value_store|{
|
||||
//unwrap because I trust my find_first_child_of_class function to
|
||||
let mut instance_values=instance_value_store.get_or_create_instance_values(run_service).ok_or_else(||mlua::Error::runtime("RunService InstanceValues missing"))?;
|
||||
let render_stepped=instance_values.get_or_create_value(&self.lua,"RenderStepped")?;
|
||||
//let stepped=instance_values.get_or_create_value(&self.lua,"Stepped")?;
|
||||
//let heartbeat=instance_values.get_or_create_value(&self.lua,"Heartbeat")?;
|
||||
Ok(render_stepped)
|
||||
})
|
||||
})?;
|
||||
if let Some(render_stepped)=render_stepped{
|
||||
let signal:&super::script_signal::ScriptSignal=&*render_stepped.borrow()?;
|
||||
signal.fire(&mlua::MultiValue::new());
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
@ -1,189 +1,172 @@
|
||||
use std::{cell::RefCell, rc::Rc};
|
||||
use std::{cell::RefCell,rc::Rc};
|
||||
|
||||
use mlua::UserDataFields;
|
||||
|
||||
#[derive(Clone)]
|
||||
struct FunctionList {
|
||||
functions: Vec<mlua::Function>,
|
||||
struct FunctionList{
|
||||
functions:Vec<mlua::Function>,
|
||||
}
|
||||
impl FunctionList {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
functions: Vec::new(),
|
||||
}
|
||||
}
|
||||
// This eats the Lua error
|
||||
pub fn fire(self, args: &mlua::MultiValue) {
|
||||
// Make a copy of the list in case Lua attempts to modify it during the loop
|
||||
for function in self.functions {
|
||||
//wee let's allocate for our function calls
|
||||
if let Err(e) = function.call::<mlua::MultiValue>(args.clone()) {
|
||||
println!("Script Signal Error: {e}");
|
||||
}
|
||||
}
|
||||
}
|
||||
impl FunctionList{
|
||||
pub fn new()->Self{
|
||||
Self{
|
||||
functions:Vec::new(),
|
||||
}
|
||||
}
|
||||
// This eats the Lua error
|
||||
pub fn fire(self,args:&mlua::MultiValue){
|
||||
// Make a copy of the list in case Lua attempts to modify it during the loop
|
||||
for function in self.functions{
|
||||
//wee let's allocate for our function calls
|
||||
if let Err(e)=function.call::<mlua::MultiValue>(args.clone()){
|
||||
println!("Script Signal Error: {e}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#[derive(Clone)]
|
||||
struct RcFunctionList {
|
||||
functions: Rc<RefCell<FunctionList>>,
|
||||
struct RcFunctionList{
|
||||
functions:Rc<RefCell<FunctionList>>,
|
||||
}
|
||||
impl RcFunctionList {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
functions: Rc::new(RefCell::new(FunctionList::new())),
|
||||
}
|
||||
}
|
||||
pub fn fire(&self, args: &mlua::MultiValue) {
|
||||
// Make a copy of the list in case Lua attempts to modify it during the loop
|
||||
self.functions.borrow().clone().fire(args)
|
||||
}
|
||||
impl RcFunctionList{
|
||||
pub fn new()->Self{
|
||||
Self{
|
||||
functions:Rc::new(RefCell::new(FunctionList::new())),
|
||||
}
|
||||
}
|
||||
pub fn fire(&self,args:&mlua::MultiValue){
|
||||
// Make a copy of the list in case Lua attempts to modify it during the loop
|
||||
self.functions.borrow().clone().fire(args)
|
||||
}
|
||||
}
|
||||
#[derive(Clone)]
|
||||
pub struct ScriptSignal {
|
||||
// Emulate the garbage roblox api.
|
||||
// ScriptConnection should not exist.
|
||||
// :Disconnect should be a method on ScriptSignal, and this would be avoided entirely.
|
||||
connections: RcFunctionList,
|
||||
once: RcFunctionList,
|
||||
wait: Rc<RefCell<Vec<mlua::Thread>>>,
|
||||
pub struct ScriptSignal{
|
||||
// Emulate the garbage roblox api.
|
||||
// ScriptConnection should not exist.
|
||||
// :Disconnect should be a method on ScriptSignal, and this would be avoided entirely.
|
||||
connections:RcFunctionList,
|
||||
once:RcFunctionList,
|
||||
wait:Rc<RefCell<Vec<mlua::Thread>>>,
|
||||
}
|
||||
pub struct ScriptConnection {
|
||||
connection: RcFunctionList,
|
||||
function: mlua::Function,
|
||||
pub struct ScriptConnection{
|
||||
connection:RcFunctionList,
|
||||
function:mlua::Function,
|
||||
}
|
||||
impl ScriptSignal {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
connections: RcFunctionList::new(),
|
||||
once: RcFunctionList::new(),
|
||||
wait: Rc::new(RefCell::new(Vec::new())),
|
||||
}
|
||||
}
|
||||
pub fn fire(&self, args: &mlua::MultiValue) {
|
||||
self.connections.fire(args);
|
||||
//Replace the FunctionList with an empty one and drop the borrow
|
||||
let once = std::mem::replace(&mut *self.once.functions.borrow_mut(), FunctionList::new());
|
||||
once.fire(args);
|
||||
//resume threads waiting for this signal
|
||||
let threads = std::mem::replace(&mut *self.wait.borrow_mut(), Vec::new());
|
||||
for thread in threads {
|
||||
if let Err(e) = thread.resume::<mlua::MultiValue>(args.clone()) {
|
||||
println!("Script Signal thread resume Error: {e}");
|
||||
}
|
||||
}
|
||||
}
|
||||
pub fn connect(&self, function: mlua::Function) -> ScriptConnection {
|
||||
self.connections
|
||||
.functions
|
||||
.borrow_mut()
|
||||
.functions
|
||||
.push(function.clone());
|
||||
ScriptConnection {
|
||||
connection: self.connections.clone(),
|
||||
function,
|
||||
}
|
||||
}
|
||||
pub fn once(&self, function: mlua::Function) -> ScriptConnection {
|
||||
self.once
|
||||
.functions
|
||||
.borrow_mut()
|
||||
.functions
|
||||
.push(function.clone());
|
||||
ScriptConnection {
|
||||
connection: self.once.clone(),
|
||||
function,
|
||||
}
|
||||
}
|
||||
pub fn wait(&self, thread: mlua::Thread) {
|
||||
self.wait.borrow_mut().push(thread);
|
||||
}
|
||||
impl ScriptSignal{
|
||||
pub fn new()->Self{
|
||||
Self{
|
||||
connections:RcFunctionList::new(),
|
||||
once:RcFunctionList::new(),
|
||||
wait:Rc::new(RefCell::new(Vec::new())),
|
||||
}
|
||||
}
|
||||
pub fn fire(&self,args:&mlua::MultiValue){
|
||||
self.connections.fire(args);
|
||||
//Replace the FunctionList with an empty one and drop the borrow
|
||||
let once=std::mem::replace(&mut *self.once.functions.borrow_mut(),FunctionList::new());
|
||||
once.fire(args);
|
||||
//resume threads waiting for this signal
|
||||
let threads=std::mem::replace(&mut *self.wait.borrow_mut(),Vec::new());
|
||||
for thread in threads{
|
||||
if let Err(e)=thread.resume::<mlua::MultiValue>(args.clone()){
|
||||
println!("Script Signal thread resume Error: {e}");
|
||||
}
|
||||
}
|
||||
}
|
||||
pub fn connect(&self,function:mlua::Function)->ScriptConnection{
|
||||
self.connections.functions.borrow_mut().functions.push(function.clone());
|
||||
ScriptConnection{
|
||||
connection:self.connections.clone(),
|
||||
function,
|
||||
}
|
||||
}
|
||||
pub fn once(&self,function:mlua::Function)->ScriptConnection{
|
||||
self.once.functions.borrow_mut().functions.push(function.clone());
|
||||
ScriptConnection{
|
||||
connection:self.once.clone(),
|
||||
function,
|
||||
}
|
||||
}
|
||||
pub fn wait(&self,thread:mlua::Thread){
|
||||
self.wait.borrow_mut().push(thread);
|
||||
}
|
||||
}
|
||||
impl ScriptConnection {
|
||||
pub fn position(&self) -> Option<usize> {
|
||||
self.connection
|
||||
.functions
|
||||
.borrow()
|
||||
.functions
|
||||
.iter()
|
||||
.position(|function| function == &self.function)
|
||||
}
|
||||
impl ScriptConnection{
|
||||
pub fn position(&self)->Option<usize>{
|
||||
self.connection.functions.borrow().functions.iter().position(|function|function==&self.function)
|
||||
}
|
||||
}
|
||||
|
||||
impl mlua::UserData for ScriptSignal {
|
||||
fn add_methods<M: mlua::UserDataMethods<Self>>(methods: &mut M) {
|
||||
methods.add_method("Connect", |_lua, this, f: mlua::Function| {
|
||||
Ok(this.connect(f))
|
||||
});
|
||||
methods.add_method("Once", |_lua, this, f: mlua::Function| Ok(this.once(f)));
|
||||
// Fire is not allowed to be called from Lua
|
||||
// methods.add_method("Fire",|_lua,this,args:mlua::MultiValue|
|
||||
// Ok(this.fire(args))
|
||||
// );
|
||||
}
|
||||
impl mlua::UserData for ScriptSignal{
|
||||
fn add_methods<M:mlua::UserDataMethods<Self>>(methods:&mut M){
|
||||
methods.add_method("Connect",|_lua,this,f:mlua::Function|
|
||||
Ok(this.connect(f))
|
||||
);
|
||||
methods.add_method("Once",|_lua,this,f:mlua::Function|
|
||||
Ok(this.once(f))
|
||||
);
|
||||
// Fire is not allowed to be called from Lua
|
||||
// methods.add_method("Fire",|_lua,this,args:mlua::MultiValue|
|
||||
// Ok(this.fire(args))
|
||||
// );
|
||||
}
|
||||
}
|
||||
impl mlua::FromLua for ScriptSignal {
|
||||
fn from_lua(value: mlua::Value, _lua: &mlua::Lua) -> Result<Self, mlua::Error> {
|
||||
match value {
|
||||
mlua::Value::UserData(ud) => Ok(ud.borrow::<Self>()?.clone()),
|
||||
other => Err(mlua::Error::runtime(format!(
|
||||
"Expected {} got {:?}",
|
||||
stringify!(ScriptSignal),
|
||||
other
|
||||
))),
|
||||
}
|
||||
}
|
||||
impl mlua::FromLua for ScriptSignal{
|
||||
fn from_lua(value:mlua::Value,_lua:&mlua::Lua)->Result<Self,mlua::Error>{
|
||||
match value{
|
||||
mlua::Value::UserData(ud)=>Ok(ud.borrow::<Self>()?.clone()),
|
||||
other=>Err(mlua::Error::runtime(format!("Expected {} got {:?}",stringify!(ScriptSignal),other))),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl mlua::UserData for ScriptConnection {
|
||||
fn add_fields<F: mlua::UserDataFields<Self>>(fields: &mut F) {
|
||||
fields.add_field_method_get("Connected", |_, this| Ok(this.position().is_some()));
|
||||
}
|
||||
fn add_methods<M: mlua::UserDataMethods<Self>>(methods: &mut M) {
|
||||
methods.add_method("Disconnect", |_, this, _: ()| {
|
||||
if let Some(index) = this.position() {
|
||||
this.connection
|
||||
.functions
|
||||
.borrow_mut()
|
||||
.functions
|
||||
.remove(index);
|
||||
}
|
||||
Ok(())
|
||||
});
|
||||
}
|
||||
impl mlua::UserData for ScriptConnection{
|
||||
fn add_fields<F:mlua::UserDataFields<Self>>(fields:&mut F){
|
||||
fields.add_field_method_get("Connected",|_,this|{
|
||||
Ok(this.position().is_some())
|
||||
});
|
||||
}
|
||||
fn add_methods<M:mlua::UserDataMethods<Self>>(methods:&mut M){
|
||||
methods.add_method("Disconnect",|_,this,_:()|{
|
||||
if let Some(index)=this.position(){
|
||||
this.connection.functions.borrow_mut().functions.remove(index);
|
||||
}
|
||||
Ok(())
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
fn wait_thread(lua: &mlua::Lua, this: ScriptSignal) -> Result<(), mlua::Error> {
|
||||
Ok(this.wait(lua.current_thread()))
|
||||
fn wait_thread(lua:&mlua::Lua,this:ScriptSignal)->Result<(),mlua::Error>{
|
||||
Ok(this.wait(lua.current_thread()))
|
||||
}
|
||||
|
||||
// This is used to avoid calling coroutine.yield from the rust side.
|
||||
const LUA_WAIT: &str = "local coroutine_yield=coroutine.yield
|
||||
const LUA_WAIT:&str=
|
||||
"local coroutine_yield=coroutine.yield
|
||||
local wait_thread=wait_thread
|
||||
return function(signal)
|
||||
wait_thread(signal)
|
||||
return coroutine_yield()
|
||||
end";
|
||||
|
||||
pub fn set_globals(lua: &mlua::Lua, globals: &mlua::Table) -> Result<(), mlua::Error> {
|
||||
let coroutine_table = globals.get::<mlua::Table>("coroutine")?;
|
||||
let wait_thread = lua.create_function(wait_thread)?;
|
||||
pub fn set_globals(lua:&mlua::Lua,globals:&mlua::Table)->Result<(),mlua::Error>{
|
||||
let coroutine_table=globals.get::<mlua::Table>("coroutine")?;
|
||||
let wait_thread=lua.create_function(wait_thread)?;
|
||||
|
||||
//create wait function environment
|
||||
let wait_env = lua.create_table()?;
|
||||
wait_env.raw_set("coroutine", coroutine_table)?;
|
||||
wait_env.raw_set("wait_thread", wait_thread)?;
|
||||
//create wait function environment
|
||||
let wait_env=lua.create_table()?;
|
||||
wait_env.raw_set("coroutine",coroutine_table)?;
|
||||
wait_env.raw_set("wait_thread",wait_thread)?;
|
||||
|
||||
//construct wait function from Lua code
|
||||
let wait = lua
|
||||
.load(LUA_WAIT)
|
||||
.set_name("wait")
|
||||
.set_environment(wait_env)
|
||||
.call::<mlua::Function>(())?;
|
||||
//construct wait function from Lua code
|
||||
let wait=lua.load(LUA_WAIT)
|
||||
.set_name("wait")
|
||||
.set_environment(wait_env)
|
||||
.call::<mlua::Function>(())?;
|
||||
|
||||
lua.register_userdata_type::<ScriptSignal>(|reg| {
|
||||
reg.add_field("Wait", wait);
|
||||
mlua::UserData::register(reg);
|
||||
})?;
|
||||
lua.register_userdata_type::<ScriptSignal>(|reg|{
|
||||
reg.add_field("Wait",wait);
|
||||
mlua::UserData::register(reg);
|
||||
})?;
|
||||
|
||||
Ok(())
|
||||
Ok(())
|
||||
}
|
||||
|
@ -1,85 +1,82 @@
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct Vector3(pub(crate) glam::Vec3A);
|
||||
#[derive(Clone,Copy)]
|
||||
pub struct Vector3(pub(crate)glam::Vec3A);
|
||||
|
||||
impl Vector3 {
|
||||
pub const fn new(x: f32, y: f32, z: f32) -> Self {
|
||||
Self(glam::vec3a(x, y, z))
|
||||
}
|
||||
impl Vector3{
|
||||
pub const fn new(x:f32,y:f32,z:f32)->Self{
|
||||
Self(glam::vec3a(x,y,z))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_globals(lua: &mlua::Lua, globals: &mlua::Table) -> Result<(), mlua::Error> {
|
||||
let vector3_table = lua.create_table()?;
|
||||
pub fn set_globals(lua:&mlua::Lua,globals:&mlua::Table)->Result<(),mlua::Error>{
|
||||
let vector3_table=lua.create_table()?;
|
||||
|
||||
//Vector3.new
|
||||
vector3_table.raw_set(
|
||||
"new",
|
||||
lua.create_function(|_, (x, y, z): (f32, f32, f32)| Ok(Vector3::new(x, y, z)))?,
|
||||
)?;
|
||||
//Vector3.new
|
||||
vector3_table.raw_set("new",
|
||||
lua.create_function(|_,(x,y,z):(f32,f32,f32)|
|
||||
Ok(Vector3::new(x,y,z))
|
||||
)?
|
||||
)?;
|
||||
|
||||
globals.set("Vector3", vector3_table)?;
|
||||
globals.set("Vector3",vector3_table)?;
|
||||
|
||||
Ok(())
|
||||
Ok(())
|
||||
}
|
||||
|
||||
impl Into<rbx_types::Vector3> for Vector3 {
|
||||
fn into(self) -> rbx_types::Vector3 {
|
||||
rbx_types::Vector3::new(self.0.x, self.0.y, self.0.z)
|
||||
}
|
||||
impl Into<rbx_types::Vector3> for Vector3{
|
||||
fn into(self)->rbx_types::Vector3{
|
||||
rbx_types::Vector3::new(self.0.x,self.0.y,self.0.z)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<rbx_types::Vector3> for Vector3 {
|
||||
fn from(value: rbx_types::Vector3) -> Vector3 {
|
||||
Vector3::new(value.x, value.y, value.z)
|
||||
}
|
||||
impl From<rbx_types::Vector3> for Vector3{
|
||||
fn from(value:rbx_types::Vector3)->Vector3{
|
||||
Vector3::new(value.x,value.y,value.z)
|
||||
}
|
||||
}
|
||||
|
||||
impl mlua::UserData for Vector3 {
|
||||
fn add_fields<F: mlua::UserDataFields<Self>>(fields: &mut F) {
|
||||
fields.add_field_method_get("magnitude", |_, this| Ok(this.0.length()));
|
||||
fields.add_field_method_get("x", |_, this| Ok(this.0.x));
|
||||
fields.add_field_method_set("x", |_, this, val| {
|
||||
this.0.x = val;
|
||||
Ok(())
|
||||
});
|
||||
fields.add_field_method_get("y", |_, this| Ok(this.0.y));
|
||||
fields.add_field_method_set("y", |_, this, val| {
|
||||
this.0.y = val;
|
||||
Ok(())
|
||||
});
|
||||
fields.add_field_method_get("z", |_, this| Ok(this.0.z));
|
||||
fields.add_field_method_set("z", |_, this, val| {
|
||||
this.0.z = val;
|
||||
Ok(())
|
||||
});
|
||||
}
|
||||
impl mlua::UserData for Vector3{
|
||||
fn add_fields<F:mlua::UserDataFields<Self>>(fields:&mut F){
|
||||
fields.add_field_method_get("magnitude",|_,this|Ok(this.0.length()));
|
||||
fields.add_field_method_get("x",|_,this|Ok(this.0.x));
|
||||
fields.add_field_method_set("x",|_,this,val|{
|
||||
this.0.x=val;
|
||||
Ok(())
|
||||
});
|
||||
fields.add_field_method_get("y",|_,this|Ok(this.0.y));
|
||||
fields.add_field_method_set("y",|_,this,val|{
|
||||
this.0.y=val;
|
||||
Ok(())
|
||||
});
|
||||
fields.add_field_method_get("z",|_,this|Ok(this.0.z));
|
||||
fields.add_field_method_set("z",|_,this,val|{
|
||||
this.0.z=val;
|
||||
Ok(())
|
||||
});
|
||||
}
|
||||
|
||||
fn add_methods<M: mlua::UserDataMethods<Self>>(methods: &mut M) {
|
||||
//methods.add_method("area",|_,this,()| Ok(this.length * this.width));
|
||||
fn add_methods<M:mlua::UserDataMethods<Self>>(methods:&mut M){
|
||||
//methods.add_method("area",|_,this,()| Ok(this.length * this.width));
|
||||
|
||||
methods.add_meta_function(mlua::MetaMethod::Add, |_, (this, val): (Self, Self)| {
|
||||
Ok(Self(this.0 + val.0))
|
||||
});
|
||||
methods.add_meta_function(
|
||||
mlua::MetaMethod::Div,
|
||||
|_, (this, val): (Self, mlua::Value)| match val {
|
||||
mlua::Value::Integer(n) => Ok(Self(this.0 / (n as f32))),
|
||||
mlua::Value::Number(n) => Ok(Self(this.0 / (n as f32))),
|
||||
mlua::Value::UserData(ud) => {
|
||||
let rhs: Vector3 = ud.take()?;
|
||||
Ok(Self(this.0 / rhs.0))
|
||||
}
|
||||
other => Err(mlua::Error::runtime(format!(
|
||||
"Attempt to divide Vector3 by {other:?}"
|
||||
))),
|
||||
},
|
||||
);
|
||||
methods.add_meta_function(mlua::MetaMethod::ToString, |_, this: Self| {
|
||||
Ok(format!(
|
||||
"Vector3.new({},{},{})",
|
||||
this.0.x, this.0.y, this.0.z,
|
||||
))
|
||||
});
|
||||
}
|
||||
methods.add_meta_function(mlua::MetaMethod::Add,|_,(this,val):(Self,Self)|Ok(Self(this.0+val.0)));
|
||||
methods.add_meta_function(mlua::MetaMethod::Div,|_,(this,val):(Self,mlua::Value)|{
|
||||
match val{
|
||||
mlua::Value::Integer(n)=>Ok(Self(this.0/(n as f32))),
|
||||
mlua::Value::Number(n)=>Ok(Self(this.0/(n as f32))),
|
||||
mlua::Value::UserData(ud)=>{
|
||||
let rhs:Vector3=ud.take()?;
|
||||
Ok(Self(this.0/rhs.0))
|
||||
},
|
||||
other=>Err(mlua::Error::runtime(format!("Attempt to divide Vector3 by {other:?}"))),
|
||||
}
|
||||
});
|
||||
methods.add_meta_function(mlua::MetaMethod::ToString,|_,this:Self|
|
||||
Ok(format!("Vector3.new({},{},{})",
|
||||
this.0.x,
|
||||
this.0.y,
|
||||
this.0.z,
|
||||
))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
type_from_lua_userdata!(Vector3);
|
||||
|
@ -1,114 +1,106 @@
|
||||
pub use tick::Tick;
|
||||
mod tick {
|
||||
#[derive(Clone, Copy, Default, Hash, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub struct Tick(u64);
|
||||
impl std::ops::Add<u64> for Tick {
|
||||
type Output = Self;
|
||||
fn add(self, rhs: u64) -> Self::Output {
|
||||
Self(self.0 + rhs)
|
||||
}
|
||||
}
|
||||
impl std::ops::Sub<u64> for Tick {
|
||||
type Output = Self;
|
||||
fn sub(self, rhs: u64) -> Self::Output {
|
||||
Self(self.0 - rhs)
|
||||
}
|
||||
}
|
||||
impl std::ops::AddAssign<u64> for Tick {
|
||||
fn add_assign(&mut self, rhs: u64) {
|
||||
self.0 += rhs;
|
||||
}
|
||||
}
|
||||
impl std::ops::SubAssign<u64> for Tick {
|
||||
fn sub_assign(&mut self, rhs: u64) {
|
||||
self.0 -= rhs;
|
||||
}
|
||||
}
|
||||
mod tick{
|
||||
#[derive(Clone,Copy,Default,Hash,PartialEq,Eq,PartialOrd,Ord)]
|
||||
pub struct Tick(u64);
|
||||
impl std::ops::Add<u64> for Tick{
|
||||
type Output=Self;
|
||||
fn add(self,rhs:u64)->Self::Output{
|
||||
Self(self.0+rhs)
|
||||
}
|
||||
}
|
||||
impl std::ops::Sub<u64> for Tick{
|
||||
type Output=Self;
|
||||
fn sub(self,rhs:u64)->Self::Output{
|
||||
Self(self.0-rhs)
|
||||
}
|
||||
}
|
||||
impl std::ops::AddAssign<u64> for Tick{
|
||||
fn add_assign(&mut self,rhs:u64){
|
||||
self.0+=rhs;
|
||||
}
|
||||
}
|
||||
impl std::ops::SubAssign<u64> for Tick{
|
||||
fn sub_assign(&mut self,rhs:u64){
|
||||
self.0-=rhs;
|
||||
}
|
||||
}
|
||||
}
|
||||
#[derive(Default)]
|
||||
pub struct Scheduler {
|
||||
tick: Tick,
|
||||
schedule: std::collections::HashMap<Tick, Vec<mlua::Thread>>,
|
||||
pub struct Scheduler{
|
||||
tick:Tick,
|
||||
schedule:std::collections::HashMap<Tick,Vec<mlua::Thread>>,
|
||||
}
|
||||
|
||||
impl Scheduler {
|
||||
pub fn has_scheduled_threads(&self) -> bool {
|
||||
!self.schedule.is_empty()
|
||||
}
|
||||
pub fn schedule_thread(&mut self, delay: u64, thread: mlua::Thread) {
|
||||
self.schedule
|
||||
.entry(self.tick + delay.max(1))
|
||||
.or_insert(Vec::new())
|
||||
.push(thread);
|
||||
}
|
||||
pub fn tick_threads(&mut self) -> Option<Vec<mlua::Thread>> {
|
||||
self.tick += 1;
|
||||
self.schedule.remove(&self.tick)
|
||||
}
|
||||
impl Scheduler{
|
||||
pub fn has_scheduled_threads(&self)->bool{
|
||||
!self.schedule.is_empty()
|
||||
}
|
||||
pub fn schedule_thread(&mut self,delay:u64,thread:mlua::Thread){
|
||||
self.schedule.entry(self.tick+delay.max(1))
|
||||
.or_insert(Vec::new())
|
||||
.push(thread);
|
||||
}
|
||||
pub fn tick_threads(&mut self)->Option<Vec<mlua::Thread>>{
|
||||
self.tick+=1;
|
||||
self.schedule.remove(&self.tick)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn scheduler_mut<T>(
|
||||
lua: &mlua::Lua,
|
||||
mut f: impl FnMut(&mut crate::scheduler::Scheduler) -> mlua::Result<T>,
|
||||
) -> mlua::Result<T> {
|
||||
let mut scheduler = lua
|
||||
.app_data_mut::<crate::scheduler::Scheduler>()
|
||||
.ok_or_else(|| mlua::Error::runtime("Scheduler missing"))?;
|
||||
f(&mut *scheduler)
|
||||
pub fn scheduler_mut<T>(lua:&mlua::Lua,mut f:impl FnMut(&mut crate::scheduler::Scheduler)->mlua::Result<T>)->mlua::Result<T>{
|
||||
let mut scheduler=lua.app_data_mut::<crate::scheduler::Scheduler>().ok_or_else(||mlua::Error::runtime("Scheduler missing"))?;
|
||||
f(&mut *scheduler)
|
||||
}
|
||||
|
||||
fn schedule_thread(lua: &mlua::Lua, dt: mlua::Value) -> Result<(), mlua::Error> {
|
||||
let delay = match dt {
|
||||
mlua::Value::Integer(i) => i.max(0) as u64 * 60,
|
||||
mlua::Value::Number(f) => {
|
||||
let delay = f.max(0.0) * 60.0;
|
||||
match delay.classify() {
|
||||
std::num::FpCategory::Nan => Err(mlua::Error::runtime("NaN"))?,
|
||||
// cases where the number is too large to schedule
|
||||
std::num::FpCategory::Infinite => return Ok(()),
|
||||
std::num::FpCategory::Normal => {
|
||||
if (u64::MAX as f64) < delay {
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
delay as u64
|
||||
}
|
||||
mlua::Value::Nil => 0,
|
||||
_ => Err(mlua::Error::runtime("Expected float"))?,
|
||||
};
|
||||
scheduler_mut(lua, |scheduler| {
|
||||
scheduler.schedule_thread(delay.max(2), lua.current_thread());
|
||||
Ok(())
|
||||
})
|
||||
fn schedule_thread(lua:&mlua::Lua,dt:mlua::Value)->Result<(),mlua::Error>{
|
||||
let delay=match dt{
|
||||
mlua::Value::Integer(i)=>i.max(0) as u64*60,
|
||||
mlua::Value::Number(f)=>{
|
||||
let delay=f.max(0.0)*60.0;
|
||||
match delay.classify(){
|
||||
std::num::FpCategory::Nan=>Err(mlua::Error::runtime("NaN"))?,
|
||||
// cases where the number is too large to schedule
|
||||
std::num::FpCategory::Infinite
|
||||
|std::num::FpCategory::Normal if (u64::MAX as f64)<delay=>{
|
||||
return Ok(());
|
||||
},
|
||||
_=>(),
|
||||
}
|
||||
delay as u64
|
||||
},
|
||||
mlua::Value::Nil=>0,
|
||||
_=>Err(mlua::Error::runtime("Expected float"))?,
|
||||
};
|
||||
scheduler_mut(lua,|scheduler|{
|
||||
scheduler.schedule_thread(delay.max(2),lua.current_thread());
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
// This is used to avoid calling coroutine.yield from the rust side.
|
||||
const LUA_WAIT: &str = "local coroutine_yield=coroutine.yield
|
||||
const LUA_WAIT:&str=
|
||||
"local coroutine_yield=coroutine.yield
|
||||
local schedule_thread=schedule_thread
|
||||
return function(dt)
|
||||
schedule_thread(dt)
|
||||
return coroutine_yield()
|
||||
end";
|
||||
|
||||
pub fn set_globals(lua: &mlua::Lua, globals: &mlua::Table) -> Result<(), mlua::Error> {
|
||||
let coroutine_table = globals.get::<mlua::Table>("coroutine")?;
|
||||
let schedule_thread = lua.create_function(schedule_thread)?;
|
||||
pub fn set_globals(lua:&mlua::Lua,globals:&mlua::Table)->Result<(),mlua::Error>{
|
||||
let coroutine_table=globals.get::<mlua::Table>("coroutine")?;
|
||||
let schedule_thread=lua.create_function(schedule_thread)?;
|
||||
|
||||
//create wait function environment
|
||||
let wait_env = lua.create_table()?;
|
||||
wait_env.raw_set("coroutine", coroutine_table)?;
|
||||
wait_env.raw_set("schedule_thread", schedule_thread)?;
|
||||
//create wait function environment
|
||||
let wait_env=lua.create_table()?;
|
||||
wait_env.raw_set("coroutine",coroutine_table)?;
|
||||
wait_env.raw_set("schedule_thread",schedule_thread)?;
|
||||
|
||||
//construct wait function from Lua code
|
||||
let wait = lua
|
||||
.load(LUA_WAIT)
|
||||
.set_name("wait")
|
||||
.set_environment(wait_env)
|
||||
.call::<mlua::Function>(())?;
|
||||
//construct wait function from Lua code
|
||||
let wait=lua.load(LUA_WAIT)
|
||||
.set_name("wait")
|
||||
.set_environment(wait_env)
|
||||
.call::<mlua::Function>(())?;
|
||||
|
||||
globals.raw_set("wait", wait)?;
|
||||
globals.raw_set("wait",wait)?;
|
||||
|
||||
Ok(())
|
||||
Ok(())
|
||||
}
|
||||
|
@ -1 +0,0 @@
|
||||
|
@ -1,101 +1,347 @@
|
||||
use binrw::{binrw, BinReaderExt};
|
||||
use binrw::{binrw,BinReaderExt,BinWrite,BinWriterExt};
|
||||
|
||||
use crate::newtypes;
|
||||
use crate::file::BlockId;
|
||||
use strafesnet_common::physics::Time;
|
||||
|
||||
const VERSION:u32=0;
|
||||
|
||||
type TimedPhysicsInstruction=strafesnet_common::instruction::TimedInstruction<strafesnet_common::physics::Instruction,strafesnet_common::physics::TimeInner>;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
InvalidHeader,
|
||||
InvalidSegment(binrw::Error),
|
||||
InvalidSegmentId(SegmentId),
|
||||
File(crate::file::Error),
|
||||
pub enum Error{
|
||||
InvalidHeader(binrw::Error),
|
||||
InvalidSegment(binrw::Error),
|
||||
SegmentConvert(newtypes::integer::RatioError),
|
||||
InstructionConvert(newtypes::physics::InstructionConvert),
|
||||
InstructionWrite(binrw::Error),
|
||||
InvalidSegmentId(SegmentId),
|
||||
InvalidData(binrw::Error),
|
||||
IO(std::io::Error),
|
||||
File(crate::file::Error),
|
||||
}
|
||||
|
||||
// Bot files are simply the sequence of instructions that the physics received during the run.
|
||||
// The instructions are partitioned into timestamped blocks for ease of streaming.
|
||||
//
|
||||
// Keyframe information required for efficient seeking
|
||||
// is part of a different file, and is generated from this file.
|
||||
|
||||
/* block types
|
||||
|
||||
BLOCK_BOT_HEADER:
|
||||
u128 map_resource_uuid //which map is this bot running
|
||||
//don't include style info in bot header because it's in the simulation state
|
||||
//blocks are laid out in chronological order, but indices may jump around.
|
||||
u64 num_segments
|
||||
// Segments are laid out in chronological order,
|
||||
// but block_id is not necessarily in ascending order.
|
||||
//
|
||||
// This is to place the final segment close to the start of the file,
|
||||
// which allows the duration of the bot to be conveniently calculated
|
||||
// from the first and last instruction timestamps.
|
||||
//
|
||||
// Use exact physics version for replay playback
|
||||
// Use highest compatible physics version for verification
|
||||
u32 physics_version
|
||||
u32 num_segments
|
||||
for _ in 0..num_segments{
|
||||
i64 time //simulation_state timestamp
|
||||
u64 block_id
|
||||
i64 time
|
||||
u32 instruction_count
|
||||
u32 block_id
|
||||
}
|
||||
|
||||
BLOCK_BOT_SEGMENT:
|
||||
//format version indicates what version of these structures to use
|
||||
SimulationState simulation_state //SimulationState is just under ClientState which includes Play/Pause events that the simulation doesn't know about.
|
||||
//to read, greedily decode instructions until eof
|
||||
loop{
|
||||
//delta encode as much as possible (time,mousepos)
|
||||
//strafe ticks are implied
|
||||
//physics can be implied in an input-only bot file
|
||||
TimedInstruction<SimulationInstruction> instruction
|
||||
// segments can potentially be losslessly compressed!
|
||||
for _ in 0..instruction_count{
|
||||
// TODO: delta encode as much as possible (time,mousepos)
|
||||
i64 time
|
||||
physics::Instruction instruction
|
||||
}
|
||||
|
||||
*/
|
||||
|
||||
//error hiding mock code
|
||||
mod simulation {
|
||||
#[super::binrw]
|
||||
#[brw(little)]
|
||||
pub struct State {}
|
||||
#[super::binrw]
|
||||
#[brw(little)]
|
||||
pub struct Instruction {}
|
||||
#[binrw]
|
||||
#[brw(little)]
|
||||
struct SegmentHeader{
|
||||
time:i64,
|
||||
instruction_count:u32,
|
||||
block_id:BlockId,
|
||||
}
|
||||
#[binrw]
|
||||
#[brw(little)]
|
||||
struct Header{
|
||||
physics_version:u32,
|
||||
num_segments:u32,
|
||||
#[br(count=num_segments)]
|
||||
segments:Vec<SegmentHeader>,
|
||||
}
|
||||
// mod instruction{
|
||||
// #[super::binrw]
|
||||
// #[brw(little)]
|
||||
// pub struct TimedInstruction<Instruction:binrw::BinRead+binrw::BinWrite>{
|
||||
// time:u64,
|
||||
// instruction:Instruction
|
||||
// }
|
||||
// }
|
||||
// mod timeline{
|
||||
// #[super::binrw]
|
||||
// #[brw(little)]
|
||||
// pub struct Timeline<Instruction:binrw::BinRead+binrw::BinWrite>{
|
||||
// #[bw(try_calc(u32::try_from(instructions.len())))]
|
||||
// instruction_count:u32,
|
||||
// #[br(count=instruction_count)]
|
||||
// instructions:Vec<super::instruction::TimedInstruction<Instruction>>
|
||||
// }
|
||||
// }
|
||||
|
||||
//serious code
|
||||
|
||||
#[binrw]
|
||||
#[brw(little)]
|
||||
#[derive(Clone, Copy, Debug, id::Id)]
|
||||
#[derive(Clone,Copy,Debug,id::Id)]
|
||||
pub struct SegmentId(u32);
|
||||
|
||||
#[binrw]
|
||||
#[brw(little)]
|
||||
pub struct Segment {
|
||||
state: simulation::State,
|
||||
//#[bw(try_calc(u32::try_from(instructions.len())))]
|
||||
//instruction_count:u32,
|
||||
//#[br(count=instruction_count)]
|
||||
//instructions:Vec<instruction::TimedInstruction<simulation::Instruction>>
|
||||
|
||||
//please remember that strafe ticks are implicit! 33% smaller bot files
|
||||
pub struct Segment{
|
||||
pub instructions:Vec<TimedPhysicsInstruction>
|
||||
}
|
||||
|
||||
pub struct StreamableBot<R: BinReaderExt> {
|
||||
file: crate::file::File<R>,
|
||||
//timeline:timeline::Timeline<SegmentId>,
|
||||
segment_id_to_block_id: Vec<crate::file::BlockId>,
|
||||
#[derive(Clone,Copy,Debug)]
|
||||
pub struct SegmentInfo{
|
||||
/// time of the first instruction in this segment.
|
||||
time:Time,
|
||||
instruction_count:u32,
|
||||
/// How many total instructions in segments up to and including this segment
|
||||
/// Alternatively, the id of the first instruction be in the _next_ segment
|
||||
instructions_subtotal:u64,
|
||||
block_id:BlockId,
|
||||
}
|
||||
impl<R: BinReaderExt> StreamableBot<R> {
|
||||
pub(crate) fn new(file: crate::file::File<R>) -> Result<Self, Error> {
|
||||
Err(Error::InvalidHeader)
|
||||
}
|
||||
pub fn load_segment(&mut self, segment_id: SegmentId) -> Result<Segment, Error> {
|
||||
let block_id = *self
|
||||
.segment_id_to_block_id
|
||||
.get(segment_id.get() as usize)
|
||||
.ok_or(Error::InvalidSegmentId(segment_id))?;
|
||||
let mut block = self.file.block_reader(block_id).map_err(Error::File)?;
|
||||
let segment = block.read_le().map_err(Error::InvalidSegment)?;
|
||||
Ok(segment)
|
||||
}
|
||||
|
||||
pub struct StreamableBot<R:BinReaderExt>{
|
||||
file:crate::file::File<R>,
|
||||
segment_map:Vec<SegmentInfo>,
|
||||
}
|
||||
impl<R:BinReaderExt> StreamableBot<R>{
|
||||
pub(crate) fn new(mut file:crate::file::File<R>)->Result<Self,Error>{
|
||||
//assume the file seek is in the right place to start reading header
|
||||
let header:Header=file.data_mut().read_le().map_err(Error::InvalidHeader)?;
|
||||
let mut instructions_subtotal=0;
|
||||
let segment_map=header.segments.into_iter().map(|SegmentHeader{time,instruction_count,block_id}|{
|
||||
instructions_subtotal+=instruction_count as u64;
|
||||
SegmentInfo{
|
||||
time:Time::raw(time),
|
||||
instruction_count,
|
||||
instructions_subtotal,
|
||||
block_id,
|
||||
}
|
||||
}).collect();
|
||||
Ok(Self{
|
||||
file,
|
||||
segment_map,
|
||||
})
|
||||
}
|
||||
fn get_segment_info(&self,segment_id:SegmentId)->Result<SegmentInfo,Error>{
|
||||
Ok(*self.segment_map.get(segment_id.get() as usize).ok_or(Error::InvalidSegmentId(segment_id))?)
|
||||
}
|
||||
pub fn find_segments_instruction_range(&self,start_instruction:u64,end_instruction:u64)->&[SegmentInfo]{
|
||||
let start=self.segment_map.partition_point(|segment_info|segment_info.instructions_subtotal<start_instruction);
|
||||
let end=self.segment_map.partition_point(|segment_info|segment_info.instructions_subtotal<end_instruction);
|
||||
&self.segment_map[start..=end]
|
||||
}
|
||||
// pub fn find_segments_time_range(&self,start_time:Time,end_time:Time)->&[SegmentInfo]{
|
||||
// // TODO: This is off by one, both should be one less
|
||||
// let start=self.segment_map.partition_point(|segment_info|segment_info.time<start_time);
|
||||
// let end=self.segment_map.partition_point(|segment_info|segment_info.time<end_time);
|
||||
// &self.segment_map[start..=end]
|
||||
// }
|
||||
fn append_to_segment(&mut self,segment_info:SegmentInfo,segment:&mut Segment)->Result<(),Error>{
|
||||
let mut block=self.file.block_reader(segment_info.block_id).map_err(Error::File)?;
|
||||
for _ in 0..segment_info.instruction_count{
|
||||
let instruction:newtypes::physics::TimedInstruction=block.read_le().map_err(Error::InvalidSegment)?;
|
||||
segment.instructions.push(instruction.try_into().map_err(Error::SegmentConvert)?);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
pub fn load_segment(&mut self,segment_info:SegmentInfo)->Result<Segment,Error>{
|
||||
let mut segment=Segment{
|
||||
instructions:Vec::with_capacity(segment_info.instruction_count as usize),
|
||||
};
|
||||
self.append_to_segment(segment_info,&mut segment)?;
|
||||
Ok(segment)
|
||||
}
|
||||
pub fn read_all(&mut self)->Result<Segment,Error>{
|
||||
let mut segment=Segment{
|
||||
instructions:Vec::new(),
|
||||
};
|
||||
for i in 0..self.segment_map.len(){
|
||||
let segment_info=self.segment_map[i];
|
||||
self.append_to_segment(segment_info,&mut segment)?;
|
||||
}
|
||||
Ok(segment)
|
||||
}
|
||||
}
|
||||
|
||||
const MAX_BLOCK_SIZE:usize=64*1024;//64 kB
|
||||
pub fn write_bot<W:BinWriterExt>(mut writer:W,physics_version:u32,instructions:impl IntoIterator<Item=TimedPhysicsInstruction>)->Result<(),Error>{
|
||||
// decide which instructions to put in which segment
|
||||
// write segment 1 to block 1
|
||||
// write segment N to block 2
|
||||
// write rest of segments
|
||||
// 1 2 3 4 5
|
||||
// becomes
|
||||
// [1 5] 2 3 4
|
||||
struct SegmentHeaderInfo{
|
||||
time:Time,
|
||||
instruction_count:u32,
|
||||
range:core::ops::Range<usize>
|
||||
}
|
||||
|
||||
let mut segment_header_infos=Vec::new();
|
||||
let mut raw_segments=std::io::Cursor::new(Vec::new());
|
||||
|
||||
// block info
|
||||
let mut start_time=Time::ZERO;
|
||||
let mut start_position=raw_segments.position() as usize;
|
||||
let mut instruction_count=0;
|
||||
|
||||
let mut last_position=start_position;
|
||||
|
||||
let mut iter=instructions.into_iter();
|
||||
|
||||
macro_rules! collect_instruction{
|
||||
($instruction:expr)=>{
|
||||
let time=$instruction.time;
|
||||
let instruction_writable:newtypes::physics::TimedInstruction=$instruction.try_into().map_err(Error::InstructionConvert)?;
|
||||
instruction_writable.write_le(&mut raw_segments).map_err(Error::InstructionWrite)?;
|
||||
instruction_count+=1;
|
||||
let position=raw_segments.position() as usize;
|
||||
// exceeds max block size
|
||||
if MAX_BLOCK_SIZE<position-last_position{
|
||||
segment_header_infos.push(SegmentHeaderInfo{
|
||||
time:start_time,
|
||||
instruction_count,
|
||||
range:start_position..last_position,
|
||||
});
|
||||
start_position=last_position;
|
||||
instruction_count=0;
|
||||
start_time=time;
|
||||
}
|
||||
last_position=position;
|
||||
}
|
||||
}
|
||||
|
||||
// unroll one loop iteration to grab the starting time
|
||||
if let Some(instruction)=iter.next(){
|
||||
start_time=instruction.time;
|
||||
collect_instruction!(instruction);
|
||||
}
|
||||
|
||||
for instruction in iter{
|
||||
collect_instruction!(instruction);
|
||||
}
|
||||
//last block, whatever size it happens to be
|
||||
{
|
||||
let final_position=raw_segments.position() as usize;
|
||||
segment_header_infos.push(SegmentHeaderInfo{
|
||||
time:start_time,
|
||||
instruction_count,
|
||||
range:start_position..final_position,
|
||||
});
|
||||
}
|
||||
// drop cursor
|
||||
let raw_segments=raw_segments.into_inner();
|
||||
|
||||
let num_segments=segment_header_infos.len();
|
||||
|
||||
// segments list is in chronological order
|
||||
let make_segment_header=|block_id,&SegmentHeaderInfo{time,instruction_count,range:ref _range}|SegmentHeader{
|
||||
time:time.get(),
|
||||
instruction_count,
|
||||
block_id,
|
||||
};
|
||||
let segments=if 2<num_segments{
|
||||
let mut segments=Vec::with_capacity(num_segments);
|
||||
// segment 1 is second block
|
||||
if let Some(seg)=segment_header_infos.first(){
|
||||
segments.push(make_segment_header(BlockId::new(1),seg));
|
||||
}
|
||||
// rest of segments start at fourth block
|
||||
for (i,seg) in segment_header_infos[1..num_segments-1].iter().enumerate(){
|
||||
make_segment_header(BlockId::new(3+i as u32),seg);
|
||||
}
|
||||
// last segment is third block
|
||||
if let Some(seg)=segment_header_infos.last(){
|
||||
segments.push(make_segment_header(BlockId::new(2),seg));
|
||||
}
|
||||
segments
|
||||
}else{
|
||||
// all segments in order
|
||||
segment_header_infos.iter().enumerate().map(|(i,seg)|
|
||||
make_segment_header(BlockId::new(1+i as u32),seg)
|
||||
).collect()
|
||||
};
|
||||
|
||||
let header=Header{
|
||||
physics_version,
|
||||
num_segments:num_segments as u32,
|
||||
segments,
|
||||
};
|
||||
|
||||
// map header is +1
|
||||
let block_count=1+num_segments as u32;
|
||||
|
||||
let mut offset=crate::file::Header::calculate_size(block_count) as u64;
|
||||
// block_location is one longer than block_count
|
||||
let mut block_location=Vec::with_capacity(1+block_count as usize);
|
||||
|
||||
//probe header length
|
||||
let mut bot_header_data=Vec::new();
|
||||
binrw::BinWrite::write_le(&header,&mut std::io::Cursor::new(&mut bot_header_data)).map_err(Error::InvalidData)?;
|
||||
|
||||
// the first block location is the map header
|
||||
block_location.push(offset);
|
||||
offset+=bot_header_data.len() as u64;
|
||||
block_location.push(offset);
|
||||
|
||||
// priming includes file header + first 3 blocks [bot header, first segment, last segment]
|
||||
let priming=if 2<num_segments{
|
||||
// segment 1 is block 2
|
||||
if let Some(seg)=segment_header_infos.first(){
|
||||
offset+=seg.range.len() as u64;
|
||||
block_location.push(offset);
|
||||
}
|
||||
// last segment is block 3
|
||||
if let Some(seg)=segment_header_infos.last(){
|
||||
offset+=seg.range.len() as u64;
|
||||
block_location.push(offset);
|
||||
}
|
||||
|
||||
let priming=offset;
|
||||
|
||||
// rest of segments
|
||||
for seg in &segment_header_infos[1..num_segments-1]{
|
||||
offset+=seg.range.len() as u64;
|
||||
block_location.push(offset);
|
||||
}
|
||||
priming
|
||||
}else{
|
||||
// all segments in order
|
||||
for seg in &segment_header_infos{
|
||||
offset+=seg.range.len() as u64;
|
||||
block_location.push(offset);
|
||||
}
|
||||
offset
|
||||
};
|
||||
|
||||
let file_header=crate::file::Header{
|
||||
fourcc:crate::file::FourCC::Bot,
|
||||
version:VERSION,
|
||||
priming,
|
||||
resource:0,
|
||||
block_count,
|
||||
block_location,
|
||||
};
|
||||
|
||||
// write file header
|
||||
writer.write_le(&file_header).map_err(Error::InvalidData)?;
|
||||
// write bot header
|
||||
writer.write(&bot_header_data).map_err(Error::IO)?;
|
||||
|
||||
// write blocks
|
||||
if 2<num_segments{
|
||||
// segment 1 is block 2
|
||||
if let Some(seg)=segment_header_infos.first(){
|
||||
writer.write(&raw_segments[seg.range.clone()]).map_err(Error::IO)?;
|
||||
}
|
||||
// last segment is block 3
|
||||
if let Some(seg)=segment_header_infos.last(){
|
||||
writer.write(&raw_segments[seg.range.clone()]).map_err(Error::IO)?;
|
||||
}
|
||||
// rest of segments
|
||||
for seg in &segment_header_infos[1..num_segments-1]{
|
||||
writer.write(&raw_segments[seg.range.clone()]).map_err(Error::IO)?;
|
||||
}
|
||||
}else{
|
||||
// all segments in order
|
||||
for seg in segment_header_infos{
|
||||
writer.write(&raw_segments[seg.range]).map_err(Error::IO)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
@ -1,23 +1,23 @@
|
||||
use binrw::BinReaderExt;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
InvalidHeader,
|
||||
pub enum Error{
|
||||
InvalidHeader,
|
||||
}
|
||||
|
||||
/*
|
||||
BLOCK_DEMO_HEADER:
|
||||
u32 num_maps
|
||||
for map_id in 0..num_maps{
|
||||
i64 simulation_time
|
||||
u128 map_resource_id
|
||||
u64 map_header_block_id
|
||||
i64 simulation_time
|
||||
u128 map_resource_id
|
||||
u64 map_header_block_id
|
||||
}
|
||||
u32 num_bots
|
||||
for bot_id in 0..num_bots{
|
||||
i64 simulation_time
|
||||
u128 bot_resource_id
|
||||
u64 bot_header_block_id
|
||||
i64 simulation_time
|
||||
u128 bot_resource_id
|
||||
u64 bot_header_block_id
|
||||
}
|
||||
|
||||
//map loading timeline
|
||||
@ -28,12 +28,12 @@ how to do worldstate for deathrun!?
|
||||
|
||||
*/
|
||||
|
||||
pub struct StreamableDemo<R: BinReaderExt> {
|
||||
map: Vec<crate::map::StreamableMap<R>>,
|
||||
bots: Vec<crate::bot::StreamableBot<R>>,
|
||||
pub struct StreamableDemo<R:BinReaderExt>{
|
||||
map:Vec<crate::map::StreamableMap<R>>,
|
||||
bots:Vec<crate::bot::StreamableBot<R>>,
|
||||
}
|
||||
impl<R: BinReaderExt> StreamableDemo<R> {
|
||||
pub(crate) fn new(file: crate::file::File<R>) -> Result<Self, Error> {
|
||||
Err(Error::InvalidHeader)
|
||||
}
|
||||
impl<R:BinReaderExt> StreamableDemo<R>{
|
||||
pub(crate) fn new(file:crate::file::File<R>)->Result<Self,Error>{
|
||||
Err(Error::InvalidHeader)
|
||||
}
|
||||
}
|
||||
|
@ -1,20 +1,20 @@
|
||||
//file format "sniff"
|
||||
|
||||
use binrw::{binrw, io::TakeSeekExt, BinReaderExt};
|
||||
use binrw::{binrw,BinReaderExt,io::TakeSeekExt};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
InvalidHeader(binrw::Error),
|
||||
UnexpectedEOF,
|
||||
InvalidBlockId(BlockId),
|
||||
Seek(std::io::Error),
|
||||
pub enum Error{
|
||||
InvalidHeader(binrw::Error),
|
||||
UnexpectedEOF,
|
||||
InvalidBlockId(BlockId),
|
||||
Seek(std::io::Error),
|
||||
}
|
||||
impl std::fmt::Display for Error {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{self:?}")
|
||||
}
|
||||
impl std::fmt::Display for Error{
|
||||
fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{
|
||||
write!(f,"{self:?}")
|
||||
}
|
||||
}
|
||||
impl std::error::Error for Error {}
|
||||
impl std::error::Error for Error{}
|
||||
|
||||
/* spec
|
||||
|
||||
@ -24,18 +24,18 @@ impl std::error::Error for Error {}
|
||||
b"SNFB"
|
||||
u32 format_version
|
||||
u64 priming_bytes
|
||||
//how many bytes of the file must be read to guarantee all of the expected
|
||||
//format-specific metadata is available to facilitate streaming the remaining contents
|
||||
//used by the database to guarantee that it serves at least the bare minimum
|
||||
//how many bytes of the file must be read to guarantee all of the expected
|
||||
//format-specific metadata is available to facilitate streaming the remaining contents
|
||||
//used by the database to guarantee that it serves at least the bare minimum
|
||||
u128 resource_uuid
|
||||
//identifies the file from anywhere for any other file
|
||||
//identifies the file from anywhere for any other file
|
||||
|
||||
//global block layout (variable size)
|
||||
u64 num_blocks
|
||||
//the start of the first block is implicitly after the global header (32)
|
||||
//num_blocks+1 used in Header.block_location is implicitly the end of the file
|
||||
for block_id in 1..num_blocks{
|
||||
u64 first_byte
|
||||
u64 first_byte
|
||||
}
|
||||
|
||||
//end global header
|
||||
@ -47,69 +47,74 @@ for block_id in 1..num_blocks{
|
||||
*/
|
||||
#[binrw]
|
||||
#[brw(little)]
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub(crate) enum FourCC {
|
||||
#[brw(magic = b"SNFM")]
|
||||
Map,
|
||||
#[brw(magic = b"SNFB")]
|
||||
Bot,
|
||||
#[brw(magic = b"SNFD")]
|
||||
Demo,
|
||||
#[derive(Clone,Copy,Debug)]
|
||||
pub(crate) enum FourCC{
|
||||
#[brw(magic=b"SNFM")]
|
||||
Map,
|
||||
#[brw(magic=b"SNFB")]
|
||||
Bot,
|
||||
#[brw(magic=b"SNFD")]
|
||||
Demo,
|
||||
}
|
||||
#[binrw]
|
||||
#[brw(little)]
|
||||
#[derive(Debug)]
|
||||
pub struct Header {
|
||||
/// Type of file
|
||||
pub fourcc: FourCC,
|
||||
/// File format version
|
||||
pub version: u32,
|
||||
/// Minimum data required to know the location of all streamable resources for this specific file
|
||||
pub priming: u64,
|
||||
/// uuid for this file
|
||||
pub resource: u128,
|
||||
//don't need try_calc: the struct will force field initialization anyways and it can be calculated there
|
||||
pub block_count: u32,
|
||||
#[br(count=block_count+1)]
|
||||
pub block_location: Vec<u64>,
|
||||
pub struct Header{
|
||||
/// Type of file
|
||||
pub fourcc:FourCC,
|
||||
/// File format version
|
||||
pub version:u32,
|
||||
/// Minimum data required to know the location of all streamable resources for this specific file
|
||||
pub priming:u64,
|
||||
/// uuid for this file
|
||||
pub resource:u128,
|
||||
//don't need try_calc: the struct will force field initialization anyways and it can be calculated there
|
||||
pub block_count:u32,
|
||||
#[br(count=block_count+1)]
|
||||
pub block_location:Vec<u64>,
|
||||
}
|
||||
impl Header{
|
||||
pub const fn calculate_size(block_count:u32)->usize{
|
||||
4 // fourcc
|
||||
+4 // version
|
||||
+8 // priming
|
||||
+16 // resource
|
||||
+4 // block_count
|
||||
+(block_count as usize+1)*8 // block_location
|
||||
}
|
||||
}
|
||||
|
||||
#[binrw]
|
||||
#[brw(little)]
|
||||
#[derive(Clone, Copy, Debug, Hash, id::Id, Eq, Ord, PartialEq, PartialOrd)]
|
||||
#[derive(Clone,Copy,Debug,Hash,id::Id,Eq,Ord,PartialEq,PartialOrd)]
|
||||
pub struct BlockId(u32);
|
||||
|
||||
pub(crate) struct File<R: BinReaderExt> {
|
||||
header: Header,
|
||||
//reference to the data
|
||||
data: R,
|
||||
pub(crate) struct File<R:BinReaderExt>{
|
||||
header:Header,
|
||||
//reference to the data
|
||||
data:R,
|
||||
}
|
||||
|
||||
impl<R: BinReaderExt> File<R> {
|
||||
pub(crate) fn new(mut input: R) -> Result<File<R>, Error> {
|
||||
Ok(File {
|
||||
header: input.read_le().map_err(Error::InvalidHeader)?,
|
||||
data: input,
|
||||
})
|
||||
}
|
||||
pub(crate) fn data_mut(&mut self) -> &mut R {
|
||||
&mut self.data
|
||||
}
|
||||
pub(crate) fn block_reader(
|
||||
&mut self,
|
||||
block_id: BlockId,
|
||||
) -> Result<binrw::io::TakeSeek<&mut R>, Error> {
|
||||
if self.header.block_location.len() as u32 <= block_id.get() {
|
||||
return Err(Error::InvalidBlockId(block_id));
|
||||
}
|
||||
let block_start = self.header.block_location[block_id.get() as usize];
|
||||
let block_end = self.header.block_location[block_id.get() as usize + 1];
|
||||
self.data
|
||||
.seek(std::io::SeekFrom::Start(block_start))
|
||||
.map_err(Error::Seek)?;
|
||||
Ok(self.data_mut().take_seek(block_end - block_start))
|
||||
}
|
||||
pub(crate) fn fourcc(&self) -> FourCC {
|
||||
self.header.fourcc
|
||||
}
|
||||
impl<R:BinReaderExt> File<R>{
|
||||
pub(crate) fn new(mut input:R)->Result<File<R>,Error>{
|
||||
Ok(File{
|
||||
header:input.read_le().map_err(Error::InvalidHeader)?,
|
||||
data:input,
|
||||
})
|
||||
}
|
||||
pub(crate) fn data_mut(&mut self)->&mut R{
|
||||
&mut self.data
|
||||
}
|
||||
pub(crate) fn block_reader(&mut self,block_id:BlockId)->Result<binrw::io::TakeSeek<&mut R>,Error>{
|
||||
if self.header.block_location.len() as u32<=block_id.get(){
|
||||
return Err(Error::InvalidBlockId(block_id))
|
||||
}
|
||||
let block_start=self.header.block_location[block_id.get() as usize];
|
||||
let block_end=self.header.block_location[block_id.get() as usize+1];
|
||||
self.data.seek(std::io::SeekFrom::Start(block_start)).map_err(Error::Seek)?;
|
||||
Ok(self.data_mut().take_seek(block_end-block_start))
|
||||
}
|
||||
pub(crate) fn fourcc(&self)->FourCC{
|
||||
self.header.fourcc
|
||||
}
|
||||
}
|
||||
|
@ -2,66 +2,67 @@ use binrw::BinReaderExt;
|
||||
|
||||
mod newtypes;
|
||||
|
||||
pub mod bot;
|
||||
pub mod demo;
|
||||
mod file;
|
||||
pub mod map;
|
||||
pub mod bot;
|
||||
pub mod demo;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
UnexpectedFourCC,
|
||||
Header(file::Error),
|
||||
Map(map::Error),
|
||||
Bot(bot::Error),
|
||||
Demo(demo::Error),
|
||||
pub enum Error{
|
||||
UnexpectedFourCC,
|
||||
Header(file::Error),
|
||||
Map(map::Error),
|
||||
Bot(bot::Error),
|
||||
Demo(demo::Error),
|
||||
}
|
||||
impl std::fmt::Display for Error {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{self:?}")
|
||||
}
|
||||
impl std::fmt::Display for Error{
|
||||
fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{
|
||||
write!(f,"{self:?}")
|
||||
}
|
||||
}
|
||||
impl std::error::Error for Error {}
|
||||
impl std::error::Error for Error{}
|
||||
|
||||
pub enum SNF<R: BinReaderExt> {
|
||||
Map(map::StreamableMap<R>),
|
||||
Bot(bot::StreamableBot<R>),
|
||||
Demo(demo::StreamableDemo<R>),
|
||||
pub enum SNF<R:BinReaderExt>{
|
||||
Map(map::StreamableMap<R>),
|
||||
Bot(bot::StreamableBot<R>),
|
||||
Demo(demo::StreamableDemo<R>),
|
||||
}
|
||||
|
||||
pub fn read_snf<R: BinReaderExt>(input: R) -> Result<SNF<R>, Error> {
|
||||
let file = file::File::new(input).map_err(Error::Header)?;
|
||||
Ok(match file.fourcc() {
|
||||
file::FourCC::Map => SNF::Map(map::StreamableMap::new(file).map_err(Error::Map)?),
|
||||
file::FourCC::Bot => SNF::Bot(bot::StreamableBot::new(file).map_err(Error::Bot)?),
|
||||
file::FourCC::Demo => SNF::Demo(demo::StreamableDemo::new(file).map_err(Error::Demo)?),
|
||||
})
|
||||
pub fn read_snf<R:BinReaderExt>(input:R)->Result<SNF<R>,Error>{
|
||||
let file=file::File::new(input).map_err(Error::Header)?;
|
||||
Ok(match file.fourcc(){
|
||||
file::FourCC::Map=>SNF::Map(map::StreamableMap::new(file).map_err(Error::Map)?),
|
||||
file::FourCC::Bot=>SNF::Bot(bot::StreamableBot::new(file).map_err(Error::Bot)?),
|
||||
file::FourCC::Demo=>SNF::Demo(demo::StreamableDemo::new(file).map_err(Error::Demo)?),
|
||||
})
|
||||
}
|
||||
pub fn read_map<R: BinReaderExt>(input: R) -> Result<map::StreamableMap<R>, Error> {
|
||||
let file = file::File::new(input).map_err(Error::Header)?;
|
||||
match file.fourcc() {
|
||||
file::FourCC::Map => Ok(map::StreamableMap::new(file).map_err(Error::Map)?),
|
||||
_ => Err(Error::UnexpectedFourCC),
|
||||
}
|
||||
pub fn read_map<R:BinReaderExt>(input:R)->Result<map::StreamableMap<R>,Error>{
|
||||
let file=file::File::new(input).map_err(Error::Header)?;
|
||||
match file.fourcc(){
|
||||
file::FourCC::Map=>Ok(map::StreamableMap::new(file).map_err(Error::Map)?),
|
||||
_=>Err(Error::UnexpectedFourCC)
|
||||
}
|
||||
}
|
||||
pub fn read_bot<R: BinReaderExt>(input: R) -> Result<bot::StreamableBot<R>, Error> {
|
||||
let file = file::File::new(input).map_err(Error::Header)?;
|
||||
match file.fourcc() {
|
||||
file::FourCC::Bot => Ok(bot::StreamableBot::new(file).map_err(Error::Bot)?),
|
||||
_ => Err(Error::UnexpectedFourCC),
|
||||
}
|
||||
pub fn read_bot<R:BinReaderExt>(input:R)->Result<bot::StreamableBot<R>,Error>{
|
||||
let file=file::File::new(input).map_err(Error::Header)?;
|
||||
match file.fourcc(){
|
||||
file::FourCC::Bot=>Ok(bot::StreamableBot::new(file).map_err(Error::Bot)?),
|
||||
_=>Err(Error::UnexpectedFourCC)
|
||||
}
|
||||
}
|
||||
pub fn read_demo<R: BinReaderExt>(input: R) -> Result<demo::StreamableDemo<R>, Error> {
|
||||
let file = file::File::new(input).map_err(Error::Header)?;
|
||||
match file.fourcc() {
|
||||
file::FourCC::Demo => Ok(demo::StreamableDemo::new(file).map_err(Error::Demo)?),
|
||||
_ => Err(Error::UnexpectedFourCC),
|
||||
}
|
||||
pub fn read_demo<R:BinReaderExt>(input:R)->Result<demo::StreamableDemo<R>,Error>{
|
||||
let file=file::File::new(input).map_err(Error::Header)?;
|
||||
match file.fourcc(){
|
||||
file::FourCC::Demo=>Ok(demo::StreamableDemo::new(file).map_err(Error::Demo)?),
|
||||
_=>Err(Error::UnexpectedFourCC)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
//use super::*;
|
||||
//use super::*;
|
||||
|
||||
#[test]
|
||||
fn test() {}
|
||||
#[test]
|
||||
fn test() {
|
||||
}
|
||||
}
|
||||
|
@ -1,32 +1,32 @@
|
||||
use std::collections::HashMap;
|
||||
use std::io::Read;
|
||||
use std::collections::HashMap;
|
||||
|
||||
use crate::file::BlockId;
|
||||
use crate::newtypes;
|
||||
use binrw::{binrw, BinReaderExt, BinWriterExt};
|
||||
use crate::file::BlockId;
|
||||
use binrw::{binrw,BinReaderExt,BinWriterExt};
|
||||
use strafesnet_common::model;
|
||||
use strafesnet_common::aabb::Aabb;
|
||||
use strafesnet_common::bvh::BvhNode;
|
||||
use strafesnet_common::gameplay_modes;
|
||||
use strafesnet_common::model;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
InvalidHeader(binrw::Error),
|
||||
InvalidMode(newtypes::gameplay_modes::ModeError),
|
||||
InvalidBlockId(BlockId),
|
||||
InvalidMeshId(model::MeshId),
|
||||
InvalidModelId(model::ModelId),
|
||||
InvalidTextureId(model::TextureId),
|
||||
InvalidData(binrw::Error),
|
||||
IO(std::io::Error),
|
||||
File(crate::file::Error),
|
||||
pub enum Error{
|
||||
InvalidHeader(binrw::Error),
|
||||
InvalidMode(newtypes::gameplay_modes::ModeError),
|
||||
InvalidBlockId(BlockId),
|
||||
InvalidMeshId(model::MeshId),
|
||||
InvalidModelId(model::ModelId),
|
||||
InvalidTextureId(model::TextureId),
|
||||
InvalidData(binrw::Error),
|
||||
IO(std::io::Error),
|
||||
File(crate::file::Error),
|
||||
}
|
||||
impl std::fmt::Display for Error {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{self:?}")
|
||||
}
|
||||
impl std::fmt::Display for Error{
|
||||
fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{
|
||||
write!(f,"{self:?}")
|
||||
}
|
||||
}
|
||||
impl std::error::Error for Error {}
|
||||
impl std::error::Error for Error{}
|
||||
|
||||
/* block types
|
||||
|
||||
@ -39,7 +39,7 @@ u32 num_resource_blocks
|
||||
u32 num_resources_external
|
||||
//node 0 parent node is implied to be None
|
||||
for node_id in 1..num_nodes{
|
||||
u32 parent_node
|
||||
u32 parent_node
|
||||
}
|
||||
|
||||
//NOTE: alternate realities are not necessary.
|
||||
@ -49,36 +49,36 @@ for node_id in 1..num_nodes{
|
||||
//ideally spacial blocks are sorted from distance to start zone
|
||||
//texture blocks are inserted before the first spacial block they are used in
|
||||
for spacial_block_id in 0..num_spacial_blocks{
|
||||
u32 node_id
|
||||
u32 block_id //data block
|
||||
Aabb extents
|
||||
u32 node_id
|
||||
u32 block_id //data block
|
||||
Aabb extents
|
||||
}
|
||||
//the order of these lists uniquely generates the incremental Ids
|
||||
//MeshId, TextureId etc. based on resource type
|
||||
//the first 8 bits of resource_uuid describe the type (mesh, texture, etc)
|
||||
//if the map file references only external resources, num_resource_blocks = 0
|
||||
for resource_idx in 0..num_resource_blocks{
|
||||
Resource resource_type
|
||||
u32 block_id
|
||||
Resource resource_type
|
||||
u32 block_id
|
||||
}
|
||||
for resource_idx in 0..num_resources_external{
|
||||
u128 resource_uuid
|
||||
u128 resource_uuid
|
||||
}
|
||||
|
||||
BLOCK_MAP_RESOURCE:
|
||||
Resource resource_type
|
||||
//an individual one of the following:
|
||||
- model (Mesh)
|
||||
- shader (compiled SPIR-V)
|
||||
- image (JpegXL)
|
||||
- sound (Opus)
|
||||
- video (AV1)
|
||||
- animation (Trey thing)
|
||||
- model (Mesh)
|
||||
- shader (compiled SPIR-V)
|
||||
- image (JpegXL)
|
||||
- sound (Opus)
|
||||
- video (AV1)
|
||||
- animation (Trey thing)
|
||||
|
||||
BLOCK_MAP_REGION:
|
||||
u64 num_models
|
||||
for model_id in 0..num_models{
|
||||
ModelInstance mode_instance
|
||||
ModelInstance mode_instance
|
||||
}
|
||||
|
||||
*/
|
||||
@ -86,422 +86,338 @@ for model_id in 0..num_models{
|
||||
//if you hash the resource itself and set the first 8 bits to this, that's the resource uuid
|
||||
#[binrw]
|
||||
#[brw(little,repr=u8)]
|
||||
enum ResourceType {
|
||||
Mesh,
|
||||
Texture,
|
||||
//Shader,
|
||||
//Sound,
|
||||
//Video,
|
||||
//Animation,
|
||||
}
|
||||
const RESOURCE_TYPE_VARIANT_COUNT: u8 = 2;
|
||||
#[binrw]
|
||||
#[brw(little)]
|
||||
struct ResourceId(u128);
|
||||
impl ResourceId {
|
||||
fn resource_type(&self) -> Option<ResourceType> {
|
||||
let discriminant = self.0 as u8;
|
||||
//TODO: use this when it is stabilized https://github.com/rust-lang/rust/issues/73662
|
||||
//if (discriminant as usize)<std::mem::variant_count::<ResourceType>(){
|
||||
match discriminant < RESOURCE_TYPE_VARIANT_COUNT {
|
||||
true => Some(unsafe { std::mem::transmute::<u8, ResourceType>(discriminant) }),
|
||||
false => None,
|
||||
}
|
||||
}
|
||||
#[repr(u8)]
|
||||
enum ResourceType{
|
||||
Mesh,
|
||||
Texture,
|
||||
//Shader,
|
||||
//Sound,
|
||||
//Video,
|
||||
//Animation,
|
||||
}
|
||||
|
||||
struct ResourceMap<T> {
|
||||
meshes: HashMap<strafesnet_common::model::MeshId, T>,
|
||||
textures: HashMap<strafesnet_common::model::TextureId, T>,
|
||||
struct ResourceMap<T>{
|
||||
meshes:HashMap<strafesnet_common::model::MeshId,T>,
|
||||
textures:HashMap<strafesnet_common::model::TextureId,T>,
|
||||
}
|
||||
impl<T> Default for ResourceMap<T> {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
meshes: HashMap::new(),
|
||||
textures: HashMap::new(),
|
||||
}
|
||||
}
|
||||
impl<T> Default for ResourceMap<T>{
|
||||
fn default()->Self{
|
||||
Self{
|
||||
meshes:HashMap::new(),
|
||||
textures:HashMap::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[binrw]
|
||||
#[brw(little)]
|
||||
struct SpacialBlockHeader {
|
||||
id: BlockId,
|
||||
extents: newtypes::aabb::Aabb,
|
||||
struct SpacialBlockHeader{
|
||||
id:BlockId,
|
||||
extents:newtypes::aabb::Aabb,
|
||||
}
|
||||
#[binrw]
|
||||
#[brw(little)]
|
||||
struct ResourceBlockHeader {
|
||||
resource: ResourceType,
|
||||
id: BlockId,
|
||||
}
|
||||
#[binrw]
|
||||
#[brw(little)]
|
||||
struct ResourceExternalHeader {
|
||||
resource_uuid: ResourceId,
|
||||
struct ResourceBlockHeader{
|
||||
resource:ResourceType,
|
||||
id:BlockId,
|
||||
}
|
||||
|
||||
#[binrw]
|
||||
#[brw(little)]
|
||||
struct MapHeader {
|
||||
num_spacial_blocks: u32,
|
||||
num_resource_blocks: u32,
|
||||
//num_resources_external:u32,
|
||||
num_modes: u32,
|
||||
num_attributes: u32,
|
||||
num_render_configs: u32,
|
||||
#[br(count=num_spacial_blocks)]
|
||||
spacial_blocks: Vec<SpacialBlockHeader>,
|
||||
#[br(count=num_resource_blocks)]
|
||||
resource_blocks: Vec<ResourceBlockHeader>,
|
||||
//#[br(count=num_resources_external)]
|
||||
//external_resources:Vec<ResourceExternalHeader>,
|
||||
#[br(count=num_modes)]
|
||||
modes: Vec<newtypes::gameplay_modes::Mode>,
|
||||
#[br(count=num_attributes)]
|
||||
attributes: Vec<newtypes::gameplay_attributes::CollisionAttributes>,
|
||||
#[br(count=num_render_configs)]
|
||||
render_configs: Vec<newtypes::model::RenderConfig>,
|
||||
struct MapHeader{
|
||||
num_spacial_blocks:u32,
|
||||
num_resource_blocks:u32,
|
||||
//num_resources_external:u32,
|
||||
num_modes:u32,
|
||||
num_attributes:u32,
|
||||
num_render_configs:u32,
|
||||
#[br(count=num_spacial_blocks)]
|
||||
spacial_blocks:Vec<SpacialBlockHeader>,
|
||||
#[br(count=num_resource_blocks)]
|
||||
resource_blocks:Vec<ResourceBlockHeader>,
|
||||
//#[br(count=num_resources_external)]
|
||||
//external_resources:Vec<ResourceExternalHeader>,
|
||||
#[br(count=num_modes)]
|
||||
modes:Vec<newtypes::gameplay_modes::Mode>,
|
||||
#[br(count=num_attributes)]
|
||||
attributes:Vec<newtypes::gameplay_attributes::CollisionAttributes>,
|
||||
#[br(count=num_render_configs)]
|
||||
render_configs:Vec<newtypes::model::RenderConfig>,
|
||||
}
|
||||
|
||||
#[binrw]
|
||||
#[brw(little)]
|
||||
struct Region {
|
||||
//consider including a bvh in the region instead of needing to rebalance the physics bvh on the fly
|
||||
model_count: u32,
|
||||
#[br(count=model_count)]
|
||||
models: Vec<(u32, newtypes::model::Model)>,
|
||||
struct Region{
|
||||
//consider including a bvh in the region instead of needing to rebalance the physics bvh on the fly
|
||||
model_count:u32,
|
||||
#[br(count=model_count)]
|
||||
models:Vec<(u32,newtypes::model::Model)>,
|
||||
}
|
||||
|
||||
//code deduplication reused in into_complete_map
|
||||
fn read_region<R: BinReaderExt>(
|
||||
file: &mut crate::file::File<R>,
|
||||
block_id: BlockId,
|
||||
) -> Result<Vec<(model::ModelId, model::Model)>, Error> {
|
||||
//load region from disk
|
||||
//parse the models and determine what resources need to be loaded
|
||||
//load resources into self.resources
|
||||
//return Region
|
||||
let mut block = file.block_reader(block_id).map_err(Error::File)?;
|
||||
let region: Region = block.read_le().map_err(Error::InvalidData)?;
|
||||
Ok(region
|
||||
.models
|
||||
.into_iter()
|
||||
.map(|(model_id, model)| (model::ModelId::new(model_id), model.into()))
|
||||
.collect())
|
||||
fn read_region<R:BinReaderExt>(file:&mut crate::file::File<R>,block_id:BlockId)->Result<Vec<(model::ModelId,model::Model)>,Error>{
|
||||
//load region from disk
|
||||
//parse the models and determine what resources need to be loaded
|
||||
//load resources into self.resources
|
||||
//return Region
|
||||
let mut block=file.block_reader(block_id).map_err(Error::File)?;
|
||||
let region:Region=block.read_le().map_err(Error::InvalidData)?;
|
||||
Ok(region.models.into_iter().map(|(model_id,model)|
|
||||
(model::ModelId::new(model_id),model.into())
|
||||
).collect())
|
||||
}
|
||||
fn read_mesh<R: BinReaderExt>(
|
||||
file: &mut crate::file::File<R>,
|
||||
block_id: BlockId,
|
||||
) -> Result<model::Mesh, Error> {
|
||||
let mut block = file.block_reader(block_id).map_err(Error::File)?;
|
||||
let mesh: newtypes::model::Mesh = block.read_le().map_err(Error::InvalidData)?;
|
||||
Ok(mesh.into())
|
||||
fn read_mesh<R:BinReaderExt>(file:&mut crate::file::File<R>,block_id:BlockId)->Result<model::Mesh,Error>{
|
||||
let mut block=file.block_reader(block_id).map_err(Error::File)?;
|
||||
let mesh:newtypes::model::Mesh=block.read_le().map_err(Error::InvalidData)?;
|
||||
Ok(mesh.into())
|
||||
}
|
||||
fn read_texture<R: BinReaderExt>(
|
||||
file: &mut crate::file::File<R>,
|
||||
block_id: BlockId,
|
||||
) -> Result<Vec<u8>, Error> {
|
||||
let mut block = file.block_reader(block_id).map_err(Error::File)?;
|
||||
let mut texture = Vec::new();
|
||||
block.read_to_end(&mut texture).map_err(Error::IO)?;
|
||||
Ok(texture)
|
||||
fn read_texture<R:BinReaderExt>(file:&mut crate::file::File<R>,block_id:BlockId)->Result<Vec<u8>,Error>{
|
||||
let mut block=file.block_reader(block_id).map_err(Error::File)?;
|
||||
let mut texture=Vec::new();
|
||||
block.read_to_end(&mut texture).map_err(Error::IO)?;
|
||||
Ok(texture)
|
||||
}
|
||||
|
||||
pub struct StreamableMap<R: BinReaderExt> {
|
||||
file: crate::file::File<R>,
|
||||
//this includes every platform... move the unconstrained datas to their appropriate data block?
|
||||
modes: gameplay_modes::Modes,
|
||||
//this is every possible attribute... need some sort of streaming system
|
||||
attributes: Vec<strafesnet_common::gameplay_attributes::CollisionAttributes>,
|
||||
//this is every possible render configuration... shaders and such... need streaming
|
||||
render_configs: Vec<strafesnet_common::model::RenderConfig>,
|
||||
//this makes sense to keep in memory for streaming, a map of which blocks occupy what space
|
||||
bvh: BvhNode<BlockId>,
|
||||
//something something resources hashmaps
|
||||
resource_blocks: ResourceMap<BlockId>,
|
||||
//resource_external:ResourceMap<ResourceId>,
|
||||
pub struct StreamableMap<R:BinReaderExt>{
|
||||
file:crate::file::File<R>,
|
||||
//this includes every platform... move the unconstrained datas to their appropriate data block?
|
||||
modes:gameplay_modes::Modes,
|
||||
//this is every possible attribute... need some sort of streaming system
|
||||
attributes:Vec<strafesnet_common::gameplay_attributes::CollisionAttributes>,
|
||||
//this is every possible render configuration... shaders and such... need streaming
|
||||
render_configs:Vec<strafesnet_common::model::RenderConfig>,
|
||||
//this makes sense to keep in memory for streaming, a map of which blocks occupy what space
|
||||
bvh:BvhNode<BlockId>,
|
||||
//something something resources hashmaps
|
||||
resource_blocks:ResourceMap<BlockId>,
|
||||
//resource_external:ResourceMap<ResourceId>,
|
||||
}
|
||||
impl<R: BinReaderExt> StreamableMap<R> {
|
||||
pub(crate) fn new(mut file: crate::file::File<R>) -> Result<Self, Error> {
|
||||
//assume the file seek is in the right place to start reading a map header
|
||||
let header: MapHeader = file.data_mut().read_le().map_err(Error::InvalidHeader)?;
|
||||
let modes = header
|
||||
.modes
|
||||
.into_iter()
|
||||
.map(TryInto::try_into)
|
||||
.collect::<Result<_, _>>()
|
||||
.map_err(Error::InvalidMode)?;
|
||||
let attributes = header.attributes.into_iter().map(Into::into).collect();
|
||||
let render_configs = header.render_configs.into_iter().map(Into::into).collect();
|
||||
let bvh = header
|
||||
.spacial_blocks
|
||||
.into_iter()
|
||||
.map(|spacial_block| (spacial_block.id, spacial_block.extents.into()))
|
||||
.collect();
|
||||
//generate mesh ids and texture ids from resource list order
|
||||
let mut resource_blocks = ResourceMap::default();
|
||||
for resource_block_header in header.resource_blocks {
|
||||
match resource_block_header.resource {
|
||||
ResourceType::Mesh => {
|
||||
resource_blocks.meshes.insert(
|
||||
//generate the id from the current length
|
||||
model::MeshId::new(resource_blocks.meshes.len() as u32),
|
||||
resource_block_header.id,
|
||||
);
|
||||
}
|
||||
ResourceType::Texture => {
|
||||
resource_blocks.textures.insert(
|
||||
model::TextureId::new(resource_blocks.textures.len() as u32),
|
||||
resource_block_header.id,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(Self {
|
||||
file,
|
||||
modes: strafesnet_common::gameplay_modes::Modes::new(modes),
|
||||
attributes,
|
||||
render_configs,
|
||||
bvh: strafesnet_common::bvh::generate_bvh(bvh),
|
||||
resource_blocks,
|
||||
//resource_external:Default::default(),
|
||||
})
|
||||
}
|
||||
pub fn get_intersecting_region_block_ids(&self, aabb: &Aabb) -> Vec<BlockId> {
|
||||
let mut block_ids = Vec::new();
|
||||
self.bvh
|
||||
.the_tester(aabb, &mut |&block_id| block_ids.push(block_id));
|
||||
block_ids
|
||||
}
|
||||
pub fn load_region(
|
||||
&mut self,
|
||||
block_id: BlockId,
|
||||
) -> Result<Vec<(model::ModelId, model::Model)>, Error> {
|
||||
read_region(&mut self.file, block_id)
|
||||
}
|
||||
pub fn load_mesh(&mut self, mesh_id: model::MeshId) -> Result<model::Mesh, Error> {
|
||||
let block_id = *self
|
||||
.resource_blocks
|
||||
.meshes
|
||||
.get(&mesh_id)
|
||||
.ok_or(Error::InvalidMeshId(mesh_id))?;
|
||||
read_mesh(&mut self.file, block_id)
|
||||
}
|
||||
pub fn load_texture(&mut self, texture_id: model::TextureId) -> Result<Vec<u8>, Error> {
|
||||
let block_id = *self
|
||||
.resource_blocks
|
||||
.textures
|
||||
.get(&texture_id)
|
||||
.ok_or(Error::InvalidTextureId(texture_id))?;
|
||||
read_texture(&mut self.file, block_id)
|
||||
}
|
||||
pub fn into_complete_map(mut self) -> Result<strafesnet_common::map::CompleteMap, Error> {
|
||||
let mut block_ids = Vec::new();
|
||||
self.bvh
|
||||
.into_visitor(&mut |block_id| block_ids.push(block_id));
|
||||
//count on reading the file in sequential order being fastest
|
||||
block_ids.sort_unstable();
|
||||
//load all regions
|
||||
let mut model_pairs = HashMap::new();
|
||||
for block_id in block_ids {
|
||||
model_pairs.extend(read_region(&mut self.file, block_id)?);
|
||||
}
|
||||
let mut models = Vec::with_capacity(model_pairs.len());
|
||||
for model_id in 0..model_pairs.len() as u32 {
|
||||
let model_id = model::ModelId::new(model_id);
|
||||
models.push(
|
||||
model_pairs
|
||||
.remove(&model_id)
|
||||
.ok_or(Error::InvalidModelId(model_id))?,
|
||||
);
|
||||
}
|
||||
//load all meshes
|
||||
let mut meshes = Vec::with_capacity(self.resource_blocks.meshes.len());
|
||||
for mesh_id in 0..self.resource_blocks.meshes.len() as u32 {
|
||||
let mesh_id = model::MeshId::new(mesh_id);
|
||||
let block_id = self.resource_blocks.meshes[&mesh_id];
|
||||
meshes.push(read_mesh(&mut self.file, block_id)?);
|
||||
}
|
||||
//load all textures
|
||||
let mut textures = Vec::with_capacity(self.resource_blocks.textures.len());
|
||||
for texture_id in 0..self.resource_blocks.textures.len() as u32 {
|
||||
let texture_id = model::TextureId::new(texture_id);
|
||||
let block_id = self.resource_blocks.textures[&texture_id];
|
||||
textures.push(read_texture(&mut self.file, block_id)?);
|
||||
}
|
||||
Ok(strafesnet_common::map::CompleteMap {
|
||||
modes: self.modes,
|
||||
attributes: self.attributes,
|
||||
meshes,
|
||||
models,
|
||||
textures,
|
||||
render_configs: self.render_configs,
|
||||
})
|
||||
}
|
||||
impl<R:BinReaderExt> StreamableMap<R>{
|
||||
pub(crate) fn new(mut file:crate::file::File<R>)->Result<Self,Error>{
|
||||
//assume the file seek is in the right place to start reading a map header
|
||||
let header:MapHeader=file.data_mut().read_le().map_err(Error::InvalidHeader)?;
|
||||
let modes=header.modes.into_iter().map(TryInto::try_into).collect::<Result<_,_>>().map_err(Error::InvalidMode)?;
|
||||
let attributes=header.attributes.into_iter().map(Into::into).collect();
|
||||
let render_configs=header.render_configs.into_iter().map(Into::into).collect();
|
||||
let bvh=header.spacial_blocks.into_iter().map(|spacial_block|
|
||||
(spacial_block.id,spacial_block.extents.into())
|
||||
).collect();
|
||||
//generate mesh ids and texture ids from resource list order
|
||||
let mut resource_blocks=ResourceMap::default();
|
||||
for resource_block_header in header.resource_blocks{
|
||||
match resource_block_header.resource{
|
||||
ResourceType::Mesh=>{
|
||||
resource_blocks.meshes.insert(
|
||||
//generate the id from the current length
|
||||
model::MeshId::new(resource_blocks.meshes.len() as u32),
|
||||
resource_block_header.id
|
||||
);
|
||||
},
|
||||
ResourceType::Texture=>{
|
||||
resource_blocks.textures.insert(
|
||||
model::TextureId::new(resource_blocks.textures.len() as u32),
|
||||
resource_block_header.id
|
||||
);
|
||||
},
|
||||
}
|
||||
}
|
||||
Ok(Self{
|
||||
file,
|
||||
modes:strafesnet_common::gameplay_modes::Modes::new(modes),
|
||||
attributes,
|
||||
render_configs,
|
||||
bvh:strafesnet_common::bvh::generate_bvh(bvh),
|
||||
resource_blocks,
|
||||
//resource_external:Default::default(),
|
||||
})
|
||||
}
|
||||
pub fn get_intersecting_region_block_ids(&self,aabb:&Aabb)->Vec<BlockId>{
|
||||
let mut block_ids=Vec::new();
|
||||
self.bvh.the_tester(aabb,&mut |&block_id|block_ids.push(block_id));
|
||||
block_ids
|
||||
}
|
||||
pub fn load_region(&mut self,block_id:BlockId)->Result<Vec<(model::ModelId,model::Model)>,Error>{
|
||||
read_region(&mut self.file,block_id)
|
||||
}
|
||||
pub fn load_mesh(&mut self,mesh_id:model::MeshId)->Result<model::Mesh,Error>{
|
||||
let block_id=*self.resource_blocks.meshes.get(&mesh_id).ok_or(Error::InvalidMeshId(mesh_id))?;
|
||||
read_mesh(&mut self.file,block_id)
|
||||
}
|
||||
pub fn load_texture(&mut self,texture_id:model::TextureId)->Result<Vec<u8>,Error>{
|
||||
let block_id=*self.resource_blocks.textures.get(&texture_id).ok_or(Error::InvalidTextureId(texture_id))?;
|
||||
read_texture(&mut self.file,block_id)
|
||||
}
|
||||
pub fn into_complete_map(mut self)->Result<strafesnet_common::map::CompleteMap,Error>{
|
||||
let mut block_ids=Vec::new();
|
||||
self.bvh.into_visitor(&mut |block_id|block_ids.push(block_id));
|
||||
//count on reading the file in sequential order being fastest
|
||||
block_ids.sort_unstable();
|
||||
//load all regions
|
||||
let mut model_pairs=HashMap::new();
|
||||
for block_id in block_ids{
|
||||
model_pairs.extend(read_region(&mut self.file,block_id)?);
|
||||
}
|
||||
let mut models=Vec::with_capacity(model_pairs.len());
|
||||
for model_id in 0..model_pairs.len() as u32{
|
||||
let model_id=model::ModelId::new(model_id);
|
||||
models.push(model_pairs.remove(&model_id).ok_or(Error::InvalidModelId(model_id))?);
|
||||
}
|
||||
//load all meshes
|
||||
let mut meshes=Vec::with_capacity(self.resource_blocks.meshes.len());
|
||||
for mesh_id in 0..self.resource_blocks.meshes.len() as u32{
|
||||
let mesh_id=model::MeshId::new(mesh_id);
|
||||
let block_id=self.resource_blocks.meshes[&mesh_id];
|
||||
meshes.push(read_mesh(&mut self.file,block_id)?);
|
||||
};
|
||||
//load all textures
|
||||
let mut textures=Vec::with_capacity(self.resource_blocks.textures.len());
|
||||
for texture_id in 0..self.resource_blocks.textures.len() as u32{
|
||||
let texture_id=model::TextureId::new(texture_id);
|
||||
let block_id=self.resource_blocks.textures[&texture_id];
|
||||
textures.push(read_texture(&mut self.file,block_id)?);
|
||||
}
|
||||
Ok(strafesnet_common::map::CompleteMap{
|
||||
modes:self.modes,
|
||||
attributes:self.attributes,
|
||||
meshes,
|
||||
models,
|
||||
textures,
|
||||
render_configs:self.render_configs,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const BVH_NODE_MAX_WEIGHT: usize = 64 * 1024; //64 kB
|
||||
const BVH_NODE_MAX_WEIGHT:usize=64*1024;//64 kB
|
||||
fn collect_spacial_blocks(
|
||||
block_location: &mut Vec<u64>,
|
||||
block_headers: &mut Vec<SpacialBlockHeader>,
|
||||
sequential_block_data: &mut std::io::Cursor<&mut Vec<u8>>,
|
||||
bvh_node: strafesnet_common::bvh::BvhWeightNode<
|
||||
usize,
|
||||
(model::ModelId, newtypes::model::Model),
|
||||
>,
|
||||
) -> Result<(), Error> {
|
||||
//inspect the node weights top-down.
|
||||
//When a node weighs less than the limit,
|
||||
//serialize its entire contents into a region block
|
||||
if *bvh_node.weight() < BVH_NODE_MAX_WEIGHT {
|
||||
let mut models = Vec::new();
|
||||
let mut model_count = 0;
|
||||
let extents = bvh_node.aabb().clone().into();
|
||||
bvh_node.into_visitor(&mut |(model_id, model)| {
|
||||
model_count += 1;
|
||||
models.push((model_id.get(), model));
|
||||
});
|
||||
let id = BlockId::new(block_headers.len() as u32 + 1);
|
||||
block_headers.push(SpacialBlockHeader { id, extents });
|
||||
let region = Region {
|
||||
model_count,
|
||||
models,
|
||||
};
|
||||
binrw::BinWrite::write_le(®ion, sequential_block_data).map_err(Error::InvalidData)?;
|
||||
block_location.push(sequential_block_data.position());
|
||||
} else {
|
||||
match bvh_node.into_content() {
|
||||
strafesnet_common::bvh::RecursiveContent::Branch(bvh_node_list) => {
|
||||
for bvh_node in bvh_node_list {
|
||||
collect_spacial_blocks(
|
||||
block_location,
|
||||
block_headers,
|
||||
sequential_block_data,
|
||||
bvh_node,
|
||||
)?;
|
||||
}
|
||||
}
|
||||
strafesnet_common::bvh::RecursiveContent::Leaf(_) => panic!(), //bvh branches are 20 leaves minimum
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
block_location:&mut Vec<u64>,
|
||||
block_headers:&mut Vec<SpacialBlockHeader>,
|
||||
sequential_block_data:&mut std::io::Cursor<&mut Vec<u8>>,
|
||||
bvh_node:strafesnet_common::bvh::BvhWeightNode<usize,(model::ModelId,newtypes::model::Model)>
|
||||
)->Result<(),Error>{
|
||||
//inspect the node weights top-down.
|
||||
//When a node weighs less than the limit,
|
||||
//serialize its entire contents into a region block
|
||||
if *bvh_node.weight()<BVH_NODE_MAX_WEIGHT{
|
||||
let mut models=Vec::new();
|
||||
let mut model_count=0;
|
||||
let extents=bvh_node.aabb().clone().into();
|
||||
bvh_node.into_visitor(&mut |(model_id,model)|{
|
||||
model_count+=1;
|
||||
models.push((model_id.get(),model));
|
||||
});
|
||||
let id=BlockId::new(block_headers.len() as u32+1);
|
||||
block_headers.push(SpacialBlockHeader{
|
||||
id,
|
||||
extents,
|
||||
});
|
||||
let region=Region{
|
||||
model_count,
|
||||
models,
|
||||
};
|
||||
binrw::BinWrite::write_le(®ion,sequential_block_data).map_err(Error::InvalidData)?;
|
||||
block_location.push(sequential_block_data.position());
|
||||
}else{
|
||||
match bvh_node.into_content(){
|
||||
strafesnet_common::bvh::RecursiveContent::Branch(bvh_node_list)=>{
|
||||
for bvh_node in bvh_node_list{
|
||||
collect_spacial_blocks(block_location,block_headers,sequential_block_data,bvh_node)?;
|
||||
}
|
||||
},
|
||||
strafesnet_common::bvh::RecursiveContent::Leaf(_)=>panic!(),//bvh branches are 20 leaves minimum
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// TODO: Optionally provide a bot that describes the path through the map
|
||||
/// otherwise sort by distance to start zone
|
||||
pub fn write_map<W: BinWriterExt>(
|
||||
mut writer: W,
|
||||
map: strafesnet_common::map::CompleteMap,
|
||||
) -> Result<(), Error> {
|
||||
//serialize models and make a bvh that knows the file size of the branch
|
||||
let boxen = map
|
||||
.models
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
.map(|(model_id, model)| {
|
||||
//grow your own aabb
|
||||
let mesh = map
|
||||
.meshes
|
||||
.get(model.mesh.get() as usize)
|
||||
.ok_or(Error::InvalidMeshId(model.mesh))?;
|
||||
let mut aabb = strafesnet_common::aabb::Aabb::default();
|
||||
for &pos in &mesh.unique_pos {
|
||||
aabb.grow(model.transform.transform_point3(pos).fix_1());
|
||||
}
|
||||
Ok(((model::ModelId::new(model_id as u32), model.into()), aabb))
|
||||
})
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
let bvh = strafesnet_common::bvh::generate_bvh(boxen)
|
||||
.weigh_contents(&|_| std::mem::size_of::<newtypes::model::Model>());
|
||||
//build blocks
|
||||
//block location is initialized with two values
|
||||
//the first value represents the location of the first byte after the file header
|
||||
//the second value represents the first byte of the second data block
|
||||
//the first data block is always the map header, so the difference is the map header size.
|
||||
//this information is filled in later after the sizes are known.
|
||||
let mut block_location = vec![0, 0]; //for file header
|
||||
let mut spacial_blocks = Vec::new(); //for map header
|
||||
let mut sequential_block_data = Vec::new();
|
||||
let mut cursor_to_data = std::io::Cursor::new(&mut sequential_block_data);
|
||||
collect_spacial_blocks(
|
||||
&mut block_location,
|
||||
&mut spacial_blocks,
|
||||
&mut cursor_to_data,
|
||||
bvh,
|
||||
)?;
|
||||
let mut block_count = spacial_blocks.len() as u32 + 1; //continue block id
|
||||
let mut resource_blocks = Vec::new(); //for map header
|
||||
//meshes
|
||||
for mesh in map.meshes.into_iter() {
|
||||
resource_blocks.push(ResourceBlockHeader {
|
||||
resource: ResourceType::Mesh,
|
||||
id: BlockId::new(block_count),
|
||||
});
|
||||
block_count += 1;
|
||||
let serializable_mesh: newtypes::model::Mesh = mesh.into();
|
||||
binrw::BinWrite::write_le(&serializable_mesh, &mut cursor_to_data)
|
||||
.map_err(Error::InvalidData)?;
|
||||
block_location.push(cursor_to_data.position());
|
||||
}
|
||||
//textures
|
||||
for mut texture in map.textures.into_iter() {
|
||||
resource_blocks.push(ResourceBlockHeader {
|
||||
resource: ResourceType::Texture,
|
||||
id: BlockId::new(block_count),
|
||||
});
|
||||
block_count += 1;
|
||||
sequential_block_data.append(&mut texture);
|
||||
block_location.push(sequential_block_data.len() as u64);
|
||||
}
|
||||
//build header
|
||||
let map_header = MapHeader {
|
||||
num_spacial_blocks: spacial_blocks.len() as u32,
|
||||
num_resource_blocks: resource_blocks.len() as u32,
|
||||
//num_resources_external:0,
|
||||
num_modes: map.modes.modes.len() as u32,
|
||||
num_attributes: map.attributes.len() as u32,
|
||||
num_render_configs: map.render_configs.len() as u32,
|
||||
spacial_blocks,
|
||||
resource_blocks,
|
||||
//external_resources:Vec::new(),
|
||||
modes: map.modes.modes.into_iter().map(Into::into).collect(),
|
||||
attributes: map.attributes.into_iter().map(Into::into).collect(),
|
||||
render_configs: map.render_configs.into_iter().map(Into::into).collect(),
|
||||
};
|
||||
let mut file_header = crate::file::Header {
|
||||
fourcc: crate::file::FourCC::Map,
|
||||
version: 0,
|
||||
priming: 0,
|
||||
resource: 0,
|
||||
block_count,
|
||||
block_location,
|
||||
};
|
||||
//probe header length
|
||||
let mut file_header_data = Vec::new();
|
||||
binrw::BinWrite::write_le(
|
||||
&file_header,
|
||||
&mut std::io::Cursor::new(&mut file_header_data),
|
||||
)
|
||||
.map_err(Error::InvalidData)?;
|
||||
let mut map_header_data = Vec::new();
|
||||
binrw::BinWrite::write_le(&map_header, &mut std::io::Cursor::new(&mut map_header_data))
|
||||
.map_err(Error::InvalidData)?;
|
||||
pub fn write_map<W:BinWriterExt>(mut writer:W,map:strafesnet_common::map::CompleteMap)->Result<(),Error>{
|
||||
//serialize models and make a bvh that knows the file size of the branch
|
||||
let boxen=map.models.into_iter().enumerate().map(|(model_id,model)|{
|
||||
//grow your own aabb
|
||||
let mesh=map.meshes.get(model.mesh.get() as usize).ok_or(Error::InvalidMeshId(model.mesh))?;
|
||||
let mut aabb=strafesnet_common::aabb::Aabb::default();
|
||||
for &pos in &mesh.unique_pos{
|
||||
aabb.grow(model.transform.transform_point3(pos).fix_1());
|
||||
}
|
||||
Ok(((model::ModelId::new(model_id as u32),model.into()),aabb))
|
||||
}).collect::<Result<Vec<_>,_>>()?;
|
||||
let bvh=strafesnet_common::bvh::generate_bvh(boxen).weigh_contents(&|_|std::mem::size_of::<newtypes::model::Model>());
|
||||
//build blocks
|
||||
//block location is initialized with two values
|
||||
//the first value represents the location of the first byte after the file header
|
||||
//the second value represents the first byte of the second data block
|
||||
//the first data block is always the map header, so the difference is the map header size.
|
||||
//this information is filled in later after the sizes are known.
|
||||
let mut block_location=vec![0,0];//for file header
|
||||
let mut spacial_blocks=Vec::new();//for map header
|
||||
let mut sequential_block_data=Vec::new();
|
||||
let mut cursor_to_data=std::io::Cursor::new(&mut sequential_block_data);
|
||||
collect_spacial_blocks(&mut block_location,&mut spacial_blocks,&mut cursor_to_data,bvh)?;
|
||||
let mut block_count=spacial_blocks.len() as u32+1;//continue block id
|
||||
let mut resource_blocks=Vec::new();//for map header
|
||||
//meshes
|
||||
for mesh in map.meshes.into_iter(){
|
||||
resource_blocks.push(ResourceBlockHeader{
|
||||
resource:ResourceType::Mesh,
|
||||
id:BlockId::new(block_count),
|
||||
});
|
||||
block_count+=1;
|
||||
let serializable_mesh:newtypes::model::Mesh=mesh.into();
|
||||
binrw::BinWrite::write_le(&serializable_mesh,&mut cursor_to_data).map_err(Error::InvalidData)?;
|
||||
block_location.push(cursor_to_data.position());
|
||||
}
|
||||
//textures
|
||||
for mut texture in map.textures.into_iter(){
|
||||
resource_blocks.push(ResourceBlockHeader{
|
||||
resource:ResourceType::Texture,
|
||||
id:BlockId::new(block_count),
|
||||
});
|
||||
block_count+=1;
|
||||
sequential_block_data.append(&mut texture);
|
||||
block_location.push(sequential_block_data.len() as u64);
|
||||
}
|
||||
//build header
|
||||
let map_header=MapHeader{
|
||||
num_spacial_blocks:spacial_blocks.len() as u32,
|
||||
num_resource_blocks:resource_blocks.len() as u32,
|
||||
//num_resources_external:0,
|
||||
num_modes:map.modes.modes.len() as u32,
|
||||
num_attributes:map.attributes.len() as u32,
|
||||
num_render_configs:map.render_configs.len() as u32,
|
||||
spacial_blocks,
|
||||
resource_blocks,
|
||||
//external_resources:Vec::new(),
|
||||
modes:map.modes.modes.into_iter().map(Into::into).collect(),
|
||||
attributes:map.attributes.into_iter().map(Into::into).collect(),
|
||||
render_configs:map.render_configs.into_iter().map(Into::into).collect(),
|
||||
};
|
||||
//probe header length
|
||||
let mut map_header_data=Vec::new();
|
||||
binrw::BinWrite::write_le(&map_header,&mut std::io::Cursor::new(&mut map_header_data)).map_err(Error::InvalidData)?;
|
||||
|
||||
//update file header according to probe data
|
||||
let mut offset = file_header_data.len() as u64;
|
||||
file_header.priming = offset;
|
||||
file_header.block_location[0] = offset;
|
||||
offset += map_header_data.len() as u64;
|
||||
for position in &mut file_header.block_location[1..] {
|
||||
*position += offset;
|
||||
}
|
||||
// calculate final file header
|
||||
let mut offset=crate::file::Header::calculate_size(block_count) as u64;
|
||||
offset+=map_header_data.len() as u64;
|
||||
// priming includes map header
|
||||
let priming=offset;
|
||||
for position in &mut block_location{
|
||||
*position+=offset;
|
||||
}
|
||||
let file_header=crate::file::Header{
|
||||
fourcc:crate::file::FourCC::Map,
|
||||
version:0,
|
||||
priming,
|
||||
resource:0,
|
||||
block_count,
|
||||
block_location,
|
||||
};
|
||||
|
||||
//write (updated) file header
|
||||
writer.write_le(&file_header).map_err(Error::InvalidData)?;
|
||||
//write map header
|
||||
writer.write(&map_header_data).map_err(Error::IO)?;
|
||||
//write blocks
|
||||
writer.write(&sequential_block_data).map_err(Error::IO)?;
|
||||
Ok(())
|
||||
//write (updated) file header
|
||||
writer.write_le(&file_header).map_err(Error::InvalidData)?;
|
||||
//write map header
|
||||
writer.write(&map_header_data).map_err(Error::IO)?;
|
||||
//write blocks
|
||||
writer.write(&sequential_block_data).map_err(Error::IO)?;
|
||||
Ok(())
|
||||
}
|
||||
|
@ -1,23 +1,23 @@
|
||||
use super::integer::Planar64Vec3;
|
||||
#[binrw::binrw]
|
||||
#[brw(little)]
|
||||
pub struct Aabb {
|
||||
pub min: Planar64Vec3,
|
||||
pub max: Planar64Vec3,
|
||||
pub struct Aabb{
|
||||
pub min:Planar64Vec3,
|
||||
pub max:Planar64Vec3,
|
||||
}
|
||||
impl Into<strafesnet_common::aabb::Aabb> for Aabb {
|
||||
fn into(self) -> strafesnet_common::aabb::Aabb {
|
||||
strafesnet_common::aabb::Aabb::new(
|
||||
strafesnet_common::integer::vec3::raw_array(self.min),
|
||||
strafesnet_common::integer::vec3::raw_array(self.max),
|
||||
)
|
||||
}
|
||||
impl Into<strafesnet_common::aabb::Aabb> for Aabb{
|
||||
fn into(self)->strafesnet_common::aabb::Aabb{
|
||||
strafesnet_common::aabb::Aabb::new(
|
||||
strafesnet_common::integer::vec3::raw_array(self.min),
|
||||
strafesnet_common::integer::vec3::raw_array(self.max),
|
||||
)
|
||||
}
|
||||
}
|
||||
impl From<strafesnet_common::aabb::Aabb> for Aabb {
|
||||
fn from(value: strafesnet_common::aabb::Aabb) -> Self {
|
||||
Self {
|
||||
max: value.max().map(|t| t.to_raw()).to_array(),
|
||||
min: value.min().map(|t| t.to_raw()).to_array(),
|
||||
}
|
||||
}
|
||||
impl From<strafesnet_common::aabb::Aabb> for Aabb{
|
||||
fn from(value:strafesnet_common::aabb::Aabb)->Self{
|
||||
Self{
|
||||
max:value.max().map(|t|t.to_raw()).to_array(),
|
||||
min:value.min().map(|t|t.to_raw()).to_array(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,3 +1,9 @@
|
||||
pub const fn flag(b: bool, mask: u8) -> u8 {
|
||||
(-(b as i8) as u8) & mask
|
||||
pub const fn flag(b:bool,mask:u8)->u8{
|
||||
(-(b as i8) as u8)&mask
|
||||
}
|
||||
pub fn bool_from_u8(value:u8)->bool{
|
||||
value!=0
|
||||
}
|
||||
pub fn bool_into_u8(value:&bool)->u8{
|
||||
*value as u8
|
||||
}
|
||||
|
@ -1,541 +1,493 @@
|
||||
use super::common::flag;
|
||||
use super::integer::{Planar64, Planar64Vec3, Time};
|
||||
use super::integer::{Time,Planar64,Planar64Vec3};
|
||||
|
||||
#[binrw::binrw]
|
||||
#[brw(little)]
|
||||
pub struct ContactingLadder {
|
||||
#[br(map=|paused:u8|paused!=0)]
|
||||
#[bw(map=|paused:&bool|*paused as u8)]
|
||||
pub sticky: bool,
|
||||
pub struct ContactingLadder{
|
||||
#[br(map=|paused:u8|paused!=0)]
|
||||
#[bw(map=|paused:&bool|*paused as u8)]
|
||||
pub sticky:bool,
|
||||
}
|
||||
impl Into<strafesnet_common::gameplay_attributes::ContactingLadder> for ContactingLadder {
|
||||
fn into(self) -> strafesnet_common::gameplay_attributes::ContactingLadder {
|
||||
strafesnet_common::gameplay_attributes::ContactingLadder {
|
||||
sticky: self.sticky.into(),
|
||||
}
|
||||
}
|
||||
impl Into<strafesnet_common::gameplay_attributes::ContactingLadder> for ContactingLadder{
|
||||
fn into(self)->strafesnet_common::gameplay_attributes::ContactingLadder{
|
||||
strafesnet_common::gameplay_attributes::ContactingLadder{
|
||||
sticky:self.sticky.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl From<strafesnet_common::gameplay_attributes::ContactingLadder> for ContactingLadder {
|
||||
fn from(value: strafesnet_common::gameplay_attributes::ContactingLadder) -> Self {
|
||||
Self {
|
||||
sticky: value.sticky.into(),
|
||||
}
|
||||
}
|
||||
impl From<strafesnet_common::gameplay_attributes::ContactingLadder> for ContactingLadder{
|
||||
fn from(value:strafesnet_common::gameplay_attributes::ContactingLadder)->Self{
|
||||
Self{
|
||||
sticky:value.sticky.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[binrw::binrw]
|
||||
#[brw(little)]
|
||||
pub enum ContactingBehaviour {
|
||||
#[brw(magic = 0u8)]
|
||||
Surf,
|
||||
#[brw(magic = 1u8)]
|
||||
Ladder(ContactingLadder),
|
||||
#[brw(magic = 2u8)]
|
||||
NoJump,
|
||||
#[brw(magic = 3u8)]
|
||||
Cling,
|
||||
#[brw(magic = 4u8)]
|
||||
Elastic(u32),
|
||||
pub enum ContactingBehaviour{
|
||||
#[brw(magic=0u8)]
|
||||
Surf,
|
||||
#[brw(magic=1u8)]
|
||||
Ladder(ContactingLadder),
|
||||
#[brw(magic=2u8)]
|
||||
NoJump,
|
||||
#[brw(magic=3u8)]
|
||||
Cling,
|
||||
#[brw(magic=4u8)]
|
||||
Elastic(u32),
|
||||
}
|
||||
impl Into<strafesnet_common::gameplay_attributes::ContactingBehaviour> for ContactingBehaviour {
|
||||
fn into(self) -> strafesnet_common::gameplay_attributes::ContactingBehaviour {
|
||||
match self {
|
||||
ContactingBehaviour::Surf => {
|
||||
strafesnet_common::gameplay_attributes::ContactingBehaviour::Surf
|
||||
}
|
||||
ContactingBehaviour::Ladder(contacting_ladder) => {
|
||||
strafesnet_common::gameplay_attributes::ContactingBehaviour::Ladder(
|
||||
contacting_ladder.into(),
|
||||
)
|
||||
}
|
||||
ContactingBehaviour::NoJump => {
|
||||
strafesnet_common::gameplay_attributes::ContactingBehaviour::NoJump
|
||||
}
|
||||
ContactingBehaviour::Cling => {
|
||||
strafesnet_common::gameplay_attributes::ContactingBehaviour::Cling
|
||||
}
|
||||
ContactingBehaviour::Elastic(elasticity) => {
|
||||
strafesnet_common::gameplay_attributes::ContactingBehaviour::Elastic(elasticity)
|
||||
}
|
||||
}
|
||||
}
|
||||
impl Into<strafesnet_common::gameplay_attributes::ContactingBehaviour> for ContactingBehaviour{
|
||||
fn into(self)->strafesnet_common::gameplay_attributes::ContactingBehaviour{
|
||||
match self{
|
||||
ContactingBehaviour::Surf=>
|
||||
strafesnet_common::gameplay_attributes::ContactingBehaviour::Surf,
|
||||
ContactingBehaviour::Ladder(contacting_ladder)=>
|
||||
strafesnet_common::gameplay_attributes::ContactingBehaviour::Ladder(
|
||||
contacting_ladder.into(),
|
||||
),
|
||||
ContactingBehaviour::NoJump=>
|
||||
strafesnet_common::gameplay_attributes::ContactingBehaviour::NoJump,
|
||||
ContactingBehaviour::Cling=>
|
||||
strafesnet_common::gameplay_attributes::ContactingBehaviour::Cling,
|
||||
ContactingBehaviour::Elastic(elasticity)=>
|
||||
strafesnet_common::gameplay_attributes::ContactingBehaviour::Elastic(elasticity),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl From<strafesnet_common::gameplay_attributes::ContactingBehaviour> for ContactingBehaviour {
|
||||
fn from(value: strafesnet_common::gameplay_attributes::ContactingBehaviour) -> Self {
|
||||
match value {
|
||||
strafesnet_common::gameplay_attributes::ContactingBehaviour::Surf => {
|
||||
ContactingBehaviour::Surf
|
||||
}
|
||||
strafesnet_common::gameplay_attributes::ContactingBehaviour::Ladder(
|
||||
contacting_ladder,
|
||||
) => ContactingBehaviour::Ladder(contacting_ladder.into()),
|
||||
strafesnet_common::gameplay_attributes::ContactingBehaviour::NoJump => {
|
||||
ContactingBehaviour::NoJump
|
||||
}
|
||||
strafesnet_common::gameplay_attributes::ContactingBehaviour::Cling => {
|
||||
ContactingBehaviour::Cling
|
||||
}
|
||||
strafesnet_common::gameplay_attributes::ContactingBehaviour::Elastic(elasticity) => {
|
||||
ContactingBehaviour::Elastic(elasticity)
|
||||
}
|
||||
}
|
||||
}
|
||||
impl From<strafesnet_common::gameplay_attributes::ContactingBehaviour> for ContactingBehaviour{
|
||||
fn from(value:strafesnet_common::gameplay_attributes::ContactingBehaviour)->Self{
|
||||
match value{
|
||||
strafesnet_common::gameplay_attributes::ContactingBehaviour::Surf=>
|
||||
ContactingBehaviour::Surf,
|
||||
strafesnet_common::gameplay_attributes::ContactingBehaviour::Ladder(contacting_ladder)=>
|
||||
ContactingBehaviour::Ladder(
|
||||
contacting_ladder.into()
|
||||
),
|
||||
strafesnet_common::gameplay_attributes::ContactingBehaviour::NoJump=>
|
||||
ContactingBehaviour::NoJump,
|
||||
strafesnet_common::gameplay_attributes::ContactingBehaviour::Cling=>
|
||||
ContactingBehaviour::Cling,
|
||||
strafesnet_common::gameplay_attributes::ContactingBehaviour::Elastic(elasticity)=>
|
||||
ContactingBehaviour::Elastic(elasticity),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[binrw::binrw]
|
||||
#[brw(little)]
|
||||
pub struct IntersectingWater {
|
||||
pub viscosity: Planar64,
|
||||
pub density: Planar64,
|
||||
pub velocity: Planar64Vec3,
|
||||
pub struct IntersectingWater{
|
||||
pub viscosity:Planar64,
|
||||
pub density:Planar64,
|
||||
pub velocity:Planar64Vec3,
|
||||
}
|
||||
impl Into<strafesnet_common::gameplay_attributes::IntersectingWater> for IntersectingWater {
|
||||
fn into(self) -> strafesnet_common::gameplay_attributes::IntersectingWater {
|
||||
strafesnet_common::gameplay_attributes::IntersectingWater {
|
||||
viscosity: strafesnet_common::integer::Planar64::raw(self.viscosity),
|
||||
density: strafesnet_common::integer::Planar64::raw(self.density),
|
||||
velocity: strafesnet_common::integer::vec3::raw_array(self.velocity),
|
||||
}
|
||||
}
|
||||
impl Into<strafesnet_common::gameplay_attributes::IntersectingWater> for IntersectingWater{
|
||||
fn into(self)->strafesnet_common::gameplay_attributes::IntersectingWater{
|
||||
strafesnet_common::gameplay_attributes::IntersectingWater{
|
||||
viscosity:strafesnet_common::integer::Planar64::raw(self.viscosity),
|
||||
density:strafesnet_common::integer::Planar64::raw(self.density),
|
||||
velocity:strafesnet_common::integer::vec3::raw_array(self.velocity),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl From<strafesnet_common::gameplay_attributes::IntersectingWater> for IntersectingWater {
|
||||
fn from(value: strafesnet_common::gameplay_attributes::IntersectingWater) -> Self {
|
||||
Self {
|
||||
viscosity: value.viscosity.to_raw(),
|
||||
density: value.density.to_raw(),
|
||||
velocity: value.velocity.map(|t| t.to_raw()).to_array(),
|
||||
}
|
||||
}
|
||||
impl From<strafesnet_common::gameplay_attributes::IntersectingWater> for IntersectingWater{
|
||||
fn from(value:strafesnet_common::gameplay_attributes::IntersectingWater)->Self{
|
||||
Self{
|
||||
viscosity:value.viscosity.to_raw(),
|
||||
density:value.density.to_raw(),
|
||||
velocity:value.velocity.map(|t|t.to_raw()).to_array(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[binrw::binrw]
|
||||
#[brw(little)]
|
||||
pub struct Accelerator {
|
||||
pub acceleration: Planar64Vec3,
|
||||
pub struct Accelerator{
|
||||
pub acceleration:Planar64Vec3
|
||||
}
|
||||
impl Into<strafesnet_common::gameplay_attributes::Accelerator> for Accelerator {
|
||||
fn into(self) -> strafesnet_common::gameplay_attributes::Accelerator {
|
||||
strafesnet_common::gameplay_attributes::Accelerator {
|
||||
acceleration: strafesnet_common::integer::vec3::raw_array(self.acceleration),
|
||||
}
|
||||
}
|
||||
impl Into<strafesnet_common::gameplay_attributes::Accelerator> for Accelerator{
|
||||
fn into(self)->strafesnet_common::gameplay_attributes::Accelerator{
|
||||
strafesnet_common::gameplay_attributes::Accelerator{
|
||||
acceleration:strafesnet_common::integer::vec3::raw_array(self.acceleration)
|
||||
}
|
||||
}
|
||||
}
|
||||
impl From<strafesnet_common::gameplay_attributes::Accelerator> for Accelerator {
|
||||
fn from(value: strafesnet_common::gameplay_attributes::Accelerator) -> Self {
|
||||
Self {
|
||||
acceleration: value.acceleration.map(|t| t.to_raw()).to_array(),
|
||||
}
|
||||
}
|
||||
impl From<strafesnet_common::gameplay_attributes::Accelerator> for Accelerator{
|
||||
fn from(value:strafesnet_common::gameplay_attributes::Accelerator)->Self{
|
||||
Self{
|
||||
acceleration:value.acceleration.map(|t|t.to_raw()).to_array(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[binrw::binrw]
|
||||
#[brw(little)]
|
||||
pub enum Booster {
|
||||
#[brw(magic = 0u8)]
|
||||
Velocity(Planar64Vec3),
|
||||
#[brw(magic = 1u8)]
|
||||
Energy {
|
||||
direction: Planar64Vec3,
|
||||
energy: Planar64,
|
||||
},
|
||||
#[brw(magic = 2u8)]
|
||||
AirTime(Time),
|
||||
#[brw(magic = 3u8)]
|
||||
Height(Planar64),
|
||||
pub enum Booster{
|
||||
#[brw(magic=0u8)]
|
||||
Velocity(Planar64Vec3),
|
||||
#[brw(magic=1u8)]
|
||||
Energy{direction:Planar64Vec3,energy:Planar64},
|
||||
#[brw(magic=2u8)]
|
||||
AirTime(Time),
|
||||
#[brw(magic=3u8)]
|
||||
Height(Planar64),
|
||||
|
||||
}
|
||||
impl Into<strafesnet_common::gameplay_attributes::Booster> for Booster {
|
||||
fn into(self) -> strafesnet_common::gameplay_attributes::Booster {
|
||||
match self {
|
||||
Booster::Velocity(velocity) => {
|
||||
strafesnet_common::gameplay_attributes::Booster::Velocity(
|
||||
strafesnet_common::integer::vec3::raw_array(velocity),
|
||||
)
|
||||
}
|
||||
Booster::Energy { direction, energy } => {
|
||||
strafesnet_common::gameplay_attributes::Booster::Energy {
|
||||
direction: strafesnet_common::integer::vec3::raw_array(direction),
|
||||
energy: strafesnet_common::integer::Planar64::raw(energy),
|
||||
}
|
||||
}
|
||||
Booster::AirTime(time) => strafesnet_common::gameplay_attributes::Booster::AirTime(
|
||||
strafesnet_common::integer::Time::raw(time),
|
||||
),
|
||||
Booster::Height(height) => strafesnet_common::gameplay_attributes::Booster::Height(
|
||||
strafesnet_common::integer::Planar64::raw(height),
|
||||
),
|
||||
}
|
||||
}
|
||||
impl Into<strafesnet_common::gameplay_attributes::Booster> for Booster{
|
||||
fn into(self)->strafesnet_common::gameplay_attributes::Booster{
|
||||
match self{
|
||||
Booster::Velocity(velocity)=>
|
||||
strafesnet_common::gameplay_attributes::Booster::Velocity(
|
||||
strafesnet_common::integer::vec3::raw_array(velocity)
|
||||
),
|
||||
Booster::Energy{direction,energy}=>
|
||||
strafesnet_common::gameplay_attributes::Booster::Energy{
|
||||
direction:strafesnet_common::integer::vec3::raw_array(direction),
|
||||
energy:strafesnet_common::integer::Planar64::raw(energy)
|
||||
},
|
||||
Booster::AirTime(time)=>
|
||||
strafesnet_common::gameplay_attributes::Booster::AirTime(
|
||||
strafesnet_common::integer::Time::raw(time)
|
||||
),
|
||||
Booster::Height(height)=>
|
||||
strafesnet_common::gameplay_attributes::Booster::Height(
|
||||
strafesnet_common::integer::Planar64::raw(height)
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl From<strafesnet_common::gameplay_attributes::Booster> for Booster {
|
||||
fn from(value: strafesnet_common::gameplay_attributes::Booster) -> Self {
|
||||
match value {
|
||||
strafesnet_common::gameplay_attributes::Booster::Velocity(velocity) => {
|
||||
Booster::Velocity(velocity.map(|t| t.to_raw()).to_array())
|
||||
}
|
||||
strafesnet_common::gameplay_attributes::Booster::Energy { direction, energy } => {
|
||||
Booster::Energy {
|
||||
direction: direction.map(|t| t.to_raw()).to_array(),
|
||||
energy: energy.to_raw(),
|
||||
}
|
||||
}
|
||||
strafesnet_common::gameplay_attributes::Booster::AirTime(time) => {
|
||||
Booster::AirTime(time.get())
|
||||
}
|
||||
strafesnet_common::gameplay_attributes::Booster::Height(height) => {
|
||||
Booster::Height(height.to_raw())
|
||||
}
|
||||
}
|
||||
}
|
||||
impl From<strafesnet_common::gameplay_attributes::Booster> for Booster{
|
||||
fn from(value:strafesnet_common::gameplay_attributes::Booster)->Self{
|
||||
match value{
|
||||
strafesnet_common::gameplay_attributes::Booster::Velocity(velocity)=>
|
||||
Booster::Velocity(velocity.map(|t|t.to_raw()).to_array()),
|
||||
strafesnet_common::gameplay_attributes::Booster::Energy{direction,energy}=>
|
||||
Booster::Energy{
|
||||
direction:direction.map(|t|t.to_raw()).to_array(),
|
||||
energy:energy.to_raw(),
|
||||
},
|
||||
strafesnet_common::gameplay_attributes::Booster::AirTime(time)=>
|
||||
Booster::AirTime(time.get()),
|
||||
strafesnet_common::gameplay_attributes::Booster::Height(height)=>
|
||||
Booster::Height(height.to_raw()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[binrw::binrw]
|
||||
#[brw(little,repr=u8)]
|
||||
pub enum TrajectoryChoice {
|
||||
HighArcLongDuration,
|
||||
LowArcShortDuration,
|
||||
pub enum TrajectoryChoice{
|
||||
HighArcLongDuration,
|
||||
LowArcShortDuration,
|
||||
}
|
||||
impl Into<strafesnet_common::gameplay_attributes::TrajectoryChoice> for TrajectoryChoice {
|
||||
fn into(self) -> strafesnet_common::gameplay_attributes::TrajectoryChoice {
|
||||
match self {
|
||||
TrajectoryChoice::HighArcLongDuration => {
|
||||
strafesnet_common::gameplay_attributes::TrajectoryChoice::HighArcLongDuration
|
||||
}
|
||||
TrajectoryChoice::LowArcShortDuration => {
|
||||
strafesnet_common::gameplay_attributes::TrajectoryChoice::LowArcShortDuration
|
||||
}
|
||||
}
|
||||
}
|
||||
impl Into<strafesnet_common::gameplay_attributes::TrajectoryChoice> for TrajectoryChoice{
|
||||
fn into(self)->strafesnet_common::gameplay_attributes::TrajectoryChoice{
|
||||
match self{
|
||||
TrajectoryChoice::HighArcLongDuration=>
|
||||
strafesnet_common::gameplay_attributes::TrajectoryChoice::HighArcLongDuration,
|
||||
TrajectoryChoice::LowArcShortDuration=>
|
||||
strafesnet_common::gameplay_attributes::TrajectoryChoice::LowArcShortDuration,
|
||||
}
|
||||
}
|
||||
}
|
||||
impl From<strafesnet_common::gameplay_attributes::TrajectoryChoice> for TrajectoryChoice {
|
||||
fn from(value: strafesnet_common::gameplay_attributes::TrajectoryChoice) -> Self {
|
||||
match value {
|
||||
strafesnet_common::gameplay_attributes::TrajectoryChoice::HighArcLongDuration => {
|
||||
TrajectoryChoice::HighArcLongDuration
|
||||
}
|
||||
strafesnet_common::gameplay_attributes::TrajectoryChoice::LowArcShortDuration => {
|
||||
TrajectoryChoice::LowArcShortDuration
|
||||
}
|
||||
}
|
||||
}
|
||||
impl From<strafesnet_common::gameplay_attributes::TrajectoryChoice> for TrajectoryChoice{
|
||||
fn from(value:strafesnet_common::gameplay_attributes::TrajectoryChoice)->Self{
|
||||
match value{
|
||||
strafesnet_common::gameplay_attributes::TrajectoryChoice::HighArcLongDuration=>
|
||||
TrajectoryChoice::HighArcLongDuration,
|
||||
strafesnet_common::gameplay_attributes::TrajectoryChoice::LowArcShortDuration=>
|
||||
TrajectoryChoice::LowArcShortDuration,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[binrw::binrw]
|
||||
#[brw(little)]
|
||||
pub enum SetTrajectory {
|
||||
#[brw(magic = 0u8)]
|
||||
AirTime(Time),
|
||||
#[brw(magic = 1u8)]
|
||||
Height(Planar64),
|
||||
#[brw(magic = 2u8)]
|
||||
DotVelocity {
|
||||
direction: Planar64Vec3,
|
||||
dot: Planar64,
|
||||
},
|
||||
#[brw(magic = 3u8)]
|
||||
TargetPointTime {
|
||||
target_point: Planar64Vec3,
|
||||
time: Time,
|
||||
},
|
||||
#[brw(magic = 4u8)]
|
||||
TargetPointSpeed {
|
||||
target_point: Planar64Vec3,
|
||||
speed: Planar64,
|
||||
trajectory_choice: TrajectoryChoice,
|
||||
},
|
||||
#[brw(magic = 5u8)]
|
||||
Velocity(Planar64Vec3),
|
||||
pub enum SetTrajectory{
|
||||
#[brw(magic=0u8)]
|
||||
AirTime(Time),
|
||||
#[brw(magic=1u8)]
|
||||
Height(Planar64),
|
||||
#[brw(magic=2u8)]
|
||||
DotVelocity{direction:Planar64Vec3,dot:Planar64},
|
||||
#[brw(magic=3u8)]
|
||||
TargetPointTime{
|
||||
target_point:Planar64Vec3,
|
||||
time:Time,
|
||||
},
|
||||
#[brw(magic=4u8)]
|
||||
TargetPointSpeed{
|
||||
target_point:Planar64Vec3,
|
||||
speed:Planar64,
|
||||
trajectory_choice:TrajectoryChoice,
|
||||
},
|
||||
#[brw(magic=5u8)]
|
||||
Velocity(Planar64Vec3),
|
||||
}
|
||||
impl Into<strafesnet_common::gameplay_attributes::SetTrajectory> for SetTrajectory {
|
||||
fn into(self) -> strafesnet_common::gameplay_attributes::SetTrajectory {
|
||||
match self {
|
||||
SetTrajectory::AirTime(time) => {
|
||||
strafesnet_common::gameplay_attributes::SetTrajectory::AirTime(
|
||||
strafesnet_common::integer::Time::raw(time),
|
||||
)
|
||||
}
|
||||
SetTrajectory::Height(height) => {
|
||||
strafesnet_common::gameplay_attributes::SetTrajectory::Height(
|
||||
strafesnet_common::integer::Planar64::raw(height),
|
||||
)
|
||||
}
|
||||
SetTrajectory::DotVelocity { direction, dot } => {
|
||||
strafesnet_common::gameplay_attributes::SetTrajectory::DotVelocity {
|
||||
direction: strafesnet_common::integer::vec3::raw_array(direction),
|
||||
dot: strafesnet_common::integer::Planar64::raw(dot),
|
||||
}
|
||||
}
|
||||
SetTrajectory::TargetPointTime { target_point, time } => {
|
||||
strafesnet_common::gameplay_attributes::SetTrajectory::TargetPointTime {
|
||||
target_point: strafesnet_common::integer::vec3::raw_array(target_point),
|
||||
time: strafesnet_common::integer::Time::raw(time),
|
||||
}
|
||||
}
|
||||
SetTrajectory::TargetPointSpeed {
|
||||
target_point,
|
||||
speed,
|
||||
trajectory_choice,
|
||||
} => strafesnet_common::gameplay_attributes::SetTrajectory::TargetPointSpeed {
|
||||
target_point: strafesnet_common::integer::vec3::raw_array(target_point),
|
||||
speed: strafesnet_common::integer::Planar64::raw(speed),
|
||||
trajectory_choice: trajectory_choice.into(),
|
||||
},
|
||||
SetTrajectory::Velocity(velocity) => {
|
||||
strafesnet_common::gameplay_attributes::SetTrajectory::Velocity(
|
||||
strafesnet_common::integer::vec3::raw_array(velocity),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
impl Into<strafesnet_common::gameplay_attributes::SetTrajectory> for SetTrajectory{
|
||||
fn into(self)->strafesnet_common::gameplay_attributes::SetTrajectory{
|
||||
match self{
|
||||
SetTrajectory::AirTime(time)=>
|
||||
strafesnet_common::gameplay_attributes::SetTrajectory::AirTime(
|
||||
strafesnet_common::integer::Time::raw(time)
|
||||
),
|
||||
SetTrajectory::Height(height)=>
|
||||
strafesnet_common::gameplay_attributes::SetTrajectory::Height(
|
||||
strafesnet_common::integer::Planar64::raw(height)
|
||||
),
|
||||
SetTrajectory::DotVelocity{direction,dot}=>
|
||||
strafesnet_common::gameplay_attributes::SetTrajectory::DotVelocity{
|
||||
direction:strafesnet_common::integer::vec3::raw_array(direction),
|
||||
dot:strafesnet_common::integer::Planar64::raw(dot),
|
||||
},
|
||||
SetTrajectory::TargetPointTime{target_point,time}=>
|
||||
strafesnet_common::gameplay_attributes::SetTrajectory::TargetPointTime{
|
||||
target_point:strafesnet_common::integer::vec3::raw_array(target_point),
|
||||
time:strafesnet_common::integer::Time::raw(time),
|
||||
},
|
||||
SetTrajectory::TargetPointSpeed{target_point,speed,trajectory_choice}=>
|
||||
strafesnet_common::gameplay_attributes::SetTrajectory::TargetPointSpeed{
|
||||
target_point:strafesnet_common::integer::vec3::raw_array(target_point),
|
||||
speed:strafesnet_common::integer::Planar64::raw(speed),
|
||||
trajectory_choice:trajectory_choice.into(),
|
||||
},
|
||||
SetTrajectory::Velocity(velocity)=>
|
||||
strafesnet_common::gameplay_attributes::SetTrajectory::Velocity(
|
||||
strafesnet_common::integer::vec3::raw_array(velocity)
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl From<strafesnet_common::gameplay_attributes::SetTrajectory> for SetTrajectory {
|
||||
fn from(value: strafesnet_common::gameplay_attributes::SetTrajectory) -> Self {
|
||||
match value {
|
||||
strafesnet_common::gameplay_attributes::SetTrajectory::AirTime(time) => {
|
||||
SetTrajectory::AirTime(time.get())
|
||||
}
|
||||
strafesnet_common::gameplay_attributes::SetTrajectory::Height(height) => {
|
||||
SetTrajectory::Height(height.to_raw())
|
||||
}
|
||||
strafesnet_common::gameplay_attributes::SetTrajectory::DotVelocity {
|
||||
direction,
|
||||
dot,
|
||||
} => SetTrajectory::DotVelocity {
|
||||
direction: direction.map(|t| t.to_raw()).to_array(),
|
||||
dot: dot.to_raw(),
|
||||
},
|
||||
strafesnet_common::gameplay_attributes::SetTrajectory::TargetPointTime {
|
||||
target_point,
|
||||
time,
|
||||
} => SetTrajectory::TargetPointTime {
|
||||
target_point: target_point.map(|t| t.to_raw()).to_array(),
|
||||
time: time.get(),
|
||||
},
|
||||
strafesnet_common::gameplay_attributes::SetTrajectory::TargetPointSpeed {
|
||||
target_point,
|
||||
speed,
|
||||
trajectory_choice,
|
||||
} => SetTrajectory::TargetPointSpeed {
|
||||
target_point: target_point.map(|t| t.to_raw()).to_array(),
|
||||
speed: speed.to_raw(),
|
||||
trajectory_choice: trajectory_choice.into(),
|
||||
},
|
||||
strafesnet_common::gameplay_attributes::SetTrajectory::Velocity(velocity) => {
|
||||
SetTrajectory::Velocity(velocity.map(|t| t.to_raw()).to_array())
|
||||
}
|
||||
}
|
||||
}
|
||||
impl From<strafesnet_common::gameplay_attributes::SetTrajectory> for SetTrajectory{
|
||||
fn from(value:strafesnet_common::gameplay_attributes::SetTrajectory)->Self{
|
||||
match value{
|
||||
strafesnet_common::gameplay_attributes::SetTrajectory::AirTime(time)=>
|
||||
SetTrajectory::AirTime(
|
||||
time.get()
|
||||
),
|
||||
strafesnet_common::gameplay_attributes::SetTrajectory::Height(height)=>
|
||||
SetTrajectory::Height(
|
||||
height.to_raw()
|
||||
),
|
||||
strafesnet_common::gameplay_attributes::SetTrajectory::DotVelocity{direction,dot}=>
|
||||
SetTrajectory::DotVelocity{
|
||||
direction:direction.map(|t|t.to_raw()).to_array(),
|
||||
dot:dot.to_raw(),
|
||||
},
|
||||
strafesnet_common::gameplay_attributes::SetTrajectory::TargetPointTime{target_point,time}=>
|
||||
SetTrajectory::TargetPointTime{
|
||||
target_point:target_point.map(|t|t.to_raw()).to_array(),
|
||||
time:time.get(),
|
||||
},
|
||||
strafesnet_common::gameplay_attributes::SetTrajectory::TargetPointSpeed{target_point,speed,trajectory_choice}=>
|
||||
SetTrajectory::TargetPointSpeed{
|
||||
target_point:target_point.map(|t|t.to_raw()).to_array(),
|
||||
speed:speed.to_raw(),
|
||||
trajectory_choice:trajectory_choice.into(),
|
||||
},
|
||||
strafesnet_common::gameplay_attributes::SetTrajectory::Velocity(velocity)=>
|
||||
SetTrajectory::Velocity(
|
||||
velocity.map(|t|t.to_raw()).to_array()
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[binrw::binrw]
|
||||
#[brw(little)]
|
||||
pub struct Wormhole {
|
||||
pub destination_model: u32,
|
||||
pub struct Wormhole{
|
||||
pub destination_model:u32,
|
||||
}
|
||||
impl Into<strafesnet_common::gameplay_attributes::Wormhole> for Wormhole {
|
||||
fn into(self) -> strafesnet_common::gameplay_attributes::Wormhole {
|
||||
strafesnet_common::gameplay_attributes::Wormhole {
|
||||
destination_model: strafesnet_common::model::ModelId::new(self.destination_model),
|
||||
}
|
||||
}
|
||||
impl Into<strafesnet_common::gameplay_attributes::Wormhole> for Wormhole{
|
||||
fn into(self)->strafesnet_common::gameplay_attributes::Wormhole{
|
||||
strafesnet_common::gameplay_attributes::Wormhole{
|
||||
destination_model:strafesnet_common::model::ModelId::new(self.destination_model),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl From<strafesnet_common::gameplay_attributes::Wormhole> for Wormhole {
|
||||
fn from(value: strafesnet_common::gameplay_attributes::Wormhole) -> Self {
|
||||
Self {
|
||||
destination_model: value.destination_model.get(),
|
||||
}
|
||||
}
|
||||
impl From<strafesnet_common::gameplay_attributes::Wormhole> for Wormhole{
|
||||
fn from(value:strafesnet_common::gameplay_attributes::Wormhole)->Self{
|
||||
Self{
|
||||
destination_model:value.destination_model.get(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[binrw::binrw]
|
||||
#[brw(little)]
|
||||
pub struct GeneralAttributes {
|
||||
pub header: u8,
|
||||
#[br(if(header&Self::BOOSTER!=0))]
|
||||
pub booster: Option<Booster>,
|
||||
#[br(if(header&Self::TRAJECTORY!=0))]
|
||||
pub trajectory: Option<SetTrajectory>,
|
||||
#[br(if(header&Self::WORMHOLE!=0))]
|
||||
pub wormhole: Option<Wormhole>,
|
||||
#[br(if(header&Self::ACCELERATOR!=0))]
|
||||
pub accelerator: Option<Accelerator>,
|
||||
pub struct GeneralAttributes{
|
||||
pub header:u8,
|
||||
#[br(if(header&Self::BOOSTER!=0))]
|
||||
pub booster:Option<Booster>,
|
||||
#[br(if(header&Self::TRAJECTORY!=0))]
|
||||
pub trajectory:Option<SetTrajectory>,
|
||||
#[br(if(header&Self::WORMHOLE!=0))]
|
||||
pub wormhole:Option<Wormhole>,
|
||||
#[br(if(header&Self::ACCELERATOR!=0))]
|
||||
pub accelerator:Option<Accelerator>,
|
||||
}
|
||||
impl GeneralAttributes {
|
||||
const BOOSTER: u8 = 1 << 0;
|
||||
const TRAJECTORY: u8 = 1 << 1;
|
||||
const WORMHOLE: u8 = 1 << 2;
|
||||
const ACCELERATOR: u8 = 1 << 3;
|
||||
impl GeneralAttributes{
|
||||
const BOOSTER:u8=1<<0;
|
||||
const TRAJECTORY:u8=1<<1;
|
||||
const WORMHOLE:u8=1<<2;
|
||||
const ACCELERATOR:u8=1<<3;
|
||||
}
|
||||
impl Into<strafesnet_common::gameplay_attributes::GeneralAttributes> for GeneralAttributes {
|
||||
fn into(self) -> strafesnet_common::gameplay_attributes::GeneralAttributes {
|
||||
strafesnet_common::gameplay_attributes::GeneralAttributes {
|
||||
booster: self.booster.map(Into::into),
|
||||
trajectory: self.trajectory.map(Into::into),
|
||||
wormhole: self.wormhole.map(Into::into),
|
||||
accelerator: self.accelerator.map(Into::into),
|
||||
}
|
||||
}
|
||||
impl Into<strafesnet_common::gameplay_attributes::GeneralAttributes> for GeneralAttributes{
|
||||
fn into(self)->strafesnet_common::gameplay_attributes::GeneralAttributes{
|
||||
strafesnet_common::gameplay_attributes::GeneralAttributes{
|
||||
booster:self.booster.map(Into::into),
|
||||
trajectory:self.trajectory.map(Into::into),
|
||||
wormhole:self.wormhole.map(Into::into),
|
||||
accelerator:self.accelerator.map(Into::into),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl From<strafesnet_common::gameplay_attributes::GeneralAttributes> for GeneralAttributes {
|
||||
fn from(value: strafesnet_common::gameplay_attributes::GeneralAttributes) -> Self {
|
||||
let header = flag(value.booster.is_some(), GeneralAttributes::BOOSTER)
|
||||
| flag(value.trajectory.is_some(), GeneralAttributes::TRAJECTORY)
|
||||
| flag(value.wormhole.is_some(), GeneralAttributes::WORMHOLE)
|
||||
| flag(value.accelerator.is_some(), GeneralAttributes::ACCELERATOR);
|
||||
Self {
|
||||
header,
|
||||
booster: value.booster.map(Into::into),
|
||||
trajectory: value.trajectory.map(Into::into),
|
||||
wormhole: value.wormhole.map(Into::into),
|
||||
accelerator: value.accelerator.map(Into::into),
|
||||
}
|
||||
}
|
||||
impl From<strafesnet_common::gameplay_attributes::GeneralAttributes> for GeneralAttributes{
|
||||
fn from(value:strafesnet_common::gameplay_attributes::GeneralAttributes)->Self{
|
||||
let header=
|
||||
flag(value.booster.is_some(),GeneralAttributes::BOOSTER)
|
||||
|flag(value.trajectory.is_some(),GeneralAttributes::TRAJECTORY)
|
||||
|flag(value.wormhole.is_some(),GeneralAttributes::WORMHOLE)
|
||||
|flag(value.accelerator.is_some(),GeneralAttributes::ACCELERATOR);
|
||||
Self{
|
||||
header,
|
||||
booster:value.booster.map(Into::into),
|
||||
trajectory:value.trajectory.map(Into::into),
|
||||
wormhole:value.wormhole.map(Into::into),
|
||||
accelerator:value.accelerator.map(Into::into),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[binrw::binrw]
|
||||
#[brw(little)]
|
||||
pub struct ContactingAttributes {
|
||||
pub header: u8,
|
||||
#[br(if(header&Self::CONTACTING_BEHAVIOUR!=0))]
|
||||
pub contact_behaviour: Option<ContactingBehaviour>,
|
||||
pub struct ContactingAttributes{
|
||||
pub header:u8,
|
||||
#[br(if(header&Self::CONTACTING_BEHAVIOUR!=0))]
|
||||
pub contact_behaviour:Option<ContactingBehaviour>,
|
||||
}
|
||||
impl ContactingAttributes {
|
||||
const CONTACTING_BEHAVIOUR: u8 = 1 << 0;
|
||||
impl ContactingAttributes{
|
||||
const CONTACTING_BEHAVIOUR:u8=1<<0;
|
||||
}
|
||||
impl Into<strafesnet_common::gameplay_attributes::ContactingAttributes> for ContactingAttributes {
|
||||
fn into(self) -> strafesnet_common::gameplay_attributes::ContactingAttributes {
|
||||
strafesnet_common::gameplay_attributes::ContactingAttributes {
|
||||
contact_behaviour: self.contact_behaviour.map(Into::into),
|
||||
}
|
||||
}
|
||||
impl Into<strafesnet_common::gameplay_attributes::ContactingAttributes> for ContactingAttributes{
|
||||
fn into(self)->strafesnet_common::gameplay_attributes::ContactingAttributes{
|
||||
strafesnet_common::gameplay_attributes::ContactingAttributes{
|
||||
contact_behaviour:self.contact_behaviour.map(Into::into),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl From<strafesnet_common::gameplay_attributes::ContactingAttributes> for ContactingAttributes {
|
||||
fn from(value: strafesnet_common::gameplay_attributes::ContactingAttributes) -> Self {
|
||||
Self {
|
||||
header: flag(
|
||||
value.contact_behaviour.is_some(),
|
||||
ContactingAttributes::CONTACTING_BEHAVIOUR,
|
||||
),
|
||||
contact_behaviour: value.contact_behaviour.map(Into::into),
|
||||
}
|
||||
}
|
||||
impl From<strafesnet_common::gameplay_attributes::ContactingAttributes> for ContactingAttributes{
|
||||
fn from(value:strafesnet_common::gameplay_attributes::ContactingAttributes)->Self{
|
||||
Self{
|
||||
header:flag(value.contact_behaviour.is_some(),ContactingAttributes::CONTACTING_BEHAVIOUR),
|
||||
contact_behaviour:value.contact_behaviour.map(Into::into),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[binrw::binrw]
|
||||
#[brw(little)]
|
||||
pub struct IntersectingAttributes {
|
||||
pub header: u8,
|
||||
#[br(if(header&Self::INTERSECTING_WATER!=0))]
|
||||
pub water: Option<IntersectingWater>,
|
||||
pub struct IntersectingAttributes{
|
||||
pub header:u8,
|
||||
#[br(if(header&Self::INTERSECTING_WATER!=0))]
|
||||
pub water:Option<IntersectingWater>,
|
||||
}
|
||||
impl IntersectingAttributes {
|
||||
const INTERSECTING_WATER: u8 = 1 << 0;
|
||||
impl IntersectingAttributes{
|
||||
const INTERSECTING_WATER:u8=1<<0;
|
||||
}
|
||||
impl Into<strafesnet_common::gameplay_attributes::IntersectingAttributes>
|
||||
for IntersectingAttributes
|
||||
{
|
||||
fn into(self) -> strafesnet_common::gameplay_attributes::IntersectingAttributes {
|
||||
strafesnet_common::gameplay_attributes::IntersectingAttributes {
|
||||
water: self.water.map(Into::into),
|
||||
}
|
||||
}
|
||||
impl Into<strafesnet_common::gameplay_attributes::IntersectingAttributes> for IntersectingAttributes{
|
||||
fn into(self)->strafesnet_common::gameplay_attributes::IntersectingAttributes{
|
||||
strafesnet_common::gameplay_attributes::IntersectingAttributes{
|
||||
water:self.water.map(Into::into),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl From<strafesnet_common::gameplay_attributes::IntersectingAttributes>
|
||||
for IntersectingAttributes
|
||||
{
|
||||
fn from(value: strafesnet_common::gameplay_attributes::IntersectingAttributes) -> Self {
|
||||
Self {
|
||||
header: flag(
|
||||
value.water.is_some(),
|
||||
IntersectingAttributes::INTERSECTING_WATER,
|
||||
),
|
||||
water: value.water.map(Into::into),
|
||||
}
|
||||
}
|
||||
impl From<strafesnet_common::gameplay_attributes::IntersectingAttributes> for IntersectingAttributes{
|
||||
fn from(value:strafesnet_common::gameplay_attributes::IntersectingAttributes)->Self{
|
||||
Self{
|
||||
header:flag(value.water.is_some(),IntersectingAttributes::INTERSECTING_WATER),
|
||||
water:value.water.map(Into::into),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[binrw::binrw]
|
||||
#[brw(little)]
|
||||
pub struct ContactAttributes {
|
||||
contacting: ContactingAttributes,
|
||||
general: GeneralAttributes,
|
||||
pub struct ContactAttributes{
|
||||
contacting:ContactingAttributes,
|
||||
general:GeneralAttributes,
|
||||
}
|
||||
impl Into<strafesnet_common::gameplay_attributes::ContactAttributes> for ContactAttributes {
|
||||
fn into(self) -> strafesnet_common::gameplay_attributes::ContactAttributes {
|
||||
strafesnet_common::gameplay_attributes::ContactAttributes {
|
||||
contacting: self.contacting.into(),
|
||||
general: self.general.into(),
|
||||
}
|
||||
}
|
||||
impl Into<strafesnet_common::gameplay_attributes::ContactAttributes> for ContactAttributes{
|
||||
fn into(self)->strafesnet_common::gameplay_attributes::ContactAttributes{
|
||||
strafesnet_common::gameplay_attributes::ContactAttributes{
|
||||
contacting:self.contacting.into(),
|
||||
general:self.general.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl From<strafesnet_common::gameplay_attributes::ContactAttributes> for ContactAttributes {
|
||||
fn from(value: strafesnet_common::gameplay_attributes::ContactAttributes) -> Self {
|
||||
Self {
|
||||
contacting: value.contacting.into(),
|
||||
general: value.general.into(),
|
||||
}
|
||||
}
|
||||
impl From<strafesnet_common::gameplay_attributes::ContactAttributes> for ContactAttributes{
|
||||
fn from(value:strafesnet_common::gameplay_attributes::ContactAttributes)->Self{
|
||||
Self{
|
||||
contacting:value.contacting.into(),
|
||||
general:value.general.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[binrw::binrw]
|
||||
#[brw(little)]
|
||||
pub struct IntersectAttributes {
|
||||
intersecting: IntersectingAttributes,
|
||||
general: GeneralAttributes,
|
||||
pub struct IntersectAttributes{
|
||||
intersecting:IntersectingAttributes,
|
||||
general:GeneralAttributes,
|
||||
}
|
||||
impl Into<strafesnet_common::gameplay_attributes::IntersectAttributes> for IntersectAttributes {
|
||||
fn into(self) -> strafesnet_common::gameplay_attributes::IntersectAttributes {
|
||||
strafesnet_common::gameplay_attributes::IntersectAttributes {
|
||||
intersecting: self.intersecting.into(),
|
||||
general: self.general.into(),
|
||||
}
|
||||
}
|
||||
impl Into<strafesnet_common::gameplay_attributes::IntersectAttributes> for IntersectAttributes{
|
||||
fn into(self)->strafesnet_common::gameplay_attributes::IntersectAttributes{
|
||||
strafesnet_common::gameplay_attributes::IntersectAttributes{
|
||||
intersecting:self.intersecting.into(),
|
||||
general:self.general.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl From<strafesnet_common::gameplay_attributes::IntersectAttributes> for IntersectAttributes {
|
||||
fn from(value: strafesnet_common::gameplay_attributes::IntersectAttributes) -> Self {
|
||||
Self {
|
||||
intersecting: value.intersecting.into(),
|
||||
general: value.general.into(),
|
||||
}
|
||||
}
|
||||
impl From<strafesnet_common::gameplay_attributes::IntersectAttributes> for IntersectAttributes{
|
||||
fn from(value:strafesnet_common::gameplay_attributes::IntersectAttributes)->Self{
|
||||
Self{
|
||||
intersecting:value.intersecting.into(),
|
||||
general:value.general.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[binrw::binrw]
|
||||
#[brw(little)]
|
||||
pub enum CollisionAttributes {
|
||||
#[brw(magic = 0u8)]
|
||||
Decoration,
|
||||
#[brw(magic = 1u8)]
|
||||
Contact(ContactAttributes),
|
||||
#[brw(magic = 2u8)]
|
||||
Intersect(IntersectAttributes),
|
||||
pub enum CollisionAttributes{
|
||||
#[brw(magic=0u8)]
|
||||
Decoration,
|
||||
#[brw(magic=1u8)]
|
||||
Contact(ContactAttributes),
|
||||
#[brw(magic=2u8)]
|
||||
Intersect(IntersectAttributes),
|
||||
}
|
||||
impl Into<strafesnet_common::gameplay_attributes::CollisionAttributes> for CollisionAttributes {
|
||||
fn into(self) -> strafesnet_common::gameplay_attributes::CollisionAttributes {
|
||||
match self {
|
||||
CollisionAttributes::Decoration => {
|
||||
strafesnet_common::gameplay_attributes::CollisionAttributes::Decoration
|
||||
}
|
||||
CollisionAttributes::Contact(attr) => {
|
||||
strafesnet_common::gameplay_attributes::CollisionAttributes::Contact(attr.into())
|
||||
}
|
||||
CollisionAttributes::Intersect(attr) => {
|
||||
strafesnet_common::gameplay_attributes::CollisionAttributes::Intersect(attr.into())
|
||||
}
|
||||
}
|
||||
}
|
||||
impl Into<strafesnet_common::gameplay_attributes::CollisionAttributes> for CollisionAttributes{
|
||||
fn into(self)->strafesnet_common::gameplay_attributes::CollisionAttributes{
|
||||
match self{
|
||||
CollisionAttributes::Decoration=>
|
||||
strafesnet_common::gameplay_attributes::CollisionAttributes::Decoration,
|
||||
CollisionAttributes::Contact(attr)=>
|
||||
strafesnet_common::gameplay_attributes::CollisionAttributes::Contact(attr.into()),
|
||||
CollisionAttributes::Intersect(attr)=>
|
||||
strafesnet_common::gameplay_attributes::CollisionAttributes::Intersect(attr.into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl From<strafesnet_common::gameplay_attributes::CollisionAttributes> for CollisionAttributes {
|
||||
fn from(value: strafesnet_common::gameplay_attributes::CollisionAttributes) -> Self {
|
||||
match value {
|
||||
strafesnet_common::gameplay_attributes::CollisionAttributes::Decoration => {
|
||||
CollisionAttributes::Decoration
|
||||
}
|
||||
strafesnet_common::gameplay_attributes::CollisionAttributes::Contact(attr) => {
|
||||
CollisionAttributes::Contact(attr.into())
|
||||
}
|
||||
strafesnet_common::gameplay_attributes::CollisionAttributes::Intersect(attr) => {
|
||||
CollisionAttributes::Intersect(attr.into())
|
||||
}
|
||||
}
|
||||
}
|
||||
impl From<strafesnet_common::gameplay_attributes::CollisionAttributes> for CollisionAttributes{
|
||||
fn from(value:strafesnet_common::gameplay_attributes::CollisionAttributes)->Self{
|
||||
match value{
|
||||
strafesnet_common::gameplay_attributes::CollisionAttributes::Decoration=>
|
||||
CollisionAttributes::Decoration,
|
||||
strafesnet_common::gameplay_attributes::CollisionAttributes::Contact(attr)=>
|
||||
CollisionAttributes::Contact(attr.into()),
|
||||
strafesnet_common::gameplay_attributes::CollisionAttributes::Intersect(attr)=>
|
||||
CollisionAttributes::Intersect(attr.into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,241 +1,220 @@
|
||||
use super::common::flag;
|
||||
|
||||
pub type ModeId = u32;
|
||||
pub type StageId = u32;
|
||||
pub type ModeId=u32;
|
||||
pub type StageId=u32;
|
||||
|
||||
#[binrw::binrw]
|
||||
#[brw(little)]
|
||||
pub struct StageElement {
|
||||
pub header: u8,
|
||||
pub stage_id: StageId,
|
||||
#[br(if(header&Self::JUMP_LIMIT!=0))]
|
||||
pub jump_limit: Option<u8>,
|
||||
pub struct StageElement{
|
||||
pub header:u8,
|
||||
pub stage_id:StageId,
|
||||
#[br(if(header&Self::JUMP_LIMIT!=0))]
|
||||
pub jump_limit:Option<u8>,
|
||||
}
|
||||
impl StageElement {
|
||||
const BEHAVIOUR: u8 = 0b00111;
|
||||
const JUMP_LIMIT: u8 = 1 << 3;
|
||||
const FORCE: u8 = 1 << 4;
|
||||
const fn behaviour(&self) -> Option<strafesnet_common::gameplay_modes::StageElementBehaviour> {
|
||||
match self.header & Self::BEHAVIOUR {
|
||||
0 => Some(strafesnet_common::gameplay_modes::StageElementBehaviour::SpawnAt),
|
||||
1 => Some(strafesnet_common::gameplay_modes::StageElementBehaviour::Trigger),
|
||||
2 => Some(strafesnet_common::gameplay_modes::StageElementBehaviour::Teleport),
|
||||
3 => Some(strafesnet_common::gameplay_modes::StageElementBehaviour::Platform),
|
||||
4 => Some(strafesnet_common::gameplay_modes::StageElementBehaviour::Check),
|
||||
5 => Some(strafesnet_common::gameplay_modes::StageElementBehaviour::Checkpoint),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
const fn force(&self) -> bool {
|
||||
self.header & Self::FORCE != 0
|
||||
}
|
||||
impl StageElement{
|
||||
const BEHAVIOUR:u8=0b00111;
|
||||
const JUMP_LIMIT:u8=1<<3;
|
||||
const FORCE:u8=1<<4;
|
||||
const fn behaviour(&self)->Option<strafesnet_common::gameplay_modes::StageElementBehaviour>{
|
||||
match self.header&Self::BEHAVIOUR{
|
||||
0=>Some(strafesnet_common::gameplay_modes::StageElementBehaviour::SpawnAt),
|
||||
1=>Some(strafesnet_common::gameplay_modes::StageElementBehaviour::Trigger),
|
||||
2=>Some(strafesnet_common::gameplay_modes::StageElementBehaviour::Teleport),
|
||||
3=>Some(strafesnet_common::gameplay_modes::StageElementBehaviour::Platform),
|
||||
4=>Some(strafesnet_common::gameplay_modes::StageElementBehaviour::Check),
|
||||
5=>Some(strafesnet_common::gameplay_modes::StageElementBehaviour::Checkpoint),
|
||||
_=>None,
|
||||
}
|
||||
}
|
||||
const fn force(&self)->bool{
|
||||
self.header&Self::FORCE!=0
|
||||
}
|
||||
}
|
||||
#[derive(Debug)]
|
||||
pub enum StageElementError {
|
||||
InvalidBehaviour,
|
||||
pub enum StageElementError{
|
||||
InvalidBehaviour,
|
||||
}
|
||||
impl std::fmt::Display for StageElementError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{self:?}")
|
||||
}
|
||||
impl std::fmt::Display for StageElementError{
|
||||
fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{
|
||||
write!(f,"{self:?}")
|
||||
}
|
||||
}
|
||||
impl std::error::Error for StageElementError {}
|
||||
impl TryInto<strafesnet_common::gameplay_modes::StageElement> for StageElement {
|
||||
type Error = StageElementError;
|
||||
fn try_into(self) -> Result<strafesnet_common::gameplay_modes::StageElement, Self::Error> {
|
||||
Ok(strafesnet_common::gameplay_modes::StageElement::new(
|
||||
strafesnet_common::gameplay_modes::StageId::new(self.stage_id),
|
||||
self.force(),
|
||||
self.behaviour()
|
||||
.ok_or(StageElementError::InvalidBehaviour)?,
|
||||
self.jump_limit,
|
||||
))
|
||||
}
|
||||
impl std::error::Error for StageElementError{}
|
||||
impl TryInto<strafesnet_common::gameplay_modes::StageElement> for StageElement{
|
||||
type Error=StageElementError;
|
||||
fn try_into(self)->Result<strafesnet_common::gameplay_modes::StageElement,Self::Error>{
|
||||
Ok(strafesnet_common::gameplay_modes::StageElement::new(
|
||||
strafesnet_common::gameplay_modes::StageId::new(self.stage_id),
|
||||
self.force(),
|
||||
self.behaviour().ok_or(StageElementError::InvalidBehaviour)?,
|
||||
self.jump_limit,
|
||||
))
|
||||
}
|
||||
}
|
||||
impl From<strafesnet_common::gameplay_modes::StageElement> for StageElement {
|
||||
fn from(value: strafesnet_common::gameplay_modes::StageElement) -> Self {
|
||||
let behaviour = match value.behaviour() {
|
||||
strafesnet_common::gameplay_modes::StageElementBehaviour::SpawnAt => 0,
|
||||
strafesnet_common::gameplay_modes::StageElementBehaviour::Trigger => 1,
|
||||
strafesnet_common::gameplay_modes::StageElementBehaviour::Teleport => 2,
|
||||
strafesnet_common::gameplay_modes::StageElementBehaviour::Platform => 3,
|
||||
strafesnet_common::gameplay_modes::StageElementBehaviour::Check => 4,
|
||||
strafesnet_common::gameplay_modes::StageElementBehaviour::Checkpoint => 5,
|
||||
};
|
||||
let header = behaviour
|
||||
| flag(value.jump_limit().is_some(), StageElement::JUMP_LIMIT)
|
||||
| flag(value.force(), StageElement::FORCE);
|
||||
Self {
|
||||
header,
|
||||
stage_id: value.stage_id().get(),
|
||||
jump_limit: value.jump_limit(),
|
||||
}
|
||||
}
|
||||
impl From<strafesnet_common::gameplay_modes::StageElement> for StageElement{
|
||||
fn from(value:strafesnet_common::gameplay_modes::StageElement)->Self{
|
||||
let behaviour=match value.behaviour(){
|
||||
strafesnet_common::gameplay_modes::StageElementBehaviour::SpawnAt=>0,
|
||||
strafesnet_common::gameplay_modes::StageElementBehaviour::Trigger=>1,
|
||||
strafesnet_common::gameplay_modes::StageElementBehaviour::Teleport=>2,
|
||||
strafesnet_common::gameplay_modes::StageElementBehaviour::Platform=>3,
|
||||
strafesnet_common::gameplay_modes::StageElementBehaviour::Check=>4,
|
||||
strafesnet_common::gameplay_modes::StageElementBehaviour::Checkpoint=>5,
|
||||
};
|
||||
let header=
|
||||
behaviour
|
||||
|flag(value.jump_limit().is_some(),StageElement::JUMP_LIMIT)
|
||||
|flag(value.force(),StageElement::FORCE);
|
||||
Self{
|
||||
header,
|
||||
stage_id:value.stage_id().get(),
|
||||
jump_limit:value.jump_limit(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[binrw::binrw]
|
||||
#[brw(little)]
|
||||
pub struct Stage {
|
||||
pub spawn: u32,
|
||||
//open world support lol
|
||||
pub ordered_checkpoints_count: u32,
|
||||
pub unordered_checkpoints_count: u32,
|
||||
//currently loaded checkpoint models
|
||||
#[br(count=ordered_checkpoints_count)]
|
||||
pub ordered_checkpoints: Vec<(u32, u32)>,
|
||||
#[br(count=unordered_checkpoints_count)]
|
||||
pub unordered_checkpoints: Vec<u32>,
|
||||
pub struct Stage{
|
||||
pub spawn:u32,
|
||||
//open world support lol
|
||||
pub ordered_checkpoints_count:u32,
|
||||
pub unordered_checkpoints_count:u32,
|
||||
//currently loaded checkpoint models
|
||||
#[br(count=ordered_checkpoints_count)]
|
||||
pub ordered_checkpoints:Vec<(u32,u32)>,
|
||||
#[br(count=unordered_checkpoints_count)]
|
||||
pub unordered_checkpoints:Vec<u32>,
|
||||
}
|
||||
impl Into<strafesnet_common::gameplay_modes::Stage> for Stage {
|
||||
fn into(self) -> strafesnet_common::gameplay_modes::Stage {
|
||||
strafesnet_common::gameplay_modes::Stage::new(
|
||||
strafesnet_common::model::ModelId::new(self.spawn),
|
||||
self.ordered_checkpoints_count,
|
||||
self.unordered_checkpoints_count,
|
||||
self.ordered_checkpoints
|
||||
.into_iter()
|
||||
.map(|(checkpoint_id, model_id)| {
|
||||
(
|
||||
strafesnet_common::gameplay_modes::CheckpointId::new(checkpoint_id),
|
||||
strafesnet_common::model::ModelId::new(model_id),
|
||||
)
|
||||
})
|
||||
.collect(),
|
||||
self.unordered_checkpoints
|
||||
.into_iter()
|
||||
.map(strafesnet_common::model::ModelId::new)
|
||||
.collect(),
|
||||
)
|
||||
}
|
||||
impl Into<strafesnet_common::gameplay_modes::Stage> for Stage{
|
||||
fn into(self)->strafesnet_common::gameplay_modes::Stage{
|
||||
strafesnet_common::gameplay_modes::Stage::new(
|
||||
strafesnet_common::model::ModelId::new(self.spawn),
|
||||
self.ordered_checkpoints_count,
|
||||
self.unordered_checkpoints_count,
|
||||
self.ordered_checkpoints.into_iter().map(|(checkpoint_id,model_id)|(
|
||||
strafesnet_common::gameplay_modes::CheckpointId::new(checkpoint_id),
|
||||
strafesnet_common::model::ModelId::new(model_id),
|
||||
)).collect(),
|
||||
self.unordered_checkpoints.into_iter()
|
||||
.map(strafesnet_common::model::ModelId::new)
|
||||
.collect(),
|
||||
)
|
||||
}
|
||||
}
|
||||
impl From<strafesnet_common::gameplay_modes::Stage> for Stage {
|
||||
fn from(value: strafesnet_common::gameplay_modes::Stage) -> Self {
|
||||
let spawn = value.spawn().get();
|
||||
let ordered_checkpoints_count = value.ordered_checkpoints_count();
|
||||
let unordered_checkpoints_count = value.unordered_checkpoints_count();
|
||||
let (ordered_checkpoints, unordered_checkpoints) = value.into_inner();
|
||||
Self {
|
||||
spawn,
|
||||
ordered_checkpoints_count,
|
||||
unordered_checkpoints_count,
|
||||
ordered_checkpoints: ordered_checkpoints
|
||||
.into_iter()
|
||||
.map(|(checkpoint_id, model_id)| (checkpoint_id.get(), model_id.get()))
|
||||
.collect(),
|
||||
unordered_checkpoints: unordered_checkpoints
|
||||
.into_iter()
|
||||
.map(|model_id| model_id.get())
|
||||
.collect(),
|
||||
}
|
||||
}
|
||||
impl From<strafesnet_common::gameplay_modes::Stage> for Stage{
|
||||
fn from(value:strafesnet_common::gameplay_modes::Stage)->Self{
|
||||
let spawn=value.spawn().get();
|
||||
let ordered_checkpoints_count=value.ordered_checkpoints_count();
|
||||
let unordered_checkpoints_count=value.unordered_checkpoints_count();
|
||||
let (ordered_checkpoints,unordered_checkpoints)=value.into_inner();
|
||||
Self{
|
||||
spawn,
|
||||
ordered_checkpoints_count,
|
||||
unordered_checkpoints_count,
|
||||
ordered_checkpoints:ordered_checkpoints.into_iter()
|
||||
.map(|(checkpoint_id,model_id)|(checkpoint_id.get(),model_id.get()))
|
||||
.collect(),
|
||||
unordered_checkpoints:unordered_checkpoints.into_iter()
|
||||
.map(|model_id|model_id.get())
|
||||
.collect(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[binrw::binrw]
|
||||
#[brw(little,repr=u8)]
|
||||
pub enum Zone {
|
||||
Start,
|
||||
Finish,
|
||||
Anticheat,
|
||||
pub enum Zone{
|
||||
Start,
|
||||
Finish,
|
||||
Anticheat,
|
||||
}
|
||||
impl Into<strafesnet_common::gameplay_modes::Zone> for Zone {
|
||||
fn into(self) -> strafesnet_common::gameplay_modes::Zone {
|
||||
match self {
|
||||
Zone::Start => strafesnet_common::gameplay_modes::Zone::Start,
|
||||
Zone::Finish => strafesnet_common::gameplay_modes::Zone::Finish,
|
||||
Zone::Anticheat => strafesnet_common::gameplay_modes::Zone::Anticheat,
|
||||
}
|
||||
}
|
||||
impl Into<strafesnet_common::gameplay_modes::Zone> for Zone{
|
||||
fn into(self)->strafesnet_common::gameplay_modes::Zone{
|
||||
match self{
|
||||
Zone::Start=>strafesnet_common::gameplay_modes::Zone::Start,
|
||||
Zone::Finish=>strafesnet_common::gameplay_modes::Zone::Finish,
|
||||
Zone::Anticheat=>strafesnet_common::gameplay_modes::Zone::Anticheat,
|
||||
}
|
||||
}
|
||||
}
|
||||
impl From<strafesnet_common::gameplay_modes::Zone> for Zone {
|
||||
fn from(value: strafesnet_common::gameplay_modes::Zone) -> Self {
|
||||
match value {
|
||||
strafesnet_common::gameplay_modes::Zone::Start => Zone::Start,
|
||||
strafesnet_common::gameplay_modes::Zone::Finish => Zone::Finish,
|
||||
strafesnet_common::gameplay_modes::Zone::Anticheat => Zone::Anticheat,
|
||||
}
|
||||
}
|
||||
impl From<strafesnet_common::gameplay_modes::Zone> for Zone{
|
||||
fn from(value:strafesnet_common::gameplay_modes::Zone)->Self{
|
||||
match value{
|
||||
strafesnet_common::gameplay_modes::Zone::Start=>Zone::Start,
|
||||
strafesnet_common::gameplay_modes::Zone::Finish=>Zone::Finish,
|
||||
strafesnet_common::gameplay_modes::Zone::Anticheat=>Zone::Anticheat,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[binrw::binrw]
|
||||
#[brw(little)]
|
||||
pub struct ModeHeader {
|
||||
pub zones: u32,
|
||||
pub stages: u32,
|
||||
pub elements: u32,
|
||||
pub struct ModeHeader{
|
||||
pub zones:u32,
|
||||
pub stages:u32,
|
||||
pub elements:u32,
|
||||
}
|
||||
#[binrw::binrw]
|
||||
#[brw(little)]
|
||||
pub struct Mode {
|
||||
pub header: ModeHeader,
|
||||
pub style: super::gameplay_style::StyleModifiers,
|
||||
pub start: u32,
|
||||
#[br(count=header.zones)]
|
||||
pub zones: Vec<(u32, Zone)>,
|
||||
#[br(count=header.stages)]
|
||||
pub stages: Vec<Stage>,
|
||||
#[br(count=header.elements)]
|
||||
pub elements: Vec<(u32, StageElement)>,
|
||||
pub struct Mode{
|
||||
pub header:ModeHeader,
|
||||
pub style:super::gameplay_style::StyleModifiers,
|
||||
pub start:u32,
|
||||
#[br(count=header.zones)]
|
||||
pub zones:Vec<(u32,Zone)>,
|
||||
#[br(count=header.stages)]
|
||||
pub stages:Vec<Stage>,
|
||||
#[br(count=header.elements)]
|
||||
pub elements:Vec<(u32,StageElement)>,
|
||||
}
|
||||
#[derive(Debug)]
|
||||
pub enum ModeError {
|
||||
StyleModifier(super::gameplay_style::StyleModifierError),
|
||||
StageElement(StageElementError),
|
||||
pub enum ModeError{
|
||||
StyleModifier(super::gameplay_style::StyleModifierError),
|
||||
StageElement(StageElementError),
|
||||
}
|
||||
impl std::fmt::Display for ModeError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{self:?}")
|
||||
}
|
||||
impl std::fmt::Display for ModeError{
|
||||
fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{
|
||||
write!(f,"{self:?}")
|
||||
}
|
||||
}
|
||||
impl std::error::Error for ModeError {}
|
||||
impl TryInto<strafesnet_common::gameplay_modes::Mode> for Mode {
|
||||
type Error = ModeError;
|
||||
fn try_into(self) -> Result<strafesnet_common::gameplay_modes::Mode, Self::Error> {
|
||||
Ok(strafesnet_common::gameplay_modes::Mode::new(
|
||||
self.style.try_into().map_err(ModeError::StyleModifier)?,
|
||||
strafesnet_common::model::ModelId::new(self.start),
|
||||
self.zones
|
||||
.into_iter()
|
||||
.map(|(model_id, zone)| {
|
||||
(
|
||||
strafesnet_common::model::ModelId::new(model_id),
|
||||
zone.into(),
|
||||
)
|
||||
})
|
||||
.collect(),
|
||||
self.stages.into_iter().map(Into::into).collect(),
|
||||
self.elements
|
||||
.into_iter()
|
||||
.map(|(model_id, stage_element)| {
|
||||
Ok((
|
||||
strafesnet_common::model::ModelId::new(model_id),
|
||||
stage_element.try_into()?,
|
||||
))
|
||||
})
|
||||
.collect::<Result<_, _>>()
|
||||
.map_err(ModeError::StageElement)?,
|
||||
))
|
||||
}
|
||||
impl std::error::Error for ModeError{}
|
||||
impl TryInto<strafesnet_common::gameplay_modes::Mode> for Mode{
|
||||
type Error=ModeError;
|
||||
fn try_into(self)->Result<strafesnet_common::gameplay_modes::Mode,Self::Error>{
|
||||
Ok(strafesnet_common::gameplay_modes::Mode::new(
|
||||
self.style.try_into().map_err(ModeError::StyleModifier)?,
|
||||
strafesnet_common::model::ModelId::new(self.start),
|
||||
self.zones.into_iter().map(|(model_id,zone)|
|
||||
(strafesnet_common::model::ModelId::new(model_id),zone.into())
|
||||
).collect(),
|
||||
self.stages.into_iter().map(Into::into).collect(),
|
||||
self.elements.into_iter().map(|(model_id,stage_element)|
|
||||
Ok((strafesnet_common::model::ModelId::new(model_id),stage_element.try_into()?))
|
||||
).collect::<Result<_,_>>().map_err(ModeError::StageElement)?,
|
||||
))
|
||||
}
|
||||
}
|
||||
impl From<strafesnet_common::gameplay_modes::Mode> for Mode {
|
||||
fn from(value: strafesnet_common::gameplay_modes::Mode) -> Self {
|
||||
let (style, start, zones, stages, elements) = value.into_inner();
|
||||
Self {
|
||||
header: ModeHeader {
|
||||
zones: zones.len() as u32,
|
||||
stages: stages.len() as u32,
|
||||
elements: elements.len() as u32,
|
||||
},
|
||||
style: style.into(),
|
||||
start: start.get(),
|
||||
zones: zones
|
||||
.into_iter()
|
||||
.map(|(model_id, zone)| (model_id.get(), zone.into()))
|
||||
.collect(),
|
||||
stages: stages.into_iter().map(Into::into).collect(),
|
||||
elements: elements
|
||||
.into_iter()
|
||||
.map(|(model_id, stage_element)| (model_id.get(), stage_element.into()))
|
||||
.collect(),
|
||||
}
|
||||
}
|
||||
impl From<strafesnet_common::gameplay_modes::Mode> for Mode{
|
||||
fn from(value:strafesnet_common::gameplay_modes::Mode)->Self{
|
||||
let (style,start,zones,stages,elements)=value.into_inner();
|
||||
Self{
|
||||
header:ModeHeader{
|
||||
zones:zones.len() as u32,
|
||||
stages:stages.len() as u32,
|
||||
elements:elements.len() as u32,
|
||||
},
|
||||
style:style.into(),
|
||||
start:start.get(),
|
||||
zones:zones.into_iter()
|
||||
.map(|(model_id,zone)|(model_id.get(),zone.into()))
|
||||
.collect(),
|
||||
stages:stages.into_iter()
|
||||
.map(Into::into)
|
||||
.collect(),
|
||||
elements:elements.into_iter()
|
||||
.map(|(model_id,stage_element)|(model_id.get(),stage_element.into()))
|
||||
.collect(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,514 +1,442 @@
|
||||
use super::common::flag;
|
||||
use super::integer::{Planar64, Planar64Vec3, Ratio64, Time};
|
||||
use super::integer::{Time,Ratio64,Planar64,Planar64Vec3};
|
||||
|
||||
pub type Controls = u32;
|
||||
pub type Controls=u32;
|
||||
#[derive(Debug)]
|
||||
pub enum ControlsError {
|
||||
UnknownBits,
|
||||
pub enum ControlsError{
|
||||
UnknownBits,
|
||||
}
|
||||
impl std::fmt::Display for ControlsError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{self:?}")
|
||||
}
|
||||
impl std::fmt::Display for ControlsError{
|
||||
fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{
|
||||
write!(f,"{self:?}")
|
||||
}
|
||||
}
|
||||
impl std::error::Error for ControlsError {}
|
||||
impl std::error::Error for ControlsError{}
|
||||
|
||||
#[binrw::binrw]
|
||||
#[brw(little)]
|
||||
pub struct StyleModifiers {
|
||||
pub header: u8,
|
||||
pub controls_mask: Controls,
|
||||
pub controls_mask_state: Controls,
|
||||
#[br(if(header&Self::STRAFE!=0))]
|
||||
pub strafe: Option<StrafeSettings>,
|
||||
#[br(if(header&Self::ROCKET!=0))]
|
||||
pub rocket: Option<PropulsionSettings>,
|
||||
#[br(if(header&Self::JUMP!=0))]
|
||||
pub jump: Option<JumpSettings>,
|
||||
#[br(if(header&Self::WALK!=0))]
|
||||
pub walk: Option<WalkSettings>,
|
||||
#[br(if(header&Self::LADDER!=0))]
|
||||
pub ladder: Option<LadderSettings>,
|
||||
#[br(if(header&Self::SWIM!=0))]
|
||||
pub swim: Option<PropulsionSettings>,
|
||||
pub gravity: Planar64Vec3,
|
||||
pub hitbox: Hitbox,
|
||||
pub camera_offset: Planar64Vec3,
|
||||
pub mass: Planar64,
|
||||
pub struct StyleModifiers{
|
||||
pub header:u8,
|
||||
pub controls_mask:Controls,
|
||||
pub controls_mask_state:Controls,
|
||||
#[br(if(header&Self::STRAFE!=0))]
|
||||
pub strafe:Option<StrafeSettings>,
|
||||
#[br(if(header&Self::ROCKET!=0))]
|
||||
pub rocket:Option<PropulsionSettings>,
|
||||
#[br(if(header&Self::JUMP!=0))]
|
||||
pub jump:Option<JumpSettings>,
|
||||
#[br(if(header&Self::WALK!=0))]
|
||||
pub walk:Option<WalkSettings>,
|
||||
#[br(if(header&Self::LADDER!=0))]
|
||||
pub ladder:Option<LadderSettings>,
|
||||
#[br(if(header&Self::SWIM!=0))]
|
||||
pub swim:Option<PropulsionSettings>,
|
||||
pub gravity:Planar64Vec3,
|
||||
pub hitbox:Hitbox,
|
||||
pub camera_offset:Planar64Vec3,
|
||||
pub mass:Planar64,
|
||||
}
|
||||
impl StyleModifiers {
|
||||
const STRAFE: u8 = 1 << 0;
|
||||
const ROCKET: u8 = 1 << 1;
|
||||
const JUMP: u8 = 1 << 2;
|
||||
const WALK: u8 = 1 << 3;
|
||||
const LADDER: u8 = 1 << 4;
|
||||
const SWIM: u8 = 1 << 5;
|
||||
impl StyleModifiers{
|
||||
const STRAFE:u8=1<<0;
|
||||
const ROCKET:u8=1<<1;
|
||||
const JUMP:u8=1<<2;
|
||||
const WALK:u8=1<<3;
|
||||
const LADDER:u8=1<<4;
|
||||
const SWIM:u8=1<<5;
|
||||
}
|
||||
#[derive(Debug)]
|
||||
pub enum StyleModifierError {
|
||||
Controls(ControlsError),
|
||||
JumpSettings(JumpSettingsError),
|
||||
StrafeSettings(StrafeSettingsError),
|
||||
pub enum StyleModifierError{
|
||||
Controls(ControlsError),
|
||||
JumpSettings(JumpSettingsError),
|
||||
StrafeSettings(StrafeSettingsError),
|
||||
}
|
||||
impl std::fmt::Display for StyleModifierError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{self:?}")
|
||||
}
|
||||
impl std::fmt::Display for StyleModifierError{
|
||||
fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{
|
||||
write!(f,"{self:?}")
|
||||
}
|
||||
}
|
||||
impl std::error::Error for StyleModifierError {}
|
||||
impl TryInto<strafesnet_common::gameplay_style::StyleModifiers> for StyleModifiers {
|
||||
type Error = StyleModifierError;
|
||||
fn try_into(self) -> Result<strafesnet_common::gameplay_style::StyleModifiers, Self::Error> {
|
||||
Ok(strafesnet_common::gameplay_style::StyleModifiers {
|
||||
controls_mask: strafesnet_common::controls_bitflag::Controls::from_bits(
|
||||
self.controls_mask,
|
||||
)
|
||||
.ok_or(StyleModifierError::Controls(ControlsError::UnknownBits))?,
|
||||
controls_mask_state: strafesnet_common::controls_bitflag::Controls::from_bits(
|
||||
self.controls_mask_state,
|
||||
)
|
||||
.ok_or(StyleModifierError::Controls(ControlsError::UnknownBits))?,
|
||||
strafe: self
|
||||
.strafe
|
||||
.map(TryInto::try_into)
|
||||
.transpose()
|
||||
.map_err(StyleModifierError::StrafeSettings)?,
|
||||
rocket: self.rocket.map(Into::into),
|
||||
jump: self
|
||||
.jump
|
||||
.map(TryInto::try_into)
|
||||
.transpose()
|
||||
.map_err(StyleModifierError::JumpSettings)?,
|
||||
walk: self.walk.map(Into::into),
|
||||
ladder: self.ladder.map(Into::into),
|
||||
swim: self.swim.map(Into::into),
|
||||
gravity: strafesnet_common::integer::vec3::raw_array(self.gravity),
|
||||
hitbox: self.hitbox.into(),
|
||||
camera_offset: strafesnet_common::integer::vec3::raw_array(self.camera_offset),
|
||||
mass: strafesnet_common::integer::Planar64::raw(self.mass),
|
||||
})
|
||||
}
|
||||
impl std::error::Error for StyleModifierError{}
|
||||
impl TryInto<strafesnet_common::gameplay_style::StyleModifiers> for StyleModifiers{
|
||||
type Error=StyleModifierError;
|
||||
fn try_into(self)->Result<strafesnet_common::gameplay_style::StyleModifiers,Self::Error>{
|
||||
Ok(strafesnet_common::gameplay_style::StyleModifiers{
|
||||
controls_mask:strafesnet_common::controls_bitflag::Controls::from_bits(self.controls_mask).ok_or(StyleModifierError::Controls(ControlsError::UnknownBits))?,
|
||||
controls_mask_state:strafesnet_common::controls_bitflag::Controls::from_bits(self.controls_mask_state).ok_or(StyleModifierError::Controls(ControlsError::UnknownBits))?,
|
||||
strafe:self.strafe.map(TryInto::try_into).transpose().map_err(StyleModifierError::StrafeSettings)?,
|
||||
rocket:self.rocket.map(Into::into),
|
||||
jump:self.jump.map(TryInto::try_into).transpose().map_err(StyleModifierError::JumpSettings)?,
|
||||
walk:self.walk.map(Into::into),
|
||||
ladder:self.ladder.map(Into::into),
|
||||
swim:self.swim.map(Into::into),
|
||||
gravity:strafesnet_common::integer::vec3::raw_array(self.gravity),
|
||||
hitbox:self.hitbox.into(),
|
||||
camera_offset:strafesnet_common::integer::vec3::raw_array(self.camera_offset),
|
||||
mass:strafesnet_common::integer::Planar64::raw(self.mass),
|
||||
})
|
||||
}
|
||||
}
|
||||
impl From<strafesnet_common::gameplay_style::StyleModifiers> for StyleModifiers {
|
||||
fn from(value: strafesnet_common::gameplay_style::StyleModifiers) -> Self {
|
||||
let header = flag(value.strafe.is_some(), StyleModifiers::STRAFE)
|
||||
| flag(value.rocket.is_some(), StyleModifiers::ROCKET)
|
||||
| flag(value.jump.is_some(), StyleModifiers::JUMP)
|
||||
| flag(value.walk.is_some(), StyleModifiers::WALK)
|
||||
| flag(value.ladder.is_some(), StyleModifiers::LADDER)
|
||||
| flag(value.swim.is_some(), StyleModifiers::SWIM);
|
||||
Self {
|
||||
header,
|
||||
controls_mask: value.controls_mask.bits(),
|
||||
controls_mask_state: value.controls_mask_state.bits(),
|
||||
strafe: value.strafe.map(Into::into),
|
||||
rocket: value.rocket.map(Into::into),
|
||||
jump: value.jump.map(Into::into),
|
||||
walk: value.walk.map(Into::into),
|
||||
ladder: value.ladder.map(Into::into),
|
||||
swim: value.swim.map(Into::into),
|
||||
gravity: value.gravity.map(|t| t.to_raw()).to_array(),
|
||||
hitbox: value.hitbox.into(),
|
||||
camera_offset: value.camera_offset.map(|t| t.to_raw()).to_array(),
|
||||
mass: value.mass.to_raw(),
|
||||
}
|
||||
}
|
||||
impl From<strafesnet_common::gameplay_style::StyleModifiers> for StyleModifiers{
|
||||
fn from(value:strafesnet_common::gameplay_style::StyleModifiers)->Self{
|
||||
let header=
|
||||
flag(value.strafe.is_some(),StyleModifiers::STRAFE)
|
||||
|flag(value.rocket.is_some(),StyleModifiers::ROCKET)
|
||||
|flag(value.jump.is_some(),StyleModifiers::JUMP)
|
||||
|flag(value.walk.is_some(),StyleModifiers::WALK)
|
||||
|flag(value.ladder.is_some(),StyleModifiers::LADDER)
|
||||
|flag(value.swim.is_some(),StyleModifiers::SWIM);
|
||||
Self{
|
||||
header,
|
||||
controls_mask:value.controls_mask.bits(),
|
||||
controls_mask_state:value.controls_mask_state.bits(),
|
||||
strafe:value.strafe.map(Into::into),
|
||||
rocket:value.rocket.map(Into::into),
|
||||
jump:value.jump.map(Into::into),
|
||||
walk:value.walk.map(Into::into),
|
||||
ladder:value.ladder.map(Into::into),
|
||||
swim:value.swim.map(Into::into),
|
||||
gravity:value.gravity.map(|t|t.to_raw()).to_array(),
|
||||
hitbox:value.hitbox.into(),
|
||||
camera_offset:value.camera_offset.map(|t|t.to_raw()).to_array(),
|
||||
mass:value.mass.to_raw(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[binrw::binrw]
|
||||
#[brw(little,repr=u8)]
|
||||
pub enum JumpCalculation {
|
||||
Max,
|
||||
BoostThenJump,
|
||||
JumpThenBoost,
|
||||
pub enum JumpCalculation{
|
||||
Max,
|
||||
BoostThenJump,
|
||||
JumpThenBoost,
|
||||
}
|
||||
impl Into<strafesnet_common::gameplay_style::JumpCalculation> for JumpCalculation {
|
||||
fn into(self) -> strafesnet_common::gameplay_style::JumpCalculation {
|
||||
match self {
|
||||
JumpCalculation::Max => strafesnet_common::gameplay_style::JumpCalculation::Max,
|
||||
JumpCalculation::BoostThenJump => {
|
||||
strafesnet_common::gameplay_style::JumpCalculation::BoostThenJump
|
||||
}
|
||||
JumpCalculation::JumpThenBoost => {
|
||||
strafesnet_common::gameplay_style::JumpCalculation::JumpThenBoost
|
||||
}
|
||||
}
|
||||
}
|
||||
impl Into<strafesnet_common::gameplay_style::JumpCalculation> for JumpCalculation{
|
||||
fn into(self)->strafesnet_common::gameplay_style::JumpCalculation{
|
||||
match self{
|
||||
JumpCalculation::Max=>strafesnet_common::gameplay_style::JumpCalculation::Max,
|
||||
JumpCalculation::BoostThenJump=>strafesnet_common::gameplay_style::JumpCalculation::BoostThenJump,
|
||||
JumpCalculation::JumpThenBoost=>strafesnet_common::gameplay_style::JumpCalculation::JumpThenBoost,
|
||||
}
|
||||
}
|
||||
}
|
||||
impl From<strafesnet_common::gameplay_style::JumpCalculation> for JumpCalculation {
|
||||
fn from(value: strafesnet_common::gameplay_style::JumpCalculation) -> Self {
|
||||
match value {
|
||||
strafesnet_common::gameplay_style::JumpCalculation::Max => JumpCalculation::Max,
|
||||
strafesnet_common::gameplay_style::JumpCalculation::BoostThenJump => {
|
||||
JumpCalculation::BoostThenJump
|
||||
}
|
||||
strafesnet_common::gameplay_style::JumpCalculation::JumpThenBoost => {
|
||||
JumpCalculation::JumpThenBoost
|
||||
}
|
||||
}
|
||||
}
|
||||
impl From<strafesnet_common::gameplay_style::JumpCalculation> for JumpCalculation{
|
||||
fn from(value:strafesnet_common::gameplay_style::JumpCalculation)->Self{
|
||||
match value{
|
||||
strafesnet_common::gameplay_style::JumpCalculation::Max=>JumpCalculation::Max,
|
||||
strafesnet_common::gameplay_style::JumpCalculation::BoostThenJump=>JumpCalculation::BoostThenJump,
|
||||
strafesnet_common::gameplay_style::JumpCalculation::JumpThenBoost=>JumpCalculation::JumpThenBoost,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub enum JumpImpulse {
|
||||
Time(Time),
|
||||
Height(Planar64),
|
||||
Linear(Planar64),
|
||||
Energy(Planar64),
|
||||
pub enum JumpImpulse{
|
||||
Time(Time),
|
||||
Height(Planar64),
|
||||
Linear(Planar64),
|
||||
Energy(Planar64),
|
||||
}
|
||||
impl Into<strafesnet_common::gameplay_style::JumpImpulse> for JumpImpulse {
|
||||
fn into(self) -> strafesnet_common::gameplay_style::JumpImpulse {
|
||||
match self {
|
||||
JumpImpulse::Time(time) => strafesnet_common::gameplay_style::JumpImpulse::Time(
|
||||
strafesnet_common::integer::Time::raw(time),
|
||||
),
|
||||
JumpImpulse::Height(height) => strafesnet_common::gameplay_style::JumpImpulse::Height(
|
||||
strafesnet_common::integer::Planar64::raw(height),
|
||||
),
|
||||
JumpImpulse::Linear(deltav) => strafesnet_common::gameplay_style::JumpImpulse::Linear(
|
||||
strafesnet_common::integer::Planar64::raw(deltav),
|
||||
),
|
||||
JumpImpulse::Energy(energy) => strafesnet_common::gameplay_style::JumpImpulse::Energy(
|
||||
strafesnet_common::integer::Planar64::raw(energy),
|
||||
),
|
||||
}
|
||||
}
|
||||
impl Into<strafesnet_common::gameplay_style::JumpImpulse> for JumpImpulse{
|
||||
fn into(self)->strafesnet_common::gameplay_style::JumpImpulse{
|
||||
match self{
|
||||
JumpImpulse::Time(time)=>strafesnet_common::gameplay_style::JumpImpulse::Time(strafesnet_common::integer::Time::raw(time)),
|
||||
JumpImpulse::Height(height)=>strafesnet_common::gameplay_style::JumpImpulse::Height(strafesnet_common::integer::Planar64::raw(height)),
|
||||
JumpImpulse::Linear(deltav)=>strafesnet_common::gameplay_style::JumpImpulse::Linear(strafesnet_common::integer::Planar64::raw(deltav)),
|
||||
JumpImpulse::Energy(energy)=>strafesnet_common::gameplay_style::JumpImpulse::Energy(strafesnet_common::integer::Planar64::raw(energy)),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl From<strafesnet_common::gameplay_style::JumpImpulse> for JumpImpulse {
|
||||
fn from(value: strafesnet_common::gameplay_style::JumpImpulse) -> Self {
|
||||
match value {
|
||||
strafesnet_common::gameplay_style::JumpImpulse::Time(time) => {
|
||||
JumpImpulse::Time(time.get())
|
||||
}
|
||||
strafesnet_common::gameplay_style::JumpImpulse::Height(height) => {
|
||||
JumpImpulse::Height(height.to_raw())
|
||||
}
|
||||
strafesnet_common::gameplay_style::JumpImpulse::Linear(deltav) => {
|
||||
JumpImpulse::Linear(deltav.to_raw())
|
||||
}
|
||||
strafesnet_common::gameplay_style::JumpImpulse::Energy(energy) => {
|
||||
JumpImpulse::Energy(energy.to_raw())
|
||||
}
|
||||
}
|
||||
}
|
||||
impl From<strafesnet_common::gameplay_style::JumpImpulse> for JumpImpulse{
|
||||
fn from(value:strafesnet_common::gameplay_style::JumpImpulse)->Self{
|
||||
match value{
|
||||
strafesnet_common::gameplay_style::JumpImpulse::Time(time)=>JumpImpulse::Time(time.get()),
|
||||
strafesnet_common::gameplay_style::JumpImpulse::Height(height)=>JumpImpulse::Height(height.to_raw()),
|
||||
strafesnet_common::gameplay_style::JumpImpulse::Linear(deltav)=>JumpImpulse::Linear(deltav.to_raw()),
|
||||
strafesnet_common::gameplay_style::JumpImpulse::Energy(energy)=>JumpImpulse::Energy(energy.to_raw()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[binrw::binrw]
|
||||
#[brw(little)]
|
||||
pub struct ControlsActivation {
|
||||
controls_mask: Controls,
|
||||
controls_intersects: Controls,
|
||||
controls_contains: Controls,
|
||||
pub struct ControlsActivation{
|
||||
controls_mask:Controls,
|
||||
controls_intersects:Controls,
|
||||
controls_contains:Controls,
|
||||
}
|
||||
impl TryInto<strafesnet_common::gameplay_style::ControlsActivation> for ControlsActivation {
|
||||
type Error = ControlsError;
|
||||
fn try_into(
|
||||
self,
|
||||
) -> Result<strafesnet_common::gameplay_style::ControlsActivation, Self::Error> {
|
||||
Ok(strafesnet_common::gameplay_style::ControlsActivation {
|
||||
controls_mask: strafesnet_common::controls_bitflag::Controls::from_bits(
|
||||
self.controls_mask,
|
||||
)
|
||||
.ok_or(ControlsError::UnknownBits)?,
|
||||
controls_intersects: strafesnet_common::controls_bitflag::Controls::from_bits(
|
||||
self.controls_intersects,
|
||||
)
|
||||
.ok_or(ControlsError::UnknownBits)?,
|
||||
controls_contains: strafesnet_common::controls_bitflag::Controls::from_bits(
|
||||
self.controls_contains,
|
||||
)
|
||||
.ok_or(ControlsError::UnknownBits)?,
|
||||
})
|
||||
}
|
||||
impl TryInto<strafesnet_common::gameplay_style::ControlsActivation> for ControlsActivation{
|
||||
type Error=ControlsError;
|
||||
fn try_into(self)->Result<strafesnet_common::gameplay_style::ControlsActivation,Self::Error>{
|
||||
Ok(strafesnet_common::gameplay_style::ControlsActivation{
|
||||
controls_mask:strafesnet_common::controls_bitflag::Controls::from_bits(self.controls_mask).ok_or(ControlsError::UnknownBits)?,
|
||||
controls_intersects:strafesnet_common::controls_bitflag::Controls::from_bits(self.controls_intersects).ok_or(ControlsError::UnknownBits)?,
|
||||
controls_contains:strafesnet_common::controls_bitflag::Controls::from_bits(self.controls_contains).ok_or(ControlsError::UnknownBits)?,
|
||||
})
|
||||
}
|
||||
}
|
||||
impl From<strafesnet_common::gameplay_style::ControlsActivation> for ControlsActivation {
|
||||
fn from(value: strafesnet_common::gameplay_style::ControlsActivation) -> Self {
|
||||
Self {
|
||||
controls_mask: value.controls_mask.bits(),
|
||||
controls_intersects: value.controls_intersects.bits(),
|
||||
controls_contains: value.controls_contains.bits(),
|
||||
}
|
||||
}
|
||||
impl From<strafesnet_common::gameplay_style::ControlsActivation> for ControlsActivation{
|
||||
fn from(value:strafesnet_common::gameplay_style::ControlsActivation)->Self{
|
||||
Self{
|
||||
controls_mask:value.controls_mask.bits(),
|
||||
controls_intersects:value.controls_intersects.bits(),
|
||||
controls_contains:value.controls_contains.bits(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[binrw::binrw]
|
||||
#[brw(little)]
|
||||
pub struct StrafeSettings {
|
||||
header: u8,
|
||||
enable: ControlsActivation,
|
||||
mv: Planar64,
|
||||
#[br(if(header&Self::AIR_ACCEL_LIMIT!=0))]
|
||||
air_accel_limit: Option<Planar64>,
|
||||
tick_rate: Ratio64,
|
||||
pub struct StrafeSettings{
|
||||
header:u8,
|
||||
enable:ControlsActivation,
|
||||
mv:Planar64,
|
||||
#[br(if(header&Self::AIR_ACCEL_LIMIT!=0))]
|
||||
air_accel_limit:Option<Planar64>,
|
||||
tick_rate:Ratio64,
|
||||
}
|
||||
impl StrafeSettings {
|
||||
const AIR_ACCEL_LIMIT: u8 = 1 << 0;
|
||||
impl StrafeSettings{
|
||||
const AIR_ACCEL_LIMIT:u8=1<<0;
|
||||
}
|
||||
#[derive(Debug)]
|
||||
pub enum StrafeSettingsError {
|
||||
Ratio(super::integer::RatioError),
|
||||
Controls(ControlsError),
|
||||
pub enum StrafeSettingsError{
|
||||
Ratio(super::integer::RatioError),
|
||||
Controls(ControlsError),
|
||||
}
|
||||
impl std::fmt::Display for StrafeSettingsError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{self:?}")
|
||||
}
|
||||
impl std::fmt::Display for StrafeSettingsError{
|
||||
fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{
|
||||
write!(f,"{self:?}")
|
||||
}
|
||||
}
|
||||
impl std::error::Error for StrafeSettingsError {}
|
||||
impl TryInto<strafesnet_common::gameplay_style::StrafeSettings> for StrafeSettings {
|
||||
type Error = StrafeSettingsError;
|
||||
fn try_into(self) -> Result<strafesnet_common::gameplay_style::StrafeSettings, Self::Error> {
|
||||
Ok(strafesnet_common::gameplay_style::StrafeSettings {
|
||||
enable: self
|
||||
.enable
|
||||
.try_into()
|
||||
.map_err(StrafeSettingsError::Controls)?,
|
||||
mv: strafesnet_common::integer::Planar64::raw(self.mv),
|
||||
air_accel_limit: self
|
||||
.air_accel_limit
|
||||
.map(strafesnet_common::integer::Planar64::raw),
|
||||
tick_rate: self
|
||||
.tick_rate
|
||||
.try_into()
|
||||
.map_err(StrafeSettingsError::Ratio)?,
|
||||
})
|
||||
}
|
||||
impl std::error::Error for StrafeSettingsError{}
|
||||
impl TryInto<strafesnet_common::gameplay_style::StrafeSettings> for StrafeSettings{
|
||||
type Error=StrafeSettingsError;
|
||||
fn try_into(self)->Result<strafesnet_common::gameplay_style::StrafeSettings,Self::Error>{
|
||||
Ok(strafesnet_common::gameplay_style::StrafeSettings{
|
||||
enable:self.enable.try_into().map_err(StrafeSettingsError::Controls)?,
|
||||
mv:strafesnet_common::integer::Planar64::raw(self.mv),
|
||||
air_accel_limit:self.air_accel_limit.map(strafesnet_common::integer::Planar64::raw),
|
||||
tick_rate:self.tick_rate.try_into().map_err(StrafeSettingsError::Ratio)?,
|
||||
})
|
||||
}
|
||||
}
|
||||
impl From<strafesnet_common::gameplay_style::StrafeSettings> for StrafeSettings {
|
||||
fn from(value: strafesnet_common::gameplay_style::StrafeSettings) -> Self {
|
||||
let header = flag(
|
||||
value.air_accel_limit.is_some(),
|
||||
StrafeSettings::AIR_ACCEL_LIMIT,
|
||||
);
|
||||
Self {
|
||||
header,
|
||||
enable: value.enable.into(),
|
||||
mv: value.mv.to_raw(),
|
||||
air_accel_limit: value.air_accel_limit.map(|a| a.to_raw()),
|
||||
tick_rate: value.tick_rate.into(),
|
||||
}
|
||||
}
|
||||
impl From<strafesnet_common::gameplay_style::StrafeSettings> for StrafeSettings{
|
||||
fn from(value:strafesnet_common::gameplay_style::StrafeSettings)->Self{
|
||||
let header=flag(value.air_accel_limit.is_some(),StrafeSettings::AIR_ACCEL_LIMIT);
|
||||
Self{
|
||||
header,
|
||||
enable:value.enable.into(),
|
||||
mv:value.mv.to_raw(),
|
||||
air_accel_limit:value.air_accel_limit.map(|a|a.to_raw()),
|
||||
tick_rate:value.tick_rate.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[binrw::binrw]
|
||||
#[brw(little)]
|
||||
pub struct PropulsionSettings {
|
||||
magnitude: Planar64,
|
||||
pub struct PropulsionSettings{
|
||||
magnitude:Planar64,
|
||||
}
|
||||
impl Into<strafesnet_common::gameplay_style::PropulsionSettings> for PropulsionSettings {
|
||||
fn into(self) -> strafesnet_common::gameplay_style::PropulsionSettings {
|
||||
strafesnet_common::gameplay_style::PropulsionSettings {
|
||||
magnitude: strafesnet_common::integer::Planar64::raw(self.magnitude),
|
||||
}
|
||||
}
|
||||
impl Into<strafesnet_common::gameplay_style::PropulsionSettings> for PropulsionSettings{
|
||||
fn into(self)->strafesnet_common::gameplay_style::PropulsionSettings{
|
||||
strafesnet_common::gameplay_style::PropulsionSettings{
|
||||
magnitude:strafesnet_common::integer::Planar64::raw(self.magnitude)
|
||||
}
|
||||
}
|
||||
}
|
||||
impl From<strafesnet_common::gameplay_style::PropulsionSettings> for PropulsionSettings {
|
||||
fn from(value: strafesnet_common::gameplay_style::PropulsionSettings) -> Self {
|
||||
Self {
|
||||
magnitude: value.magnitude.to_raw(),
|
||||
}
|
||||
}
|
||||
impl From<strafesnet_common::gameplay_style::PropulsionSettings> for PropulsionSettings{
|
||||
fn from(value:strafesnet_common::gameplay_style::PropulsionSettings)->Self{
|
||||
Self{
|
||||
magnitude:value.magnitude.to_raw(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[binrw::binrw]
|
||||
#[brw(little)]
|
||||
pub struct JumpSettings {
|
||||
header: u8,
|
||||
impulse: i64,
|
||||
pub struct JumpSettings{
|
||||
header:u8,
|
||||
impulse:i64,
|
||||
}
|
||||
impl JumpSettings {
|
||||
const IMPULSE: u8 = 0b00011;
|
||||
const CALCULATION: u8 = 0b01100;
|
||||
const LIMIT_MINIMUM: u8 = 0b10000;
|
||||
const fn impulse(&self) -> Option<strafesnet_common::gameplay_style::JumpImpulse> {
|
||||
match self.header & Self::IMPULSE {
|
||||
0 => Some(strafesnet_common::gameplay_style::JumpImpulse::Time(
|
||||
strafesnet_common::integer::Time::raw(self.impulse),
|
||||
)),
|
||||
1 => Some(strafesnet_common::gameplay_style::JumpImpulse::Height(
|
||||
strafesnet_common::integer::Planar64::raw(self.impulse),
|
||||
)),
|
||||
2 => Some(strafesnet_common::gameplay_style::JumpImpulse::Linear(
|
||||
strafesnet_common::integer::Planar64::raw(self.impulse),
|
||||
)),
|
||||
3 => Some(strafesnet_common::gameplay_style::JumpImpulse::Energy(
|
||||
strafesnet_common::integer::Planar64::raw(self.impulse),
|
||||
)),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
const fn calculation(&self) -> Option<strafesnet_common::gameplay_style::JumpCalculation> {
|
||||
match (self.header & Self::CALCULATION) >> 2 {
|
||||
0 => Some(strafesnet_common::gameplay_style::JumpCalculation::Max),
|
||||
1 => Some(strafesnet_common::gameplay_style::JumpCalculation::JumpThenBoost),
|
||||
2 => Some(strafesnet_common::gameplay_style::JumpCalculation::BoostThenJump),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
const fn limit_minimum(&self) -> bool {
|
||||
self.header & Self::LIMIT_MINIMUM != 0
|
||||
}
|
||||
impl JumpSettings{
|
||||
const IMPULSE:u8=0b00011;
|
||||
const CALCULATION:u8=0b01100;
|
||||
const LIMIT_MINIMUM:u8=0b10000;
|
||||
const fn impulse(&self)->Option<strafesnet_common::gameplay_style::JumpImpulse>{
|
||||
match self.header&Self::IMPULSE{
|
||||
0=>Some(strafesnet_common::gameplay_style::JumpImpulse::Time(strafesnet_common::integer::Time::raw(self.impulse))),
|
||||
1=>Some(strafesnet_common::gameplay_style::JumpImpulse::Height(strafesnet_common::integer::Planar64::raw(self.impulse))),
|
||||
2=>Some(strafesnet_common::gameplay_style::JumpImpulse::Linear(strafesnet_common::integer::Planar64::raw(self.impulse))),
|
||||
3=>Some(strafesnet_common::gameplay_style::JumpImpulse::Energy(strafesnet_common::integer::Planar64::raw(self.impulse))),
|
||||
_=>None,
|
||||
}
|
||||
}
|
||||
const fn calculation(&self)->Option<strafesnet_common::gameplay_style::JumpCalculation>{
|
||||
match (self.header&Self::CALCULATION)>>2{
|
||||
0=>Some(strafesnet_common::gameplay_style::JumpCalculation::Max),
|
||||
1=>Some(strafesnet_common::gameplay_style::JumpCalculation::JumpThenBoost),
|
||||
2=>Some(strafesnet_common::gameplay_style::JumpCalculation::BoostThenJump),
|
||||
_=>None,
|
||||
}
|
||||
}
|
||||
const fn limit_minimum(&self)->bool{
|
||||
self.header&Self::LIMIT_MINIMUM!=0
|
||||
}
|
||||
}
|
||||
#[derive(Debug)]
|
||||
pub enum JumpSettingsError {
|
||||
InvalidImpulseDiscriminant,
|
||||
InvalidCalculationDiscriminant,
|
||||
pub enum JumpSettingsError{
|
||||
InvalidImpulseDiscriminant,
|
||||
InvalidCalculationDiscriminant,
|
||||
}
|
||||
impl TryInto<strafesnet_common::gameplay_style::JumpSettings> for JumpSettings {
|
||||
type Error = JumpSettingsError;
|
||||
fn try_into(self) -> Result<strafesnet_common::gameplay_style::JumpSettings, Self::Error> {
|
||||
Ok(strafesnet_common::gameplay_style::JumpSettings {
|
||||
impulse: self
|
||||
.impulse()
|
||||
.ok_or(JumpSettingsError::InvalidImpulseDiscriminant)?,
|
||||
calculation: self
|
||||
.calculation()
|
||||
.ok_or(JumpSettingsError::InvalidCalculationDiscriminant)?,
|
||||
limit_minimum: self.limit_minimum(),
|
||||
})
|
||||
}
|
||||
impl TryInto<strafesnet_common::gameplay_style::JumpSettings> for JumpSettings{
|
||||
type Error=JumpSettingsError;
|
||||
fn try_into(self)->Result<strafesnet_common::gameplay_style::JumpSettings,Self::Error>{
|
||||
Ok(strafesnet_common::gameplay_style::JumpSettings{
|
||||
impulse:self.impulse().ok_or(JumpSettingsError::InvalidImpulseDiscriminant)?,
|
||||
calculation:self.calculation().ok_or(JumpSettingsError::InvalidCalculationDiscriminant)?,
|
||||
limit_minimum:self.limit_minimum(),
|
||||
})
|
||||
}
|
||||
}
|
||||
impl From<strafesnet_common::gameplay_style::JumpSettings> for JumpSettings {
|
||||
fn from(value: strafesnet_common::gameplay_style::JumpSettings) -> Self {
|
||||
let (impulse, impulse_header) = match value.impulse {
|
||||
strafesnet_common::gameplay_style::JumpImpulse::Time(impulse) => (impulse.get(), 0),
|
||||
strafesnet_common::gameplay_style::JumpImpulse::Height(impulse) => {
|
||||
(impulse.to_raw(), 1)
|
||||
}
|
||||
strafesnet_common::gameplay_style::JumpImpulse::Linear(impulse) => {
|
||||
(impulse.to_raw(), 2)
|
||||
}
|
||||
strafesnet_common::gameplay_style::JumpImpulse::Energy(impulse) => {
|
||||
(impulse.to_raw(), 3)
|
||||
}
|
||||
};
|
||||
let calculation_header = match value.calculation {
|
||||
strafesnet_common::gameplay_style::JumpCalculation::Max => 0,
|
||||
strafesnet_common::gameplay_style::JumpCalculation::JumpThenBoost => 1,
|
||||
strafesnet_common::gameplay_style::JumpCalculation::BoostThenJump => 2,
|
||||
};
|
||||
let header =
|
||||
impulse_header | (calculation_header << 2) | ((value.limit_minimum as u8) << 4);
|
||||
Self { header, impulse }
|
||||
}
|
||||
impl From<strafesnet_common::gameplay_style::JumpSettings> for JumpSettings{
|
||||
fn from(value:strafesnet_common::gameplay_style::JumpSettings)->Self{
|
||||
let (impulse,impulse_header)=match value.impulse{
|
||||
strafesnet_common::gameplay_style::JumpImpulse::Time(impulse)=>(impulse.get(),0),
|
||||
strafesnet_common::gameplay_style::JumpImpulse::Height(impulse)=>(impulse.to_raw(),1),
|
||||
strafesnet_common::gameplay_style::JumpImpulse::Linear(impulse)=>(impulse.to_raw(),2),
|
||||
strafesnet_common::gameplay_style::JumpImpulse::Energy(impulse)=>(impulse.to_raw(),3),
|
||||
};
|
||||
let calculation_header=match value.calculation{
|
||||
strafesnet_common::gameplay_style::JumpCalculation::Max=>0,
|
||||
strafesnet_common::gameplay_style::JumpCalculation::JumpThenBoost=>1,
|
||||
strafesnet_common::gameplay_style::JumpCalculation::BoostThenJump=>2,
|
||||
};
|
||||
let header=
|
||||
impulse_header
|
||||
|(calculation_header<<2)
|
||||
|((value.limit_minimum as u8)<<4);
|
||||
Self{
|
||||
header,
|
||||
impulse,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[binrw::binrw]
|
||||
#[brw(little)]
|
||||
pub struct AccelerateSettings {
|
||||
accel: Planar64,
|
||||
topspeed: Planar64,
|
||||
pub struct AccelerateSettings{
|
||||
accel:Planar64,
|
||||
topspeed:Planar64,
|
||||
}
|
||||
impl Into<strafesnet_common::gameplay_style::AccelerateSettings> for AccelerateSettings {
|
||||
fn into(self) -> strafesnet_common::gameplay_style::AccelerateSettings {
|
||||
strafesnet_common::gameplay_style::AccelerateSettings {
|
||||
accel: strafesnet_common::integer::Planar64::raw(self.accel),
|
||||
topspeed: strafesnet_common::integer::Planar64::raw(self.topspeed),
|
||||
}
|
||||
}
|
||||
impl Into<strafesnet_common::gameplay_style::AccelerateSettings> for AccelerateSettings{
|
||||
fn into(self)->strafesnet_common::gameplay_style::AccelerateSettings{
|
||||
strafesnet_common::gameplay_style::AccelerateSettings{
|
||||
accel:strafesnet_common::integer::Planar64::raw(self.accel),
|
||||
topspeed:strafesnet_common::integer::Planar64::raw(self.topspeed),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl From<strafesnet_common::gameplay_style::AccelerateSettings> for AccelerateSettings {
|
||||
fn from(value: strafesnet_common::gameplay_style::AccelerateSettings) -> Self {
|
||||
Self {
|
||||
accel: value.accel.to_raw(),
|
||||
topspeed: value.topspeed.to_raw(),
|
||||
}
|
||||
}
|
||||
impl From<strafesnet_common::gameplay_style::AccelerateSettings> for AccelerateSettings{
|
||||
fn from(value:strafesnet_common::gameplay_style::AccelerateSettings)->Self{
|
||||
Self{
|
||||
accel:value.accel.to_raw(),
|
||||
topspeed:value.topspeed.to_raw(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[binrw::binrw]
|
||||
#[brw(little)]
|
||||
pub struct WalkSettings {
|
||||
accelerate: AccelerateSettings,
|
||||
static_friction: Planar64,
|
||||
kinetic_friction: Planar64,
|
||||
surf_dot: Planar64,
|
||||
pub struct WalkSettings{
|
||||
accelerate:AccelerateSettings,
|
||||
static_friction:Planar64,
|
||||
kinetic_friction:Planar64,
|
||||
surf_dot:Planar64,
|
||||
}
|
||||
impl Into<strafesnet_common::gameplay_style::WalkSettings> for WalkSettings {
|
||||
fn into(self) -> strafesnet_common::gameplay_style::WalkSettings {
|
||||
strafesnet_common::gameplay_style::WalkSettings {
|
||||
accelerate: self.accelerate.into(),
|
||||
static_friction: strafesnet_common::integer::Planar64::raw(self.static_friction),
|
||||
kinetic_friction: strafesnet_common::integer::Planar64::raw(self.kinetic_friction),
|
||||
surf_dot: strafesnet_common::integer::Planar64::raw(self.surf_dot),
|
||||
}
|
||||
}
|
||||
impl Into<strafesnet_common::gameplay_style::WalkSettings> for WalkSettings{
|
||||
fn into(self)->strafesnet_common::gameplay_style::WalkSettings{
|
||||
strafesnet_common::gameplay_style::WalkSettings{
|
||||
accelerate:self.accelerate.into(),
|
||||
static_friction:strafesnet_common::integer::Planar64::raw(self.static_friction),
|
||||
kinetic_friction:strafesnet_common::integer::Planar64::raw(self.kinetic_friction),
|
||||
surf_dot:strafesnet_common::integer::Planar64::raw(self.surf_dot),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl From<strafesnet_common::gameplay_style::WalkSettings> for WalkSettings {
|
||||
fn from(value: strafesnet_common::gameplay_style::WalkSettings) -> Self {
|
||||
Self {
|
||||
accelerate: value.accelerate.into(),
|
||||
static_friction: value.static_friction.to_raw(),
|
||||
kinetic_friction: value.kinetic_friction.to_raw(),
|
||||
surf_dot: value.surf_dot.to_raw(),
|
||||
}
|
||||
}
|
||||
impl From<strafesnet_common::gameplay_style::WalkSettings> for WalkSettings{
|
||||
fn from(value:strafesnet_common::gameplay_style::WalkSettings)->Self{
|
||||
Self{
|
||||
accelerate:value.accelerate.into(),
|
||||
static_friction:value.static_friction.to_raw(),
|
||||
kinetic_friction:value.kinetic_friction.to_raw(),
|
||||
surf_dot:value.surf_dot.to_raw(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[binrw::binrw]
|
||||
#[brw(little)]
|
||||
pub struct LadderSettings {
|
||||
accelerate: AccelerateSettings,
|
||||
dot: Planar64,
|
||||
pub struct LadderSettings{
|
||||
accelerate:AccelerateSettings,
|
||||
dot:Planar64,
|
||||
}
|
||||
impl Into<strafesnet_common::gameplay_style::LadderSettings> for LadderSettings {
|
||||
fn into(self) -> strafesnet_common::gameplay_style::LadderSettings {
|
||||
strafesnet_common::gameplay_style::LadderSettings {
|
||||
accelerate: self.accelerate.into(),
|
||||
dot: strafesnet_common::integer::Planar64::raw(self.dot),
|
||||
}
|
||||
}
|
||||
impl Into<strafesnet_common::gameplay_style::LadderSettings> for LadderSettings{
|
||||
fn into(self)->strafesnet_common::gameplay_style::LadderSettings{
|
||||
strafesnet_common::gameplay_style::LadderSettings{
|
||||
accelerate:self.accelerate.into(),
|
||||
dot:strafesnet_common::integer::Planar64::raw(self.dot),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl From<strafesnet_common::gameplay_style::LadderSettings> for LadderSettings {
|
||||
fn from(value: strafesnet_common::gameplay_style::LadderSettings) -> Self {
|
||||
Self {
|
||||
accelerate: value.accelerate.into(),
|
||||
dot: value.dot.to_raw(),
|
||||
}
|
||||
}
|
||||
impl From<strafesnet_common::gameplay_style::LadderSettings> for LadderSettings{
|
||||
fn from(value:strafesnet_common::gameplay_style::LadderSettings)->Self{
|
||||
Self{
|
||||
accelerate:value.accelerate.into(),
|
||||
dot:value.dot.to_raw(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[binrw::binrw]
|
||||
#[brw(little,repr=u8)]
|
||||
pub enum HitboxMesh {
|
||||
Box,
|
||||
Cylinder,
|
||||
pub enum HitboxMesh{
|
||||
Box,
|
||||
Cylinder,
|
||||
}
|
||||
impl Into<strafesnet_common::gameplay_style::HitboxMesh> for HitboxMesh {
|
||||
fn into(self) -> strafesnet_common::gameplay_style::HitboxMesh {
|
||||
match self {
|
||||
HitboxMesh::Box => strafesnet_common::gameplay_style::HitboxMesh::Box,
|
||||
HitboxMesh::Cylinder => strafesnet_common::gameplay_style::HitboxMesh::Cylinder,
|
||||
}
|
||||
}
|
||||
impl Into<strafesnet_common::gameplay_style::HitboxMesh> for HitboxMesh{
|
||||
fn into(self)->strafesnet_common::gameplay_style::HitboxMesh{
|
||||
match self{
|
||||
HitboxMesh::Box=>strafesnet_common::gameplay_style::HitboxMesh::Box,
|
||||
HitboxMesh::Cylinder=>strafesnet_common::gameplay_style::HitboxMesh::Cylinder,
|
||||
}
|
||||
}
|
||||
}
|
||||
impl From<strafesnet_common::gameplay_style::HitboxMesh> for HitboxMesh {
|
||||
fn from(value: strafesnet_common::gameplay_style::HitboxMesh) -> Self {
|
||||
match value {
|
||||
strafesnet_common::gameplay_style::HitboxMesh::Box => HitboxMesh::Box,
|
||||
strafesnet_common::gameplay_style::HitboxMesh::Cylinder => HitboxMesh::Cylinder,
|
||||
}
|
||||
}
|
||||
impl From<strafesnet_common::gameplay_style::HitboxMesh> for HitboxMesh{
|
||||
fn from(value:strafesnet_common::gameplay_style::HitboxMesh)->Self{
|
||||
match value{
|
||||
strafesnet_common::gameplay_style::HitboxMesh::Box=>HitboxMesh::Box,
|
||||
strafesnet_common::gameplay_style::HitboxMesh::Cylinder=>HitboxMesh::Cylinder,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[binrw::binrw]
|
||||
#[brw(little)]
|
||||
pub struct Hitbox {
|
||||
pub halfsize: Planar64Vec3,
|
||||
pub mesh: HitboxMesh,
|
||||
pub struct Hitbox{
|
||||
pub halfsize:Planar64Vec3,
|
||||
pub mesh:HitboxMesh,
|
||||
}
|
||||
impl Into<strafesnet_common::gameplay_style::Hitbox> for Hitbox {
|
||||
fn into(self) -> strafesnet_common::gameplay_style::Hitbox {
|
||||
strafesnet_common::gameplay_style::Hitbox {
|
||||
halfsize: strafesnet_common::integer::vec3::raw_array(self.halfsize),
|
||||
mesh: self.mesh.into(),
|
||||
}
|
||||
}
|
||||
impl Into<strafesnet_common::gameplay_style::Hitbox> for Hitbox{
|
||||
fn into(self)->strafesnet_common::gameplay_style::Hitbox{
|
||||
strafesnet_common::gameplay_style::Hitbox{
|
||||
halfsize:strafesnet_common::integer::vec3::raw_array(self.halfsize),
|
||||
mesh:self.mesh.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl From<strafesnet_common::gameplay_style::Hitbox> for Hitbox {
|
||||
fn from(value: strafesnet_common::gameplay_style::Hitbox) -> Self {
|
||||
Self {
|
||||
halfsize: value.halfsize.map(|t| t.to_raw()).to_array(),
|
||||
mesh: value.mesh.into(),
|
||||
}
|
||||
}
|
||||
impl From<strafesnet_common::gameplay_style::Hitbox> for Hitbox{
|
||||
fn from(value:strafesnet_common::gameplay_style::Hitbox)->Self{
|
||||
Self{
|
||||
halfsize:value.halfsize.map(|t|t.to_raw()).to_array(),
|
||||
mesh:value.mesh.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,46 +1,63 @@
|
||||
pub type Time = i64;
|
||||
pub type Time=i64;
|
||||
|
||||
#[binrw::binrw]
|
||||
#[brw(little)]
|
||||
pub struct Ratio64 {
|
||||
num: i64,
|
||||
den: u64,
|
||||
pub struct Ratio64{
|
||||
num:i64,
|
||||
den:u64,
|
||||
}
|
||||
#[derive(Debug)]
|
||||
pub enum RatioError {
|
||||
ZeroDenominator,
|
||||
pub enum RatioError{
|
||||
ZeroDenominator,
|
||||
}
|
||||
impl std::fmt::Display for RatioError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{self:?}")
|
||||
}
|
||||
impl std::fmt::Display for RatioError{
|
||||
fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{
|
||||
write!(f,"{self:?}")
|
||||
}
|
||||
}
|
||||
impl std::error::Error for RatioError {}
|
||||
impl TryInto<strafesnet_common::integer::Ratio64> for Ratio64 {
|
||||
type Error = RatioError;
|
||||
fn try_into(self) -> Result<strafesnet_common::integer::Ratio64, Self::Error> {
|
||||
strafesnet_common::integer::Ratio64::new(self.num, self.den)
|
||||
.ok_or(RatioError::ZeroDenominator)
|
||||
}
|
||||
impl std::error::Error for RatioError{}
|
||||
impl TryInto<strafesnet_common::integer::Ratio64> for Ratio64{
|
||||
type Error=RatioError;
|
||||
fn try_into(self)->Result<strafesnet_common::integer::Ratio64,Self::Error>{
|
||||
strafesnet_common::integer::Ratio64::new(self.num,self.den)
|
||||
.ok_or(RatioError::ZeroDenominator)
|
||||
}
|
||||
}
|
||||
impl From<strafesnet_common::integer::Ratio64> for Ratio64 {
|
||||
fn from(value: strafesnet_common::integer::Ratio64) -> Self {
|
||||
Self {
|
||||
num: value.num(),
|
||||
den: value.den(),
|
||||
}
|
||||
}
|
||||
impl From<strafesnet_common::integer::Ratio64> for Ratio64{
|
||||
fn from(value:strafesnet_common::integer::Ratio64)->Self{
|
||||
Self{
|
||||
num:value.num(),
|
||||
den:value.den(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[binrw::binrw]
|
||||
#[brw(little)]
|
||||
pub struct Ratio64Vec2 {
|
||||
pub x: Ratio64,
|
||||
pub y: Ratio64,
|
||||
pub struct Ratio64Vec2{
|
||||
pub x:Ratio64,
|
||||
pub y:Ratio64,
|
||||
}
|
||||
impl TryInto<strafesnet_common::integer::Ratio64Vec2> for Ratio64Vec2{
|
||||
type Error=RatioError;
|
||||
fn try_into(self)->Result<strafesnet_common::integer::Ratio64Vec2,Self::Error>{
|
||||
Ok(strafesnet_common::integer::Ratio64Vec2{
|
||||
x:self.x.try_into()?,
|
||||
y:self.y.try_into()?,
|
||||
})
|
||||
}
|
||||
}
|
||||
impl From<strafesnet_common::integer::Ratio64Vec2> for Ratio64Vec2{
|
||||
fn from(value:strafesnet_common::integer::Ratio64Vec2)->Self{
|
||||
Self{
|
||||
x:value.x.into(),
|
||||
y:value.y.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub type Angle32 = i32;
|
||||
pub type Planar64 = i64;
|
||||
pub type Planar64Vec3 = [i64; 3];
|
||||
pub type Planar64Mat3 = [i64; 9];
|
||||
pub type Planar64Affine3 = [i64; 12];
|
||||
pub type Angle32=i32;
|
||||
pub type Planar64=i64;
|
||||
pub type Planar64Vec3=[i64;3];
|
||||
pub type Planar64Mat3=[i64;9];
|
||||
pub type Planar64Affine3=[i64;12];
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user