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 |
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,4 +1,4 @@
|
||||
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{
|
||||
@ -7,7 +7,7 @@ fn valve_transform([x, y, z]: [f32; 3]) -> integer::Planar64Vec3 {
|
||||
pub fn convert_bsp<AcquireRenderConfigId,AcquireMeshId>(
|
||||
bsp:&vbsp::Bsp,
|
||||
mut acquire_render_config_id:AcquireRenderConfigId,
|
||||
mut acquire_mesh_id: AcquireMeshId,
|
||||
mut acquire_mesh_id:AcquireMeshId
|
||||
)->PartialMap1
|
||||
where
|
||||
AcquireRenderConfigId:FnMut(Option<&str>)->model::RenderConfigId,
|
||||
@ -16,14 +16,11 @@ where
|
||||
//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);
|
||||
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| {
|
||||
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
|
||||
@ -35,27 +32,22 @@ where
|
||||
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))
|
||||
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(),
|
||||
*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();
|
||||
}).collect();
|
||||
|
||||
//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| {
|
||||
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();
|
||||
@ -63,20 +55,13 @@ where
|
||||
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_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);
|
||||
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
|
||||
@ -94,8 +79,7 @@ where
|
||||
|
||||
//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=glam::vec2(texture_transform_u.dot(pos),texture_transform_v.dot(pos));
|
||||
let tex_idx=spam_tex.len() as u32;
|
||||
spam_tex.push(tex);
|
||||
|
||||
@ -109,17 +93,12 @@ where
|
||||
vertex_id
|
||||
});
|
||||
let polygon_list=std::iter::from_fn(move||{
|
||||
match (
|
||||
polygon_iter.next(),
|
||||
polygon_iter.next(),
|
||||
polygon_iter.next(),
|
||||
) {
|
||||
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();
|
||||
}).collect();
|
||||
if face.is_visible(){
|
||||
//TODO: deduplicate graphics groups by render id
|
||||
graphics_groups.push(model::IndexedGraphicsGroup{
|
||||
@ -129,8 +108,7 @@ where
|
||||
}
|
||||
physics_group.groups.push(polygon_group_id);
|
||||
model::PolygonGroup::PolygonList(model::PolygonList::new(polygon_list))
|
||||
})
|
||||
.collect();
|
||||
}).collect();
|
||||
model::Mesh{
|
||||
unique_pos:spam_pos,
|
||||
unique_tex:spam_tex,
|
||||
@ -141,8 +119,7 @@ where
|
||||
graphics_groups,
|
||||
physics_groups:vec![physics_group],
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
}).collect();
|
||||
|
||||
let world_models:Vec<model::Model>=
|
||||
//one instance of the main world mesh
|
||||
@ -243,58 +220,33 @@ impl PartialMap2 {
|
||||
//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();
|
||||
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| {
|
||||
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()
|
||||
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)| {
|
||||
.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.texture=render_config.texture.and_then(|texture_id|
|
||||
texture_id_map.get(&texture_id).copied()
|
||||
);
|
||||
render_config
|
||||
})
|
||||
.collect();
|
||||
}).collect();
|
||||
map::CompleteMap{
|
||||
modes:self.modes,
|
||||
attributes:self.attributes,
|
||||
@ -337,15 +289,10 @@ where
|
||||
}
|
||||
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_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 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())
|
||||
@ -362,24 +309,17 @@ where
|
||||
});
|
||||
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| {
|
||||
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(),
|
||||
),
|
||||
(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()
|
||||
))
|
||||
})
|
||||
.collect();
|
||||
}).collect();
|
||||
Ok(model::Mesh{
|
||||
unique_pos:spam_pos,
|
||||
unique_normal:spam_normal,
|
||||
|
@ -21,15 +21,13 @@ pub fn read<R: std::io::Read>(mut input: R) -> Result<Bsp, ReadError> {
|
||||
//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,
|
||||
acquire_mesh_id:AcquireMeshId
|
||||
)->bsp::PartialMap1
|
||||
where
|
||||
AcquireRenderConfigId:FnMut(Option<&str>)->strafesnet_common::model::RenderConfigId,
|
||||
|
@ -8,10 +8,7 @@ pub struct Aabb {
|
||||
|
||||
impl Default for Aabb{
|
||||
fn default()->Self{
|
||||
Self {
|
||||
min: vec3::MAX,
|
||||
max: vec3::MIN,
|
||||
}
|
||||
Self{min:vec3::MAX,max:vec3::MIN}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -41,8 +41,7 @@ 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 {
|
||||
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,
|
||||
@ -50,24 +49,18 @@ impl<T> BvhNode<T> {
|
||||
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 {
|
||||
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> {
|
||||
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),
|
||||
@ -75,16 +68,15 @@ impl<T> BvhNode<T> {
|
||||
aabb:self.aabb,
|
||||
},
|
||||
RecursiveContent::Branch(children)=>{
|
||||
let branch: Vec<BvhWeightNode<W, T>> = children
|
||||
.into_iter()
|
||||
.map(|child| child.weigh_contents(f))
|
||||
.collect();
|
||||
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,
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -102,11 +94,9 @@ impl<W, T> BvhWeightNode<W, T> {
|
||||
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 {
|
||||
RecursiveContent::Branch(children)=>for child in children{
|
||||
child.into_visitor(f)
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -119,16 +109,13 @@ 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| {
|
||||
let nodes=boxen.into_iter().map(|b|{
|
||||
aabb.join(&b.1);
|
||||
BvhNode{
|
||||
content:RecursiveContent::Leaf(b.0),
|
||||
aabb:b.1,
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
}).collect();
|
||||
BvhNode{
|
||||
content:RecursiveContent::Branch(nodes),
|
||||
aabb,
|
||||
@ -159,24 +146,9 @@ fn generate_bvh_node<T>(boxen: Vec<(T, Aabb)>, force: bool) -> BvhNode<T> {
|
||||
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
|
||||
};
|
||||
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..]{
|
||||
@ -193,8 +165,7 @@ fn generate_bvh_node<T>(boxen: Vec<(T, Aabb)>, force: bool) -> BvhNode<T> {
|
||||
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)
|
||||
{
|
||||
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();
|
||||
@ -210,14 +181,11 @@ fn generate_bvh_node<T>(boxen: Vec<(T, Aabb)>, force: bool) -> BvhNode<T> {
|
||||
}else{
|
||||
BvhNode{
|
||||
content:RecursiveContent::Branch(
|
||||
list_list
|
||||
.into_iter()
|
||||
.map(|b| {
|
||||
list_list.into_iter().map(|b|{
|
||||
let node=generate_bvh_node(b,false);
|
||||
aabb.join(&node.aabb);
|
||||
node
|
||||
})
|
||||
.collect(),
|
||||
}).collect()
|
||||
),
|
||||
aabb,
|
||||
}
|
||||
|
@ -1,10 +1,10 @@
|
||||
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,
|
||||
pub sticky:bool
|
||||
}
|
||||
#[derive(Clone,Hash,Eq,PartialEq)]
|
||||
pub enum ContactingBehaviour{
|
||||
@ -24,16 +24,13 @@ pub struct IntersectingWater {
|
||||
//All models can be given these attributes
|
||||
#[derive(Clone,Hash,Eq,PartialEq)]
|
||||
pub struct Accelerator{
|
||||
pub acceleration: Planar64Vec3,
|
||||
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
|
||||
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
|
||||
}
|
||||
@ -46,7 +43,7 @@ impl Booster {
|
||||
//let d=direction.dot(velocity);
|
||||
//TODO: think about negative
|
||||
//velocity+direction.with_length((d*d+energy).sqrt()-d)
|
||||
}
|
||||
},
|
||||
Booster::AirTime(_)=>todo!(),
|
||||
Booster::Height(_)=>todo!(),
|
||||
}
|
||||
@ -62,18 +59,13 @@ 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
|
||||
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
|
||||
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
|
||||
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,
|
||||
@ -85,19 +77,9 @@ impl SetTrajectory {
|
||||
match self{
|
||||
SetTrajectory::AirTime(_)
|
||||
|SetTrajectory::Height(_)
|
||||
| SetTrajectory::DotVelocity {
|
||||
direction: _,
|
||||
dot: _,
|
||||
} => false,
|
||||
SetTrajectory::TargetPointTime {
|
||||
target_point: _,
|
||||
time: _,
|
||||
}
|
||||
| SetTrajectory::TargetPointSpeed {
|
||||
target_point: _,
|
||||
speed: _,
|
||||
trajectory_choice: _,
|
||||
}
|
||||
|SetTrajectory::DotVelocity{direction:_,dot:_}=>false,
|
||||
SetTrajectory::TargetPointTime{target_point:_,time:_}
|
||||
|SetTrajectory::TargetPointSpeed{target_point:_,speed:_,trajectory_choice:_}
|
||||
|SetTrajectory::Velocity(_)=>true,
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
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{
|
||||
@ -12,12 +12,7 @@ pub struct StageElement {
|
||||
}
|
||||
impl StageElement{
|
||||
#[inline]
|
||||
pub const fn new(
|
||||
stage_id: StageId,
|
||||
force: bool,
|
||||
behaviour: StageElementBehaviour,
|
||||
jump_limit: Option<u8>,
|
||||
) -> Self {
|
||||
pub const fn new(stage_id:StageId,force:bool,behaviour:StageElementBehaviour,jump_limit:Option<u8>)->Self{
|
||||
Self{
|
||||
stage_id,
|
||||
force,
|
||||
@ -121,23 +116,12 @@ impl Stage {
|
||||
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
|
||||
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)
|
||||
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{
|
||||
@ -153,8 +137,7 @@ pub struct StageUpdate {
|
||||
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);
|
||||
self.unordered_checkpoints.extend(update.unordered_checkpoints);
|
||||
}
|
||||
}
|
||||
|
||||
@ -204,9 +187,7 @@ impl Mode {
|
||||
elements:HashMap::new(),
|
||||
}
|
||||
}
|
||||
pub fn into_inner(
|
||||
self,
|
||||
) -> (
|
||||
pub fn into_inner(self)->(
|
||||
gameplay_style::StyleModifiers,
|
||||
ModelId,
|
||||
HashMap<ModelId,Zone>,
|
||||
@ -250,36 +231,27 @@ impl Mode {
|
||||
//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 {
|
||||
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 {
|
||||
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 {
|
||||
self.elements.insert(model,StageElement{
|
||||
stage_id:StageId(stage_id as u32),
|
||||
force:false,
|
||||
behaviour:StageElementBehaviour::Checkpoint,
|
||||
jump_limit:None,
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -332,7 +304,9 @@ pub struct Modes {
|
||||
}
|
||||
impl Modes{
|
||||
pub const fn new(modes:Vec<Mode>)->Self{
|
||||
Self { modes }
|
||||
Self{
|
||||
modes,
|
||||
}
|
||||
}
|
||||
pub fn into_inner(self)->Vec<Mode>{
|
||||
self.modes
|
||||
|
@ -1,7 +1,7 @@
|
||||
const VALVE_SCALE:Planar64=Planar64::raw(1<<28);// 1/16
|
||||
|
||||
use crate::integer::{int,vec3::int as int3,AbsoluteTime,Ratio64,Planar64,Planar64Vec3};
|
||||
use crate::controls_bitflag::Controls;
|
||||
use crate::integer::{int, vec3::int as int3, AbsoluteTime, Planar64, Planar64Vec3, Ratio64};
|
||||
use crate::physics::Time as PhysicsTime;
|
||||
|
||||
#[derive(Clone,Debug)]
|
||||
@ -75,21 +75,16 @@ impl JumpImpulse {
|
||||
let v_g=gravity.dot(velocity);
|
||||
//do it backwards
|
||||
let radicand=v_g*v_g+(g*height*2).fix_4();
|
||||
velocity
|
||||
- (*gravity * (radicand.sqrt().fix_2() + v_g) / gg)
|
||||
.divide()
|
||||
.fix_1()
|
||||
}
|
||||
&JumpImpulse::Linear(jump_speed) => {
|
||||
velocity + (jump_dir * jump_speed / jump_dir.length()).divide().fix_1()
|
||||
}
|
||||
velocity-(*gravity*(radicand.sqrt().fix_2()+v_g)/gg).divide().fix_1()
|
||||
},
|
||||
&JumpImpulse::Linear(jump_speed)=>velocity+(jump_dir*jump_speed/jump_dir.length()).divide().fix_1(),
|
||||
&JumpImpulse::Energy(energy)=>{
|
||||
//calculate energy
|
||||
//let e=gravity.dot(velocity);
|
||||
//add
|
||||
//you get the idea
|
||||
todo!()
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
//TODO: remove this and implement JumpCalculation properly
|
||||
@ -139,7 +134,7 @@ impl JumpSettings {
|
||||
//activate booster normally, jump does nothing
|
||||
boost_vel
|
||||
}
|
||||
}
|
||||
},
|
||||
(true,_)=>{
|
||||
//the source calculation (?)
|
||||
let boost_vel=match booster{
|
||||
@ -172,7 +167,7 @@ impl JumpSettings {
|
||||
//activate booster normally, jump does nothing
|
||||
boost_vel
|
||||
}
|
||||
}
|
||||
},
|
||||
//the strafe client calculation
|
||||
(false,_)=>{
|
||||
let boost_vel=match booster{
|
||||
@ -180,7 +175,7 @@ impl JumpSettings {
|
||||
None=>rel_velocity,
|
||||
};
|
||||
boost_vel+jump_dir.with_length(jump_speed).divide().fix_1()
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -230,9 +225,7 @@ impl ControlsActivation {
|
||||
//Half-Sideways
|
||||
pub const fn half_sideways()->Self{
|
||||
Self{
|
||||
controls_mask: Controls::MoveForward
|
||||
.union(Controls::MoveLeft)
|
||||
.union(Controls::MoveRight),
|
||||
controls_mask:Controls::MoveForward.union(Controls::MoveLeft).union(Controls::MoveRight),
|
||||
controls_intersects:Controls::MoveLeft.union(Controls::MoveRight),
|
||||
controls_contains:Controls::MoveForward,
|
||||
}
|
||||
@ -240,10 +233,7 @@ impl ControlsActivation {
|
||||
//Surf Half-Sideways
|
||||
pub const fn surf_half_sideways()->Self{
|
||||
Self{
|
||||
controls_mask: Controls::MoveForward
|
||||
.union(Controls::MoveBackward)
|
||||
.union(Controls::MoveLeft)
|
||||
.union(Controls::MoveRight),
|
||||
controls_mask:Controls::MoveForward.union(Controls::MoveBackward).union(Controls::MoveLeft).union(Controls::MoveRight),
|
||||
controls_intersects:Controls::MoveForward.union(Controls::MoveBackward),
|
||||
controls_contains:Controls::empty(),
|
||||
}
|
||||
@ -275,30 +265,16 @@ pub struct StrafeSettings {
|
||||
pub tick_rate:Ratio64,
|
||||
}
|
||||
impl StrafeSettings{
|
||||
pub fn tick_velocity(
|
||||
&self,
|
||||
velocity: Planar64Vec3,
|
||||
control_dir: Planar64Vec3,
|
||||
) -> Option<Planar64Vec3> {
|
||||
pub fn tick_velocity(&self,velocity:Planar64Vec3,control_dir:Planar64Vec3)->Option<Planar64Vec3>{
|
||||
let d=velocity.dot(control_dir);
|
||||
let mv=self.mv.fix_2();
|
||||
match d<mv{
|
||||
true => Some(
|
||||
velocity
|
||||
+ (control_dir
|
||||
* self
|
||||
.air_accel_limit
|
||||
.map_or(mv - d, |limit| limit.fix_2().min(mv - d)))
|
||||
.fix_1(),
|
||||
),
|
||||
true=>Some(velocity+(control_dir*self.air_accel_limit.map_or(mv-d,|limit|limit.fix_2().min(mv-d))).fix_1()),
|
||||
false=>None,
|
||||
}
|
||||
}
|
||||
pub fn next_tick(&self,time:PhysicsTime)->PhysicsTime{
|
||||
PhysicsTime::from_nanos(
|
||||
self.tick_rate
|
||||
.rhs_div_int(self.tick_rate.mul_int(time.nanos()) + 1),
|
||||
)
|
||||
PhysicsTime::from_nanos(self.tick_rate.rhs_div_int(self.tick_rate.mul_int(time.nanos())+1))
|
||||
}
|
||||
pub const fn activates(&self,controls:Controls)->bool{
|
||||
self.enable.activates(controls)
|
||||
@ -342,11 +318,7 @@ impl WalkSettings {
|
||||
};
|
||||
self.accelerate.accel.min((-gravity.y*friction).fix_1())
|
||||
}
|
||||
pub fn get_walk_target_velocity(
|
||||
&self,
|
||||
control_dir: Planar64Vec3,
|
||||
normal: Planar64Vec3,
|
||||
) -> Planar64Vec3 {
|
||||
pub fn get_walk_target_velocity(&self,control_dir:Planar64Vec3,normal:Planar64Vec3)->Planar64Vec3{
|
||||
if control_dir==crate::integer::vec3::ZERO{
|
||||
return control_dir;
|
||||
}
|
||||
@ -360,9 +332,7 @@ impl WalkSettings {
|
||||
if cr==crate::integer::vec3::ZERO_2{
|
||||
crate::integer::vec3::ZERO
|
||||
}else{
|
||||
(cr.cross(normal) * self.accelerate.topspeed / ((nn * (nnmm - dd)).sqrt()))
|
||||
.divide()
|
||||
.fix_1()
|
||||
(cr.cross(normal)*self.accelerate.topspeed/((nn*(nnmm-dd)).sqrt())).divide().fix_1()
|
||||
}
|
||||
}else{
|
||||
crate::integer::vec3::ZERO
|
||||
@ -389,11 +359,7 @@ impl LadderSettings {
|
||||
//TODO: fallible ladder accel
|
||||
self.accelerate.accel
|
||||
}
|
||||
pub fn get_ladder_target_velocity(
|
||||
&self,
|
||||
mut control_dir: Planar64Vec3,
|
||||
normal: Planar64Vec3,
|
||||
) -> Planar64Vec3 {
|
||||
pub fn get_ladder_target_velocity(&self,mut control_dir:Planar64Vec3,normal:Planar64Vec3)->Planar64Vec3{
|
||||
if control_dir==crate::integer::vec3::ZERO{
|
||||
return control_dir;
|
||||
}
|
||||
@ -419,9 +385,7 @@ impl LadderSettings {
|
||||
if cr==crate::integer::vec3::ZERO_2{
|
||||
crate::integer::vec3::ZERO
|
||||
}else{
|
||||
(cr.cross(normal) * self.accelerate.topspeed / ((nn * (nnmm - dd)).sqrt()))
|
||||
.divide()
|
||||
.fix_1()
|
||||
(cr.cross(normal)*self.accelerate.topspeed/((nn*(nnmm-dd)).sqrt())).divide().fix_1()
|
||||
}
|
||||
}else{
|
||||
crate::integer::vec3::ZERO
|
||||
@ -498,7 +462,9 @@ impl StyleModifiers {
|
||||
},
|
||||
dot:(int(1)/2).sqrt(),
|
||||
}),
|
||||
swim: Some(PropulsionSettings { magnitude: int(12) }),
|
||||
swim:Some(PropulsionSettings{
|
||||
magnitude:int(12),
|
||||
}),
|
||||
hitbox:Hitbox::roblox(),
|
||||
camera_offset:int3(0,2,0),//4.5-2.5=2
|
||||
}
|
||||
@ -538,7 +504,9 @@ impl StyleModifiers {
|
||||
},
|
||||
dot:(int(1)/2).sqrt(),
|
||||
}),
|
||||
swim: Some(PropulsionSettings { magnitude: int(12) }),
|
||||
swim:Some(PropulsionSettings{
|
||||
magnitude:int(12),
|
||||
}),
|
||||
hitbox:Hitbox::roblox(),
|
||||
camera_offset:int3(0,2,0),//4.5-2.5=2
|
||||
}
|
||||
|
@ -1,27 +1,47 @@
|
||||
use crate::integer::Time;
|
||||
|
||||
#[derive(Debug)]
|
||||
#[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;
|
||||
/// 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<Self::Instruction, Self::TimeInner>>;
|
||||
fn next_instruction(&self,time_limit:Time<Self::TimeInner>)->Option<TimedInstruction<I,Self::TimeInner>>;
|
||||
}
|
||||
pub trait InstructionConsumer {
|
||||
type Instruction;
|
||||
/// Apply an atomic state update
|
||||
pub trait InstructionConsumer<I>{
|
||||
type TimeInner;
|
||||
fn process_instruction(
|
||||
&mut self,
|
||||
instruction: TimedInstruction<Self::Instruction, Self::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>{
|
||||
@ -29,38 +49,34 @@ pub struct InstructionCollector<I, T> {
|
||||
instruction:Option<I>,
|
||||
}
|
||||
impl<I,T> InstructionCollector<I,T>
|
||||
where
|
||||
Time<T>: Copy + PartialOrd,
|
||||
where Time<T>:Copy+PartialOrd,
|
||||
{
|
||||
#[inline]
|
||||
pub const fn new(time:Time<T>)->Self{
|
||||
Self{
|
||||
time,
|
||||
instruction: None,
|
||||
instruction:None
|
||||
}
|
||||
}
|
||||
#[inline]
|
||||
pub const fn time(&self)->Time<T>{
|
||||
self.time
|
||||
}
|
||||
#[inline]
|
||||
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);
|
||||
if let Some(ins)=instruction{
|
||||
if ins.time<self.time{
|
||||
self.time=ins.time;
|
||||
self.instruction=Some(ins.instruction);
|
||||
}
|
||||
}
|
||||
None => (),
|
||||
}
|
||||
}
|
||||
pub fn instruction(self) -> Option<TimedInstruction<I, T>> {
|
||||
#[inline]
|
||||
pub fn take(self)->Option<TimedInstruction<I,T>>{
|
||||
//STEAL INSTRUCTION AND DESTROY INSTRUCTIONCOLLECTOR
|
||||
match self.instruction {
|
||||
Some(instruction) => Some(TimedInstruction {
|
||||
self.instruction.map(|instruction|TimedInstruction{
|
||||
time:self.time,
|
||||
instruction,
|
||||
}),
|
||||
None => None,
|
||||
}
|
||||
instruction
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
pub use fixed_wide::fixed::{Fix, Fixed};
|
||||
pub use ratio_ops::ratio::{Divide, Ratio};
|
||||
pub use fixed_wide::fixed::{Fixed,Fix};
|
||||
pub use ratio_ops::ratio::{Ratio,Divide};
|
||||
|
||||
//integer units
|
||||
|
||||
@ -14,6 +14,7 @@ impl<T> Time<T> {
|
||||
pub const MIN:Self=Self::raw(i64::MIN);
|
||||
pub const MAX:Self=Self::raw(i64::MAX);
|
||||
pub const ZERO:Self=Self::raw(0);
|
||||
pub const EPSILON:Self=Self::raw(1);
|
||||
pub const ONE_SECOND:Self=Self::raw(1_000_000_000);
|
||||
pub const ONE_MILLISECOND:Self=Self::raw(1_000_000);
|
||||
pub const ONE_MICROSECOND:Self=Self::raw(1_000);
|
||||
@ -70,23 +71,13 @@ where
|
||||
{
|
||||
#[inline]
|
||||
fn from(value:Ratio<Num,Den>)->Self{
|
||||
Self::raw(
|
||||
(value * Planar64::raw(1_000_000_000))
|
||||
.divide()
|
||||
.fix()
|
||||
.to_raw(),
|
||||
)
|
||||
Self::raw((value*Planar64::raw(1_000_000_000)).divide().fix().to_raw())
|
||||
}
|
||||
}
|
||||
impl<T> std::fmt::Display for Time<T>{
|
||||
#[inline]
|
||||
fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{
|
||||
write!(
|
||||
f,
|
||||
"{}s+{:09}ns",
|
||||
self.0 / Self::ONE_SECOND.0,
|
||||
self.0 % Self::ONE_SECOND.0
|
||||
)
|
||||
write!(f,"{}s+{:09}ns",self.0/Self::ONE_SECOND.0,self.0%Self::ONE_SECOND.0)
|
||||
}
|
||||
}
|
||||
impl<T> std::default::Default for Time<T>{
|
||||
@ -132,10 +123,7 @@ impl<T> std::ops::Mul for Time<T> {
|
||||
type Output=Ratio<fixed_wide::fixed::Fixed<2,64>,fixed_wide::fixed::Fixed<2,64>>;
|
||||
#[inline]
|
||||
fn mul(self,rhs:Self)->Self::Output{
|
||||
Ratio::new(
|
||||
Fixed::raw(self.0) * Fixed::raw(rhs.0),
|
||||
Fixed::raw_digit(1_000_000_000i64.pow(2)),
|
||||
)
|
||||
Ratio::new(Fixed::raw(self.0)*Fixed::raw(rhs.0),Fixed::raw_digit(1_000_000_000i64.pow(2)))
|
||||
}
|
||||
}
|
||||
impl<T> std::ops::Div<i64> for Time<T>{
|
||||
@ -175,25 +163,13 @@ mod test_time {
|
||||
#[test]
|
||||
fn time_squared(){
|
||||
let a=Time::from_secs(2);
|
||||
assert_eq!(
|
||||
a * a,
|
||||
Ratio::new(
|
||||
Fixed::<2, 64>::raw_digit(1_000_000_000i64.pow(2)) * 4,
|
||||
Fixed::<2, 64>::raw_digit(1_000_000_000i64.pow(2))
|
||||
)
|
||||
);
|
||||
assert_eq!(a*a,Ratio::new(Fixed::<2,64>::raw_digit(1_000_000_000i64.pow(2))*4,Fixed::<2,64>::raw_digit(1_000_000_000i64.pow(2))));
|
||||
}
|
||||
#[test]
|
||||
fn time_times_planar64(){
|
||||
let a=Time::from_secs(2);
|
||||
let b=Planar64::from(2);
|
||||
assert_eq!(
|
||||
b * a,
|
||||
Ratio::new(
|
||||
Fixed::<2, 64>::raw_digit(1_000_000_000 * (1 << 32)) << 2,
|
||||
Fixed::<1, 32>::raw_digit(1_000_000_000)
|
||||
)
|
||||
);
|
||||
assert_eq!(b*a,Ratio::new(Fixed::<2,64>::raw_digit(1_000_000_000*(1<<32))<<2,Fixed::<1,32>::raw_digit(1_000_000_000)));
|
||||
}
|
||||
}
|
||||
|
||||
@ -201,7 +177,7 @@ mod test_time {
|
||||
const fn gcd(mut a:u64,mut b:u64)->u64{
|
||||
while b!=0{
|
||||
(a,b)=(b,a.rem_euclid(b));
|
||||
}
|
||||
};
|
||||
a
|
||||
}
|
||||
#[derive(Clone,Copy,Debug,Hash)]
|
||||
@ -218,10 +194,7 @@ impl Ratio64 {
|
||||
None
|
||||
}else{
|
||||
let d=gcd(num.unsigned_abs(),den);
|
||||
Some(Self {
|
||||
num: num / (d as i64),
|
||||
den: den / d,
|
||||
})
|
||||
Some(Self{num:num/(d as i64),den:den/d})
|
||||
}
|
||||
}
|
||||
#[inline]
|
||||
@ -333,9 +306,8 @@ impl TryFrom<f32> for Ratio64 {
|
||||
std::num::FpCategory::Nan=>Err(Self::Error::Nan),
|
||||
std::num::FpCategory::Infinite=>Err(Self::Error::Infinite),
|
||||
std::num::FpCategory::Zero=>Ok(Self::ZERO),
|
||||
std::num::FpCategory::Subnormal | std::num::FpCategory::Normal => {
|
||||
ratio64_from_mes(integer_decode_f32(value))
|
||||
}
|
||||
std::num::FpCategory::Subnormal
|
||||
|std::num::FpCategory::Normal=>ratio64_from_mes(integer_decode_f32(value)),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -347,9 +319,8 @@ impl TryFrom<f64> for Ratio64 {
|
||||
std::num::FpCategory::Nan=>Err(Self::Error::Nan),
|
||||
std::num::FpCategory::Infinite=>Err(Self::Error::Infinite),
|
||||
std::num::FpCategory::Zero=>Ok(Self::ZERO),
|
||||
std::num::FpCategory::Subnormal | std::num::FpCategory::Normal => {
|
||||
ratio64_from_mes(integer_decode_f64(value))
|
||||
}
|
||||
std::num::FpCategory::Subnormal
|
||||
|std::num::FpCategory::Normal=>ratio64_from_mes(integer_decode_f64(value)),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -391,17 +362,17 @@ pub struct Ratio64Vec2 {
|
||||
pub y:Ratio64,
|
||||
}
|
||||
impl Ratio64Vec2{
|
||||
pub const ONE: Self = Self {
|
||||
x: Ratio64::ONE,
|
||||
y: Ratio64::ONE,
|
||||
};
|
||||
pub const ONE:Self=Self{x:Ratio64::ONE,y:Ratio64::ONE};
|
||||
#[inline]
|
||||
pub const fn new(x:Ratio64,y:Ratio64)->Self{
|
||||
Self{x,y}
|
||||
}
|
||||
#[inline]
|
||||
pub const fn mul_int(&self,rhs:glam::I64Vec2)->glam::I64Vec2{
|
||||
glam::i64vec2(self.x.mul_int(rhs.x), self.y.mul_int(rhs.y))
|
||||
glam::i64vec2(
|
||||
self.x.mul_int(rhs.x),
|
||||
self.y.mul_int(rhs.y),
|
||||
)
|
||||
}
|
||||
}
|
||||
impl std::ops::Mul<i64> for Ratio64Vec2{
|
||||
@ -427,9 +398,7 @@ impl Angle32 {
|
||||
pub const fn wrap_from_i64(theta:i64)->Self{
|
||||
//take lower bits
|
||||
//note: this was checked on compiler explorer and compiles to 1 instruction!
|
||||
Self(i32::from_ne_bytes(
|
||||
((theta & ((1 << 32) - 1)) as u32).to_ne_bytes(),
|
||||
))
|
||||
Self(i32::from_ne_bytes(((theta&((1<<32)-1)) as u32).to_ne_bytes()))
|
||||
}
|
||||
#[inline]
|
||||
pub fn clamp_from_i64(theta:i64)->Self{
|
||||
@ -446,15 +415,18 @@ impl Angle32 {
|
||||
#[inline]
|
||||
pub fn clamp(&self,theta_min:Self,theta_max:Self)->Self{
|
||||
//((max-min as u32)/2 as i32)+min
|
||||
let midpoint = (((theta_max.0 as u32).wrapping_sub(theta_min.0 as u32) / 2) as i32) //(u32::MAX/2) as i32 ALWAYS works
|
||||
let midpoint=((
|
||||
(theta_max.0 as u32)
|
||||
.wrapping_sub(theta_min.0 as u32)
|
||||
/2
|
||||
) as i32)//(u32::MAX/2) as i32 ALWAYS works
|
||||
.wrapping_add(theta_min.0);
|
||||
//(theta-mid).clamp(max-mid,min-mid)+mid
|
||||
Self(
|
||||
self.0
|
||||
.wrapping_sub(midpoint)
|
||||
self.0.wrapping_sub(midpoint)
|
||||
.max(theta_min.0.wrapping_sub(midpoint))
|
||||
.min(theta_max.0.wrapping_sub(midpoint))
|
||||
.wrapping_add(midpoint),
|
||||
.wrapping_add(midpoint)
|
||||
)
|
||||
}
|
||||
#[inline]
|
||||
@ -480,10 +452,7 @@ impl Angle32 {
|
||||
(Planar64::raw(x),Planar64::raw(y))
|
||||
*/
|
||||
let (s,c)=(self.0 as f64*Self::ANGLE32_TO_FLOAT64_RADIANS).sin_cos();
|
||||
(
|
||||
Planar64::raw((c * ((1u64 << 32) as f64)) as i64),
|
||||
Planar64::raw((s * ((1u64 << 32) as f64)) as i64),
|
||||
)
|
||||
(Planar64::raw((c*((1u64<<32) as f64)) as i64),Planar64::raw((s*((1u64<<32) as f64)) as i64))
|
||||
}
|
||||
}
|
||||
impl Into<f32> for Angle32{
|
||||
@ -540,14 +509,8 @@ fn angle_sin_cos() {
|
||||
println!("cordic s={} c={}",(s/h).divide(),(c/h).divide());
|
||||
let (fs,fc)=f.sin_cos();
|
||||
println!("float s={} c={}",fs,fc);
|
||||
assert!(close_enough(
|
||||
(c / h).divide().fix_1(),
|
||||
Planar64::raw((fc * ((1u64 << 32) as f64)) as i64)
|
||||
));
|
||||
assert!(close_enough(
|
||||
(s / h).divide().fix_1(),
|
||||
Planar64::raw((fs * ((1u64 << 32) as f64)) as i64)
|
||||
));
|
||||
assert!(close_enough((c/h).divide().fix_1(),Planar64::raw((fc*((1u64<<32) as f64)) as i64)));
|
||||
assert!(close_enough((s/h).divide().fix_1(),Planar64::raw((fs*((1u64<<32) as f64)) as i64)));
|
||||
}
|
||||
test_angle(1.0);
|
||||
test_angle(std::f64::consts::PI/4.0);
|
||||
@ -588,27 +551,18 @@ pub mod vec3 {
|
||||
pub const MIN:Planar64Vec3=Planar64Vec3::new([Planar64::MIN;3]);
|
||||
pub const MAX:Planar64Vec3=Planar64Vec3::new([Planar64::MAX;3]);
|
||||
pub const ZERO:Planar64Vec3=Planar64Vec3::new([Planar64::ZERO;3]);
|
||||
pub const ZERO_2: linear_ops::types::Vector3<Fixed<2, 64>> =
|
||||
linear_ops::types::Vector3::new([Fixed::<2, 64>::ZERO; 3]);
|
||||
pub const ZERO_2:linear_ops::types::Vector3<Fixed::<2,64>>=linear_ops::types::Vector3::new([Fixed::<2,64>::ZERO;3]);
|
||||
pub const X:Planar64Vec3=Planar64Vec3::new([Planar64::ONE,Planar64::ZERO,Planar64::ZERO]);
|
||||
pub const Y:Planar64Vec3=Planar64Vec3::new([Planar64::ZERO,Planar64::ONE,Planar64::ZERO]);
|
||||
pub const Z:Planar64Vec3=Planar64Vec3::new([Planar64::ZERO,Planar64::ZERO,Planar64::ONE]);
|
||||
pub const ONE:Planar64Vec3=Planar64Vec3::new([Planar64::ONE,Planar64::ONE,Planar64::ONE]);
|
||||
pub const NEG_X: Planar64Vec3 =
|
||||
Planar64Vec3::new([Planar64::NEG_ONE, Planar64::ZERO, Planar64::ZERO]);
|
||||
pub const NEG_Y: Planar64Vec3 =
|
||||
Planar64Vec3::new([Planar64::ZERO, Planar64::NEG_ONE, Planar64::ZERO]);
|
||||
pub const NEG_Z: Planar64Vec3 =
|
||||
Planar64Vec3::new([Planar64::ZERO, Planar64::ZERO, Planar64::NEG_ONE]);
|
||||
pub const NEG_ONE: Planar64Vec3 =
|
||||
Planar64Vec3::new([Planar64::NEG_ONE, Planar64::NEG_ONE, Planar64::NEG_ONE]);
|
||||
pub const NEG_X:Planar64Vec3=Planar64Vec3::new([Planar64::NEG_ONE,Planar64::ZERO,Planar64::ZERO]);
|
||||
pub const NEG_Y:Planar64Vec3=Planar64Vec3::new([Planar64::ZERO,Planar64::NEG_ONE,Planar64::ZERO]);
|
||||
pub const NEG_Z:Planar64Vec3=Planar64Vec3::new([Planar64::ZERO,Planar64::ZERO,Planar64::NEG_ONE]);
|
||||
pub const NEG_ONE:Planar64Vec3=Planar64Vec3::new([Planar64::NEG_ONE,Planar64::NEG_ONE,Planar64::NEG_ONE]);
|
||||
#[inline]
|
||||
pub const fn int(x:i32,y:i32,z:i32)->Planar64Vec3{
|
||||
Planar64Vec3::new([
|
||||
Planar64::raw((x as i64) << 32),
|
||||
Planar64::raw((y as i64) << 32),
|
||||
Planar64::raw((z as i64) << 32),
|
||||
])
|
||||
Planar64Vec3::new([Planar64::raw((x as i64)<<32),Planar64::raw((y as i64)<<32),Planar64::raw((z as i64)<<32)])
|
||||
}
|
||||
#[inline]
|
||||
pub fn raw_array(array:[i64;3])->Planar64Vec3{
|
||||
@ -619,9 +573,7 @@ pub mod vec3 {
|
||||
Planar64Vec3::new([Planar64::raw(x),Planar64::raw(y),Planar64::raw(z)])
|
||||
}
|
||||
#[inline]
|
||||
pub fn try_from_f32_array(
|
||||
[x, y, z]: [f32; 3],
|
||||
) -> Result<Planar64Vec3, Planar64TryFromFloatError> {
|
||||
pub fn try_from_f32_array([x,y,z]:[f32;3])->Result<Planar64Vec3,Planar64TryFromFloatError>{
|
||||
Ok(Planar64Vec3::new([
|
||||
try_from_f32(x)?,
|
||||
try_from_f32(y)?,
|
||||
@ -681,9 +633,7 @@ pub mod mat3 {
|
||||
])
|
||||
}
|
||||
#[inline]
|
||||
pub fn try_from_f32_array_2d(
|
||||
[x_axis, y_axis, z_axis]: [[f32; 3]; 3],
|
||||
) -> Result<Planar64Mat3, Planar64TryFromFloatError> {
|
||||
pub fn try_from_f32_array_2d([x_axis,y_axis,z_axis]:[[f32;3];3])->Result<Planar64Mat3,Planar64TryFromFloatError>{
|
||||
Ok(Planar64Mat3::new([
|
||||
vec3::try_from_f32_array(x_axis)?.to_array(),
|
||||
vec3::try_from_f32_array(y_axis)?.to_array(),
|
||||
@ -700,10 +650,7 @@ pub struct Planar64Affine3 {
|
||||
impl Planar64Affine3{
|
||||
#[inline]
|
||||
pub const fn new(matrix3:Planar64Mat3,translation:Planar64Vec3)->Self{
|
||||
Self {
|
||||
matrix3,
|
||||
translation,
|
||||
}
|
||||
Self{matrix3,translation}
|
||||
}
|
||||
#[inline]
|
||||
pub fn transform_point3(&self,point:Planar64Vec3)->vec3::Vector3<Fixed<2,64>>{
|
||||
@ -713,28 +660,13 @@ impl Planar64Affine3 {
|
||||
impl Into<glam::Mat4> for Planar64Affine3{
|
||||
#[inline]
|
||||
fn into(self)->glam::Mat4{
|
||||
let matrix3 = self
|
||||
.matrix3
|
||||
.to_array()
|
||||
.map(|row| row.map(Into::<f32>::into));
|
||||
let matrix3=self.matrix3.to_array().map(|row|row.map(Into::<f32>::into));
|
||||
let translation=self.translation.to_array().map(Into::<f32>::into);
|
||||
glam::Mat4::from_cols_array(&[
|
||||
matrix3[0][0],
|
||||
matrix3[0][1],
|
||||
matrix3[0][2],
|
||||
0.0,
|
||||
matrix3[1][0],
|
||||
matrix3[1][1],
|
||||
matrix3[1][2],
|
||||
0.0,
|
||||
matrix3[2][0],
|
||||
matrix3[2][1],
|
||||
matrix3[2][2],
|
||||
0.0,
|
||||
translation[0],
|
||||
translation[1],
|
||||
translation[2],
|
||||
1.0,
|
||||
matrix3[0][0],matrix3[0][1],matrix3[0][2],0.0,
|
||||
matrix3[1][0],matrix3[1][1],matrix3[1][2],0.0,
|
||||
matrix3[2][0],matrix3[2][1],matrix3[2][2],0.0,
|
||||
translation[0],translation[1],translation[2],1.0
|
||||
])
|
||||
}
|
||||
}
|
||||
|
@ -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,6 +1,6 @@
|
||||
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{
|
||||
|
@ -1,5 +1,5 @@
|
||||
use crate::integer::{Planar64Vec3,Planar64Affine3};
|
||||
use crate::gameplay_attributes;
|
||||
use crate::integer::{Planar64Affine3, Planar64Vec3};
|
||||
|
||||
pub type TextureCoordinate=glam::Vec2;
|
||||
pub type Color4=glam::Vec4;
|
||||
@ -44,12 +44,7 @@ impl PolygonIter for PolygonList {
|
||||
}
|
||||
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(),
|
||||
)
|
||||
Self(self.0.into_iter().map(|ivl|ivl.into_iter().map(&f).collect()).collect())
|
||||
}
|
||||
}
|
||||
// pub struct TriangleStrip(IndexedVertexList);
|
||||
|
@ -14,8 +14,7 @@ impl<T> Default for MouseState<T> {
|
||||
}
|
||||
}
|
||||
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();
|
||||
|
@ -1,14 +1,33 @@
|
||||
use crate::mouse::MouseState;
|
||||
use crate::gameplay_modes::{ModeId,StageId};
|
||||
|
||||
#[derive(Clone,Copy,Hash,Eq,PartialEq,PartialOrd,Debug)]
|
||||
pub enum TimeInner{}
|
||||
pub type Time=crate::integer::Time<TimeInner>;
|
||||
|
||||
#[derive(Clone,Debug)]
|
||||
pub enum Instruction{
|
||||
ReplaceMouse(
|
||||
crate::mouse::MouseState<TimeInner>,
|
||||
crate::mouse::MouseState<TimeInner>,
|
||||
),
|
||||
SetNextMouse(crate::mouse::MouseState<TimeInner>),
|
||||
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),
|
||||
@ -17,21 +36,21 @@ pub enum Instruction {
|
||||
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.
|
||||
Restart,
|
||||
/// This runs when you press R or teleport to a bonus
|
||||
Restart(ModeId),
|
||||
/// 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.
|
||||
/// 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,6 +1,6 @@
|
||||
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{}
|
||||
@ -55,12 +55,8 @@ 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>,
|
||||
},
|
||||
Started{timer:TimerFixed<Realtime<PhysicsTimeInner,TimeInner>,Unpaused>},
|
||||
Finished{timer:TimerFixed<Realtime<PhysicsTimeInner,TimeInner>,Paused>},
|
||||
}
|
||||
|
||||
#[derive(Clone,Copy,Debug)]
|
||||
@ -80,7 +76,7 @@ impl Run {
|
||||
match &self.state{
|
||||
RunState::Created=>Time::ZERO,
|
||||
RunState::Started{timer}=>timer.time(time),
|
||||
RunState::Finished { timer } => timer.time(time),
|
||||
RunState::Finished{timer}=>timer.time(),
|
||||
}
|
||||
}
|
||||
pub fn start(&mut self,time:PhysicsTime)->Result<(),Error>{
|
||||
@ -90,7 +86,7 @@ impl Run {
|
||||
timer:TimerFixed::new(time,Time::ZERO),
|
||||
};
|
||||
Ok(())
|
||||
}
|
||||
},
|
||||
RunState::Started{..}=>Err(Error::AlreadyStarted),
|
||||
RunState::Finished{..}=>Err(Error::AlreadyFinished),
|
||||
}
|
||||
@ -104,7 +100,7 @@ impl Run {
|
||||
timer:timer.into_paused(time),
|
||||
};
|
||||
Ok(())
|
||||
}
|
||||
},
|
||||
RunState::Finished{..}=>Err(Error::AlreadyFinished),
|
||||
}
|
||||
}
|
||||
@ -114,4 +110,10 @@ impl Run {
|
||||
self.flagged=Some(flag_reason);
|
||||
}
|
||||
}
|
||||
pub fn get_finish_time(&self)->Option<Time>{
|
||||
match &self.state{
|
||||
RunState::Finished{timer}=>Some(timer.time()),
|
||||
_=>None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
use crate::integer::{Ratio64, Time};
|
||||
use crate::integer::{Time,Ratio64};
|
||||
|
||||
#[derive(Clone,Copy,Debug)]
|
||||
pub struct Paused;
|
||||
@ -23,7 +23,7 @@ impl PauseState for Unpaused {
|
||||
}
|
||||
|
||||
#[derive(Clone,Copy,Hash,Eq,PartialEq,PartialOrd,Debug)]
|
||||
enum Inner {}
|
||||
pub enum Inner{}
|
||||
type InnerTime=Time<Inner>;
|
||||
|
||||
#[derive(Clone,Copy,Debug)]
|
||||
@ -50,8 +50,7 @@ pub struct Scaled<In, Out> {
|
||||
_out:core::marker::PhantomData<Out>,
|
||||
}
|
||||
impl<In,Out> Scaled<In,Out>
|
||||
where
|
||||
Time<In>: Copy,
|
||||
where Time<In>:Copy,
|
||||
{
|
||||
pub const fn new(scale:Ratio64,offset:InnerTime)->Self{
|
||||
Self{
|
||||
@ -106,8 +105,7 @@ impl<In, Out> TimerState for Realtime<In, Out> {
|
||||
}
|
||||
}
|
||||
impl<In,Out> TimerState for Scaled<In,Out>
|
||||
where
|
||||
Time<In>: Copy,
|
||||
where Time<In>:Copy,
|
||||
{
|
||||
type In=In;
|
||||
type Out=Out;
|
||||
@ -136,8 +134,7 @@ pub struct TimerFixed<T: TimerState, P: PauseState> {
|
||||
|
||||
//scaled timer methods are generic across PauseState
|
||||
impl<P:PauseState,In,Out> TimerFixed<Scaled<In,Out>,P>
|
||||
where
|
||||
Time<In>: Copy,
|
||||
where Time<In>:Copy,
|
||||
{
|
||||
pub fn scaled(time:Time<In>,new_time:Time<Out>,scale:Ratio64)->Self{
|
||||
let mut timer=Self{
|
||||
@ -157,11 +154,10 @@ where
|
||||
|
||||
//pause and unpause is generic across TimerState
|
||||
impl<T:TimerState> TimerFixed<T,Paused>
|
||||
where
|
||||
Time<T::In>: Copy,
|
||||
where Time<T::In>:Copy,
|
||||
{
|
||||
pub fn into_unpaused(self,time:Time<T::In>)->TimerFixed<T,Unpaused>{
|
||||
let new_time = self.time(time);
|
||||
let new_time=self.time();
|
||||
let mut timer=TimerFixed{
|
||||
state:self.state,
|
||||
_paused:Unpaused,
|
||||
@ -169,10 +165,12 @@ where
|
||||
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,
|
||||
where Time<T::In>:Copy,
|
||||
{
|
||||
pub fn into_paused(self,time:Time<T::In>)->TimerFixed<T,Paused>{
|
||||
let new_time=self.time(time);
|
||||
@ -183,6 +181,9 @@ where
|
||||
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
|
||||
@ -204,12 +205,6 @@ impl<T: TimerState, P: PauseState> TimerFixed<T, P> {
|
||||
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()),
|
||||
@ -261,7 +256,7 @@ where
|
||||
}
|
||||
pub fn time(&self,time:Time<T::In>)->Time<T::Out>{
|
||||
match self{
|
||||
Self::Paused(timer) => timer.time(time),
|
||||
Self::Paused(timer)=>timer.time(),
|
||||
Self::Unpaused(timer)=>timer.time(time),
|
||||
}
|
||||
}
|
||||
@ -300,8 +295,7 @@ where
|
||||
}
|
||||
//scaled timer methods are generic across PauseState
|
||||
impl<In,Out> Timer<Scaled<In,Out>>
|
||||
where
|
||||
Time<In>: Copy,
|
||||
where Time<In>:Copy,
|
||||
{
|
||||
pub const fn get_scale(&self)->Ratio64{
|
||||
match self{
|
||||
@ -333,12 +327,9 @@ mod test {
|
||||
#[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),
|
||||
));
|
||||
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));
|
||||
assert_eq!(timer.time(),sec!(0));
|
||||
|
||||
//unpause it after one second
|
||||
let timer=timer.into_unpaused(sec!(1));
|
||||
@ -348,7 +339,7 @@ mod test {
|
||||
//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));
|
||||
assert_eq!(timer.time(),sec!(5));
|
||||
}
|
||||
#[test]
|
||||
fn test_timer()->Result<(),Error>{
|
||||
|
@ -1,3 +1,5 @@
|
||||
// This whole thing should be a drive macro
|
||||
|
||||
pub trait Updatable<Updater>{
|
||||
fn update(&mut self,update:Updater);
|
||||
}
|
||||
@ -18,7 +20,7 @@ struct Outer {
|
||||
enum Update<I,U>{
|
||||
Insert(I),
|
||||
Update(U),
|
||||
Remove,
|
||||
Remove
|
||||
}
|
||||
|
||||
struct InnerUpdate{
|
||||
@ -53,4 +55,3 @@ impl Updatable<OuterUpdate> for Outer {
|
||||
}
|
||||
}
|
||||
}
|
||||
//*/
|
||||
|
@ -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" }
|
||||
|
@ -1,6 +1,6 @@
|
||||
use bnum::{cast::As, BInt};
|
||||
use bnum::{BInt,cast::As};
|
||||
|
||||
#[derive(Clone, Copy, Debug, Default, Hash)]
|
||||
#[derive(Clone,Copy,Debug,Default,Hash,PartialEq,Eq,PartialOrd,Ord)]
|
||||
/// A Fixed point number for which multiply operations widen the bits in the output. (when the wide-mul feature is enabled)
|
||||
/// N is the number of u64s to use
|
||||
/// F is the number of fractional bits (always N*32 lol)
|
||||
@ -23,8 +23,10 @@ impl<const N: usize, const F: usize> Fixed<N, F> {
|
||||
}
|
||||
impl<const N:usize,const F:usize> Fixed<N,F>{
|
||||
#[inline]
|
||||
pub const fn from_bits(bits: BInt<N>) -> Self {
|
||||
Self { bits }
|
||||
pub const fn from_bits(bits:BInt::<N>)->Self{
|
||||
Self{
|
||||
bits,
|
||||
}
|
||||
}
|
||||
#[inline]
|
||||
pub const fn to_bits(self)->BInt<N>{
|
||||
@ -80,48 +82,32 @@ macro_rules! impl_from {
|
||||
)*
|
||||
};
|
||||
}
|
||||
impl_from!(u8, u16, u32, u64, u128, usize, i8, i16, i32, i64, i128, isize);
|
||||
impl_from!(
|
||||
u8,u16,u32,u64,u128,usize,
|
||||
i8,i16,i32,i64,i128,isize
|
||||
);
|
||||
|
||||
impl<const N: usize, const F: usize> PartialEq for Fixed<N, F> {
|
||||
#[inline]
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.bits.eq(&other.bits)
|
||||
}
|
||||
}
|
||||
impl<const N:usize,const F:usize,T> PartialEq<T> for Fixed<N,F>
|
||||
where
|
||||
T:Copy,
|
||||
BInt<N>: From<T>,
|
||||
BInt::<N>:From<T>,
|
||||
{
|
||||
#[inline]
|
||||
fn eq(&self,&other:&T)->bool{
|
||||
self.bits.eq(&other.into())
|
||||
}
|
||||
}
|
||||
impl<const N: usize, const F: usize> Eq for Fixed<N, F> {}
|
||||
|
||||
impl<const N: usize, const F: usize> PartialOrd for Fixed<N, F> {
|
||||
#[inline]
|
||||
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
|
||||
self.bits.partial_cmp(&other.bits)
|
||||
}
|
||||
}
|
||||
impl<const N:usize,const F:usize,T> PartialOrd<T> for Fixed<N,F>
|
||||
where
|
||||
T:Copy,
|
||||
BInt<N>: From<T>,
|
||||
BInt::<N>:From<T>,
|
||||
{
|
||||
#[inline]
|
||||
fn partial_cmp(&self,&other:&T)->Option<std::cmp::Ordering>{
|
||||
self.bits.partial_cmp(&other.into())
|
||||
}
|
||||
}
|
||||
impl<const N: usize, const F: usize> Ord for Fixed<N, F> {
|
||||
#[inline]
|
||||
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
|
||||
self.bits.cmp(&other.bits)
|
||||
}
|
||||
}
|
||||
|
||||
impl<const N:usize,const F:usize> std::ops::Neg for Fixed<N,F>{
|
||||
type Output=Self;
|
||||
@ -156,11 +142,7 @@ macro_rules! impl_into_float {
|
||||
const DIGIT_SHIFT:u32=6;//Log2[64]
|
||||
// SBBB BBBB
|
||||
// 1001 1110 0000 0000
|
||||
let sign = if self.bits.is_negative() {
|
||||
(1 as $unsigned) << (<$unsigned>::BITS - 1)
|
||||
} else {
|
||||
0
|
||||
};
|
||||
let sign=if self.bits.is_negative(){(1 as $unsigned)<<(<$unsigned>::BITS-1)}else{0};
|
||||
let unsigned=self.bits.unsigned_abs();
|
||||
let most_significant_bit=unsigned.bits();
|
||||
let exp=if unsigned.is_zero(){
|
||||
@ -191,7 +173,7 @@ macro_rules! impl_into_float {
|
||||
<$output>::from_bits(bits)
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
impl_into_float!(f32,u32,8,24);
|
||||
impl_into_float!(f64,u64,11,53);
|
||||
@ -250,7 +232,9 @@ macro_rules! impl_from_float {
|
||||
std::num::FpCategory::Nan=>Err(FixedFromFloatError::Nan),
|
||||
std::num::FpCategory::Infinite=>Err(FixedFromFloatError::Infinite),
|
||||
std::num::FpCategory::Zero=>Ok(Self::ZERO),
|
||||
std::num::FpCategory::Subnormal | std::num::FpCategory::Normal => {
|
||||
std::num::FpCategory::Subnormal
|
||||
|std::num::FpCategory::Normal
|
||||
=>{
|
||||
let (m,e,s)=$decode(value);
|
||||
let mut digits=[0u64;N];
|
||||
let most_significant_bit=e as i32+$mantissa_bits as i32+F as i32;
|
||||
@ -258,19 +242,15 @@ macro_rules! impl_from_float {
|
||||
return Err(FixedFromFloatError::Underflow);
|
||||
}
|
||||
let digit_index=most_significant_bit>>DIGIT_SHIFT;
|
||||
let digit = digits
|
||||
.get_mut(digit_index as usize)
|
||||
.ok_or(FixedFromFloatError::Overflow)?;
|
||||
let digit=digits.get_mut(digit_index as usize).ok_or(FixedFromFloatError::Overflow)?;
|
||||
let take_bits=most_significant_bit-(digit_index<<DIGIT_SHIFT);
|
||||
let rest_of_mantissa=-($mantissa_bits as i32-(take_bits as i32));
|
||||
*digit=signed_shift(m,rest_of_mantissa);
|
||||
if rest_of_mantissa<0&&digit_index!=0{
|
||||
//we don't care if some float bits are partially truncated
|
||||
if let Some(digit)=digits.get_mut((digit_index-1) as usize){
|
||||
let take_bits =
|
||||
most_significant_bit - ((digit_index - 1) << DIGIT_SHIFT);
|
||||
let rest_of_mantissa =
|
||||
-($mantissa_bits as i32 - (take_bits as i32));
|
||||
let take_bits=most_significant_bit-((digit_index-1)<<DIGIT_SHIFT);
|
||||
let rest_of_mantissa=-($mantissa_bits as i32-(take_bits as i32));
|
||||
*digit=signed_shift(m,rest_of_mantissa);
|
||||
}
|
||||
}
|
||||
@ -280,11 +260,11 @@ macro_rules! impl_from_float {
|
||||
}else{
|
||||
Self::from_bits(bits)
|
||||
})
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
impl_from_float!(integer_decode_f32,f32,24);
|
||||
impl_from_float!(integer_decode_f64,f64,53);
|
||||
@ -314,7 +294,7 @@ macro_rules! impl_additive_operator {
|
||||
}
|
||||
impl<const N:usize,const F:usize,U> core::ops::$trait<U> for $struct<N,F>
|
||||
where
|
||||
BInt<N>: From<U>,
|
||||
BInt::<N>:From<U>,
|
||||
{
|
||||
type Output = $output;
|
||||
#[inline]
|
||||
@ -334,7 +314,7 @@ macro_rules! impl_additive_assign_operator {
|
||||
}
|
||||
impl<const N:usize,const F:usize,U> core::ops::$trait<U> for $struct<N,F>
|
||||
where
|
||||
BInt<N>: From<U>,
|
||||
BInt::<N>:From<U>,
|
||||
{
|
||||
#[inline]
|
||||
fn $method(&mut self, other: U) {
|
||||
@ -407,10 +387,7 @@ macro_rules! impl_multiply_operator_not_const_generic {
|
||||
}
|
||||
}
|
||||
#[cfg(not(feature="wide-mul"))]
|
||||
impl_multiplicative_operator_not_const_generic!(
|
||||
($struct, $trait, $method, $output),
|
||||
$width
|
||||
);
|
||||
impl_multiplicative_operator_not_const_generic!(($struct, $trait, $method, $output ), $width);
|
||||
#[cfg(feature="deferred-division")]
|
||||
impl ratio_ops::ratio::Divide<i64> for Fixed<$width,{$width*32}>{
|
||||
type Output=Self;
|
||||
@ -419,7 +396,7 @@ macro_rules! impl_multiply_operator_not_const_generic {
|
||||
Self::from_bits(self.bits.div_euclid(BInt::from(other)))
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
macro_rules! impl_divide_operator_not_const_generic {
|
||||
( ($struct: ident, $trait: ident, $method: ident, $output: ty ), $width:expr ) => {
|
||||
@ -435,10 +412,7 @@ macro_rules! impl_divide_operator_not_const_generic {
|
||||
}
|
||||
}
|
||||
#[cfg(all(not(feature="wide-mul"),not(feature="deferred-division")))]
|
||||
impl_multiplicative_operator_not_const_generic!(
|
||||
($struct, $trait, $method, $output),
|
||||
$width
|
||||
);
|
||||
impl_multiplicative_operator_not_const_generic!(($struct, $trait, $method, $output ), $width);
|
||||
#[cfg(all(not(feature="wide-mul"),feature="deferred-division"))]
|
||||
impl<const F:usize> ratio_ops::ratio::Divide for $struct<$width,F>{
|
||||
type Output = $output;
|
||||
@ -456,7 +430,7 @@ macro_rules! impl_multiplicative_operator {
|
||||
( $struct: ident, $trait: ident, $method: ident, $inner_method: ident, $output: ty ) => {
|
||||
impl<const N:usize,const F:usize,U> core::ops::$trait<U> for $struct<N,F>
|
||||
where
|
||||
BInt<N>: From<U> + core::ops::$trait,
|
||||
BInt::<N>:From<U>+core::ops::$trait,
|
||||
{
|
||||
type Output = $output;
|
||||
#[inline]
|
||||
@ -470,7 +444,7 @@ macro_rules! impl_multiplicative_assign_operator {
|
||||
( $struct: ident, $trait: ident, $method: ident, $not_assign_method: ident ) => {
|
||||
impl<const N:usize,const F:usize,U> core::ops::$trait<U> for $struct<N,F>
|
||||
where
|
||||
BInt<N>: From<U> + core::ops::$trait,
|
||||
BInt::<N>:From<U>+core::ops::$trait,
|
||||
{
|
||||
#[inline]
|
||||
fn $method(&mut self,other:U){
|
||||
@ -495,33 +469,19 @@ macro_rules! macro_repeated{
|
||||
macro_rules! macro_16 {
|
||||
( $macro: ident, $any:tt ) => {
|
||||
macro_repeated!($macro,$any,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
macro_16!(
|
||||
impl_multiplicative_assign_operator_not_const_generic,
|
||||
(Fixed, MulAssign, mul_assign, mul)
|
||||
);
|
||||
macro_16!(
|
||||
impl_multiply_operator_not_const_generic,
|
||||
(Fixed, Mul, mul, Self)
|
||||
);
|
||||
macro_16!(
|
||||
impl_multiplicative_assign_operator_not_const_generic,
|
||||
(Fixed, DivAssign, div_assign, div)
|
||||
);
|
||||
macro_16!(
|
||||
impl_divide_operator_not_const_generic,
|
||||
(Fixed, Div, div, Self)
|
||||
);
|
||||
macro_16!( impl_multiplicative_assign_operator_not_const_generic, (Fixed, MulAssign, mul_assign, mul) );
|
||||
macro_16!( impl_multiply_operator_not_const_generic, (Fixed, Mul, mul, Self) );
|
||||
macro_16!( impl_multiplicative_assign_operator_not_const_generic, (Fixed, DivAssign, div_assign, div) );
|
||||
macro_16!( impl_divide_operator_not_const_generic, (Fixed, Div, div, Self) );
|
||||
impl_multiplicative_assign_operator!( Fixed, MulAssign, mul_assign, mul );
|
||||
impl_multiplicative_operator!( Fixed, Mul, mul, mul, Self );
|
||||
impl_multiplicative_assign_operator!( Fixed, DivAssign, div_assign, div_euclid );
|
||||
impl_multiplicative_operator!( Fixed, Div, div, div_euclid, Self );
|
||||
#[cfg(feature="deferred-division")]
|
||||
impl<const LHS_N: usize, const LHS_F: usize, const RHS_N: usize, const RHS_F: usize>
|
||||
core::ops::Div<Fixed<RHS_N, RHS_F>> for Fixed<LHS_N, LHS_F>
|
||||
{
|
||||
impl<const LHS_N:usize,const LHS_F:usize,const RHS_N:usize,const RHS_F:usize> core::ops::Div<Fixed<RHS_N,RHS_F>> for Fixed<LHS_N,LHS_F>{
|
||||
type Output=ratio_ops::ratio::Ratio<Fixed<LHS_N,LHS_F>,Fixed<RHS_N,RHS_F>>;
|
||||
#[inline]
|
||||
fn div(self, other: Fixed<RHS_N,RHS_F>)->Self::Output{
|
||||
@ -594,7 +554,7 @@ macro_rules! impl_wide_operators {
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// WIDE MUL: multiply into a wider type
|
||||
@ -669,133 +629,26 @@ macro_rules! impl_wide_same_size_not_const_generic{
|
||||
|
||||
//const generics sidestepped wahoo
|
||||
macro_repeated!(
|
||||
impl_wide_not_const_generic,
|
||||
(),
|
||||
(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),
|
||||
(1, 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),
|
||||
(1, 3),
|
||||
(2, 3),
|
||||
(4, 3),
|
||||
(5, 3),
|
||||
(6, 3),
|
||||
(7, 3),
|
||||
(8, 3),
|
||||
(9, 3),
|
||||
(10, 3),
|
||||
(11, 3),
|
||||
(12, 3),
|
||||
(13, 3),
|
||||
(1, 4),
|
||||
(2, 4),
|
||||
(3, 4),
|
||||
(5, 4),
|
||||
(6, 4),
|
||||
(7, 4),
|
||||
(8, 4),
|
||||
(9, 4),
|
||||
(10, 4),
|
||||
(11, 4),
|
||||
(12, 4),
|
||||
(1, 5),
|
||||
(2, 5),
|
||||
(3, 5),
|
||||
(4, 5),
|
||||
(6, 5),
|
||||
(7, 5),
|
||||
(8, 5),
|
||||
(9, 5),
|
||||
(10, 5),
|
||||
(11, 5),
|
||||
(1, 6),
|
||||
(2, 6),
|
||||
(3, 6),
|
||||
(4, 6),
|
||||
(5, 6),
|
||||
(7, 6),
|
||||
(8, 6),
|
||||
(9, 6),
|
||||
(10, 6),
|
||||
(1, 7),
|
||||
(2, 7),
|
||||
(3, 7),
|
||||
(4, 7),
|
||||
(5, 7),
|
||||
(6, 7),
|
||||
(8, 7),
|
||||
(9, 7),
|
||||
(1, 8),
|
||||
(2, 8),
|
||||
(3, 8),
|
||||
(4, 8),
|
||||
(5, 8),
|
||||
(6, 8),
|
||||
(7, 8),
|
||||
(9, 8),
|
||||
(1, 9),
|
||||
(2, 9),
|
||||
(3, 9),
|
||||
(4, 9),
|
||||
(5, 9),
|
||||
(6, 9),
|
||||
(7, 9),
|
||||
(1, 10),
|
||||
(2, 10),
|
||||
(3, 10),
|
||||
(4, 10),
|
||||
(5, 10),
|
||||
(6, 10),
|
||||
(1, 11),
|
||||
(2, 11),
|
||||
(3, 11),
|
||||
(4, 11),
|
||||
(5, 11),
|
||||
(1, 12),
|
||||
(2, 12),
|
||||
(3, 12),
|
||||
(4, 12),
|
||||
(1, 13),
|
||||
(2, 13),
|
||||
(3, 13),
|
||||
(1, 14),
|
||||
(2, 14),
|
||||
impl_wide_not_const_generic,(),
|
||||
(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),
|
||||
(1,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),
|
||||
(1,3),(2,3), (4,3),(5,3),(6,3),(7,3),(8,3),(9,3),(10,3),(11,3),(12,3),(13,3),
|
||||
(1,4),(2,4),(3,4), (5,4),(6,4),(7,4),(8,4),(9,4),(10,4),(11,4),(12,4),
|
||||
(1,5),(2,5),(3,5),(4,5), (6,5),(7,5),(8,5),(9,5),(10,5),(11,5),
|
||||
(1,6),(2,6),(3,6),(4,6),(5,6), (7,6),(8,6),(9,6),(10,6),
|
||||
(1,7),(2,7),(3,7),(4,7),(5,7),(6,7), (8,7),(9,7),
|
||||
(1,8),(2,8),(3,8),(4,8),(5,8),(6,8),(7,8), (9,8),
|
||||
(1,9),(2,9),(3,9),(4,9),(5,9),(6,9),(7,9),
|
||||
(1,10),(2,10),(3,10),(4,10),(5,10),(6,10),
|
||||
(1,11),(2,11),(3,11),(4,11),(5,11),
|
||||
(1,12),(2,12),(3,12),(4,12),
|
||||
(1,13),(2,13),(3,13),
|
||||
(1,14),(2,14),
|
||||
(1,15)
|
||||
);
|
||||
macro_repeated!(
|
||||
impl_wide_same_size_not_const_generic,
|
||||
(),
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
4,
|
||||
5,
|
||||
6,
|
||||
7,
|
||||
8
|
||||
impl_wide_same_size_not_const_generic,(),
|
||||
1,2,3,4,5,6,7,8
|
||||
);
|
||||
|
||||
pub trait Fix<Out>{
|
||||
@ -853,7 +706,8 @@ macro_rules! impl_fix_lhs_eq_rhs_not_const_generic {
|
||||
(),
|
||||
($lhs:expr,$rhs:expr)
|
||||
)=>{
|
||||
impl Fixed<$lhs, { $lhs * 32 }> {
|
||||
impl Fixed<$lhs,{$lhs*32}>
|
||||
{
|
||||
paste::item!{
|
||||
#[inline]
|
||||
pub fn [<fix_ $rhs>](self)->Fixed<$rhs,{$rhs*32}>{
|
||||
@ -868,279 +722,50 @@ macro_rules! impl_fix_lhs_eq_rhs_not_const_generic {
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// I LOVE NOT BEING ABLE TO USE CONST GENERICS
|
||||
|
||||
macro_repeated!(
|
||||
impl_fix_rhs_lt_lhs_not_const_generic,
|
||||
(),
|
||||
(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),
|
||||
(17, 1),
|
||||
(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),
|
||||
(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),
|
||||
(5, 4),
|
||||
(6, 4),
|
||||
(7, 4),
|
||||
(8, 4),
|
||||
(9, 4),
|
||||
(10, 4),
|
||||
(11, 4),
|
||||
(12, 4),
|
||||
(13, 4),
|
||||
(14, 4),
|
||||
(15, 4),
|
||||
(16, 4),
|
||||
(6, 5),
|
||||
(7, 5),
|
||||
(8, 5),
|
||||
(9, 5),
|
||||
(10, 5),
|
||||
(11, 5),
|
||||
(12, 5),
|
||||
(13, 5),
|
||||
(14, 5),
|
||||
(15, 5),
|
||||
(16, 5),
|
||||
(7, 6),
|
||||
(8, 6),
|
||||
(9, 6),
|
||||
(10, 6),
|
||||
(11, 6),
|
||||
(12, 6),
|
||||
(13, 6),
|
||||
(14, 6),
|
||||
(15, 6),
|
||||
(16, 6),
|
||||
(8, 7),
|
||||
(9, 7),
|
||||
(10, 7),
|
||||
(11, 7),
|
||||
(12, 7),
|
||||
(13, 7),
|
||||
(14, 7),
|
||||
(15, 7),
|
||||
(16, 7),
|
||||
(9, 8),
|
||||
(10, 8),
|
||||
(11, 8),
|
||||
(12, 8),
|
||||
(13, 8),
|
||||
(14, 8),
|
||||
(15, 8),
|
||||
(16, 8),
|
||||
(10, 9),
|
||||
(11, 9),
|
||||
(12, 9),
|
||||
(13, 9),
|
||||
(14, 9),
|
||||
(15, 9),
|
||||
(16, 9),
|
||||
(11, 10),
|
||||
(12, 10),
|
||||
(13, 10),
|
||||
(14, 10),
|
||||
(15, 10),
|
||||
(16, 10),
|
||||
(12, 11),
|
||||
(13, 11),
|
||||
(14, 11),
|
||||
(15, 11),
|
||||
(16, 11),
|
||||
(13, 12),
|
||||
(14, 12),
|
||||
(15, 12),
|
||||
(16, 12),
|
||||
(14, 13),
|
||||
(15, 13),
|
||||
(16, 13),
|
||||
(15, 14),
|
||||
(16, 14),
|
||||
impl_fix_rhs_lt_lhs_not_const_generic,(),
|
||||
(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),(17,1),
|
||||
(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),
|
||||
(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),
|
||||
(5,4),(6,4),(7,4),(8,4),(9,4),(10,4),(11,4),(12,4),(13,4),(14,4),(15,4),(16,4),
|
||||
(6,5),(7,5),(8,5),(9,5),(10,5),(11,5),(12,5),(13,5),(14,5),(15,5),(16,5),
|
||||
(7,6),(8,6),(9,6),(10,6),(11,6),(12,6),(13,6),(14,6),(15,6),(16,6),
|
||||
(8,7),(9,7),(10,7),(11,7),(12,7),(13,7),(14,7),(15,7),(16,7),
|
||||
(9,8),(10,8),(11,8),(12,8),(13,8),(14,8),(15,8),(16,8),
|
||||
(10,9),(11,9),(12,9),(13,9),(14,9),(15,9),(16,9),
|
||||
(11,10),(12,10),(13,10),(14,10),(15,10),(16,10),
|
||||
(12,11),(13,11),(14,11),(15,11),(16,11),
|
||||
(13,12),(14,12),(15,12),(16,12),
|
||||
(14,13),(15,13),(16,13),
|
||||
(15,14),(16,14),
|
||||
(16,15)
|
||||
);
|
||||
macro_repeated!(
|
||||
impl_fix_lhs_lt_rhs_not_const_generic,
|
||||
(),
|
||||
impl_fix_lhs_lt_rhs_not_const_generic,(),
|
||||
(1,2),
|
||||
(1, 3),
|
||||
(2, 3),
|
||||
(1, 4),
|
||||
(2, 4),
|
||||
(3, 4),
|
||||
(1, 5),
|
||||
(2, 5),
|
||||
(3, 5),
|
||||
(4, 5),
|
||||
(1, 6),
|
||||
(2, 6),
|
||||
(3, 6),
|
||||
(4, 6),
|
||||
(5, 6),
|
||||
(1, 7),
|
||||
(2, 7),
|
||||
(3, 7),
|
||||
(4, 7),
|
||||
(5, 7),
|
||||
(6, 7),
|
||||
(1, 8),
|
||||
(2, 8),
|
||||
(3, 8),
|
||||
(4, 8),
|
||||
(5, 8),
|
||||
(6, 8),
|
||||
(7, 8),
|
||||
(1, 9),
|
||||
(2, 9),
|
||||
(3, 9),
|
||||
(4, 9),
|
||||
(5, 9),
|
||||
(6, 9),
|
||||
(7, 9),
|
||||
(8, 9),
|
||||
(1, 10),
|
||||
(2, 10),
|
||||
(3, 10),
|
||||
(4, 10),
|
||||
(5, 10),
|
||||
(6, 10),
|
||||
(7, 10),
|
||||
(8, 10),
|
||||
(9, 10),
|
||||
(1, 11),
|
||||
(2, 11),
|
||||
(3, 11),
|
||||
(4, 11),
|
||||
(5, 11),
|
||||
(6, 11),
|
||||
(7, 11),
|
||||
(8, 11),
|
||||
(9, 11),
|
||||
(10, 11),
|
||||
(1, 12),
|
||||
(2, 12),
|
||||
(3, 12),
|
||||
(4, 12),
|
||||
(5, 12),
|
||||
(6, 12),
|
||||
(7, 12),
|
||||
(8, 12),
|
||||
(9, 12),
|
||||
(10, 12),
|
||||
(11, 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),
|
||||
(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),
|
||||
(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),
|
||||
(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)
|
||||
(1,3),(2,3),
|
||||
(1,4),(2,4),(3,4),
|
||||
(1,5),(2,5),(3,5),(4,5),
|
||||
(1,6),(2,6),(3,6),(4,6),(5,6),
|
||||
(1,7),(2,7),(3,7),(4,7),(5,7),(6,7),
|
||||
(1,8),(2,8),(3,8),(4,8),(5,8),(6,8),(7,8),
|
||||
(1,9),(2,9),(3,9),(4,9),(5,9),(6,9),(7,9),(8,9),
|
||||
(1,10),(2,10),(3,10),(4,10),(5,10),(6,10),(7,10),(8,10),(9,10),
|
||||
(1,11),(2,11),(3,11),(4,11),(5,11),(6,11),(7,11),(8,11),(9,11),(10,11),
|
||||
(1,12),(2,12),(3,12),(4,12),(5,12),(6,12),(7,12),(8,12),(9,12),(10,12),(11,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),
|
||||
(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),
|
||||
(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),
|
||||
(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)
|
||||
);
|
||||
macro_repeated!(
|
||||
impl_fix_lhs_eq_rhs_not_const_generic,
|
||||
(),
|
||||
(1, 1),
|
||||
(2, 2),
|
||||
(3, 3),
|
||||
(4, 4),
|
||||
(5, 5),
|
||||
(6, 6),
|
||||
(7, 7),
|
||||
(8, 8),
|
||||
(9, 9),
|
||||
(10, 10),
|
||||
(11, 11),
|
||||
(12, 12),
|
||||
(13, 13),
|
||||
(14, 14),
|
||||
(15, 15),
|
||||
(16, 16)
|
||||
impl_fix_lhs_eq_rhs_not_const_generic,(),
|
||||
(1,1),(2,2),(3,3),(4,4),(5,5),(6,6),(7,7),(8,8),(9,9),(10,10),(11,11),(12,12),(13,13),(14,14),(15,15),(16,16)
|
||||
);
|
||||
|
||||
macro_rules! impl_not_const_generic{
|
||||
@ -1192,7 +817,7 @@ macro_rules! impl_not_const_generic {
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
impl_not_const_generic!(1,2);
|
||||
impl_not_const_generic!(2,4);
|
||||
|
@ -1,5 +1,5 @@
|
||||
use crate::types::I256F256;
|
||||
use crate::types::I32F32;
|
||||
use crate::types::I256F256;
|
||||
|
||||
#[test]
|
||||
fn you_can_add_numbers(){
|
||||
@ -49,9 +49,7 @@ fn from_f32() {
|
||||
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();
|
||||
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);
|
||||
@ -59,7 +57,7 @@ fn from_f32() {
|
||||
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 _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
|
||||
@ -69,9 +67,7 @@ fn from_f32() {
|
||||
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 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));
|
||||
@ -90,9 +86,7 @@ fn from_f64() {
|
||||
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();
|
||||
let b:Result<I256F256,_>=(0b101011110101001010101010000000000000000000000000000u64 as f64*2.0f64.powi(16)).try_into();
|
||||
assert_eq!(b,Ok(a));
|
||||
}
|
||||
|
||||
@ -204,15 +198,9 @@ 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])
|
||||
);
|
||||
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])
|
||||
);
|
||||
assert_eq!(zeroes,arrayvec::ArrayVec::from_iter([I32F32::NEG_ONE*3,I32F32::ONE]));
|
||||
}
|
||||
#[test]
|
||||
#[cfg(all(feature="zeroes",feature="deferred-division"))]
|
||||
|
@ -6,16 +6,10 @@ 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> {
|
||||
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::Equal=>return ArrayVec::from_iter(Self::zeroes1(a0,a1).into_iter()),
|
||||
Ordering::Less=>false,
|
||||
};
|
||||
let radicand=a1*a1-a2*a0*4;
|
||||
@ -26,25 +20,13 @@ macro_rules! impl_zeroes {
|
||||
}
|
||||
//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),
|
||||
],
|
||||
(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(),
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
mod macros;
|
||||
pub mod matrix;
|
||||
pub mod types;
|
||||
pub mod vector;
|
||||
pub mod matrix;
|
||||
|
||||
#[cfg(feature="named-fields")]
|
||||
mod named;
|
||||
|
@ -7,24 +7,15 @@ macro_rules! impl_fixed_wide_vector_not_const_generic {
|
||||
) => {
|
||||
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 {
|
||||
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
|
||||
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,
|
||||
>,
|
||||
V:core::ops::Div<<fixed_wide::fixed::Fixed::<$n,{$n*32}> as core::ops::Mul>::Output>,
|
||||
{
|
||||
self*length/self.length()
|
||||
}
|
||||
@ -37,7 +28,7 @@ macro_rules! impl_fixed_wide_vector_not_const_generic {
|
||||
macro_rules! macro_4 {
|
||||
( $macro: ident, $any:tt ) => {
|
||||
$crate::macro_repeated!($macro,$any,1,2,3,4);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
@ -47,264 +38,23 @@ 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)
|
||||
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)
|
||||
);
|
||||
};
|
||||
}
|
||||
@ -316,7 +66,8 @@ macro_rules! impl_fix_not_const_generic {
|
||||
(),
|
||||
($lhs:expr,$rhs:expr)
|
||||
)=>{
|
||||
impl<const N: usize> Vector<N, fixed_wide::fixed::Fixed<$lhs, { $lhs * 32 }>> {
|
||||
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}>>{
|
||||
@ -324,5 +75,5 @@ macro_rules! impl_fix_not_const_generic {
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -12,25 +12,32 @@ macro_rules! impl_matrix {
|
||||
self.array
|
||||
}
|
||||
#[inline]
|
||||
pub fn from_cols(cols: [Vector<Y, T>; X]) -> Self {
|
||||
Matrix::new(cols.map(|col| col.array))
|
||||
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,
|
||||
F:Fn(T)->U
|
||||
{
|
||||
Matrix::new(self.array.map(|inner| inner.map(&f)))
|
||||
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())
|
||||
}))
|
||||
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>
|
||||
@ -43,15 +50,16 @@ macro_rules! impl_matrix {
|
||||
{
|
||||
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: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()
|
||||
})
|
||||
}),
|
||||
.map(|(lhs_iter,&rhs_value)|
|
||||
lhs_iter.next().unwrap()*rhs_value
|
||||
).sum()
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
#[inline]
|
||||
@ -63,18 +71,21 @@ macro_rules! impl_matrix {
|
||||
U:Copy,
|
||||
{
|
||||
let mut array_of_iterators=self.array.map(|axis|axis.into_iter());
|
||||
Vector::new(core::array::from_fn(|_| {
|
||||
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()
|
||||
}))
|
||||
.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,
|
||||
T:Copy
|
||||
{
|
||||
#[inline(always)]
|
||||
pub const fn from_value(value:T)->Self{
|
||||
@ -85,15 +96,13 @@ macro_rules! impl_matrix {
|
||||
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())
|
||||
}))
|
||||
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>
|
||||
{
|
||||
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]{
|
||||
@ -108,8 +117,7 @@ macro_rules! impl_matrix {
|
||||
}
|
||||
}
|
||||
|
||||
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>
|
||||
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,
|
||||
@ -121,8 +129,7 @@ macro_rules! impl_matrix {
|
||||
self.dot(rhs)
|
||||
}
|
||||
}
|
||||
impl<const X: usize, const Y: usize, T, U, V> core::ops::Mul<Vector<X, U>>
|
||||
for Matrix<X, Y, T>
|
||||
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,
|
||||
@ -136,21 +143,14 @@ macro_rules! impl_matrix {
|
||||
}
|
||||
#[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>
|
||||
{
|
||||
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{
|
||||
@ -164,7 +164,7 @@ macro_rules! impl_matrix_deferred_division {
|
||||
ratio_ops::ratio::Ratio::new(self,rhs)
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
@ -175,20 +175,22 @@ macro_rules! impl_matrix_extend {
|
||||
#[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()))
|
||||
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()));
|
||||
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)]
|
||||
@ -211,7 +213,7 @@ macro_rules! impl_matrix_named_fields_shape {
|
||||
unsafe{core::mem::transmute(&mut self.array)}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
@ -260,23 +262,11 @@ macro_rules! impl_matrix_3x3 {
|
||||
{
|
||||
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,
|
||||
],
|
||||
[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,6 +1,6 @@
|
||||
pub mod common;
|
||||
pub mod matrix;
|
||||
pub mod vector;
|
||||
pub mod matrix;
|
||||
|
||||
#[cfg(feature="fixed-wide")]
|
||||
pub mod fixed_wide;
|
||||
|
@ -14,9 +14,11 @@ macro_rules! impl_vector {
|
||||
#[inline]
|
||||
pub fn map<F,U>(self,f:F)->Vector<N,U>
|
||||
where
|
||||
F: Fn(T) -> U,
|
||||
F:Fn(T)->U
|
||||
{
|
||||
Vector::new(self.array.map(f))
|
||||
Vector::new(
|
||||
self.array.map(f)
|
||||
)
|
||||
}
|
||||
#[inline]
|
||||
pub fn map_zip<F,U,V>(self,other:Vector<N,U>,f:F)->Vector<N,V>
|
||||
@ -24,7 +26,9 @@ macro_rules! impl_vector {
|
||||
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())))
|
||||
Vector::new(
|
||||
core::array::from_fn(|_|f(iter.next().unwrap())),
|
||||
)
|
||||
}
|
||||
}
|
||||
impl<const N:usize,T:Copy> Vector<N,T>{
|
||||
@ -37,7 +41,9 @@ macro_rules! impl_vector {
|
||||
impl<const N:usize,T:Default> Default for Vector<N,T>{
|
||||
#[inline]
|
||||
fn default()->Self{
|
||||
Self::new(core::array::from_fn(|_| Default::default()))
|
||||
Self::new(
|
||||
core::array::from_fn(|_|Default::default())
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@ -98,22 +104,21 @@ macro_rules! impl_vector {
|
||||
type Output=Vector<N,V>;
|
||||
#[inline]
|
||||
fn neg(self)->Self::Output{
|
||||
Vector::new(self.array.map(|t| -t))
|
||||
Vector::new(
|
||||
self.array.map(|t|-t)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<const N: usize, T> Vector<N, 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()
|
||||
self.array.into_iter().zip(rhs.array).map(|(a,b)|a*b).sum()
|
||||
}
|
||||
}
|
||||
|
||||
@ -163,15 +168,13 @@ macro_rules! impl_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>
|
||||
{
|
||||
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{
|
||||
@ -185,130 +188,113 @@ macro_rules! impl_vector_deferred_division {
|
||||
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>
|
||||
{
|
||||
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>
|
||||
{
|
||||
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>
|
||||
{
|
||||
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>
|
||||
{
|
||||
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))
|
||||
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>
|
||||
{
|
||||
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)
|
||||
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))
|
||||
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>
|
||||
{
|
||||
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>
|
||||
{
|
||||
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>
|
||||
{
|
||||
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)
|
||||
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))
|
||||
self.array.iter_mut()
|
||||
.for_each(|t|t.$method(rhs))
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
@ -319,10 +305,12 @@ macro_rules! impl_vector_extend {
|
||||
#[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()))
|
||||
Vector::new(
|
||||
core::array::from_fn(|_|iter.next().unwrap()),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
@ -342,14 +330,15 @@ macro_rules! impl_vector_named_fields {
|
||||
unsafe{core::mem::transmute(&mut self.array)}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
#[macro_export(local_inner_macros)]
|
||||
macro_rules! impl_vector_3 {
|
||||
()=>{
|
||||
impl<T> Vector<3, T> {
|
||||
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
|
||||
@ -364,5 +353,5 @@ macro_rules! impl_vector_3 {
|
||||
])
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
use crate::matrix::Matrix;
|
||||
use crate::vector::Vector;
|
||||
use crate::matrix::Matrix;
|
||||
|
||||
#[repr(C)]
|
||||
pub struct Vector2<T> {
|
||||
@ -45,7 +45,15 @@ pub struct Matrix4<T> {
|
||||
|
||||
crate::impl_matrix_named_fields!(
|
||||
//outer struct
|
||||
((Matrix2, 2), (Matrix3, 3), (Matrix4, 4)),
|
||||
(
|
||||
(Matrix2, 2),
|
||||
(Matrix3, 3),
|
||||
(Matrix4, 4)
|
||||
),
|
||||
//inner struct
|
||||
((2), (3), (4))
|
||||
(
|
||||
(2),
|
||||
(3),
|
||||
(4)
|
||||
)
|
||||
);
|
||||
|
@ -12,10 +12,7 @@ fn wide_vec3() {
|
||||
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]
|
||||
@ -41,33 +38,16 @@ fn wide_vec3_length_squared() {
|
||||
#[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();
|
||||
[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();
|
||||
]).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}}
|
||||
@ -78,9 +58,7 @@ fn wide_matrix_dot() {
|
||||
[Planar64Wide1::from(50),Planar64Wide1::from(60)],
|
||||
[Planar64Wide1::from(114),Planar64Wide1::from(140)],
|
||||
[Planar64Wide1::from(178),Planar64Wide1::from(220)],
|
||||
])
|
||||
.transpose()
|
||||
.array
|
||||
]).transpose().array
|
||||
);
|
||||
}
|
||||
|
||||
@ -110,22 +88,9 @@ fn wide_matrix_adjugate() {
|
||||
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
|
||||
[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,4 +1,4 @@
|
||||
use crate::types::{Matrix3, Vector3};
|
||||
use crate::types::{Vector3,Matrix3};
|
||||
|
||||
#[test]
|
||||
fn test_vector(){
|
||||
@ -14,6 +14,7 @@ fn test_vector() {
|
||||
assert_eq!(v.y,10);
|
||||
}
|
||||
|
||||
|
||||
#[test]
|
||||
fn test_matrix(){
|
||||
let mut v=Matrix3::from_value(2);
|
||||
|
@ -1,4 +1,4 @@
|
||||
use crate::types::{Matrix2x3, Matrix3x2, Matrix3x4, Matrix4x2, Vector2, Vector3};
|
||||
use crate::types::{Vector2,Vector3,Matrix3x4,Matrix4x2,Matrix3x2,Matrix2x3};
|
||||
|
||||
#[test]
|
||||
fn test_bool(){
|
||||
@ -21,7 +21,10 @@ fn test_arithmetic() {
|
||||
|
||||
#[test]
|
||||
fn matrix_transform_vector(){
|
||||
let m = Matrix2x3::new([[1, 2, 3], [4, 5, 6]]).transpose();
|
||||
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);
|
||||
@ -30,22 +33,27 @@ fn matrix_transform_vector() {
|
||||
#[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([
|
||||
// | | |
|
||||
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();
|
||||
]).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
|
||||
Matrix3x2::new([
|
||||
[50.0,60.0],
|
||||
[114.0,140.0],
|
||||
[178.0,220.0],
|
||||
]).transpose().array
|
||||
);
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
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>;
|
||||
|
@ -30,10 +30,7 @@ where
|
||||
|
||||
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>
|
||||
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>,
|
||||
@ -41,10 +38,7 @@ impl<LhsNum, LhsDen> Ratio<LhsNum, LhsDen> {
|
||||
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>
|
||||
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>,
|
||||
@ -56,13 +50,7 @@ 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,
|
||||
>
|
||||
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>,
|
||||
@ -71,10 +59,7 @@ macro_rules! impl_ratio_method {
|
||||
RhsDen:Copy,
|
||||
LhsCrossMul:core::ops::$trait<RhsCrossMul>,
|
||||
{
|
||||
Ratio::new(
|
||||
(self.num * rhs.den).$method(self.den * rhs.num),
|
||||
self.den * rhs.den,
|
||||
)
|
||||
Ratio::new((self.num*rhs.den).$method(self.den*rhs.num),self.den*rhs.den)
|
||||
}
|
||||
}
|
||||
};
|
||||
@ -130,10 +115,7 @@ 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
|
||||
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>,
|
||||
@ -145,7 +127,7 @@ macro_rules! impl_ratio_ord_method {
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
//PartialEq
|
||||
impl_ratio_ord_method!(eq,eq_ratio,bool);
|
||||
@ -269,8 +251,7 @@ 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>
|
||||
impl<LhsNum,LhsDen,RhsNum,RhsDen,T,U> PartialEq<Ratio<RhsNum,RhsDen>> for Ratio<LhsNum,LhsDen>
|
||||
where
|
||||
LhsNum:Copy,
|
||||
LhsDen:Copy,
|
||||
@ -287,8 +268,7 @@ where
|
||||
}
|
||||
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>
|
||||
impl<LhsNum,LhsDen,RhsNum,RhsDen,T,U> PartialOrd<Ratio<RhsNum,RhsDen>> for Ratio<LhsNum,LhsDen>
|
||||
where
|
||||
LhsNum:Copy,
|
||||
LhsDen:Copy,
|
||||
|
@ -1,9 +1,9 @@
|
||||
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>);
|
||||
@ -42,7 +42,7 @@ pub struct Place {
|
||||
services:roblox_emulator::context::Services,
|
||||
}
|
||||
impl Place{
|
||||
fn new(dom: WeakDom) -> Option<Self> {
|
||||
pub fn new(dom:WeakDom)->Option<Self>{
|
||||
let context=roblox_emulator::context::Context::from_ref(&dom);
|
||||
Some(Self{
|
||||
services:context.find_services()?,
|
||||
@ -54,9 +54,7 @@ impl Place {
|
||||
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();
|
||||
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}");
|
||||
@ -88,12 +86,8 @@ 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),
|
||||
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),
|
||||
}
|
||||
}
|
||||
@ -103,7 +97,7 @@ pub fn read<R: Read>(input: R) -> Result<Model, ReadError> {
|
||||
pub fn convert<AcquireRenderConfigId,AcquireMeshId>(
|
||||
dom:impl AsRef<WeakDom>,
|
||||
acquire_render_config_id:AcquireRenderConfigId,
|
||||
acquire_mesh_id: AcquireMeshId,
|
||||
acquire_mesh_id:AcquireMeshId
|
||||
)->rbx::PartialMap1
|
||||
where
|
||||
AcquireRenderConfigId:FnMut(Option<&str>)->strafesnet_common::model::RenderConfigId,
|
||||
|
@ -1,18 +1,13 @@
|
||||
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),
|
||||
RbxMesh(rbx_mesh::mesh::Error)
|
||||
}
|
||||
impl std::fmt::Display for Error{
|
||||
fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{
|
||||
@ -21,7 +16,13 @@ impl std::fmt::Display for Error {
|
||||
}
|
||||
impl std::error::Error for Error{}
|
||||
|
||||
fn ingest_vertices2<AcquirePosId, AcquireTexId, AcquireNormalId, AcquireColorId, AcquireVertexId>(
|
||||
fn ingest_vertices2<
|
||||
AcquirePosId,
|
||||
AcquireTexId,
|
||||
AcquireNormalId,
|
||||
AcquireColorId,
|
||||
AcquireVertexId,
|
||||
>(
|
||||
vertices:Vec<Vertex2>,
|
||||
acquire_pos_id:&mut AcquirePosId,
|
||||
acquire_tex_id:&mut AcquireTexId,
|
||||
@ -38,23 +39,22 @@ where
|
||||
{
|
||||
//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((
|
||||
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)),
|
||||
color:acquire_color_id(vertex.color.map(|f|f as f32/255.0f32))
|
||||
}),
|
||||
))
|
||||
})
|
||||
.collect::<Result<_, _>>()?)
|
||||
))).collect::<Result<_,_>>()?)
|
||||
}
|
||||
fn ingest_vertices_truncated2<AcquirePosId, AcquireTexId, AcquireNormalId, AcquireVertexId>(
|
||||
fn ingest_vertices_truncated2<
|
||||
AcquirePosId,
|
||||
AcquireTexId,
|
||||
AcquireNormalId,
|
||||
AcquireVertexId,
|
||||
>(
|
||||
vertices:Vec<Vertex2Truncated>,
|
||||
acquire_pos_id:&mut AcquirePosId,
|
||||
acquire_tex_id:&mut AcquireTexId,
|
||||
@ -70,44 +70,29 @@ where
|
||||
{
|
||||
//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((
|
||||
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,
|
||||
color:static_color_id
|
||||
}),
|
||||
))
|
||||
})
|
||||
.collect::<Result<_, _>>()?)
|
||||
))).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>,
|
||||
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.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>{
|
||||
@ -165,110 +150,52 @@ pub fn convert(roblox_mesh_bytes: crate::data::RobloxMeshBytes) -> Result<model:
|
||||
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 {
|
||||
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<_, _>>()?,
|
||||
)));
|
||||
}
|
||||
}));
|
||||
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,
|
||||
),
|
||||
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(),
|
||||
)));
|
||||
}
|
||||
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_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,
|
||||
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,
|
||||
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,
|
||||
|
@ -1,9 +1,5 @@
|
||||
use strafesnet_common::model::{Color4,TextureCoordinate,Mesh,IndexedGraphicsGroup,IndexedPhysicsGroup,IndexedVertex,PolygonGroupId,PolygonGroup,PolygonList,PositionId,TextureCoordinateId,NormalId,ColorId,VertexId,RenderConfigId};
|
||||
use strafesnet_common::integer::{vec3,Planar64Vec3};
|
||||
use strafesnet_common::model::{
|
||||
Color4, ColorId, IndexedGraphicsGroup, IndexedPhysicsGroup, IndexedVertex, IndexedVertexList,
|
||||
Mesh, NormalId, PolygonGroup, PolygonGroupId, PolygonList, PositionId, RenderConfigId,
|
||||
TextureCoordinate, TextureCoordinateId, VertexId,
|
||||
};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Primitives{
|
||||
@ -55,15 +51,40 @@ const CUBE_DEFAULT_POLYS: [[[u32; 3]; 4]; 6] = [
|
||||
[1,3,0],
|
||||
],
|
||||
// top (0, 1, 0)
|
||||
[[5, 3, 1], [4, 2, 1], [3, 1, 1], [2, 0, 1]],
|
||||
[
|
||||
[5,3,1],
|
||||
[4,2,1],
|
||||
[3,1,1],
|
||||
[2,0,1],
|
||||
],
|
||||
// back (0, 0, 1)
|
||||
[[0, 3, 2], [1, 2, 2], [2, 1, 2], [3, 0, 2]],
|
||||
[
|
||||
[0,3,2],
|
||||
[1,2,2],
|
||||
[2,1,2],
|
||||
[3,0,2],
|
||||
],
|
||||
// left (-1, 0, 0)
|
||||
[[0, 2, 3], [3, 1, 3], [4, 0, 3], [7, 3, 3]],
|
||||
[
|
||||
[0,2,3],
|
||||
[3,1,3],
|
||||
[4,0,3],
|
||||
[7,3,3],
|
||||
],
|
||||
// bottom (0,-1, 0)
|
||||
[[1, 1, 4], [0, 0, 4], [7, 3, 4], [6, 2, 4]],
|
||||
[
|
||||
[1,1,4],
|
||||
[0,0,4],
|
||||
[7,3,4],
|
||||
[6,2,4],
|
||||
],
|
||||
// front (0, 0,-1)
|
||||
[[4, 1, 5], [5, 0, 5], [6, 3, 5], [7, 2, 5]],
|
||||
[
|
||||
[4,1,5],
|
||||
[5,0,5],
|
||||
[6,3,5],
|
||||
[7,2,5],
|
||||
],
|
||||
];
|
||||
|
||||
#[derive(Hash,PartialEq,Eq)]
|
||||
@ -105,25 +126,14 @@ const CORNERWEDGE_DEFAULT_NORMALS: [Planar64Vec3; 5] = [
|
||||
vec3::int( 0,-1, 0),//CornerWedge::Bottom
|
||||
vec3::int( 0, 0,-1),//CornerWedge::Front
|
||||
];
|
||||
pub fn unit_sphere(render: RenderConfigId) -> Mesh {
|
||||
unit_cube(render)
|
||||
}
|
||||
#[derive(Default)]
|
||||
pub struct CubeFaceDescription([Option<FaceDescription>;6]);
|
||||
impl CubeFaceDescription{
|
||||
pub fn insert(&mut self,index:CubeFace,value:FaceDescription){
|
||||
self.0[index as usize]=Some(value);
|
||||
}
|
||||
pub fn pairs(
|
||||
self,
|
||||
) -> std::iter::FilterMap<
|
||||
std::iter::Enumerate<std::array::IntoIter<Option<FaceDescription>, 6>>,
|
||||
impl FnMut((usize, Option<FaceDescription>)) -> Option<(usize, FaceDescription)>,
|
||||
> {
|
||||
self.0
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
.filter_map(|v| v.1.map(|u| (v.0, u)))
|
||||
pub fn pairs(self)->std::iter::FilterMap<std::iter::Enumerate<std::array::IntoIter<Option<FaceDescription>,6>>,impl FnMut((usize,Option<FaceDescription>))->Option<(usize,FaceDescription)>>{
|
||||
self.0.into_iter().enumerate().filter_map(|v|v.1.map(|u|(v.0,u)))
|
||||
}
|
||||
}
|
||||
pub fn unit_cube(render:RenderConfigId)->Mesh{
|
||||
@ -132,95 +142,48 @@ pub fn unit_cube(render: RenderConfigId) -> Mesh {
|
||||
t.insert(CubeFace::Top,FaceDescription::new_with_render_id(render));
|
||||
t.insert(CubeFace::Back,FaceDescription::new_with_render_id(render));
|
||||
t.insert(CubeFace::Left,FaceDescription::new_with_render_id(render));
|
||||
t.insert(
|
||||
CubeFace::Bottom,
|
||||
FaceDescription::new_with_render_id(render),
|
||||
);
|
||||
t.insert(CubeFace::Bottom,FaceDescription::new_with_render_id(render));
|
||||
t.insert(CubeFace::Front,FaceDescription::new_with_render_id(render));
|
||||
generate_partial_unit_cube(t)
|
||||
}
|
||||
pub fn unit_cylinder(render: RenderConfigId) -> Mesh {
|
||||
//lmao
|
||||
unit_cube(render)
|
||||
}
|
||||
#[derive(Default)]
|
||||
pub struct WedgeFaceDescription([Option<FaceDescription>;5]);
|
||||
impl WedgeFaceDescription{
|
||||
pub fn insert(&mut self,index:WedgeFace,value:FaceDescription){
|
||||
self.0[index as usize]=Some(value);
|
||||
}
|
||||
pub fn pairs(
|
||||
self,
|
||||
) -> std::iter::FilterMap<
|
||||
std::iter::Enumerate<std::array::IntoIter<Option<FaceDescription>, 5>>,
|
||||
impl FnMut((usize, Option<FaceDescription>)) -> Option<(usize, FaceDescription)>,
|
||||
> {
|
||||
self.0
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
.filter_map(|v| v.1.map(|u| (v.0, u)))
|
||||
pub fn pairs(self)->std::iter::FilterMap<std::iter::Enumerate<std::array::IntoIter<Option<FaceDescription>,5>>,impl FnMut((usize,Option<FaceDescription>))->Option<(usize,FaceDescription)>>{
|
||||
self.0.into_iter().enumerate().filter_map(|v|v.1.map(|u|(v.0,u)))
|
||||
}
|
||||
}
|
||||
pub fn unit_wedge(render: RenderConfigId) -> Mesh {
|
||||
let mut t = WedgeFaceDescription::default();
|
||||
t.insert(
|
||||
WedgeFace::Right,
|
||||
FaceDescription::new_with_render_id(render),
|
||||
);
|
||||
t.insert(
|
||||
WedgeFace::TopFront,
|
||||
FaceDescription::new_with_render_id(render),
|
||||
);
|
||||
t.insert(WedgeFace::Back, FaceDescription::new_with_render_id(render));
|
||||
t.insert(WedgeFace::Left, FaceDescription::new_with_render_id(render));
|
||||
t.insert(
|
||||
WedgeFace::Bottom,
|
||||
FaceDescription::new_with_render_id(render),
|
||||
);
|
||||
generate_partial_unit_wedge(t)
|
||||
}
|
||||
// pub fn unit_wedge(render:RenderConfigId)->Mesh{
|
||||
// let mut t=WedgeFaceDescription::default();
|
||||
// t.insert(WedgeFace::Right,FaceDescription::new_with_render_id(render));
|
||||
// t.insert(WedgeFace::TopFront,FaceDescription::new_with_render_id(render));
|
||||
// t.insert(WedgeFace::Back,FaceDescription::new_with_render_id(render));
|
||||
// t.insert(WedgeFace::Left,FaceDescription::new_with_render_id(render));
|
||||
// t.insert(WedgeFace::Bottom,FaceDescription::new_with_render_id(render));
|
||||
// generate_partial_unit_wedge(t)
|
||||
// }
|
||||
#[derive(Default)]
|
||||
pub struct CornerWedgeFaceDescription([Option<FaceDescription>;5]);
|
||||
impl CornerWedgeFaceDescription{
|
||||
pub fn insert(&mut self,index:CornerWedgeFace,value:FaceDescription){
|
||||
self.0[index as usize]=Some(value);
|
||||
}
|
||||
pub fn pairs(
|
||||
self,
|
||||
) -> std::iter::FilterMap<
|
||||
std::iter::Enumerate<std::array::IntoIter<Option<FaceDescription>, 5>>,
|
||||
impl FnMut((usize, Option<FaceDescription>)) -> Option<(usize, FaceDescription)>,
|
||||
> {
|
||||
self.0
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
.filter_map(|v| v.1.map(|u| (v.0, u)))
|
||||
pub fn pairs(self)->std::iter::FilterMap<std::iter::Enumerate<std::array::IntoIter<Option<FaceDescription>,5>>,impl FnMut((usize,Option<FaceDescription>))->Option<(usize,FaceDescription)>>{
|
||||
self.0.into_iter().enumerate().filter_map(|v|v.1.map(|u|(v.0,u)))
|
||||
}
|
||||
}
|
||||
pub fn unit_cornerwedge(render: RenderConfigId) -> Mesh {
|
||||
let mut t = CornerWedgeFaceDescription::default();
|
||||
t.insert(
|
||||
CornerWedgeFace::Right,
|
||||
FaceDescription::new_with_render_id(render),
|
||||
);
|
||||
t.insert(
|
||||
CornerWedgeFace::TopBack,
|
||||
FaceDescription::new_with_render_id(render),
|
||||
);
|
||||
t.insert(
|
||||
CornerWedgeFace::TopLeft,
|
||||
FaceDescription::new_with_render_id(render),
|
||||
);
|
||||
t.insert(
|
||||
CornerWedgeFace::Bottom,
|
||||
FaceDescription::new_with_render_id(render),
|
||||
);
|
||||
t.insert(
|
||||
CornerWedgeFace::Front,
|
||||
FaceDescription::new_with_render_id(render),
|
||||
);
|
||||
generate_partial_unit_cornerwedge(t)
|
||||
}
|
||||
// pub fn unit_cornerwedge(render:RenderConfigId)->Mesh{
|
||||
// let mut t=CornerWedgeFaceDescription::default();
|
||||
// t.insert(CornerWedgeFace::Right,FaceDescription::new_with_render_id(render));
|
||||
// t.insert(CornerWedgeFace::TopBack,FaceDescription::new_with_render_id(render));
|
||||
// t.insert(CornerWedgeFace::TopLeft,FaceDescription::new_with_render_id(render));
|
||||
// t.insert(CornerWedgeFace::Bottom,FaceDescription::new_with_render_id(render));
|
||||
// t.insert(CornerWedgeFace::Front,FaceDescription::new_with_render_id(render));
|
||||
// generate_partial_unit_cornerwedge(t)
|
||||
// }
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct FaceDescription{
|
||||
@ -250,25 +213,18 @@ pub fn generate_partial_unit_cube(face_descriptions: CubeFaceDescription) -> Mes
|
||||
//note that on a cube every vertex is guaranteed to be unique, so there's no need to hash them against existing vertices.
|
||||
for (face_id,face_description) in face_descriptions.pairs(){
|
||||
//assume that scanning short lists is faster than hashing.
|
||||
let transform_index = if let Some(transform_index) = transforms
|
||||
.iter()
|
||||
.position(|&transform| transform == face_description.transform)
|
||||
{
|
||||
let transform_index=if let Some(transform_index)=transforms.iter().position(|&transform|transform==face_description.transform){
|
||||
transform_index
|
||||
}else{
|
||||
//create new transform_index
|
||||
let transform_index=transforms.len();
|
||||
transforms.push(face_description.transform);
|
||||
generated_tex.extend(
|
||||
CUBE_DEFAULT_TEXTURE_COORDS
|
||||
.map(|tex| face_description.transform.transform_point2(tex)),
|
||||
);
|
||||
generated_tex.extend(CUBE_DEFAULT_TEXTURE_COORDS.map(|tex|
|
||||
face_description.transform.transform_point2(tex)
|
||||
));
|
||||
transform_index
|
||||
} as u32;
|
||||
let color_index = if let Some(color_index) = generated_color
|
||||
.iter()
|
||||
.position(|&color| color == face_description.color)
|
||||
{
|
||||
let color_index=if let Some(color_index)=generated_color.iter().position(|&color|color==face_description.color){
|
||||
color_index
|
||||
}else{
|
||||
//create new color_index
|
||||
@ -282,11 +238,9 @@ pub fn generate_partial_unit_cube(face_descriptions: CubeFaceDescription) -> Mes
|
||||
//push vertices as they are needed
|
||||
let group_id=PolygonGroupId::new(polygon_groups.len() as u32);
|
||||
polygon_groups.push(PolygonGroup::PolygonList(PolygonList::new(vec![
|
||||
CUBE_DEFAULT_POLYS[face_id]
|
||||
.map(|tup| {
|
||||
CUBE_DEFAULT_POLYS[face_id].map(|tup|{
|
||||
let pos=CUBE_DEFAULT_VERTICES[tup[0] as usize];
|
||||
let pos_index =
|
||||
if let Some(pos_index) = generated_pos.iter().position(|&p| p == pos) {
|
||||
let pos_index=if let Some(pos_index)=generated_pos.iter().position(|&p|p==pos){
|
||||
pos_index
|
||||
}else{
|
||||
//create new pos_index
|
||||
@ -304,8 +258,7 @@ pub fn generate_partial_unit_cube(face_descriptions: CubeFaceDescription) -> Mes
|
||||
let vert_index=generated_vertices.len();
|
||||
generated_vertices.push(vertex);
|
||||
VertexId::new(vert_index as u32)
|
||||
})
|
||||
.to_vec(),
|
||||
}).to_vec(),
|
||||
])));
|
||||
graphics_groups.push(IndexedGraphicsGroup{
|
||||
render:face_description.render,
|
||||
@ -334,13 +287,32 @@ pub fn generate_partial_unit_wedge(face_descriptions: WedgeFaceDescription) -> M
|
||||
[1,3,0],
|
||||
],
|
||||
// FrontTop (0, 1, -1)
|
||||
vec![[3, 1, 1], [2, 0, 1], [6, 3, 1], [7, 2, 1]],
|
||||
vec![
|
||||
[3,1,1],
|
||||
[2,0,1],
|
||||
[6,3,1],
|
||||
[7,2,1],
|
||||
],
|
||||
// back (0, 0, 1)
|
||||
vec![[0, 3, 2], [1, 2, 2], [2, 1, 2], [3, 0, 2]],
|
||||
vec![
|
||||
[0,3,2],
|
||||
[1,2,2],
|
||||
[2,1,2],
|
||||
[3,0,2],
|
||||
],
|
||||
// left (-1, 0, 0)
|
||||
vec![[0, 2, 3], [3, 1, 3], [7, 3, 3]],
|
||||
vec![
|
||||
[0,2,3],
|
||||
[3,1,3],
|
||||
[7,3,3],
|
||||
],
|
||||
// bottom (0,-1, 0)
|
||||
vec![[1, 1, 4], [0, 0, 4], [7, 3, 4], [6, 2, 4]],
|
||||
vec![
|
||||
[1,1,4],
|
||||
[0,0,4],
|
||||
[7,3,4],
|
||||
[6,2,4],
|
||||
],
|
||||
];
|
||||
let mut generated_pos=Vec::new();
|
||||
let mut generated_tex=Vec::new();
|
||||
@ -354,25 +326,18 @@ pub fn generate_partial_unit_wedge(face_descriptions: WedgeFaceDescription) -> M
|
||||
//note that on a cube every vertex is guaranteed to be unique, so there's no need to hash them against existing vertices.
|
||||
for (face_id,face_description) in face_descriptions.pairs(){
|
||||
//assume that scanning short lists is faster than hashing.
|
||||
let transform_index = if let Some(transform_index) = transforms
|
||||
.iter()
|
||||
.position(|&transform| transform == face_description.transform)
|
||||
{
|
||||
let transform_index=if let Some(transform_index)=transforms.iter().position(|&transform|transform==face_description.transform){
|
||||
transform_index
|
||||
}else{
|
||||
//create new transform_index
|
||||
let transform_index=transforms.len();
|
||||
transforms.push(face_description.transform);
|
||||
generated_tex.extend(
|
||||
CUBE_DEFAULT_TEXTURE_COORDS
|
||||
.map(|tex| face_description.transform.transform_point2(tex)),
|
||||
);
|
||||
generated_tex.extend(CUBE_DEFAULT_TEXTURE_COORDS.map(|tex|
|
||||
face_description.transform.transform_point2(tex)
|
||||
));
|
||||
transform_index
|
||||
} as u32;
|
||||
let color_index = if let Some(color_index) = generated_color
|
||||
.iter()
|
||||
.position(|&color| color == face_description.color)
|
||||
{
|
||||
let color_index=if let Some(color_index)=generated_color.iter().position(|&color|color==face_description.color){
|
||||
color_index
|
||||
}else{
|
||||
//create new color_index
|
||||
@ -386,12 +351,9 @@ pub fn generate_partial_unit_wedge(face_descriptions: WedgeFaceDescription) -> M
|
||||
//push vertices as they are needed
|
||||
let group_id=PolygonGroupId::new(polygon_groups.len() as u32);
|
||||
polygon_groups.push(PolygonGroup::PolygonList(PolygonList::new(vec![
|
||||
wedge_default_polys[face_id]
|
||||
.iter()
|
||||
.map(|tup| {
|
||||
wedge_default_polys[face_id].iter().map(|tup|{
|
||||
let pos=CUBE_DEFAULT_VERTICES[tup[0] as usize];
|
||||
let pos_index =
|
||||
if let Some(pos_index) = generated_pos.iter().position(|&p| p == pos) {
|
||||
let pos_index=if let Some(pos_index)=generated_pos.iter().position(|&p|p==pos){
|
||||
pos_index
|
||||
}else{
|
||||
//create new pos_index
|
||||
@ -409,8 +371,7 @@ pub fn generate_partial_unit_wedge(face_descriptions: WedgeFaceDescription) -> M
|
||||
let vert_index=generated_vertices.len();
|
||||
generated_vertices.push(vertex);
|
||||
VertexId::new(vert_index as u32)
|
||||
})
|
||||
.collect(),
|
||||
}).collect()
|
||||
])));
|
||||
graphics_groups.push(IndexedGraphicsGroup{
|
||||
render:face_description.render,
|
||||
@ -439,13 +400,30 @@ pub fn generate_partial_unit_cornerwedge(face_descriptions: CornerWedgeFaceDescr
|
||||
[1,3,0],
|
||||
],
|
||||
// BackTop (0, 1, 1)
|
||||
vec![[5, 3, 1], [0, 1, 1], [1, 0, 1]],
|
||||
vec![
|
||||
[5,3,1],
|
||||
[0,1,1],
|
||||
[1,0,1],
|
||||
],
|
||||
// LeftTop (-1, 1, 0)
|
||||
vec![[5, 3, 2], [7, 2, 2], [0, 1, 2]],
|
||||
vec![
|
||||
[5,3,2],
|
||||
[7,2,2],
|
||||
[0,1,2],
|
||||
],
|
||||
// bottom (0,-1, 0)
|
||||
vec![[1, 1, 3], [0, 0, 3], [7, 3, 3], [6, 2, 3]],
|
||||
vec![
|
||||
[1,1,3],
|
||||
[0,0,3],
|
||||
[7,3,3],
|
||||
[6,2,3],
|
||||
],
|
||||
// front (0, 0,-1)
|
||||
vec![[5, 0, 4], [6, 3, 4], [7, 2, 4]],
|
||||
vec![
|
||||
[5,0,4],
|
||||
[6,3,4],
|
||||
[7,2,4],
|
||||
],
|
||||
];
|
||||
let mut generated_pos=Vec::new();
|
||||
let mut generated_tex=Vec::new();
|
||||
@ -459,25 +437,18 @@ pub fn generate_partial_unit_cornerwedge(face_descriptions: CornerWedgeFaceDescr
|
||||
//note that on a cube every vertex is guaranteed to be unique, so there's no need to hash them against existing vertices.
|
||||
for (face_id,face_description) in face_descriptions.pairs(){
|
||||
//assume that scanning short lists is faster than hashing.
|
||||
let transform_index = if let Some(transform_index) = transforms
|
||||
.iter()
|
||||
.position(|&transform| transform == face_description.transform)
|
||||
{
|
||||
let transform_index=if let Some(transform_index)=transforms.iter().position(|&transform|transform==face_description.transform){
|
||||
transform_index
|
||||
}else{
|
||||
//create new transform_index
|
||||
let transform_index=transforms.len();
|
||||
transforms.push(face_description.transform);
|
||||
generated_tex.extend(
|
||||
CUBE_DEFAULT_TEXTURE_COORDS
|
||||
.map(|tex| face_description.transform.transform_point2(tex)),
|
||||
);
|
||||
generated_tex.extend(CUBE_DEFAULT_TEXTURE_COORDS.map(|tex|
|
||||
face_description.transform.transform_point2(tex)
|
||||
));
|
||||
transform_index
|
||||
} as u32;
|
||||
let color_index = if let Some(color_index) = generated_color
|
||||
.iter()
|
||||
.position(|&color| color == face_description.color)
|
||||
{
|
||||
let color_index=if let Some(color_index)=generated_color.iter().position(|&color|color==face_description.color){
|
||||
color_index
|
||||
}else{
|
||||
//create new color_index
|
||||
@ -491,12 +462,9 @@ pub fn generate_partial_unit_cornerwedge(face_descriptions: CornerWedgeFaceDescr
|
||||
//push vertices as they are needed
|
||||
let group_id=PolygonGroupId::new(polygon_groups.len() as u32);
|
||||
polygon_groups.push(PolygonGroup::PolygonList(PolygonList::new(vec![
|
||||
cornerwedge_default_polys[face_id]
|
||||
.iter()
|
||||
.map(|tup| {
|
||||
cornerwedge_default_polys[face_id].iter().map(|tup|{
|
||||
let pos=CUBE_DEFAULT_VERTICES[tup[0] as usize];
|
||||
let pos_index =
|
||||
if let Some(pos_index) = generated_pos.iter().position(|&p| p == pos) {
|
||||
let pos_index=if let Some(pos_index)=generated_pos.iter().position(|&p|p==pos){
|
||||
pos_index
|
||||
}else{
|
||||
//create new pos_index
|
||||
@ -514,8 +482,7 @@ pub fn generate_partial_unit_cornerwedge(face_descriptions: CornerWedgeFaceDescr
|
||||
let vert_index=generated_vertices.len();
|
||||
generated_vertices.push(vertex);
|
||||
VertexId::new(vert_index as u32)
|
||||
})
|
||||
.collect(),
|
||||
}).collect(),
|
||||
])));
|
||||
graphics_groups.push(IndexedGraphicsGroup{
|
||||
render:face_description.render,
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -2,15 +2,12 @@ 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))
|
||||
})
|
||||
||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)]
|
||||
@ -22,21 +19,16 @@ impl Context {
|
||||
pub const fn new(dom:WeakDom)->Self{
|
||||
Self{dom}
|
||||
}
|
||||
pub fn script_singleton(
|
||||
source: String,
|
||||
) -> (Context, crate::runner::instance::Instance, Services) {
|
||||
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),
|
||||
InstanceBuilder::new("DataModel")
|
||||
.with_child(script)
|
||||
));
|
||||
let services=context.convert_into_place();
|
||||
(
|
||||
context,
|
||||
crate::runner::instance::Instance::new(script_ref),
|
||||
services,
|
||||
)
|
||||
(context,crate::runner::instance::Instance::new(script_ref),services)
|
||||
}
|
||||
pub fn from_ref(dom:&WeakDom)->&Context{
|
||||
unsafe{&*(dom as *const WeakDom as *const Context)}
|
||||
@ -46,24 +38,19 @@ impl 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())
|
||||
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()
|
||||
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")
|
||||
})?,
|
||||
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(),
|
||||
})
|
||||
}
|
||||
@ -74,20 +61,17 @@ impl Context {
|
||||
//insert services
|
||||
let game=self.dom.root_ref();
|
||||
let terrain_bldr=InstanceBuilder::new("Terrain");
|
||||
let workspace = self.dom.insert(
|
||||
game,
|
||||
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),
|
||||
.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));
|
||||
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"));
|
||||
|
||||
@ -96,7 +80,10 @@ impl Context {
|
||||
self.dom.transfer_within(instance,workspace);
|
||||
}
|
||||
|
||||
Services { game, workspace }
|
||||
Services{
|
||||
game,
|
||||
workspace,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
pub mod context;
|
||||
pub mod runner;
|
||||
pub mod context;
|
||||
#[cfg(feature="run-service")]
|
||||
pub(crate) mod scheduler;
|
||||
|
||||
|
@ -5,38 +5,25 @@ 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,
|
||||
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(xz,yz,zz)
|
||||
),
|
||||
glam::vec3(x, y, z),
|
||||
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,
|
||||
)))
|
||||
Self(glam::Affine3A::from_mat3(glam::Mat3::from_euler(glam::EulerRot::YXZ,y,x,z)))
|
||||
}
|
||||
}
|
||||
|
||||
@ -55,7 +42,7 @@ impl Into<rbx_types::CFrame> for CFrame {
|
||||
vec3_to_glam(self.0.matrix3.x_axis),
|
||||
vec3_to_glam(self.0.matrix3.y_axis),
|
||||
vec3_to_glam(self.0.matrix3.z_axis),
|
||||
),
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -67,7 +54,7 @@ impl From<rbx_types::CFrame> for CFrame {
|
||||
vec3_from_glam(value.orientation.y),
|
||||
vec3_from_glam(value.orientation.z),
|
||||
),
|
||||
translation: vec3_from_glam(value.position),
|
||||
translation:vec3_from_glam(value.position)
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -76,102 +63,61 @@ pub fn set_globals(lua: &mlua::Lua, globals: &mlua::Table) -> Result<(), mlua::E
|
||||
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>,
|
||||
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,
|
||||
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,
|
||||
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,
|
||||
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,
|
||||
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")),
|
||||
},
|
||||
)?,
|
||||
_=>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_table.raw_set("Angles",
|
||||
lua.create_function(|_,(x,y,z):(f32,f32,f32)|
|
||||
Ok(CFrame::angles(x,y,z))
|
||||
)?
|
||||
)?;
|
||||
|
||||
globals.set("CFrame",cframe_table)?;
|
||||
@ -186,8 +132,7 @@ impl mlua::UserData for CFrame {
|
||||
}
|
||||
|
||||
fn add_methods<M:mlua::UserDataMethods<Self>>(methods:&mut M){
|
||||
methods.add_method("components", |_, this, ()| {
|
||||
Ok((
|
||||
methods.add_method("components",|_,this,()|Ok((
|
||||
this.0.translation.x,
|
||||
this.0.translation.y,
|
||||
this.0.translation.z,
|
||||
@ -200,19 +145,15 @@ impl mlua::UserData for CFrame {
|
||||
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| {
|
||||
)));
|
||||
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({},{},{},{},{},{},{},{},{},{},{},{})",
|
||||
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,
|
||||
@ -226,7 +167,7 @@ impl mlua::UserData for CFrame {
|
||||
this.0.matrix3.y_axis.z,
|
||||
this.0.matrix3.z_axis.z,
|
||||
))
|
||||
});
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -18,19 +18,15 @@ impl Into<rbx_types::Color3> for Color3 {
|
||||
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("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("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)?;
|
||||
@ -60,13 +56,13 @@ impl mlua::UserData for Color3 {
|
||||
});
|
||||
}
|
||||
fn add_methods<M:mlua::UserDataMethods<Self>>(methods:&mut M){
|
||||
methods.add_method("Lerp", |_, this, (other, t): (Self, f32)| {
|
||||
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);
|
||||
|
@ -8,7 +8,7 @@ impl ColorSequence {
|
||||
impl Into<rbx_types::ColorSequence> for ColorSequence{
|
||||
fn into(self)->rbx_types::ColorSequence{
|
||||
rbx_types::ColorSequence{
|
||||
keypoints: Vec::new(),
|
||||
keypoints:Vec::new()
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -16,9 +16,10 @@ impl Into<rbx_types::ColorSequence> for ColorSequence {
|
||||
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)?;
|
||||
|
@ -26,42 +26,38 @@ pub fn set_globals(_lua: &mlua::Lua, globals: &mlua::Table) -> Result<(), mlua::
|
||||
}
|
||||
|
||||
impl mlua::UserData for EnumItem<'_>{
|
||||
fn add_fields<F: mlua::UserDataFields<Self>>(_fields: &mut F) {}
|
||||
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()?)
|
||||
{
|
||||
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_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)| {
|
||||
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) {}
|
||||
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);
|
||||
|
@ -1,8 +1,8 @@
|
||||
use std::collections::{hash_map::Entry,HashMap};
|
||||
|
||||
use mlua::{FromLua,FromLuaMulti,IntoLua,IntoLuaMulti};
|
||||
use rbx_dom_weak::{InstanceBuilder, WeakDom};
|
||||
use rbx_types::Ref;
|
||||
use rbx_dom_weak::{InstanceBuilder,WeakDom};
|
||||
|
||||
use crate::runner::vector3::Vector3;
|
||||
|
||||
@ -14,22 +14,15 @@ pub fn set_globals(lua: &mlua::Lua, globals: &mlua::Table) -> Result<(), mlua::E
|
||||
let instance_table=lua.create_table()?;
|
||||
|
||||
//Instance.new
|
||||
instance_table.raw_set(
|
||||
"new",
|
||||
lua.create_function(
|
||||
|lua, (class_name, parent): (mlua::String, Option<Instance>)| {
|
||||
instance_table.raw_set("new",
|
||||
lua.create_function(|lua,(class_name,parent):(mlua::String,Option<Instance>)|{
|
||||
let class_name_str=&*class_name.to_str()?;
|
||||
let parent =
|
||||
parent.ok_or_else(|| mlua::Error::runtime("Nil Parent not yet supported"))?;
|
||||
let parent=parent.ok_or_else(||mlua::Error::runtime("Nil Parent not yet supported"))?;
|
||||
dom_mut(lua,|dom|{
|
||||
//TODO: Nil instances
|
||||
Ok(Instance::new(dom.insert(
|
||||
parent.referent,
|
||||
InstanceBuilder::new(class_name_str),
|
||||
)))
|
||||
Ok(Instance::new(dom.insert(parent.referent,InstanceBuilder::new(class_name_str))))
|
||||
})
|
||||
},
|
||||
)?,
|
||||
})?
|
||||
)?;
|
||||
|
||||
globals.set("Instance",instance_table)?;
|
||||
@ -38,14 +31,9 @@ pub fn set_globals(lua: &mlua::Lua, globals: &mlua::Table) -> Result<(), mlua::E
|
||||
}
|
||||
|
||||
// LMAO look at this function!
|
||||
pub fn dom_mut<T>(
|
||||
lua: &mlua::Lua,
|
||||
mut f: impl FnMut(&mut WeakDom) -> mlua::Result<T>,
|
||||
) -> mlua::Result<T> {
|
||||
let mut dom = lua
|
||||
.app_data_mut::<&'static mut WeakDom>()
|
||||
.ok_or_else(|| mlua::Error::runtime("DataModel missing"))?;
|
||||
f(&mut *dom)
|
||||
pub fn dom_mut<T>(lua:&mlua::Lua,mut f:impl FnMut(&mut WeakDom)->mlua::Result<T>)->mlua::Result<T>{
|
||||
let mut dom=lua.app_data_mut::<&'static mut WeakDom>().ok_or_else(||mlua::Error::runtime("DataModel missing"))?;
|
||||
f(*dom)
|
||||
}
|
||||
|
||||
fn coerce_float32(value:&mlua::Value)->Option<f32>{
|
||||
@ -78,44 +66,18 @@ pub fn get_name_source(lua: &mlua::Lua, script: Instance) -> Result<(String, Str
|
||||
})
|
||||
}
|
||||
|
||||
pub fn find_first_child<'a>(
|
||||
dom: &'a rbx_dom_weak::WeakDom,
|
||||
instance: &rbx_dom_weak::Instance,
|
||||
name: &str,
|
||||
) -> Option<&'a rbx_dom_weak::Instance> {
|
||||
instance
|
||||
.children()
|
||||
.iter()
|
||||
.filter_map(|&r| dom.get_by_ref(r))
|
||||
.find(|inst| inst.name == name)
|
||||
pub fn find_first_child<'a>(dom:&'a rbx_dom_weak::WeakDom,instance:&rbx_dom_weak::Instance,name:&str)->Option<&'a rbx_dom_weak::Instance>{
|
||||
instance.children().iter().filter_map(|&r|dom.get_by_ref(r)).find(|inst|inst.name==name)
|
||||
}
|
||||
pub fn find_first_descendant<'a>(
|
||||
dom: &'a rbx_dom_weak::WeakDom,
|
||||
instance: &rbx_dom_weak::Instance,
|
||||
name: &str,
|
||||
) -> Option<&'a rbx_dom_weak::Instance> {
|
||||
dom.descendants_of(instance.referent())
|
||||
.find(|&inst| inst.name == name)
|
||||
pub fn find_first_descendant<'a>(dom:&'a rbx_dom_weak::WeakDom,instance:&rbx_dom_weak::Instance,name:&str)->Option<&'a rbx_dom_weak::Instance>{
|
||||
dom.descendants_of(instance.referent()).find(|&inst|inst.name==name)
|
||||
}
|
||||
|
||||
pub fn find_first_child_of_class<'a>(
|
||||
dom: &'a rbx_dom_weak::WeakDom,
|
||||
instance: &rbx_dom_weak::Instance,
|
||||
class: &str,
|
||||
) -> Option<&'a rbx_dom_weak::Instance> {
|
||||
instance
|
||||
.children()
|
||||
.iter()
|
||||
.filter_map(|&r| dom.get_by_ref(r))
|
||||
.find(|inst| inst.class == class)
|
||||
pub fn find_first_child_of_class<'a>(dom:&'a rbx_dom_weak::WeakDom,instance:&rbx_dom_weak::Instance,class:&str)->Option<&'a rbx_dom_weak::Instance>{
|
||||
instance.children().iter().filter_map(|&r|dom.get_by_ref(r)).find(|inst|inst.class==class)
|
||||
}
|
||||
pub fn find_first_descendant_of_class<'a>(
|
||||
dom: &'a rbx_dom_weak::WeakDom,
|
||||
instance: &rbx_dom_weak::Instance,
|
||||
class: &str,
|
||||
) -> Option<&'a rbx_dom_weak::Instance> {
|
||||
dom.descendants_of(instance.referent())
|
||||
.find(|&inst| inst.class == class)
|
||||
pub fn find_first_descendant_of_class<'a>(dom:&'a rbx_dom_weak::WeakDom,instance:&rbx_dom_weak::Instance,class:&str)->Option<&'a rbx_dom_weak::Instance>{
|
||||
dom.descendants_of(instance.referent()).find(|&inst|inst.class==class)
|
||||
}
|
||||
|
||||
#[derive(Clone,Copy)]
|
||||
@ -127,15 +89,10 @@ impl Instance {
|
||||
Self{referent}
|
||||
}
|
||||
pub fn get<'a>(&self,dom:&'a WeakDom)->mlua::Result<&'a rbx_dom_weak::Instance>{
|
||||
dom.get_by_ref(self.referent)
|
||||
.ok_or_else(|| mlua::Error::runtime("Instance missing"))
|
||||
dom.get_by_ref(self.referent).ok_or_else(||mlua::Error::runtime("Instance missing"))
|
||||
}
|
||||
pub fn get_mut<'a>(
|
||||
&self,
|
||||
dom: &'a mut WeakDom,
|
||||
) -> mlua::Result<&'a mut rbx_dom_weak::Instance> {
|
||||
dom.get_by_ref_mut(self.referent)
|
||||
.ok_or_else(|| mlua::Error::runtime("Instance missing"))
|
||||
pub fn get_mut<'a>(&self,dom:&'a mut WeakDom)->mlua::Result<&'a mut rbx_dom_weak::Instance>{
|
||||
dom.get_by_ref_mut(self.referent).ok_or_else(||mlua::Error::runtime("Instance missing"))
|
||||
}
|
||||
}
|
||||
type_from_lua_userdata!(Instance);
|
||||
@ -196,7 +153,7 @@ impl mlua::UserData for Instance {
|
||||
});
|
||||
}
|
||||
fn add_methods<M:mlua::UserDataMethods<Self>>(methods:&mut M){
|
||||
methods.add_method("GetChildren", |lua, this, _: ()| {
|
||||
methods.add_method("GetChildren",|lua,this,_:()|
|
||||
dom_mut(lua,|dom|{
|
||||
let instance=this.get(dom)?;
|
||||
let children:Vec<_>=instance
|
||||
@ -207,61 +164,61 @@ impl mlua::UserData for Instance {
|
||||
.collect();
|
||||
Ok(children)
|
||||
})
|
||||
});
|
||||
fn ffc(
|
||||
lua: &mlua::Lua,
|
||||
this: &Instance,
|
||||
(name, search_descendants): (mlua::String, Option<bool>),
|
||||
) -> mlua::Result<Option<Instance>> {
|
||||
);
|
||||
fn ffc(lua:&mlua::Lua,this:&Instance,(name,search_descendants):(mlua::String,Option<bool>))->mlua::Result<Option<Instance>>{
|
||||
let name_str=&*name.to_str()?;
|
||||
dom_mut(lua,|dom|{
|
||||
let instance=this.get(dom)?;
|
||||
Ok(match search_descendants.unwrap_or(false) {
|
||||
Ok(
|
||||
match search_descendants.unwrap_or(false){
|
||||
true=>find_first_descendant(dom,instance,name_str),
|
||||
false=>find_first_child(dom,instance,name_str),
|
||||
}
|
||||
.map(|instance| Instance::new(instance.referent())))
|
||||
.map(|instance|
|
||||
Instance::new(instance.referent())
|
||||
)
|
||||
)
|
||||
})
|
||||
}
|
||||
methods.add_method("FindFirstChild",ffc);
|
||||
methods.add_method("WaitForChild",ffc);
|
||||
methods.add_method(
|
||||
"FindFirstChildOfClass",
|
||||
|lua, this, (class, search_descendants): (mlua::String, Option<bool>)| {
|
||||
methods.add_method("FindFirstChildOfClass",|lua,this,(class,search_descendants):(mlua::String,Option<bool>)|{
|
||||
let class_str=&*class.to_str()?;
|
||||
dom_mut(lua,|dom|{
|
||||
Ok(match search_descendants.unwrap_or(false) {
|
||||
Ok(
|
||||
match search_descendants.unwrap_or(false){
|
||||
true=>find_first_descendant_of_class(dom,this.get(dom)?,class_str),
|
||||
false=>find_first_child_of_class(dom,this.get(dom)?,class_str),
|
||||
}
|
||||
.map(|instance| Instance::new(instance.referent())))
|
||||
.map(|instance|
|
||||
Instance::new(instance.referent())
|
||||
)
|
||||
)
|
||||
})
|
||||
},
|
||||
);
|
||||
methods.add_method("GetDescendants", |lua, this, _: ()| {
|
||||
});
|
||||
methods.add_method("GetDescendants",|lua,this,_:()|
|
||||
dom_mut(lua,|dom|{
|
||||
let children:Vec<_>=dom
|
||||
.descendants_of(this.referent)
|
||||
.map(|instance| Instance::new(instance.referent()))
|
||||
.map(|instance|
|
||||
Instance::new(instance.referent())
|
||||
)
|
||||
.collect();
|
||||
Ok(children)
|
||||
})
|
||||
});
|
||||
methods.add_method("IsA", |lua, this, classname: mlua::String| {
|
||||
);
|
||||
methods.add_method("IsA",|lua,this,classname:mlua::String|
|
||||
dom_mut(lua,|dom|{
|
||||
let instance=this.get(dom)?;
|
||||
Ok(crate::context::class_is_a(
|
||||
instance.class.as_str(),
|
||||
&*classname.to_str()?,
|
||||
))
|
||||
Ok(crate::context::class_is_a(instance.class.as_str(),&*classname.to_str()?))
|
||||
})
|
||||
});
|
||||
methods.add_method("Destroy", |lua, this, ()| {
|
||||
);
|
||||
methods.add_method("Destroy",|lua,this,()|
|
||||
dom_mut(lua,|dom|{
|
||||
dom.destroy(this.referent);
|
||||
Ok(())
|
||||
})
|
||||
});
|
||||
);
|
||||
methods.add_meta_function(mlua::MetaMethod::ToString,|lua,this:Instance|{
|
||||
dom_mut(lua,|dom|{
|
||||
let instance=this.get(dom)?;
|
||||
@ -332,139 +289,68 @@ impl mlua::UserData for Instance {
|
||||
.into_lua(lua)
|
||||
})
|
||||
});
|
||||
methods.add_meta_function(
|
||||
mlua::MetaMethod::NewIndex,
|
||||
|lua, (this, index, value): (Instance, mlua::String, mlua::Value)| {
|
||||
methods.add_meta_function(mlua::MetaMethod::NewIndex,|lua,(this,index,value):(Instance,mlua::String,mlua::Value)|{
|
||||
dom_mut(lua,|dom|{
|
||||
let instance=this.get_mut(dom)?;
|
||||
//println!("__newindex t={} i={index:?} v={value:?}",instance.name);
|
||||
let index_str=&*index.to_str()?;
|
||||
let db=rbx_reflection_database::get();
|
||||
let class = db
|
||||
.classes
|
||||
.get(instance.class.as_str())
|
||||
.ok_or_else(|| mlua::Error::runtime("Class missing"))?;
|
||||
let class=db.classes.get(instance.class.as_str()).ok_or_else(||mlua::Error::runtime("Class missing"))?;
|
||||
let mut iter=SuperClassIter{
|
||||
database:db,
|
||||
descriptor:Some(class),
|
||||
};
|
||||
let property = iter
|
||||
.find_map(|cls| cls.properties.get(index_str))
|
||||
.ok_or_else(|| {
|
||||
mlua::Error::runtime(format!(
|
||||
"Property '{index_str}' missing on class '{}'",
|
||||
class.name
|
||||
))
|
||||
})?;
|
||||
let property=iter.find_map(|cls|cls.properties.get(index_str)).ok_or_else(||mlua::Error::runtime(format!("Property '{index_str}' missing on class '{}'",class.name)))?;
|
||||
match &property.data_type{
|
||||
rbx_reflection::DataType::Value(rbx_types::VariantType::Vector3)=>{
|
||||
let typed_value: Vector3 = *value
|
||||
.as_userdata()
|
||||
.ok_or_else(|| mlua::Error::runtime("Expected Userdata"))?
|
||||
.borrow()?;
|
||||
instance.properties.insert(
|
||||
index_str.to_owned(),
|
||||
rbx_types::Variant::Vector3(typed_value.into()),
|
||||
);
|
||||
}
|
||||
let typed_value:Vector3=*value.as_userdata().ok_or_else(||mlua::Error::runtime("Expected Userdata"))?.borrow()?;
|
||||
instance.properties.insert(index_str.to_owned(),rbx_types::Variant::Vector3(typed_value.into()));
|
||||
},
|
||||
rbx_reflection::DataType::Value(rbx_types::VariantType::Float32)=>{
|
||||
let typed_value: f32 = coerce_float32(&value)
|
||||
.ok_or_else(|| mlua::Error::runtime("Expected f32"))?;
|
||||
instance.properties.insert(
|
||||
index_str.to_owned(),
|
||||
rbx_types::Variant::Float32(typed_value),
|
||||
);
|
||||
}
|
||||
let typed_value:f32=coerce_float32(&value).ok_or_else(||mlua::Error::runtime("Expected f32"))?;
|
||||
instance.properties.insert(index_str.to_owned(),rbx_types::Variant::Float32(typed_value));
|
||||
},
|
||||
rbx_reflection::DataType::Enum(enum_name)=>{
|
||||
let typed_value=match &value{
|
||||
&mlua::Value::Integer(int) => {
|
||||
Ok(rbx_types::Enum::from_u32(int as u32))
|
||||
}
|
||||
&mlua::Value::Number(num) => {
|
||||
Ok(rbx_types::Enum::from_u32(num as u32))
|
||||
}
|
||||
&mlua::Value::Integer(int)=>Ok(rbx_types::Enum::from_u32(int as u32)),
|
||||
&mlua::Value::Number(num)=>Ok(rbx_types::Enum::from_u32(num as u32)),
|
||||
mlua::Value::String(s)=>{
|
||||
let e = db.enums.get(enum_name).ok_or_else(|| {
|
||||
mlua::Error::runtime(
|
||||
"Database DataType Enum name does not exist",
|
||||
)
|
||||
})?;
|
||||
Ok(rbx_types::Enum::from_u32(
|
||||
*e.items.get(&*s.to_str()?).ok_or_else(|| {
|
||||
mlua::Error::runtime("Invalid enum item")
|
||||
})?,
|
||||
))
|
||||
}
|
||||
let e=db.enums.get(enum_name).ok_or_else(||mlua::Error::runtime("Database DataType Enum name does not exist"))?;
|
||||
Ok(rbx_types::Enum::from_u32(*e.items.get(&*s.to_str()?).ok_or_else(||mlua::Error::runtime("Invalid enum item"))?))
|
||||
},
|
||||
mlua::Value::UserData(any_user_data)=>{
|
||||
let e:crate::runner::r#enum::Enum=*any_user_data.borrow()?;
|
||||
Ok(e.into())
|
||||
}
|
||||
},
|
||||
_=>Err(mlua::Error::runtime("Expected Enum")),
|
||||
}?;
|
||||
instance.properties.insert(
|
||||
index_str.to_owned(),
|
||||
rbx_types::Variant::Enum(typed_value),
|
||||
);
|
||||
}
|
||||
instance.properties.insert(index_str.to_owned(),rbx_types::Variant::Enum(typed_value));
|
||||
},
|
||||
rbx_reflection::DataType::Value(rbx_types::VariantType::Color3)=>{
|
||||
let typed_value: crate::runner::color3::Color3 = *value
|
||||
.as_userdata()
|
||||
.ok_or_else(|| mlua::Error::runtime("Expected Color3"))?
|
||||
.borrow()?;
|
||||
instance.properties.insert(
|
||||
index_str.to_owned(),
|
||||
rbx_types::Variant::Color3(typed_value.into()),
|
||||
);
|
||||
}
|
||||
let typed_value:crate::runner::color3::Color3=*value.as_userdata().ok_or_else(||mlua::Error::runtime("Expected Color3"))?.borrow()?;
|
||||
instance.properties.insert(index_str.to_owned(),rbx_types::Variant::Color3(typed_value.into()));
|
||||
},
|
||||
rbx_reflection::DataType::Value(rbx_types::VariantType::Bool)=>{
|
||||
let typed_value = value
|
||||
.as_boolean()
|
||||
.ok_or_else(|| mlua::Error::runtime("Expected boolean"))?;
|
||||
instance.properties.insert(
|
||||
index_str.to_owned(),
|
||||
rbx_types::Variant::Bool(typed_value),
|
||||
);
|
||||
}
|
||||
let typed_value=value.as_boolean().ok_or_else(||mlua::Error::runtime("Expected boolean"))?;
|
||||
instance.properties.insert(index_str.to_owned(),rbx_types::Variant::Bool(typed_value));
|
||||
},
|
||||
rbx_reflection::DataType::Value(rbx_types::VariantType::String)=>{
|
||||
let typed_value = value
|
||||
.as_str()
|
||||
.ok_or_else(|| mlua::Error::runtime("Expected boolean"))?;
|
||||
instance.properties.insert(
|
||||
index_str.to_owned(),
|
||||
rbx_types::Variant::String(typed_value.to_owned()),
|
||||
);
|
||||
}
|
||||
let typed_value=value.as_str().ok_or_else(||mlua::Error::runtime("Expected boolean"))?;
|
||||
instance.properties.insert(index_str.to_owned(),rbx_types::Variant::String(typed_value.to_owned()));
|
||||
},
|
||||
rbx_reflection::DataType::Value(rbx_types::VariantType::NumberSequence)=>{
|
||||
let typed_value: crate::runner::number_sequence::NumberSequence =
|
||||
*value
|
||||
.as_userdata()
|
||||
.ok_or_else(|| mlua::Error::runtime("Expected NumberSequence"))?
|
||||
.borrow()?;
|
||||
instance.properties.insert(
|
||||
index_str.to_owned(),
|
||||
rbx_types::Variant::NumberSequence(typed_value.into()),
|
||||
);
|
||||
}
|
||||
let typed_value:crate::runner::number_sequence::NumberSequence=*value.as_userdata().ok_or_else(||mlua::Error::runtime("Expected NumberSequence"))?.borrow()?;
|
||||
instance.properties.insert(index_str.to_owned(),rbx_types::Variant::NumberSequence(typed_value.into()));
|
||||
},
|
||||
rbx_reflection::DataType::Value(rbx_types::VariantType::ColorSequence)=>{
|
||||
let typed_value: crate::runner::color_sequence::ColorSequence = *value
|
||||
.as_userdata()
|
||||
.ok_or_else(|| mlua::Error::runtime("Expected ColorSequence"))?
|
||||
.borrow()?;
|
||||
instance.properties.insert(
|
||||
index_str.to_owned(),
|
||||
rbx_types::Variant::ColorSequence(typed_value.into()),
|
||||
);
|
||||
}
|
||||
other => {
|
||||
return Err(mlua::Error::runtime(format!(
|
||||
"Unimplemented property type: {other:?}"
|
||||
)))
|
||||
}
|
||||
let typed_value:crate::runner::color_sequence::ColorSequence=*value.as_userdata().ok_or_else(||mlua::Error::runtime("Expected ColorSequence"))?.borrow()?;
|
||||
instance.properties.insert(index_str.to_owned(),rbx_types::Variant::ColorSequence(typed_value.into()));
|
||||
},
|
||||
other=>return Err(mlua::Error::runtime(format!("Unimplemented property type: {other:?}"))),
|
||||
}
|
||||
Ok(())
|
||||
})
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -481,12 +367,10 @@ type ClassFunctionPointer = fn(&mlua::Lua, mlua::MultiValue) -> mlua::Result<mlu
|
||||
// TODO: use macros to define these with better organization
|
||||
/// A double hash map of function pointers.
|
||||
/// The class tree is walked by the Instance.__index metamethod to find available class methods.
|
||||
type CFD = phf::Map<
|
||||
&'static str, // Class name
|
||||
phf::Map<
|
||||
&'static str, // Method name
|
||||
ClassFunctionPointer,
|
||||
>,
|
||||
type CFD=phf::Map<&'static str,// Class name
|
||||
phf::Map<&'static str,// Method name
|
||||
ClassFunctionPointer
|
||||
>
|
||||
>;
|
||||
static CLASS_FUNCTION_DATABASE:CFD=phf::phf_map!{
|
||||
"DataModel"=>phf::phf_map!{
|
||||
@ -517,28 +401,25 @@ static CLASS_FUNCTION_DATABASE: CFD = phf::phf_map! {
|
||||
/// Functions are created the first time they are accessed and stored in this data structure.
|
||||
#[derive(Default)]
|
||||
struct ClassMethodsStore{
|
||||
classes: HashMap<
|
||||
&'static str, //ClassName
|
||||
HashMap<
|
||||
&'static str, //Method name
|
||||
mlua::Function,
|
||||
>,
|
||||
>,
|
||||
classes:HashMap<&'static str,//ClassName
|
||||
HashMap<&'static str,//Method name
|
||||
mlua::Function
|
||||
>
|
||||
>
|
||||
}
|
||||
impl ClassMethodsStore{
|
||||
/// return self.classes[class] or create the ClassMethods and then return it
|
||||
fn get_or_create_class_methods(&mut self,class:&str)->Option<ClassMethods>{
|
||||
// Use get_entry to get the &'static str keys of the database
|
||||
// and use it as a key for the classes hashmap
|
||||
CLASS_FUNCTION_DATABASE
|
||||
.get_entry(class)
|
||||
.map(|(&static_class_str, method_pointers)| ClassMethods {
|
||||
CLASS_FUNCTION_DATABASE.get_entry(class)
|
||||
.map(|(&static_class_str,method_pointers)|
|
||||
ClassMethods{
|
||||
method_pointers,
|
||||
methods: self
|
||||
.classes
|
||||
.entry(static_class_str)
|
||||
methods:self.classes.entry(static_class_str)
|
||||
.or_insert_with(||HashMap::new()),
|
||||
})
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
struct ClassMethods<'a>{
|
||||
@ -547,49 +428,44 @@ struct ClassMethods<'a> {
|
||||
}
|
||||
impl ClassMethods<'_>{
|
||||
/// return self.methods[index] or create the function in the hashmap and then return it
|
||||
fn get_or_create_function(
|
||||
&mut self,
|
||||
lua: &mlua::Lua,
|
||||
index: &str,
|
||||
) -> mlua::Result<Option<mlua::Function>> {
|
||||
fn get_or_create_function(&mut self,lua:&mlua::Lua,index:&str)->mlua::Result<Option<mlua::Function>>{
|
||||
Ok(match self.method_pointers.get_entry(index){
|
||||
Some((&static_index_str, &function_pointer)) => {
|
||||
Some(match self.methods.entry(static_index_str) {
|
||||
Some((&static_index_str,&function_pointer))=>Some(
|
||||
match self.methods.entry(static_index_str){
|
||||
Entry::Occupied(entry)=>entry.get().clone(),
|
||||
Entry::Vacant(entry) => {
|
||||
entry.insert(lua.create_function(function_pointer)?).clone()
|
||||
}
|
||||
})
|
||||
Entry::Vacant(entry)=>entry.insert(
|
||||
lua.create_function(function_pointer)?
|
||||
).clone(),
|
||||
}
|
||||
),
|
||||
None=>None,
|
||||
})
|
||||
}
|
||||
}
|
||||
fn class_methods_store_mut<T>(
|
||||
lua: &mlua::Lua,
|
||||
mut f: impl FnMut(&mut ClassMethodsStore) -> mlua::Result<T>,
|
||||
) -> mlua::Result<T> {
|
||||
let mut cf = lua
|
||||
.app_data_mut::<ClassMethodsStore>()
|
||||
.ok_or_else(|| mlua::Error::runtime("ClassMethodsStore missing"))?;
|
||||
fn class_methods_store_mut<T>(lua:&mlua::Lua,mut f:impl FnMut(&mut ClassMethodsStore)->mlua::Result<T>)->mlua::Result<T>{
|
||||
let mut cf=lua.app_data_mut::<ClassMethodsStore>().ok_or_else(||mlua::Error::runtime("ClassMethodsStore missing"))?;
|
||||
f(&mut *cf)
|
||||
}
|
||||
|
||||
/// A virtual property pointer definition shorthand.
|
||||
type VirtualPropertyFunctionPointer=fn(&rbx_types::Variant)->Option<rbx_types::Variant>;
|
||||
const fn vpp(property: &'static str, pointer: VirtualPropertyFunctionPointer) -> VirtualProperty {
|
||||
VirtualProperty { property, pointer }
|
||||
const fn vpp(
|
||||
property:&'static str,
|
||||
pointer:VirtualPropertyFunctionPointer,
|
||||
)->VirtualProperty{
|
||||
VirtualProperty{
|
||||
property,
|
||||
pointer,
|
||||
}
|
||||
}
|
||||
struct VirtualProperty{
|
||||
property:&'static str,// Source property name
|
||||
pointer:VirtualPropertyFunctionPointer,
|
||||
}
|
||||
type VPD = phf::Map<
|
||||
&'static str, // Class name
|
||||
phf::Map<
|
||||
&'static str, // Virtual property name
|
||||
VirtualProperty,
|
||||
>,
|
||||
type VPD=phf::Map<&'static str,// Class name
|
||||
phf::Map<&'static str,// Virtual property name
|
||||
VirtualProperty
|
||||
>
|
||||
>;
|
||||
static VIRTUAL_PROPERTY_DATABASE:VPD=phf::phf_map!{
|
||||
"BasePart"=>phf::phf_map!{
|
||||
@ -606,7 +482,7 @@ static VIRTUAL_PROPERTY_DATABASE: VPD = phf::phf_map! {
|
||||
fn find_virtual_property(
|
||||
properties:&HashMap<String,rbx_types::Variant>,
|
||||
class:&rbx_reflection::ClassDescriptor,
|
||||
index: &str,
|
||||
index:&str
|
||||
)->Option<rbx_types::Variant>{
|
||||
//Find virtual property
|
||||
let class_virtual_properties=VIRTUAL_PROPERTY_DATABASE.get(&class.name)?;
|
||||
@ -623,12 +499,10 @@ fn find_virtual_property(
|
||||
// This whole thing is a bad idea and a garbage collection nightmare.
|
||||
// TODO: recreate rbx_dom_weak with my own instance type that owns this data.
|
||||
type CreateUserData=fn(&mlua::Lua)->mlua::Result<mlua::AnyUserData>;
|
||||
type LUD = phf::Map<
|
||||
&'static str, // Class name
|
||||
phf::Map<
|
||||
&'static str, // Value name
|
||||
CreateUserData,
|
||||
>,
|
||||
type LUD=phf::Map<&'static str,// Class name
|
||||
phf::Map<&'static str,// Value name
|
||||
CreateUserData
|
||||
>
|
||||
>;
|
||||
static LAZY_USER_DATA:LUD=phf::phf_map!{
|
||||
"RunService"=>phf::phf_map!{
|
||||
@ -639,52 +513,45 @@ static LAZY_USER_DATA: LUD = phf::phf_map! {
|
||||
};
|
||||
#[derive(Default)]
|
||||
pub struct InstanceValueStore{
|
||||
values: HashMap<Ref, HashMap<&'static str, mlua::AnyUserData>>,
|
||||
values:HashMap<Ref,
|
||||
HashMap<&'static str,
|
||||
mlua::AnyUserData
|
||||
>
|
||||
>,
|
||||
}
|
||||
pub struct InstanceValues<'a>{
|
||||
named_values:&'static phf::Map<&'static str,CreateUserData>,
|
||||
values:&'a mut HashMap<&'static str,mlua::AnyUserData>,
|
||||
}
|
||||
impl InstanceValueStore{
|
||||
pub fn get_or_create_instance_values(
|
||||
&mut self,
|
||||
instance: &rbx_dom_weak::Instance,
|
||||
) -> Option<InstanceValues> {
|
||||
LAZY_USER_DATA
|
||||
.get(instance.class.as_str())
|
||||
.map(|named_values| InstanceValues {
|
||||
pub fn get_or_create_instance_values(&mut self,instance:&rbx_dom_weak::Instance)->Option<InstanceValues>{
|
||||
LAZY_USER_DATA.get(instance.class.as_str())
|
||||
.map(|named_values|
|
||||
InstanceValues{
|
||||
named_values,
|
||||
values: self
|
||||
.values
|
||||
.entry(instance.referent())
|
||||
values:self.values.entry(instance.referent())
|
||||
.or_insert_with(||HashMap::new()),
|
||||
})
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
impl InstanceValues<'_>{
|
||||
pub fn get_or_create_value(
|
||||
&mut self,
|
||||
lua: &mlua::Lua,
|
||||
index: &str,
|
||||
) -> mlua::Result<Option<mlua::AnyUserData>> {
|
||||
pub fn get_or_create_value(&mut self,lua:&mlua::Lua,index:&str)->mlua::Result<Option<mlua::AnyUserData>>{
|
||||
Ok(match self.named_values.get_entry(index){
|
||||
Some((&static_index_str, &function_pointer)) => {
|
||||
Some(match self.values.entry(static_index_str) {
|
||||
Some((&static_index_str,&function_pointer))=>Some(
|
||||
match self.values.entry(static_index_str){
|
||||
Entry::Occupied(entry)=>entry.get().clone(),
|
||||
Entry::Vacant(entry) => entry.insert(function_pointer(lua)?).clone(),
|
||||
})
|
||||
Entry::Vacant(entry)=>entry.insert(
|
||||
function_pointer(lua)?
|
||||
).clone(),
|
||||
}
|
||||
),
|
||||
None=>None,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub fn instance_value_store_mut<T>(
|
||||
lua: &mlua::Lua,
|
||||
mut f: impl FnMut(&mut InstanceValueStore) -> mlua::Result<T>,
|
||||
) -> mlua::Result<T> {
|
||||
let mut cf = lua
|
||||
.app_data_mut::<InstanceValueStore>()
|
||||
.ok_or_else(|| mlua::Error::runtime("InstanceValueStore missing"))?;
|
||||
pub fn instance_value_store_mut<T>(lua:&mlua::Lua,mut f:impl FnMut(&mut InstanceValueStore)->mlua::Result<T>)->mlua::Result<T>{
|
||||
let mut cf=lua.app_data_mut::<InstanceValueStore>().ok_or_else(||mlua::Error::runtime("InstanceValueStore missing"))?;
|
||||
f(&mut *cf)
|
||||
}
|
||||
|
@ -1,30 +1,22 @@
|
||||
macro_rules! type_from_lua_userdata{
|
||||
($asd:ident) => {
|
||||
impl mlua::FromLua for $asd {
|
||||
($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!($asd),
|
||||
other
|
||||
))),
|
||||
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> {
|
||||
($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!($asd),
|
||||
other
|
||||
))),
|
||||
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};
|
||||
|
@ -8,7 +8,7 @@ impl NumberSequence {
|
||||
impl Into<rbx_types::NumberSequence> for NumberSequence{
|
||||
fn into(self)->rbx_types::NumberSequence{
|
||||
rbx_types::NumberSequence{
|
||||
keypoints: Vec::new(),
|
||||
keypoints:Vec::new()
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -16,9 +16,10 @@ impl Into<rbx_types::NumberSequence> for NumberSequence {
|
||||
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)?;
|
||||
|
@ -7,7 +7,10 @@ pub struct Runner {
|
||||
}
|
||||
#[derive(Debug)]
|
||||
pub enum Error{
|
||||
Lua { source: String, error: mlua::Error },
|
||||
Lua{
|
||||
source:String,
|
||||
error:mlua::Error
|
||||
},
|
||||
RustLua(mlua::Error),
|
||||
NoServices,
|
||||
}
|
||||
@ -54,34 +57,19 @@ impl Runner {
|
||||
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> {
|
||||
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)?;
|
||||
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)
|
||||
});
|
||||
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());
|
||||
self.lua.set_app_data::<crate::scheduler::Scheduler>(crate::scheduler::Scheduler::default());
|
||||
Ok(Runnable{
|
||||
lua:self.lua,
|
||||
_lifetime: &std::marker::PhantomData,
|
||||
_lifetime:&std::marker::PhantomData
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -89,53 +77,42 @@ impl Runner {
|
||||
//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<()>,
|
||||
_lifetime:&'a std::marker::PhantomData<()>
|
||||
}
|
||||
impl Runnable<'_>{
|
||||
pub fn drop_context(self)->Runner{
|
||||
self.lua
|
||||
.remove_app_data::<&'static mut rbx_dom_weak::WeakDom>();
|
||||
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 }
|
||||
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)?;
|
||||
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 })?;
|
||||
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()))
|
||||
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,
|
||||
});
|
||||
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}"),
|
||||
@ -147,16 +124,11 @@ impl Runnable<'_> {
|
||||
#[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"))?;
|
||||
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 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)
|
||||
|
@ -73,22 +73,14 @@ impl ScriptSignal {
|
||||
}
|
||||
}
|
||||
pub fn connect(&self,function:mlua::Function)->ScriptConnection{
|
||||
self.connections
|
||||
.functions
|
||||
.borrow_mut()
|
||||
.functions
|
||||
.push(function.clone());
|
||||
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());
|
||||
self.once.functions.borrow_mut().functions.push(function.clone());
|
||||
ScriptConnection{
|
||||
connection:self.once.clone(),
|
||||
function,
|
||||
@ -100,21 +92,18 @@ impl ScriptSignal {
|
||||
}
|
||||
impl ScriptConnection{
|
||||
pub fn position(&self)->Option<usize>{
|
||||
self.connection
|
||||
.functions
|
||||
.borrow()
|
||||
.functions
|
||||
.iter()
|
||||
.position(|function| function == &self.function)
|
||||
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| {
|
||||
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)));
|
||||
);
|
||||
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))
|
||||
@ -125,27 +114,21 @@ 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
|
||||
))),
|
||||
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()));
|
||||
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);
|
||||
this.connection.functions.borrow_mut().functions.remove(index);
|
||||
}
|
||||
Ok(())
|
||||
});
|
||||
@ -157,7 +140,8 @@ fn wait_thread(lua: &mlua::Lua, this: ScriptSignal) -> Result<(), mlua::Error> {
|
||||
}
|
||||
|
||||
// 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)
|
||||
@ -174,8 +158,7 @@ pub fn set_globals(lua: &mlua::Lua, globals: &mlua::Table) -> Result<(), mlua::E
|
||||
wait_env.raw_set("wait_thread",wait_thread)?;
|
||||
|
||||
//construct wait function from Lua code
|
||||
let wait = lua
|
||||
.load(LUA_WAIT)
|
||||
let wait=lua.load(LUA_WAIT)
|
||||
.set_name("wait")
|
||||
.set_environment(wait_env)
|
||||
.call::<mlua::Function>(())?;
|
||||
|
@ -11,9 +11,10 @@ pub fn set_globals(lua: &mlua::Lua, globals: &mlua::Table) -> Result<(), mlua::E
|
||||
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_table.raw_set("new",
|
||||
lua.create_function(|_,(x,y,z):(f32,f32,f32)|
|
||||
Ok(Vector3::new(x,y,z))
|
||||
)?
|
||||
)?;
|
||||
|
||||
globals.set("Vector3",vector3_table)?;
|
||||
@ -56,29 +57,25 @@ impl mlua::UserData for Vector3 {
|
||||
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 {
|
||||
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,
|
||||
))
|
||||
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,
|
||||
))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -36,8 +36,7 @@ impl Scheduler {
|
||||
!self.schedule.is_empty()
|
||||
}
|
||||
pub fn schedule_thread(&mut self,delay:u64,thread:mlua::Thread){
|
||||
self.schedule
|
||||
.entry(self.tick + delay.max(1))
|
||||
self.schedule.entry(self.tick+delay.max(1))
|
||||
.or_insert(Vec::new())
|
||||
.push(thread);
|
||||
}
|
||||
@ -47,13 +46,8 @@ impl 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"))?;
|
||||
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)
|
||||
}
|
||||
|
||||
@ -65,16 +59,14 @@ fn schedule_thread(lua: &mlua::Lua, dt: mlua::Value) -> Result<(), mlua::Error>
|
||||
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 {
|
||||
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"))?,
|
||||
};
|
||||
@ -85,7 +77,8 @@ fn schedule_thread(lua: &mlua::Lua, dt: mlua::Value) -> Result<(), mlua::Error>
|
||||
}
|
||||
|
||||
// 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)
|
||||
@ -102,8 +95,7 @@ pub fn set_globals(lua: &mlua::Lua, globals: &mlua::Table) -> Result<(), mlua::E
|
||||
wait_env.raw_set("schedule_thread",schedule_thread)?;
|
||||
|
||||
//construct wait function from Lua code
|
||||
let wait = lua
|
||||
.load(LUA_WAIT)
|
||||
let wait=lua.load(LUA_WAIT)
|
||||
.set_name("wait")
|
||||
.set_environment(wait_env)
|
||||
.call::<mlua::Function>(())?;
|
||||
|
@ -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,
|
||||
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]
|
||||
#[binrw]
|
||||
#[brw(little)]
|
||||
pub struct State {}
|
||||
#[super::binrw]
|
||||
#[brw(little)]
|
||||
pub struct Instruction {}
|
||||
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)]
|
||||
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>>
|
||||
pub instructions:Vec<TimedPhysicsInstruction>
|
||||
}
|
||||
|
||||
//please remember that strafe ticks are implicit! 33% smaller bot files
|
||||
#[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,
|
||||
}
|
||||
|
||||
pub struct StreamableBot<R:BinReaderExt>{
|
||||
file:crate::file::File<R>,
|
||||
//timeline:timeline::Timeline<SegmentId>,
|
||||
segment_id_to_block_id: Vec<crate::file::BlockId>,
|
||||
segment_map:Vec<SegmentInfo>,
|
||||
}
|
||||
impl<R:BinReaderExt> StreamableBot<R>{
|
||||
pub(crate) fn new(file: crate::file::File<R>) -> Result<Self, Error> {
|
||||
Err(Error::InvalidHeader)
|
||||
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)?;
|
||||
}
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
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,6 +1,6 @@
|
||||
//file format "sniff"
|
||||
|
||||
use binrw::{binrw, io::TakeSeekExt, BinReaderExt};
|
||||
use binrw::{binrw,BinReaderExt,io::TakeSeekExt};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Error{
|
||||
@ -73,6 +73,16 @@ pub struct Header {
|
||||
#[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)]
|
||||
@ -95,18 +105,13 @@ impl<R: BinReaderExt> File<R> {
|
||||
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> {
|
||||
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));
|
||||
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)?;
|
||||
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{
|
||||
|
@ -2,10 +2,10 @@ 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{
|
||||
@ -40,21 +40,21 @@ pub fn read_map<R: BinReaderExt>(input: R) -> Result<map::StreamableMap<R>, Erro
|
||||
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),
|
||||
_=>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),
|
||||
_=>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),
|
||||
_=>Err(Error::UnexpectedFourCC)
|
||||
}
|
||||
}
|
||||
|
||||
@ -63,5 +63,6 @@ mod tests {
|
||||
//use super::*;
|
||||
|
||||
#[test]
|
||||
fn test() {}
|
||||
fn test() {
|
||||
}
|
||||
}
|
||||
|
@ -1,13 +1,13 @@
|
||||
use std::collections::HashMap;
|
||||
use std::io::Read;
|
||||
use std::collections::HashMap;
|
||||
|
||||
use crate::file::BlockId;
|
||||
use crate::newtypes;
|
||||
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{
|
||||
@ -86,6 +86,7 @@ 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)]
|
||||
#[repr(u8)]
|
||||
enum ResourceType{
|
||||
Mesh,
|
||||
Texture,
|
||||
@ -94,21 +95,6 @@ enum ResourceType {
|
||||
//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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct ResourceMap<T>{
|
||||
meshes:HashMap<strafesnet_common::model::MeshId,T>,
|
||||
@ -135,11 +121,6 @@ struct ResourceBlockHeader {
|
||||
resource:ResourceType,
|
||||
id:BlockId,
|
||||
}
|
||||
#[binrw]
|
||||
#[brw(little)]
|
||||
struct ResourceExternalHeader {
|
||||
resource_uuid: ResourceId,
|
||||
}
|
||||
|
||||
#[binrw]
|
||||
#[brw(little)]
|
||||
@ -174,34 +155,23 @@ struct Region {
|
||||
}
|
||||
|
||||
//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> {
|
||||
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())
|
||||
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> {
|
||||
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> {
|
||||
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)?;
|
||||
@ -226,19 +196,12 @@ 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 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();
|
||||
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{
|
||||
@ -247,15 +210,15 @@ impl<R: BinReaderExt> StreamableMap<R> {
|
||||
resource_blocks.meshes.insert(
|
||||
//generate the id from the current length
|
||||
model::MeshId::new(resource_blocks.meshes.len() as u32),
|
||||
resource_block_header.id,
|
||||
resource_block_header.id
|
||||
);
|
||||
}
|
||||
},
|
||||
ResourceType::Texture=>{
|
||||
resource_blocks.textures.insert(
|
||||
model::TextureId::new(resource_blocks.textures.len() as u32),
|
||||
resource_block_header.id,
|
||||
resource_block_header.id
|
||||
);
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
Ok(Self{
|
||||
@ -270,36 +233,23 @@ impl<R: BinReaderExt> StreamableMap<R> {
|
||||
}
|
||||
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));
|
||||
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> {
|
||||
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))?;
|
||||
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))?;
|
||||
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));
|
||||
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
|
||||
@ -310,11 +260,7 @@ impl<R: BinReaderExt> StreamableMap<R> {
|
||||
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))?,
|
||||
);
|
||||
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());
|
||||
@ -322,7 +268,7 @@ impl<R: BinReaderExt> StreamableMap<R> {
|
||||
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{
|
||||
@ -346,10 +292,7 @@ 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),
|
||||
>,
|
||||
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,
|
||||
@ -363,7 +306,10 @@ fn collect_spacial_blocks(
|
||||
models.push((model_id.get(),model));
|
||||
});
|
||||
let id=BlockId::new(block_headers.len() as u32+1);
|
||||
block_headers.push(SpacialBlockHeader { id, extents });
|
||||
block_headers.push(SpacialBlockHeader{
|
||||
id,
|
||||
extents,
|
||||
});
|
||||
let region=Region{
|
||||
model_count,
|
||||
models,
|
||||
@ -374,14 +320,9 @@ fn collect_spacial_blocks(
|
||||
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,
|
||||
)?;
|
||||
}
|
||||
collect_spacial_blocks(block_location,block_headers,sequential_block_data,bvh_node)?;
|
||||
}
|
||||
},
|
||||
strafesnet_common::bvh::RecursiveContent::Leaf(_)=>panic!(),//bvh branches are 20 leaves minimum
|
||||
}
|
||||
}
|
||||
@ -390,30 +331,18 @@ fn collect_spacial_blocks(
|
||||
|
||||
/// 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> {
|
||||
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)| {
|
||||
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 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>());
|
||||
}).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
|
||||
@ -424,12 +353,7 @@ pub fn write_map<W: BinWriterExt>(
|
||||
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,
|
||||
)?;
|
||||
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
|
||||
@ -440,8 +364,7 @@ pub fn write_map<W: BinWriterExt>(
|
||||
});
|
||||
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)?;
|
||||
binrw::BinWrite::write_le(&serializable_mesh,&mut cursor_to_data).map_err(Error::InvalidData)?;
|
||||
block_location.push(cursor_to_data.position());
|
||||
}
|
||||
//textures
|
||||
@ -469,33 +392,26 @@ pub fn write_map<W: BinWriterExt>(
|
||||
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 {
|
||||
//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)?;
|
||||
|
||||
// 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: 0,
|
||||
priming,
|
||||
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)?;
|
||||
|
||||
//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;
|
||||
}
|
||||
|
||||
//write (updated) file header
|
||||
writer.write_le(&file_header).map_err(Error::InvalidData)?;
|
||||
|
@ -1,3 +1,9 @@
|
||||
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,5 +1,5 @@
|
||||
use super::common::flag;
|
||||
use super::integer::{Planar64, Planar64Vec3, Time};
|
||||
use super::integer::{Time,Planar64,Planar64Vec3};
|
||||
|
||||
#[binrw::binrw]
|
||||
#[brw(little)]
|
||||
@ -40,44 +40,36 @@ pub enum ContactingBehaviour {
|
||||
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) => {
|
||||
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)
|
||||
}
|
||||
),
|
||||
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)
|
||||
}
|
||||
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),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -111,12 +103,12 @@ impl From<strafesnet_common::gameplay_attributes::IntersectingWater> for Interse
|
||||
#[binrw::binrw]
|
||||
#[brw(little)]
|
||||
pub struct Accelerator{
|
||||
pub acceleration: Planar64Vec3,
|
||||
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),
|
||||
acceleration:strafesnet_common::integer::vec3::raw_array(self.acceleration)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -134,34 +126,32 @@ pub enum Booster {
|
||||
#[brw(magic=0u8)]
|
||||
Velocity(Planar64Vec3),
|
||||
#[brw(magic=1u8)]
|
||||
Energy {
|
||||
direction: Planar64Vec3,
|
||||
energy: Planar64,
|
||||
},
|
||||
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) => {
|
||||
Booster::Velocity(velocity)=>
|
||||
strafesnet_common::gameplay_attributes::Booster::Velocity(
|
||||
strafesnet_common::integer::vec3::raw_array(velocity),
|
||||
)
|
||||
}
|
||||
Booster::Energy { direction, energy } => {
|
||||
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),
|
||||
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),
|
||||
Booster::Height(height)=>
|
||||
strafesnet_common::gameplay_attributes::Booster::Height(
|
||||
strafesnet_common::integer::Planar64::raw(height)
|
||||
),
|
||||
}
|
||||
}
|
||||
@ -169,21 +159,17 @@ impl Into<strafesnet_common::gameplay_attributes::Booster> for Booster {
|
||||
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 } => {
|
||||
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())
|
||||
}
|
||||
},
|
||||
strafesnet_common::gameplay_attributes::Booster::AirTime(time)=>
|
||||
Booster::AirTime(time.get()),
|
||||
strafesnet_common::gameplay_attributes::Booster::Height(height)=>
|
||||
Booster::Height(height.to_raw()),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -197,24 +183,20 @@ pub enum TrajectoryChoice {
|
||||
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
|
||||
}
|
||||
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
|
||||
}
|
||||
strafesnet_common::gameplay_attributes::TrajectoryChoice::HighArcLongDuration=>
|
||||
TrajectoryChoice::HighArcLongDuration,
|
||||
strafesnet_common::gameplay_attributes::TrajectoryChoice::LowArcShortDuration=>
|
||||
TrajectoryChoice::LowArcShortDuration,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -227,10 +209,7 @@ pub enum SetTrajectory {
|
||||
#[brw(magic=1u8)]
|
||||
Height(Planar64),
|
||||
#[brw(magic=2u8)]
|
||||
DotVelocity {
|
||||
direction: Planar64Vec3,
|
||||
dot: Planar64,
|
||||
},
|
||||
DotVelocity{direction:Planar64Vec3,dot:Planar64},
|
||||
#[brw(magic=3u8)]
|
||||
TargetPointTime{
|
||||
target_point:Planar64Vec3,
|
||||
@ -248,80 +227,68 @@ pub enum SetTrajectory {
|
||||
impl Into<strafesnet_common::gameplay_attributes::SetTrajectory> for SetTrajectory{
|
||||
fn into(self)->strafesnet_common::gameplay_attributes::SetTrajectory{
|
||||
match self{
|
||||
SetTrajectory::AirTime(time) => {
|
||||
SetTrajectory::AirTime(time)=>
|
||||
strafesnet_common::gameplay_attributes::SetTrajectory::AirTime(
|
||||
strafesnet_common::integer::Time::raw(time),
|
||||
)
|
||||
}
|
||||
SetTrajectory::Height(height) => {
|
||||
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::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 } => {
|
||||
},
|
||||
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 {
|
||||
},
|
||||
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) => {
|
||||
SetTrajectory::Velocity(velocity)=>
|
||||
strafesnet_common::gameplay_attributes::SetTrajectory::Velocity(
|
||||
strafesnet_common::integer::vec3::raw_array(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 {
|
||||
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 {
|
||||
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 {
|
||||
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())
|
||||
}
|
||||
strafesnet_common::gameplay_attributes::SetTrajectory::Velocity(velocity)=>
|
||||
SetTrajectory::Velocity(
|
||||
velocity.map(|t|t.to_raw()).to_array()
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -377,7 +344,8 @@ impl Into<strafesnet_common::gameplay_attributes::GeneralAttributes> for General
|
||||
}
|
||||
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)
|
||||
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);
|
||||
@ -411,10 +379,7 @@ impl Into<strafesnet_common::gameplay_attributes::ContactingAttributes> for Cont
|
||||
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,
|
||||
),
|
||||
header:flag(value.contact_behaviour.is_some(),ContactingAttributes::CONTACTING_BEHAVIOUR),
|
||||
contact_behaviour:value.contact_behaviour.map(Into::into),
|
||||
}
|
||||
}
|
||||
@ -430,24 +395,17 @@ pub struct IntersectingAttributes {
|
||||
impl IntersectingAttributes{
|
||||
const INTERSECTING_WATER:u8=1<<0;
|
||||
}
|
||||
impl Into<strafesnet_common::gameplay_attributes::IntersectingAttributes>
|
||||
for IntersectingAttributes
|
||||
{
|
||||
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
|
||||
{
|
||||
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,
|
||||
),
|
||||
header:flag(value.water.is_some(),IntersectingAttributes::INTERSECTING_WATER),
|
||||
water:value.water.map(Into::into),
|
||||
}
|
||||
}
|
||||
@ -512,30 +470,24 @@ pub enum CollisionAttributes {
|
||||
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())
|
||||
}
|
||||
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())
|
||||
}
|
||||
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()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -46,8 +46,7 @@ impl TryInto<strafesnet_common::gameplay_modes::StageElement> for StageElement {
|
||||
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.behaviour().ok_or(StageElementError::InvalidBehaviour)?,
|
||||
self.jump_limit,
|
||||
))
|
||||
}
|
||||
@ -62,7 +61,8 @@ impl From<strafesnet_common::gameplay_modes::StageElement> for StageElement {
|
||||
strafesnet_common::gameplay_modes::StageElementBehaviour::Check=>4,
|
||||
strafesnet_common::gameplay_modes::StageElementBehaviour::Checkpoint=>5,
|
||||
};
|
||||
let header = behaviour
|
||||
let header=
|
||||
behaviour
|
||||
|flag(value.jump_limit().is_some(),StageElement::JUMP_LIMIT)
|
||||
|flag(value.force(),StageElement::FORCE);
|
||||
Self{
|
||||
@ -92,17 +92,11 @@ impl Into<strafesnet_common::gameplay_modes::Stage> for Stage {
|
||||
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)| {
|
||||
(
|
||||
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()
|
||||
)).collect(),
|
||||
self.unordered_checkpoints.into_iter()
|
||||
.map(strafesnet_common::model::ModelId::new)
|
||||
.collect(),
|
||||
)
|
||||
@ -118,12 +112,10 @@ impl From<strafesnet_common::gameplay_modes::Stage> for Stage {
|
||||
spawn,
|
||||
ordered_checkpoints_count,
|
||||
unordered_checkpoints_count,
|
||||
ordered_checkpoints: ordered_checkpoints
|
||||
.into_iter()
|
||||
ordered_checkpoints:ordered_checkpoints.into_iter()
|
||||
.map(|(checkpoint_id,model_id)|(checkpoint_id.get(),model_id.get()))
|
||||
.collect(),
|
||||
unordered_checkpoints: unordered_checkpoints
|
||||
.into_iter()
|
||||
unordered_checkpoints:unordered_checkpoints.into_iter()
|
||||
.map(|model_id|model_id.get())
|
||||
.collect(),
|
||||
}
|
||||
@ -193,26 +185,13 @@ impl TryInto<strafesnet_common::gameplay_modes::Mode> for Mode {
|
||||
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.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)?,
|
||||
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)?,
|
||||
))
|
||||
}
|
||||
}
|
||||
@ -227,13 +206,13 @@ impl From<strafesnet_common::gameplay_modes::Mode> for Mode {
|
||||
},
|
||||
style:style.into(),
|
||||
start:start.get(),
|
||||
zones: zones
|
||||
.into_iter()
|
||||
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()
|
||||
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,5 +1,5 @@
|
||||
use super::common::flag;
|
||||
use super::integer::{Planar64, Planar64Vec3, Ratio64, Time};
|
||||
use super::integer::{Time,Ratio64,Planar64,Planar64Vec3};
|
||||
|
||||
pub type Controls=u32;
|
||||
#[derive(Debug)]
|
||||
@ -60,25 +60,11 @@ impl TryInto<strafesnet_common::gameplay_style::StyleModifiers> for StyleModifie
|
||||
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)?,
|
||||
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)?,
|
||||
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),
|
||||
@ -91,7 +77,8 @@ impl TryInto<strafesnet_common::gameplay_style::StyleModifiers> for StyleModifie
|
||||
}
|
||||
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)
|
||||
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)
|
||||
@ -126,12 +113,8 @@ impl Into<strafesnet_common::gameplay_style::JumpCalculation> for JumpCalculatio
|
||||
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
|
||||
}
|
||||
JumpCalculation::BoostThenJump=>strafesnet_common::gameplay_style::JumpCalculation::BoostThenJump,
|
||||
JumpCalculation::JumpThenBoost=>strafesnet_common::gameplay_style::JumpCalculation::JumpThenBoost,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -139,12 +122,8 @@ impl From<strafesnet_common::gameplay_style::JumpCalculation> for JumpCalculatio
|
||||
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
|
||||
}
|
||||
strafesnet_common::gameplay_style::JumpCalculation::BoostThenJump=>JumpCalculation::BoostThenJump,
|
||||
strafesnet_common::gameplay_style::JumpCalculation::JumpThenBoost=>JumpCalculation::JumpThenBoost,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -158,36 +137,20 @@ pub enum JumpImpulse {
|
||||
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),
|
||||
),
|
||||
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())
|
||||
}
|
||||
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()),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -201,22 +164,11 @@ pub struct ControlsActivation {
|
||||
}
|
||||
impl TryInto<strafesnet_common::gameplay_style::ControlsActivation> for ControlsActivation{
|
||||
type Error=ControlsError;
|
||||
fn try_into(
|
||||
self,
|
||||
) -> Result<strafesnet_common::gameplay_style::ControlsActivation, Self::Error> {
|
||||
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)?,
|
||||
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)?,
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -258,27 +210,16 @@ impl TryInto<strafesnet_common::gameplay_style::StrafeSettings> for StrafeSettin
|
||||
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)?,
|
||||
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)?,
|
||||
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,
|
||||
);
|
||||
let header=flag(value.air_accel_limit.is_some(),StrafeSettings::AIR_ACCEL_LIMIT);
|
||||
Self{
|
||||
header,
|
||||
enable:value.enable.into(),
|
||||
@ -297,7 +238,7 @@ pub struct PropulsionSettings {
|
||||
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),
|
||||
magnitude:strafesnet_common::integer::Planar64::raw(self.magnitude)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -321,18 +262,10 @@ impl JumpSettings {
|
||||
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),
|
||||
)),
|
||||
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,
|
||||
}
|
||||
}
|
||||
@ -357,12 +290,8 @@ 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)?,
|
||||
impulse:self.impulse().ok_or(JumpSettingsError::InvalidImpulseDiscriminant)?,
|
||||
calculation:self.calculation().ok_or(JumpSettingsError::InvalidCalculationDiscriminant)?,
|
||||
limit_minimum:self.limit_minimum(),
|
||||
})
|
||||
}
|
||||
@ -371,15 +300,9 @@ 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)
|
||||
}
|
||||
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,
|
||||
@ -387,8 +310,13 @@ impl From<strafesnet_common::gameplay_style::JumpSettings> for JumpSettings {
|
||||
strafesnet_common::gameplay_style::JumpCalculation::BoostThenJump=>2,
|
||||
};
|
||||
let header=
|
||||
impulse_header | (calculation_header << 2) | ((value.limit_minimum as u8) << 4);
|
||||
Self { header, impulse }
|
||||
impulse_header
|
||||
|(calculation_header<<2)
|
||||
|((value.limit_minimum as u8)<<4);
|
||||
Self{
|
||||
header,
|
||||
impulse,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -38,6 +38,23 @@ 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;
|
||||
|
@ -1,7 +1,9 @@
|
||||
pub mod aabb;
|
||||
mod common;
|
||||
pub mod gameplay_attributes;
|
||||
pub mod aabb;
|
||||
pub mod model;
|
||||
pub mod mouse;
|
||||
pub mod integer;
|
||||
pub mod physics;
|
||||
pub mod gameplay_modes;
|
||||
pub mod gameplay_style;
|
||||
pub mod integer;
|
||||
pub mod model;
|
||||
pub mod gameplay_attributes;
|
@ -1,7 +1,7 @@
|
||||
use super::common::flag;
|
||||
use strafesnet_common::model::PolygonIter;
|
||||
|
||||
use super::integer::{Planar64Affine3, Planar64Vec3};
|
||||
use super::integer::{Planar64Vec3,Planar64Affine3};
|
||||
|
||||
pub type TextureCoordinate=[f32;2];
|
||||
pub type Color4=[f32;4];
|
||||
@ -43,16 +43,13 @@ impl From<strafesnet_common::model::PolygonGroup> for PolygonGroup {
|
||||
fn from(value:strafesnet_common::model::PolygonGroup)->Self{
|
||||
match value{
|
||||
strafesnet_common::model::PolygonGroup::PolygonList(polygon_list)=>{
|
||||
let polys: Vec<Polygon> = polygon_list
|
||||
.polys()
|
||||
.map(|poly| {
|
||||
let polys:Vec<Polygon>=polygon_list.polys().map(|poly|{
|
||||
let vertices:Vec<u32>=poly.iter().map(|vert|vert.get()).collect();
|
||||
Polygon{
|
||||
count:vertices.len() as u32,
|
||||
vertices,
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
}).collect();
|
||||
Self{
|
||||
count:polys.len() as u32,
|
||||
polys,
|
||||
@ -100,11 +97,7 @@ impl From<strafesnet_common::model::IndexedGraphicsGroup> for IndexedGraphicsGro
|
||||
Self{
|
||||
count:value.groups.len() as u32,
|
||||
render:value.render.get(),
|
||||
groups: value
|
||||
.groups
|
||||
.into_iter()
|
||||
.map(|group_id| group_id.get())
|
||||
.collect(),
|
||||
groups:value.groups.into_iter().map(|group_id|group_id.get()).collect(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -119,11 +112,7 @@ impl From<strafesnet_common::model::IndexedPhysicsGroup> for IndexedPhysicsGroup
|
||||
fn from(value:strafesnet_common::model::IndexedPhysicsGroup)->Self{
|
||||
Self{
|
||||
count:value.groups.len() as u32,
|
||||
groups: value
|
||||
.groups
|
||||
.into_iter()
|
||||
.map(|group_id| group_id.get())
|
||||
.collect(),
|
||||
groups:value.groups.into_iter().map(|group_id|group_id.get()).collect(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -164,79 +153,36 @@ pub struct Mesh {
|
||||
impl Into<strafesnet_common::model::Mesh> for Mesh{
|
||||
fn into(self)->strafesnet_common::model::Mesh{
|
||||
strafesnet_common::model::Mesh{
|
||||
unique_pos: self
|
||||
.unique_pos
|
||||
.into_iter()
|
||||
.map(strafesnet_common::integer::vec3::raw_array)
|
||||
.collect(),
|
||||
unique_normal: self
|
||||
.unique_normal
|
||||
.into_iter()
|
||||
.map(strafesnet_common::integer::vec3::raw_array)
|
||||
.collect(),
|
||||
unique_tex: self
|
||||
.unique_tex
|
||||
.into_iter()
|
||||
.map(strafesnet_common::model::TextureCoordinate::from_array)
|
||||
.collect(),
|
||||
unique_color: self
|
||||
.unique_color
|
||||
.into_iter()
|
||||
.map(strafesnet_common::model::Color4::from_array)
|
||||
.collect(),
|
||||
unique_vertices: self
|
||||
.unique_vertices
|
||||
.into_iter()
|
||||
.map(|vert| strafesnet_common::model::IndexedVertex {
|
||||
unique_pos:self.unique_pos.into_iter().map(strafesnet_common::integer::vec3::raw_array).collect(),
|
||||
unique_normal:self.unique_normal.into_iter().map(strafesnet_common::integer::vec3::raw_array).collect(),
|
||||
unique_tex:self.unique_tex.into_iter().map(strafesnet_common::model::TextureCoordinate::from_array).collect(),
|
||||
unique_color:self.unique_color.into_iter().map(strafesnet_common::model::Color4::from_array).collect(),
|
||||
unique_vertices:self.unique_vertices.into_iter().map(|vert|strafesnet_common::model::IndexedVertex{
|
||||
pos:strafesnet_common::model::PositionId::new(vert.pos),
|
||||
tex:strafesnet_common::model::TextureCoordinateId::new(vert.tex),
|
||||
normal:strafesnet_common::model::NormalId::new(vert.normal),
|
||||
color:strafesnet_common::model::ColorId::new(vert.color),
|
||||
})
|
||||
.collect(),
|
||||
polygon_groups: self
|
||||
.polygon_groups
|
||||
.into_iter()
|
||||
.map(|group| {
|
||||
}).collect(),
|
||||
polygon_groups:self.polygon_groups.into_iter().map(|group|
|
||||
strafesnet_common::model::PolygonGroup::PolygonList(
|
||||
strafesnet_common::model::PolygonList::new(
|
||||
group
|
||||
.polys
|
||||
.into_iter()
|
||||
.map(|vert| {
|
||||
vert.vertices
|
||||
.into_iter()
|
||||
.map(strafesnet_common::model::VertexId::new)
|
||||
.collect()
|
||||
})
|
||||
.collect(),
|
||||
),
|
||||
group.polys.into_iter().map(|vert|
|
||||
vert.vertices.into_iter().map(strafesnet_common::model::VertexId::new).collect()
|
||||
).collect()
|
||||
)
|
||||
})
|
||||
.collect(),
|
||||
graphics_groups: self
|
||||
.graphics_groups
|
||||
.into_iter()
|
||||
.map(|group| strafesnet_common::model::IndexedGraphicsGroup {
|
||||
)
|
||||
).collect(),
|
||||
graphics_groups:self.graphics_groups.into_iter().map(|group|
|
||||
strafesnet_common::model::IndexedGraphicsGroup{
|
||||
render:strafesnet_common::model::RenderConfigId::new(group.render),
|
||||
groups: group
|
||||
.groups
|
||||
.into_iter()
|
||||
.map(strafesnet_common::model::PolygonGroupId::new)
|
||||
.collect(),
|
||||
})
|
||||
.collect(),
|
||||
physics_groups: self
|
||||
.physics_groups
|
||||
.into_iter()
|
||||
.map(|group| strafesnet_common::model::IndexedPhysicsGroup {
|
||||
groups: group
|
||||
.groups
|
||||
.into_iter()
|
||||
.map(strafesnet_common::model::PolygonGroupId::new)
|
||||
.collect(),
|
||||
})
|
||||
.collect(),
|
||||
groups:group.groups.into_iter().map(strafesnet_common::model::PolygonGroupId::new).collect(),
|
||||
}
|
||||
).collect(),
|
||||
physics_groups:self.physics_groups.into_iter().map(|group|
|
||||
strafesnet_common::model::IndexedPhysicsGroup{
|
||||
groups:group.groups.into_iter().map(strafesnet_common::model::PolygonGroupId::new).collect(),
|
||||
}
|
||||
).collect(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -253,30 +199,30 @@ impl From<strafesnet_common::model::Mesh> for Mesh {
|
||||
graphics_groups:value.graphics_groups.len() as u32,
|
||||
physics_groups:value.physics_groups.len() as u32,
|
||||
},
|
||||
unique_pos: value
|
||||
.unique_pos
|
||||
.into_iter()
|
||||
unique_pos:value.unique_pos.into_iter()
|
||||
.map(|pos|pos.map(|t|t.to_raw()).to_array())
|
||||
.collect(),
|
||||
unique_normal: value
|
||||
.unique_normal
|
||||
.into_iter()
|
||||
unique_normal:value.unique_normal.into_iter()
|
||||
.map(|normal|normal.map(|t|t.to_raw()).to_array())
|
||||
.collect(),
|
||||
unique_tex: value
|
||||
.unique_tex
|
||||
.into_iter()
|
||||
unique_tex:value.unique_tex.into_iter()
|
||||
.map(|tex|tex.to_array())
|
||||
.collect(),
|
||||
unique_color: value
|
||||
.unique_color
|
||||
.into_iter()
|
||||
unique_color:value.unique_color.into_iter()
|
||||
.map(|color|color.to_array())
|
||||
.collect(),
|
||||
unique_vertices: value.unique_vertices.into_iter().map(Into::into).collect(),
|
||||
polygon_groups: value.polygon_groups.into_iter().map(Into::into).collect(),
|
||||
graphics_groups: value.graphics_groups.into_iter().map(Into::into).collect(),
|
||||
physics_groups: value.physics_groups.into_iter().map(Into::into).collect(),
|
||||
unique_vertices:value.unique_vertices.into_iter()
|
||||
.map(Into::into)
|
||||
.collect(),
|
||||
polygon_groups:value.polygon_groups.into_iter()
|
||||
.map(Into::into)
|
||||
.collect(),
|
||||
graphics_groups:value.graphics_groups.into_iter()
|
||||
.map(Into::into)
|
||||
.collect(),
|
||||
physics_groups:value.physics_groups.into_iter()
|
||||
.map(Into::into)
|
||||
.collect(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -294,43 +240,31 @@ impl Into<strafesnet_common::model::Model> for Model {
|
||||
let [_0,_1,_2,_3,_4,_5,_6,_7,_8,_9,_a,_b]=self.transform;
|
||||
strafesnet_common::model::Model{
|
||||
mesh:strafesnet_common::model::MeshId::new(self.mesh),
|
||||
attributes: strafesnet_common::gameplay_attributes::CollisionAttributesId::new(
|
||||
self.attributes,
|
||||
),
|
||||
attributes:strafesnet_common::gameplay_attributes::CollisionAttributesId::new(self.attributes),
|
||||
color:strafesnet_common::model::Color4::from_array(self.color),
|
||||
transform:strafesnet_common::integer::Planar64Affine3::new(
|
||||
strafesnet_common::integer::Planar64Mat3::from_cols([
|
||||
strafesnet_common::integer::vec3::raw_xyz(_0,_1,_2),
|
||||
strafesnet_common::integer::vec3::raw_xyz(_3,_4,_5),
|
||||
strafesnet_common::integer::vec3::raw_xyz(_6, _7, _8),
|
||||
strafesnet_common::integer::vec3::raw_xyz(_6,_7,_8)
|
||||
]),
|
||||
strafesnet_common::integer::vec3::raw_xyz(_9, _a, _b),
|
||||
strafesnet_common::integer::vec3::raw_xyz(_9,_a,_b)
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl From<strafesnet_common::model::Model> for Model{
|
||||
fn from(value:strafesnet_common::model::Model)->Self{
|
||||
let ([_0, _1, _2], [_3, _4, _5], [_6, _7, _8], [_9, _a, _b]) = (
|
||||
value
|
||||
.transform
|
||||
.matrix3
|
||||
.x_axis
|
||||
.map(|t| t.to_raw())
|
||||
.to_array(),
|
||||
value
|
||||
.transform
|
||||
.matrix3
|
||||
.y_axis
|
||||
.map(|t| t.to_raw())
|
||||
.to_array(),
|
||||
value
|
||||
.transform
|
||||
.matrix3
|
||||
.z_axis
|
||||
.map(|t| t.to_raw())
|
||||
.to_array(),
|
||||
value.transform.translation.map(|t| t.to_raw()).to_array(),
|
||||
let (
|
||||
[_0,_1,_2],
|
||||
[_3,_4,_5],
|
||||
[_6,_7,_8],
|
||||
[_9,_a,_b]
|
||||
)=(
|
||||
value.transform.matrix3.x_axis.map(|t|t.to_raw()).to_array(),
|
||||
value.transform.matrix3.y_axis.map(|t|t.to_raw()).to_array(),
|
||||
value.transform.matrix3.z_axis.map(|t|t.to_raw()).to_array(),
|
||||
value.transform.translation.map(|t|t.to_raw()).to_array()
|
||||
);
|
||||
Self{
|
||||
mesh:value.mesh.get(),
|
||||
|
25
lib/snf/src/newtypes/mouse.rs
Normal file
25
lib/snf/src/newtypes/mouse.rs
Normal file
@ -0,0 +1,25 @@
|
||||
use super::integer::Time;
|
||||
|
||||
#[binrw::binrw]
|
||||
#[brw(little)]
|
||||
pub struct MouseState{
|
||||
pub pos:[i32;2],
|
||||
pub time:Time,
|
||||
}
|
||||
|
||||
impl<T> Into<strafesnet_common::mouse::MouseState<T>> for MouseState{
|
||||
fn into(self)->strafesnet_common::mouse::MouseState<T>{
|
||||
strafesnet_common::mouse::MouseState{
|
||||
pos:self.pos.into(),
|
||||
time:strafesnet_common::integer::Time::raw(self.time),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl<T> From<strafesnet_common::mouse::MouseState<T>> for MouseState{
|
||||
fn from(value:strafesnet_common::mouse::MouseState<T>)->Self{
|
||||
Self{
|
||||
pos:value.pos.to_array(),
|
||||
time:value.time.get(),
|
||||
}
|
||||
}
|
||||
}
|
156
lib/snf/src/newtypes/physics.rs
Normal file
156
lib/snf/src/newtypes/physics.rs
Normal file
@ -0,0 +1,156 @@
|
||||
use super::integer::Time;
|
||||
use super::common::{bool_from_u8,bool_into_u8};
|
||||
|
||||
type TimedPhysicsInstruction=strafesnet_common::instruction::TimedInstruction<strafesnet_common::physics::Instruction,strafesnet_common::physics::TimeInner>;
|
||||
|
||||
#[binrw::binrw]
|
||||
#[brw(little)]
|
||||
pub struct TimedInstruction{
|
||||
pub time:Time,
|
||||
pub instruction:Instruction,
|
||||
}
|
||||
|
||||
impl TryInto<TimedPhysicsInstruction> for TimedInstruction{
|
||||
type Error=super::integer::RatioError;
|
||||
fn try_into(self)->Result<TimedPhysicsInstruction,Self::Error>{
|
||||
Ok(strafesnet_common::instruction::TimedInstruction{
|
||||
time:strafesnet_common::integer::Time::raw(self.time),
|
||||
instruction:self.instruction.try_into()?,
|
||||
})
|
||||
}
|
||||
}
|
||||
impl TryFrom<TimedPhysicsInstruction> for TimedInstruction{
|
||||
type Error=super::physics::InstructionConvert;
|
||||
fn try_from(value:TimedPhysicsInstruction)->Result<Self,Self::Error>{
|
||||
Ok(Self{
|
||||
time:value.time.get(),
|
||||
instruction:value.instruction.try_into()?,
|
||||
})
|
||||
}
|
||||
}
|
||||
#[binrw::binrw]
|
||||
#[brw(little)]
|
||||
pub enum Instruction{
|
||||
#[brw(magic=0u8)]
|
||||
ReplaceMouse{
|
||||
m0:super::mouse::MouseState,
|
||||
m1:super::mouse::MouseState
|
||||
},
|
||||
#[brw(magic=1u8)]
|
||||
SetNextMouse(super::mouse::MouseState),
|
||||
#[brw(magic=2u8)]
|
||||
SetMoveRight(
|
||||
#[br(map=bool_from_u8)]
|
||||
#[bw(map=bool_into_u8)]
|
||||
bool),
|
||||
#[brw(magic=3u8)]
|
||||
SetMoveUp(
|
||||
#[br(map=bool_from_u8)]
|
||||
#[bw(map=bool_into_u8)]
|
||||
bool),
|
||||
#[brw(magic=4u8)]
|
||||
SetMoveBack(
|
||||
#[br(map=bool_from_u8)]
|
||||
#[bw(map=bool_into_u8)]
|
||||
bool),
|
||||
#[brw(magic=5u8)]
|
||||
SetMoveLeft(
|
||||
#[br(map=bool_from_u8)]
|
||||
#[bw(map=bool_into_u8)]
|
||||
bool),
|
||||
#[brw(magic=6u8)]
|
||||
SetMoveDown(
|
||||
#[br(map=bool_from_u8)]
|
||||
#[bw(map=bool_into_u8)]
|
||||
bool),
|
||||
#[brw(magic=7u8)]
|
||||
SetMoveForward(
|
||||
#[br(map=bool_from_u8)]
|
||||
#[bw(map=bool_into_u8)]
|
||||
bool),
|
||||
#[brw(magic=8u8)]
|
||||
SetJump(
|
||||
#[br(map=bool_from_u8)]
|
||||
#[bw(map=bool_into_u8)]
|
||||
bool),
|
||||
#[brw(magic=9u8)]
|
||||
SetZoom(
|
||||
#[br(map=bool_from_u8)]
|
||||
#[bw(map=bool_into_u8)]
|
||||
bool),
|
||||
#[brw(magic=10u8)]
|
||||
Reset,
|
||||
#[brw(magic=11u8)]
|
||||
Restart(super::gameplay_modes::ModeId),
|
||||
#[brw(magic=12u8)]
|
||||
Spawn(super::gameplay_modes::ModeId,super::gameplay_modes::StageId),
|
||||
#[brw(magic=13u8)]
|
||||
PracticeFly,
|
||||
#[brw(magic=14u8)]
|
||||
SetSensitivity(super::integer::Ratio64Vec2),
|
||||
#[brw(magic=255u8)]
|
||||
Idle,
|
||||
}
|
||||
#[derive(Debug)]
|
||||
pub enum InstructionConvert{
|
||||
/// This is an instruction that can be dropped when serializing
|
||||
DropInstruction,
|
||||
}
|
||||
impl std::fmt::Display for InstructionConvert{
|
||||
fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{
|
||||
write!(f,"{self:?}")
|
||||
}
|
||||
}
|
||||
impl std::error::Error for InstructionConvert{}
|
||||
impl TryInto<strafesnet_common::physics::Instruction> for Instruction{
|
||||
type Error=super::integer::RatioError;
|
||||
fn try_into(self)->Result<strafesnet_common::physics::Instruction,Self::Error>{
|
||||
Ok(match self{
|
||||
Instruction::ReplaceMouse{m0,m1}=>strafesnet_common::physics::Instruction::Mouse(strafesnet_common::physics::MouseInstruction::ReplaceMouse{m0:m0.into(),m1:m1.into()}),
|
||||
Instruction::SetNextMouse(m)=>strafesnet_common::physics::Instruction::Mouse(strafesnet_common::physics::MouseInstruction::SetNextMouse(m.into())),
|
||||
Instruction::SetMoveRight(state)=>strafesnet_common::physics::Instruction::SetControl(strafesnet_common::physics::SetControlInstruction::SetMoveRight(state.into())),
|
||||
Instruction::SetMoveUp(state)=>strafesnet_common::physics::Instruction::SetControl(strafesnet_common::physics::SetControlInstruction::SetMoveUp(state.into())),
|
||||
Instruction::SetMoveBack(state)=>strafesnet_common::physics::Instruction::SetControl(strafesnet_common::physics::SetControlInstruction::SetMoveBack(state.into())),
|
||||
Instruction::SetMoveLeft(state)=>strafesnet_common::physics::Instruction::SetControl(strafesnet_common::physics::SetControlInstruction::SetMoveLeft(state.into())),
|
||||
Instruction::SetMoveDown(state)=>strafesnet_common::physics::Instruction::SetControl(strafesnet_common::physics::SetControlInstruction::SetMoveDown(state.into())),
|
||||
Instruction::SetMoveForward(state)=>strafesnet_common::physics::Instruction::SetControl(strafesnet_common::physics::SetControlInstruction::SetMoveForward(state.into())),
|
||||
Instruction::SetJump(state)=>strafesnet_common::physics::Instruction::SetControl(strafesnet_common::physics::SetControlInstruction::SetJump(state.into())),
|
||||
Instruction::SetZoom(state)=>strafesnet_common::physics::Instruction::SetControl(strafesnet_common::physics::SetControlInstruction::SetZoom(state.into())),
|
||||
Instruction::Reset=>strafesnet_common::physics::Instruction::Mode(strafesnet_common::physics::ModeInstruction::Reset),
|
||||
Instruction::Restart(mode_id)=>strafesnet_common::physics::Instruction::Mode(strafesnet_common::physics::ModeInstruction::Restart(strafesnet_common::gameplay_modes::ModeId::new(mode_id))),
|
||||
Instruction::Spawn(mode_id,stage_id)=>strafesnet_common::physics::Instruction::Mode(strafesnet_common::physics::ModeInstruction::Spawn(
|
||||
strafesnet_common::gameplay_modes::ModeId::new(mode_id),
|
||||
strafesnet_common::gameplay_modes::StageId::new(stage_id),
|
||||
)),
|
||||
Instruction::PracticeFly=>strafesnet_common::physics::Instruction::Misc(strafesnet_common::physics::MiscInstruction::PracticeFly),
|
||||
Instruction::SetSensitivity(sensitivity)=>strafesnet_common::physics::Instruction::Misc(strafesnet_common::physics::MiscInstruction::SetSensitivity(sensitivity.try_into()?)),
|
||||
Instruction::Idle=>strafesnet_common::physics::Instruction::Idle,
|
||||
})
|
||||
}
|
||||
}
|
||||
impl TryFrom<strafesnet_common::physics::Instruction> for Instruction{
|
||||
type Error=InstructionConvert;
|
||||
fn try_from(value:strafesnet_common::physics::Instruction)->Result<Self,Self::Error>{
|
||||
match value{
|
||||
strafesnet_common::physics::Instruction::Mouse(strafesnet_common::physics::MouseInstruction::ReplaceMouse{m0,m1})=>Ok(Instruction::ReplaceMouse{m0:m0.into(),m1:m1.into()}),
|
||||
strafesnet_common::physics::Instruction::Mouse(strafesnet_common::physics::MouseInstruction::SetNextMouse(m))=>Ok(Instruction::SetNextMouse(m.into())),
|
||||
strafesnet_common::physics::Instruction::SetControl(strafesnet_common::physics::SetControlInstruction::SetMoveRight(state))=>Ok(Instruction::SetMoveRight(state.into())),
|
||||
strafesnet_common::physics::Instruction::SetControl(strafesnet_common::physics::SetControlInstruction::SetMoveUp(state))=>Ok(Instruction::SetMoveUp(state.into())),
|
||||
strafesnet_common::physics::Instruction::SetControl(strafesnet_common::physics::SetControlInstruction::SetMoveBack(state))=>Ok(Instruction::SetMoveBack(state.into())),
|
||||
strafesnet_common::physics::Instruction::SetControl(strafesnet_common::physics::SetControlInstruction::SetMoveLeft(state))=>Ok(Instruction::SetMoveLeft(state.into())),
|
||||
strafesnet_common::physics::Instruction::SetControl(strafesnet_common::physics::SetControlInstruction::SetMoveDown(state))=>Ok(Instruction::SetMoveDown(state.into())),
|
||||
strafesnet_common::physics::Instruction::SetControl(strafesnet_common::physics::SetControlInstruction::SetMoveForward(state))=>Ok(Instruction::SetMoveForward(state.into())),
|
||||
strafesnet_common::physics::Instruction::SetControl(strafesnet_common::physics::SetControlInstruction::SetJump(state))=>Ok(Instruction::SetJump(state.into())),
|
||||
strafesnet_common::physics::Instruction::SetControl(strafesnet_common::physics::SetControlInstruction::SetZoom(state))=>Ok(Instruction::SetZoom(state.into())),
|
||||
strafesnet_common::physics::Instruction::Mode(strafesnet_common::physics::ModeInstruction::Reset)=>Ok(Instruction::Reset),
|
||||
strafesnet_common::physics::Instruction::Mode(strafesnet_common::physics::ModeInstruction::Restart(mode_id))=>Ok(Instruction::Restart(mode_id.get())),
|
||||
strafesnet_common::physics::Instruction::Mode(strafesnet_common::physics::ModeInstruction::Spawn(mode_id,stage_id))=>Ok(Instruction::Spawn(
|
||||
mode_id.get(),
|
||||
stage_id.get(),
|
||||
)),
|
||||
strafesnet_common::physics::Instruction::Misc(strafesnet_common::physics::MiscInstruction::PracticeFly)=>Ok(Instruction::PracticeFly),
|
||||
strafesnet_common::physics::Instruction::Misc(strafesnet_common::physics::MiscInstruction::SetSensitivity(sensitivity))=>Ok(Instruction::SetSensitivity(sensitivity.into())),
|
||||
strafesnet_common::physics::Instruction::Idle=>Ok(Instruction::Idle),
|
||||
}
|
||||
}
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "strafe-client"
|
||||
version = "0.10.5"
|
||||
version = "0.11.0"
|
||||
edition = "2021"
|
||||
repository = "https://git.itzana.me/StrafesNET/strafe-project"
|
||||
license = "Custom"
|
||||
@ -9,24 +9,24 @@ authors = ["Rhys Lloyd <krakow20@gmail.com>"]
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
[features]
|
||||
user-install=[] # as opposed to portable install
|
||||
default = ["snf"]
|
||||
snf = ["dep:strafesnet_snf"]
|
||||
source = ["dep:strafesnet_deferred_loader", "dep:strafesnet_bsp_loader"]
|
||||
roblox = ["dep:strafesnet_deferred_loader", "dep:strafesnet_rbx_loader"]
|
||||
|
||||
[dependencies]
|
||||
arrayvec = "0.7.6"
|
||||
bytemuck = { version = "1.13.1", features = ["derive"] }
|
||||
configparser = "3.0.2"
|
||||
ddsfile = "0.5.1"
|
||||
glam = "0.29.0"
|
||||
id = { version = "0.1.0", registry = "strafesnet" }
|
||||
parking_lot = "0.12.1"
|
||||
pollster = "0.4.0"
|
||||
strafesnet_bsp_loader = { path = "../lib/bsp_loader", registry = "strafesnet", optional = true }
|
||||
strafesnet_common = { path = "../lib/common", registry = "strafesnet" }
|
||||
strafesnet_deferred_loader = { path = "../lib/deferred_loader", features = ["legacy"], registry = "strafesnet", optional = true }
|
||||
strafesnet_graphics = { path = "../engine/graphics", registry = "strafesnet" }
|
||||
strafesnet_physics = { path = "../engine/physics", registry = "strafesnet" }
|
||||
strafesnet_rbx_loader = { path = "../lib/rbx_loader", registry = "strafesnet", optional = true }
|
||||
strafesnet_session = { path = "../engine/session", registry = "strafesnet" }
|
||||
strafesnet_settings = { path = "../engine/settings", registry = "strafesnet" }
|
||||
strafesnet_snf = { path = "../lib/snf", registry = "strafesnet", optional = true }
|
||||
wgpu = "23.0.1"
|
||||
wgpu = "24.0.0"
|
||||
winit = "0.30.7"
|
||||
|
68
strafe-client/src/app.rs
Normal file
68
strafe-client/src/app.rs
Normal file
@ -0,0 +1,68 @@
|
||||
use crate::window::Instruction;
|
||||
use strafesnet_common::integer;
|
||||
use strafesnet_common::instruction::TimedInstruction;
|
||||
use strafesnet_common::session::TimeInner as SessionTimeInner;
|
||||
|
||||
pub struct App<'a>{
|
||||
root_time:std::time::Instant,
|
||||
window_thread:crate::compat_worker::QNWorker<'a,TimedInstruction<Instruction,SessionTimeInner>>,
|
||||
}
|
||||
impl<'a> App<'a>{
|
||||
pub fn new(
|
||||
root_time:std::time::Instant,
|
||||
window_thread:crate::compat_worker::QNWorker<'a,TimedInstruction<Instruction,SessionTimeInner>>,
|
||||
)->App<'a>{
|
||||
Self{
|
||||
root_time,
|
||||
window_thread,
|
||||
}
|
||||
}
|
||||
fn send_timed_instruction(&mut self,instruction:Instruction){
|
||||
let time=integer::Time::from_nanos(self.root_time.elapsed().as_nanos() as i64);
|
||||
self.window_thread.send(TimedInstruction{time,instruction}).unwrap();
|
||||
}
|
||||
}
|
||||
impl winit::application::ApplicationHandler for App<'_>{
|
||||
fn resumed(&mut self,_event_loop:&winit::event_loop::ActiveEventLoop){
|
||||
//
|
||||
}
|
||||
|
||||
fn window_event(
|
||||
&mut self,
|
||||
event_loop:&winit::event_loop::ActiveEventLoop,
|
||||
_window_id:winit::window::WindowId,
|
||||
event:winit::event::WindowEvent,
|
||||
){
|
||||
match event{
|
||||
winit::event::WindowEvent::KeyboardInput{
|
||||
event:winit::event::KeyEvent{
|
||||
logical_key:winit::keyboard::Key::Named(winit::keyboard::NamedKey::Escape),
|
||||
state:winit::event::ElementState::Pressed,
|
||||
..
|
||||
},
|
||||
..
|
||||
}
|
||||
|winit::event::WindowEvent::CloseRequested=>{
|
||||
event_loop.exit();
|
||||
},
|
||||
_=>(),
|
||||
}
|
||||
self.send_timed_instruction(Instruction::WindowEvent(event));
|
||||
}
|
||||
|
||||
fn device_event(
|
||||
&mut self,
|
||||
_event_loop:&winit::event_loop::ActiveEventLoop,
|
||||
_device_id:winit::event::DeviceId,
|
||||
event:winit::event::DeviceEvent,
|
||||
){
|
||||
self.send_timed_instruction(Instruction::DeviceEvent(event));
|
||||
}
|
||||
|
||||
fn about_to_wait(
|
||||
&mut self,
|
||||
_event_loop:&winit::event_loop::ActiveEventLoop
|
||||
){
|
||||
self.send_timed_instruction(Instruction::WindowEvent(winit::event::WindowEvent::RedrawRequested));
|
||||
}
|
||||
}
|
@ -1,182 +0,0 @@
|
||||
use strafesnet_common::aabb;
|
||||
use strafesnet_common::integer::{self, vec3, Planar64, Planar64Vec3, Time};
|
||||
#[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 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_physics::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 const fn relative<'a>(body0: &'a Body<T>, body1: &'a Body<T>) -> VirtualBody<'a, T> {
|
||||
//(p0,v0,a0,t0)
|
||||
//(p1,v1,a1,t1)
|
||||
VirtualBody { body0, body1 }
|
||||
}
|
||||
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,
|
||||
)
|
||||
}
|
||||
}
|
@ -1,185 +0,0 @@
|
||||
use crate::model_physics::{DirectedEdge, GigaTime, MeshQuery, FEV};
|
||||
use crate::physics::{Body, Time};
|
||||
use strafesnet_common::integer::{vec3::Vector3, Fixed, Ratio};
|
||||
|
||||
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<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)
|
||||
}
|
||||
}
|
@ -1,5 +1,6 @@
|
||||
use std::io::Read;
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[derive(Debug)]
|
||||
pub enum ReadError{
|
||||
#[cfg(feature="roblox")]
|
||||
@ -10,6 +11,8 @@ pub enum ReadError {
|
||||
StrafesNET(strafesnet_snf::Error),
|
||||
#[cfg(feature="snf")]
|
||||
StrafesNETMap(strafesnet_snf::map::Error),
|
||||
#[cfg(feature="snf")]
|
||||
StrafesNETBot(strafesnet_snf::bot::Error),
|
||||
Io(std::io::Error),
|
||||
UnknownFileFormat,
|
||||
}
|
||||
@ -20,38 +23,44 @@ impl std::fmt::Display for ReadError {
|
||||
}
|
||||
impl std::error::Error for ReadError{}
|
||||
|
||||
pub enum DataStructure {
|
||||
pub enum ReadFormat{
|
||||
#[cfg(feature="roblox")]
|
||||
Roblox(strafesnet_rbx_loader::Model),
|
||||
#[cfg(feature="source")]
|
||||
Source(strafesnet_bsp_loader::Bsp),
|
||||
#[cfg(feature="snf")]
|
||||
StrafesNET(strafesnet_common::map::CompleteMap),
|
||||
SNFM(strafesnet_common::map::CompleteMap),
|
||||
#[cfg(feature="snf")]
|
||||
SNFB(strafesnet_snf::bot::Segment),
|
||||
}
|
||||
|
||||
pub fn read<R: Read + std::io::Seek>(input: R) -> Result<DataStructure, ReadError> {
|
||||
pub fn read<R:Read+std::io::Seek>(input:R)->Result<ReadFormat,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..4] {
|
||||
let peek=std::io::BufRead::fill_buf(&mut buf).map_err(ReadError::Io)?[0..4].to_owned();
|
||||
// reading the entire file is way faster than round tripping the disk constantly
|
||||
let mut entire_file=Vec::new();
|
||||
buf.read_to_end(&mut entire_file).map_err(ReadError::Io)?;
|
||||
let cursor=std::io::Cursor::new(entire_file);
|
||||
match peek.as_slice(){
|
||||
#[cfg(feature="roblox")]
|
||||
b"<rob" => Ok(DataStructure::Roblox(
|
||||
strafesnet_rbx_loader::read(buf).map_err(ReadError::Roblox)?,
|
||||
)),
|
||||
b"<rob"=>Ok(ReadFormat::Roblox(strafesnet_rbx_loader::read(cursor).map_err(ReadError::Roblox)?)),
|
||||
#[cfg(feature="source")]
|
||||
b"VBSP" => Ok(DataStructure::Source(
|
||||
strafesnet_bsp_loader::read(buf).map_err(ReadError::Source)?,
|
||||
b"VBSP"=>Ok(ReadFormat::Source(strafesnet_bsp_loader::read(cursor).map_err(ReadError::Source)?)),
|
||||
#[cfg(feature="snf")]
|
||||
b"SNFM"=>Ok(ReadFormat::SNFM(
|
||||
strafesnet_snf::read_map(cursor).map_err(ReadError::StrafesNET)?
|
||||
.into_complete_map().map_err(ReadError::StrafesNETMap)?
|
||||
)),
|
||||
#[cfg(feature="snf")]
|
||||
b"SNFM" => Ok(DataStructure::StrafesNET(
|
||||
strafesnet_snf::read_map(buf)
|
||||
.map_err(ReadError::StrafesNET)?
|
||||
.into_complete_map()
|
||||
.map_err(ReadError::StrafesNETMap)?,
|
||||
b"SNFB"=>Ok(ReadFormat::SNFB(
|
||||
strafesnet_snf::read_bot(cursor).map_err(ReadError::StrafesNET)?
|
||||
.read_all().map_err(ReadError::StrafesNETBot)?
|
||||
)),
|
||||
_=>Err(ReadError::UnknownFileFormat),
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[derive(Debug)]
|
||||
pub enum LoadError{
|
||||
ReadError(ReadError),
|
||||
@ -65,16 +74,23 @@ impl std::fmt::Display for LoadError {
|
||||
}
|
||||
impl std::error::Error for LoadError{}
|
||||
|
||||
pub fn load<P: AsRef<std::path::Path>>(
|
||||
path: P,
|
||||
) -> Result<strafesnet_common::map::CompleteMap, LoadError> {
|
||||
pub enum LoadFormat{
|
||||
#[cfg(feature="snf")]
|
||||
Map(strafesnet_common::map::CompleteMap),
|
||||
#[cfg(feature="snf")]
|
||||
Bot(strafesnet_snf::bot::Segment),
|
||||
}
|
||||
|
||||
pub fn load<P:AsRef<std::path::Path>>(path:P)->Result<LoadFormat,LoadError>{
|
||||
//blocking because it's simpler...
|
||||
let file=std::fs::File::open(path).map_err(LoadError::File)?;
|
||||
match read(file).map_err(LoadError::ReadError)?{
|
||||
#[cfg(feature="snf")]
|
||||
DataStructure::StrafesNET(map) => Ok(map),
|
||||
ReadFormat::SNFB(bot)=>Ok(LoadFormat::Bot(bot)),
|
||||
#[cfg(feature="snf")]
|
||||
ReadFormat::SNFM(map)=>Ok(LoadFormat::Map(map)),
|
||||
#[cfg(feature="roblox")]
|
||||
DataStructure::Roblox(model) => {
|
||||
ReadFormat::Roblox(model)=>{
|
||||
let mut place=model.into_place();
|
||||
place.run_scripts();
|
||||
|
||||
@ -91,35 +107,26 @@ pub fn load<P: AsRef<std::path::Path>>(
|
||||
let meshpart_meshes=mesh_loader.load_meshes().map_err(LoadError::Io)?;
|
||||
|
||||
let map_step2=map_step1.add_meshpart_meshes_and_calculate_attributes(
|
||||
meshpart_meshes.into_iter().map(|(mesh_id, loader_model)| {
|
||||
(
|
||||
mesh_id,
|
||||
strafesnet_rbx_loader::data::RobloxMeshBytes::new(loader_model.get()),
|
||||
meshpart_meshes.into_iter().map(|(mesh_id,loader_model)|
|
||||
(mesh_id,strafesnet_rbx_loader::data::RobloxMeshBytes::new(loader_model.get()))
|
||||
)
|
||||
}),
|
||||
);
|
||||
|
||||
let (textures, render_configs) = loader
|
||||
.into_render_configs()
|
||||
.map_err(LoadError::Io)?
|
||||
.consume();
|
||||
let (textures,render_configs)=loader.into_render_configs().map_err(LoadError::Io)?.consume();
|
||||
|
||||
let map=map_step2.add_render_configs_and_textures(
|
||||
render_configs.into_iter(),
|
||||
textures.into_iter().map(|(texture_id, texture)| {
|
||||
(
|
||||
texture_id,
|
||||
match texture {
|
||||
textures.into_iter().map(|(texture_id,texture)|
|
||||
(texture_id,match texture{
|
||||
strafesnet_deferred_loader::texture::Texture::ImageDDS(data)=>data,
|
||||
},
|
||||
})
|
||||
)
|
||||
}),
|
||||
);
|
||||
|
||||
Ok(map)
|
||||
}
|
||||
Ok(LoadFormat::Map(map))
|
||||
},
|
||||
#[cfg(feature="source")]
|
||||
DataStructure::Source(bsp) => {
|
||||
ReadFormat::Source(bsp)=>{
|
||||
let mut loader=strafesnet_deferred_loader::source_legacy();
|
||||
|
||||
let (texture_loader,mesh_loader)=loader.get_inner_mut();
|
||||
@ -134,37 +141,28 @@ pub fn load<P: AsRef<std::path::Path>>(
|
||||
|
||||
let map_step2=map_step1.add_prop_meshes(
|
||||
//the type conflagulator 9000
|
||||
prop_meshes.into_iter().map(|(mesh_id, loader_model)| {
|
||||
(
|
||||
mesh_id,
|
||||
strafesnet_bsp_loader::data::ModelData {
|
||||
prop_meshes.into_iter().map(|(mesh_id,loader_model)|
|
||||
(mesh_id,strafesnet_bsp_loader::data::ModelData{
|
||||
mdl:strafesnet_bsp_loader::data::MdlData::new(loader_model.mdl.get()),
|
||||
vtx:strafesnet_bsp_loader::data::VtxData::new(loader_model.vtx.get()),
|
||||
vvd:strafesnet_bsp_loader::data::VvdData::new(loader_model.vvd.get()),
|
||||
},
|
||||
)
|
||||
}),
|
||||
})
|
||||
),
|
||||
|name|texture_loader.acquire_render_config_id(name),
|
||||
);
|
||||
|
||||
let (textures, render_configs) = loader
|
||||
.into_render_configs()
|
||||
.map_err(LoadError::Io)?
|
||||
.consume();
|
||||
let (textures,render_configs)=loader.into_render_configs().map_err(LoadError::Io)?.consume();
|
||||
|
||||
let map=map_step2.add_render_configs_and_textures(
|
||||
render_configs.into_iter(),
|
||||
textures.into_iter().map(|(texture_id, texture)| {
|
||||
(
|
||||
texture_id,
|
||||
match texture {
|
||||
textures.into_iter().map(|(texture_id,texture)|
|
||||
(texture_id,match texture{
|
||||
strafesnet_deferred_loader::texture::Texture::ImageDDS(data)=>data,
|
||||
},
|
||||
)
|
||||
}),
|
||||
})
|
||||
),
|
||||
);
|
||||
|
||||
Ok(map)
|
||||
}
|
||||
Ok(LoadFormat::Map(map))
|
||||
},
|
||||
}
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -1,7 +1,11 @@
|
||||
use strafesnet_graphics::graphics;
|
||||
use strafesnet_session::session;
|
||||
use strafesnet_settings::settings;
|
||||
|
||||
pub enum Instruction{
|
||||
Render(crate::physics_worker::FrameState),
|
||||
//UpdateModel(crate::graphics::GraphicsModelUpdate),
|
||||
Resize(winit::dpi::PhysicalSize<u32>, crate::settings::UserSettings),
|
||||
Render(session::FrameState),
|
||||
//UpdateModel(graphics::GraphicsModelUpdate),
|
||||
Resize(winit::dpi::PhysicalSize<u32>,settings::UserSettings),
|
||||
ChangeMap(strafesnet_common::map::CompleteMap),
|
||||
}
|
||||
|
||||
@ -14,25 +18,20 @@ WorkerDescription{
|
||||
*/
|
||||
//up to three frames in flight, dropping new frame requests when all three are busy, and dropping output frames when one renders out of order
|
||||
|
||||
pub fn new<'a>(
|
||||
mut graphics: crate::graphics::GraphicsState,
|
||||
pub fn new(
|
||||
mut graphics:graphics::GraphicsState,
|
||||
mut config:wgpu::SurfaceConfiguration,
|
||||
surface: wgpu::Surface<'a>,
|
||||
surface:wgpu::Surface,
|
||||
device:wgpu::Device,
|
||||
queue:wgpu::Queue,
|
||||
) -> crate::compat_worker::INWorker<'a, Instruction> {
|
||||
let mut resize = None;
|
||||
)->crate::compat_worker::INWorker<'_,Instruction>{
|
||||
crate::compat_worker::INWorker::new(move |ins:Instruction|{
|
||||
match ins{
|
||||
Instruction::ChangeMap(map)=>{
|
||||
graphics.clear();
|
||||
graphics.generate_models(&device,&queue,&map);
|
||||
}
|
||||
},
|
||||
Instruction::Resize(size,user_settings)=>{
|
||||
resize = Some((size, user_settings));
|
||||
}
|
||||
Instruction::Render(frame_state) => {
|
||||
if let Some((size, user_settings)) = resize.take() {
|
||||
println!("Resizing to {:?}",size);
|
||||
let t0=std::time::Instant::now();
|
||||
config.width=size.width.max(1);
|
||||
@ -41,6 +40,7 @@ pub fn new<'a>(
|
||||
graphics.resize(&device,&config,&user_settings);
|
||||
println!("Resize took {:?}",t0.elapsed());
|
||||
}
|
||||
Instruction::Render(frame_state)=>{
|
||||
//this has to go deeper somehow
|
||||
let frame=match surface.get_current_texture(){
|
||||
Ok(frame)=>frame,
|
||||
|
@ -1,18 +1,11 @@
|
||||
mod body;
|
||||
mod compat_worker;
|
||||
mod face_crawler;
|
||||
mod app;
|
||||
mod file;
|
||||
mod graphics;
|
||||
mod graphics_worker;
|
||||
mod model_graphics;
|
||||
mod model_physics;
|
||||
mod physics;
|
||||
mod physics_worker;
|
||||
mod push_solve;
|
||||
mod settings;
|
||||
mod setup;
|
||||
mod window;
|
||||
mod worker;
|
||||
mod compat_worker;
|
||||
mod physics_worker;
|
||||
mod graphics_worker;
|
||||
|
||||
const TITLE:&'static str=concat!("Strafe Client v",env!("CARGO_PKG_VERSION"));
|
||||
|
||||
|
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