Compare commits

..

98 Commits

Author SHA1 Message Date
be9eb30a5c minimize lints 2025-01-24 05:20:22 -08:00
42310c5d2b convert to app 2025-01-23 06:07:46 -08:00
988750a3c6 physics: typo 2025-01-22 14:52:47 -08:00
2f26662dda it: drop strafe-client::file dependency 2025-01-22 09:37:16 -08:00
6f739dba8d add integration testing 2025-01-22 09:36:35 -08:00
2eaddd493d session: use replay directory 2025-01-22 08:57:15 -08:00
d5a6f8e1bc compute UserSettings from directories 2025-01-22 08:54:05 -08:00
95fb43b908 settings: implement directories 2025-01-22 08:52:27 -08:00
3e814cb41a settings: add directories dep 2025-01-22 07:47:41 -08:00
ea854a548f Update README.md 2025-01-22 07:10:04 -08:00
5f2cf8f32e physics: change body api 2025-01-22 07:00:42 -08:00
b6b090de78 physics: place version code into lib.rs 2025-01-22 06:45:56 -08:00
affbada62e move replay tests into test module 2025-01-22 05:48:08 -08:00
8d2ba28700 move engine components into modules 2025-01-22 05:48:08 -08:00
3eb4e76ab2 replace .map_or(None, with .and_then( 2025-01-22 05:47:32 -08:00
5d31419370 use Range to express time range 2025-01-22 05:47:32 -08:00
44a58044c7 readme: try it out 2025-01-22 05:47:32 -08:00
83ac776b78 Revert "bodge surfs"
This reverts commit 65b49d27261754903b2e39194c80ac096afc1da2.
2025-01-22 05:47:32 -08:00
871aadb78a physics version 2025-01-21 09:43:29 -08:00
65b49d2726 bodge surfs 2025-01-21 09:42:07 -08:00
ed70b7c484 tests: fix thread limit 2025-01-21 09:00:12 -08:00
c7575f2e21 deref bool 2025-01-21 08:49:44 -08:00
c2f78eab48 CrawlResult impls 2025-01-21 08:30:11 -08:00
195014400f apply setvelocity before teleport and jump 2025-01-21 07:47:39 -08:00
ee9585a4c2 accept slice in push_solve 2025-01-21 07:20:58 -08:00
77012d6caa remove unnecessary reference 2025-01-21 07:20:34 -08:00
f9509353dd change constrain_{velocity|acceleration} function signature 2025-01-21 07:20:22 -08:00
5bce4a84cf tweak ReachWalkTargetVelocity 2025-01-21 06:05:47 -08:00
4bbccd68ed tweak version plan 2025-01-21 05:47:39 -08:00
8be3be4002 write comment about handling identical timestamps implicitly 2025-01-21 05:39:12 -08:00
76bafa4d0a fix divide by zero crashes when mouse has not moved 2025-01-21 05:34:45 -08:00
a612d1f864 quiet down physics 2025-01-21 05:18:30 -08:00
ad9ef141bf print when bot file finishes writing 2025-01-21 05:18:30 -08:00
4c11980989 headless replay test 2025-01-21 05:18:30 -08:00
091da88b5c roblox_emulator: name macro variable 2025-01-20 15:46:26 -08:00
045e540020 roblox_emulator: use match guard 2025-01-20 15:46:13 -08:00
c14f8975fc snf: bot: fix lint SegmentId 2025-01-20 10:37:21 -08:00
8e228033e0 snf: bot: version const 2025-01-20 10:37:21 -08:00
eaecbc5b73 physics versioning plan 2025-01-20 10:36:40 -08:00
91a1b3d65e read entire file 2025-01-20 09:14:01 -08:00
e1d51ff2da rename stupidly named file enums 2025-01-20 09:11:09 -08:00
c5f2395b3a tools: copy all bash args in map scripts 2025-01-20 07:57:56 -08:00
77aa83d6ac physics: explicit start_time 2025-01-20 07:51:39 -08:00
184b12a9cc tools: copy all bash args in run script 2025-01-20 05:56:26 -08:00
9ba3484e25 create replays folder + write replay using spawned thread :) 2025-01-20 05:40:11 -08:00
adcd7db4f1 common: timer: the time of a paused timer does not depend on the parent time 2025-01-20 05:25:13 -08:00
710670d78e don't use mold
It makes compilation slightly more difficult for non-experts.  It can still be enabled the same way in the system-wide config located at ~/.cargo/config.toml
2025-01-18 22:43:12 -08:00
70e1514ef9 v0.11.0 replays 2025-01-18 09:24:34 -08:00
45e9b3d0ce use existing replay timer 2025-01-18 09:24:34 -08:00
1eac734c35 don't make a new replay if you are spectating 2025-01-18 09:24:34 -08:00
25e1312fe4 document another bind 2025-01-18 09:24:34 -08:00
cc8f6b059d SessionControlInstruction::LoadIntoReplayState (J) 2025-01-18 09:24:34 -08:00
4cd287dbb8 unpause sim on bot file export 2025-01-18 09:24:34 -08:00
40ea0e6c7d rename last_instruction_id to next_instruction_id 2025-01-18 09:24:34 -08:00
b45b92c627 allow writing idle instructions 2025-01-18 09:24:34 -08:00
e90f53a111 integrate replay save/load 2025-01-18 09:24:34 -08:00
6beb6c5f9a implement bot file 2025-01-18 09:24:21 -08:00
a1507d4f26 implement newtypes for bot files 2025-01-18 09:24:05 -08:00
77db5a7a6b tweak instruction collector 2025-01-18 05:22:02 -08:00
75c8bc2bbb fix timescale 2025-01-17 21:47:02 -08:00
d49a6b2f0a file::Header::calculate_size 2025-01-17 21:29:04 -08:00
19778ac7aa update wgpu 2025-01-17 11:47:23 -08:00
5b62052222 snf: move newtypes.rs into newtypes/mod.rs 2025-01-16 21:29:46 -08:00
5a8bc141d3 tweak ModeInstruction and document meaning 2025-01-16 20:54:02 -08:00
83a067320b fix usage of Cow in model_physics 2025-01-16 19:31:49 -08:00
2faa61225f refactor physics to use shared context for multiple simulations 2025-01-16 18:51:22 -08:00
28499800cb comment about bad algorithm 2025-01-16 07:40:08 -08:00
57cc49dc1a explicitly reset and spawn instead of implicitly 2025-01-16 06:35:36 -08:00
d517b78a8c replay controls 2025-01-16 05:10:39 -08:00
e6d1d69241 delete replay on StopSpectate 2025-01-16 05:10:39 -08:00
b28fa25279 comment about determinism 2025-01-16 05:10:39 -08:00
713b235816 maintain replay state according to real time 2025-01-16 05:10:39 -08:00
d2002383cb idle is special 2025-01-16 00:17:39 -08:00
52f7de809d rename variable 2025-01-16 00:17:39 -08:00
4efe1209b8 implement copy instruction into replay 2025-01-15 23:44:42 -08:00
15a9136fc4 session instruction changes for control and playback 2025-01-15 22:59:59 -08:00
035736e7af record 2025-01-15 21:41:44 -08:00
7f9a16a56d refactor physics enums so Mouse-NonMouse distinction is private to mouse_interpolator 2025-01-15 21:09:08 -08:00
814e573d91 rename physics instructions 2025-01-15 20:19:20 -08:00
6fa0f1c83e model_physics: use entry or_insert_with pattern 2025-01-15 08:46:26 -08:00
870cb56dac do not use ResourceType 2025-01-15 08:46:15 -08:00
1aac2a9303 model_physics: remove pointless unsafe usage from the before times 2025-01-15 08:46:06 -08:00
38661b3a68 fixed_wide: another derivable trait 2025-01-15 04:15:41 -08:00
7ad76740d4 common: tidy instruction module 2025-01-15 03:29:48 -08:00
c2d6af8bda TimedInstruction::set_time 2025-01-15 03:29:48 -08:00
fbacef83b9 fix non-determinism bug 2025-01-15 03:16:00 -08:00
2a9e848541 explain non-determinism bug 2025-01-15 03:03:34 -08:00
3413ec8740 mouse_interpolator tweaks 2025-01-15 02:59:34 -08:00
168d6708d1 fix some physics lints 2025-01-15 01:34:10 -08:00
13cae4c7c5 rewrite mouse_interpolator, introduce session 2025-01-15 01:01:24 -08:00
0dc462a3b1 comment infinite loop avoidance 2025-01-09 20:38:32 -08:00
ca003edbc3 reintroduce generics to Instruction traits 2025-01-09 20:11:00 -08:00
16abe23e97 push solve tweaks 2025-01-09 06:27:50 -08:00
67f8569178 push_solve infallible type signature 2025-01-09 05:56:11 -08:00
121c9c5258 resize immediately 2025-01-09 05:36:55 -08:00
411b997b87 use mold linker because it's faster 2025-01-09 05:36:55 -08:00
4d587b6c0b fixed_wide: clippy angry about derivable traits 2025-01-08 23:33:39 -08:00
6ff74f08ab roblox_emulator: deref returns correct type 2025-01-08 23:31:56 -08:00
128 changed files with 17030 additions and 19079 deletions

504
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -1,5 +1,10 @@
[workspace]
members = [
"engine/graphics",
"engine/physics",
"engine/session",
"engine/settings",
"integration-testing",
"lib/bsp_loader",
"lib/common",
"lib/deferred_loader",

View File

@ -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.

View 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
View 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
*******************************************************/

View 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
}

View File

@ -0,0 +1,2 @@
pub mod model;
pub mod graphics;

View 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
View 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
View 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
View 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)
}
}

View 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
View 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

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View 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
View 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
View 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
*******************************************************/

View File

@ -0,0 +1,2 @@
mod mouse_interpolator;
pub mod session;

View 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);
}
}

View 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)
}
}

View 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
View 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
*******************************************************/

View 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"),
})
}
}

View File

@ -0,0 +1,2 @@
pub mod settings;
pub mod directories;

View 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()
}
}

View 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" }

View 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(())
}

View File

@ -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,

View File

@ -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,

View File

@ -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}
}
}

View File

@ -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,
}

View File

@ -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,
}
}

View File

@ -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

View File

@ -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
}

View File

@ -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
})
}
}

View File

@ -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
])
}
}

View File

@ -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;

View File

@ -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{

View File

@ -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);

View File

@ -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();

View File

@ -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),
}

View File

@ -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,
}
}
}

View File

@ -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>{

View File

@ -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 {
}
}
}
//*/

View File

@ -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" }

View File

@ -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);

View File

@ -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"))]

View File

@ -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(),
}

View File

@ -1,7 +1,7 @@
mod macros;
pub mod matrix;
pub mod types;
pub mod vector;
pub mod matrix;
#[cfg(feature="named-fields")]
mod named;

View File

@ -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 {
}
}
}
};
}
}

View File

@ -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],
])
}
}
};
}
}

View File

@ -1,6 +1,6 @@
pub mod common;
pub mod matrix;
pub mod vector;
pub mod matrix;
#[cfg(feature="fixed-wide")]
pub mod fixed_wide;

View File

@ -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 {
])
}
}
};
}
}

View File

@ -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)
)
);

View File

@ -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
);
}

View File

@ -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);

View File

@ -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
);
}

View File

@ -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>;

View File

@ -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,

View File

@ -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,

View File

@ -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,

View File

@ -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

View File

@ -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,
}
}
}

View File

@ -1,5 +1,5 @@
pub mod context;
pub mod runner;
pub mod context;
#[cfg(feature="run-service")]
pub(crate) mod scheduler;

View File

@ -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,
))
});
);
}
}

View File

@ -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);

View File

@ -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)?;

View File

@ -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);

View File

@ -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)
}

View File

@ -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))),
}
}
}

View File

@ -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};

View File

@ -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)?;

View File

@ -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)

View File

@ -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>(())?;

View File

@ -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,
))
);
}
}

View File

@ -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>(())?;

View File

@ -1 +0,0 @@

View File

@ -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(())
}

View File

@ -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{

View File

@ -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() {
}
}

View File

@ -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)?;

View File

@ -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
}

View File

@ -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()),
}
}
}

View File

@ -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(),
}

View File

@ -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,
}
}
}

View File

@ -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;

View File

@ -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;

View File

@ -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(),

View 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(),
}
}
}

View 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),
}
}
}

View File

@ -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
View 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));
}
}

View File

@ -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,
)
}
}

View File

@ -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)
}
}

View File

@ -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

View File

@ -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,

View File

@ -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