Compare commits

...

29 Commits

Author SHA1 Message Date
fd82fa6203 reject meshes with zero vertices 2026-02-25 13:17:47 -08:00
36f44ffd43 common v0.8.2: Ratio64 fixes 2026-02-25 08:56:34 -08:00
7f16468104 common: integer: test Ratio64 from float 2026-02-25 08:48:02 -08:00
a870899743 common: integer: fix Ratio64::mul_int with large numbers 2026-02-25 08:48:02 -08:00
b5431c0732 common: v0.8.1 pub RunState 2026-02-20 07:01:34 -08:00
36ccbdc6b2 print when zero size mesh 2026-02-19 14:40:56 -08:00
a68f009658 ignore empty face 2026-02-19 14:37:15 -08:00
118a1639a7 common: tweak run 2026-02-19 10:11:02 -08:00
3212fb1d41 graphics v0.0.2 2026-02-18 09:20:01 -08:00
424c7ce9a6 refactor setup 2026-02-18 09:19:14 -08:00
3174f9caa9 fix divide by zero 2026-02-18 09:19:14 -08:00
b8e5343464 strafesnet_graphics v0.0.1 2026-02-10 08:54:51 -08:00
51e7703933 graphics: move images and shader 2026-02-10 08:54:51 -08:00
e46f54efe0 graphics: remove settings dep 2026-02-10 08:51:21 -08:00
b2993995bb graphics: remove session dep 2026-02-10 08:44:26 -08:00
7963c3632b PhysicsCamera::set_sensitivity 2026-02-06 09:23:42 -08:00
67680312dd fixed_wide v0.2.2 mul_sign div_sign 2026-02-05 07:36:18 -08:00
1a0a3403f0 specify crate versions in workspace 2026-02-05 07:34:19 -08:00
9e65a6eb95 physics v0.0.1 2026-02-05 07:34:19 -08:00
ebc897aad8 snf v0.3.2 sprint control 2026-02-05 07:28:37 -08:00
23a6655bb0 specify crate versions in workspace 2026-02-05 07:23:27 -08:00
0422c223cd common v0.8.0 2026-02-05 06:58:21 -08:00
5db2ce076d update glam 2026-02-05 06:51:07 -08:00
c98364b68d update deps 2026-02-05 06:42:19 -08:00
b7e8fb6e18 simplify adapter selection with iterators 2026-02-03 10:13:46 -08:00
d343056664 more correct 2026-02-03 08:01:34 -08:00
534f2a2141 fixed: silence lint 2026-01-30 07:52:52 -08:00
79ea88fc74 fixed: remove pub(crate) field visibility 2026-01-30 07:52:42 -08:00
3fd507be94 noclip 2026-01-30 07:33:04 -08:00
38 changed files with 594 additions and 539 deletions

505
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -35,3 +35,22 @@ unused_lifetimes = "warn"
unused_qualifications = "warn"
# variant_size_differences = "warn"
unexpected_cfgs = "warn"
[workspace.dependencies]
glam = "0.31.0"
# engine
strafesnet_graphics = { path = "engine/graphics", registry = "strafesnet" }
strafesnet_physics = { version = "0.0.1", path = "engine/physics", registry = "strafesnet" }
strafesnet_session = { path = "engine/session", registry = "strafesnet" }
strafesnet_settings = { path = "engine/settings", registry = "strafesnet" }
# lib
fixed_wide = { version = "0.2.2", path = "lib/fixed_wide", registry = "strafesnet" }
linear_ops = { version = "0.1.1", path = "lib/linear_ops", registry = "strafesnet" }
ratio_ops = { version = "0.1.0", path = "lib/ratio_ops", registry = "strafesnet" }
strafesnet_bsp_loader = { path = "lib/bsp_loader", registry = "strafesnet" }
strafesnet_common = { version = "0.8.2", path = "lib/common", registry = "strafesnet" }
strafesnet_deferred_loader = { version = "0.5.1", path = "lib/deferred_loader", registry = "strafesnet" }
strafesnet_rbx_loader = { path = "lib/rbx_loader", registry = "strafesnet" }
strafesnet_snf = { version = "0.3.2", path = "lib/snf", registry = "strafesnet" }

View File

@@ -1,16 +1,14 @@
[package]
name = "strafesnet_graphics"
version = "0.1.0"
version = "0.0.2"
edition = "2024"
[dependencies]
bytemuck = { version = "1.13.1", features = ["derive"] }
ddsfile = "0.5.1"
glam = "0.30.0"
glam.workspace = true
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" }
strafesnet_common.workspace = true
wgpu = "28.0.0"
[lints]

View File

@@ -1,8 +1,6 @@
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};
@@ -54,6 +52,10 @@ struct GraphicsPipelines{
model:wgpu::RenderPipeline,
}
pub fn view_inv(pos:glam::Vec3,angles:glam::Vec2)->glam::Mat4{
//f32 good enough for view matrix
glam::Mat4::from_mat3_translation(glam::Mat3::from_euler(glam::EulerRot::YXZ,angles.x,angles.y,0f32),pos)
}
struct GraphicsCamera{
screen_size:glam::UVec2,
fov:glam::Vec2,//slope
@@ -75,15 +77,10 @@ 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]{
pub fn to_uniform_data(&self,view_inv:glam::Mat4)->[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];
@@ -162,9 +159,6 @@ impl GraphicsState{
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<model::TextureId,wgpu::TextureView>=map.textures.iter().enumerate().filter_map(|(texture_id,texture_data)|{
@@ -634,7 +628,7 @@ impl GraphicsState{
// 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"))),
source:wgpu::ShaderSource::Wgsl(Cow::Borrowed(include_str!("../shaders/shader.wgsl"))),
});
//load textures
@@ -662,10 +656,10 @@ impl GraphicsState{
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")[..],
}=>&include_bytes!("../images/astc.dds")[..],
wgpu::TextureFormat::Etc2Rgb8UnormSrgb=>&include_bytes!("../images/etc2.dds")[..],
wgpu::TextureFormat::Bc1RgbaUnormSrgb=>&include_bytes!("../images/bc1.dds")[..],
wgpu::TextureFormat::Bgra8UnormSrgb=>&include_bytes!("../images/bgra.dds")[..],
_=>unreachable!(),
};
@@ -708,7 +702,7 @@ impl GraphicsState{
//squid
let squid_texture_view={
let bytes=include_bytes!("../../../strafe-client/images/squid.dds");
let bytes=include_bytes!("../images/squid.dds");
let image=ddsfile::Dds::read(&mut std::io::Cursor::new(bytes)).unwrap();
@@ -833,7 +827,7 @@ impl GraphicsState{
});
let camera=GraphicsCamera::default();
let camera_uniforms=camera.to_uniform_data(glam::Vec3::ZERO,glam::Vec2::ZERO);
let camera_uniforms=camera.to_uniform_data(view_inv(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),
@@ -890,28 +884,25 @@ impl GraphicsState{
&mut self,
device:&wgpu::Device,
config:&wgpu::SurfaceConfiguration,
user_settings:&settings::UserSettings,
fov:glam::Vec2,
){
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);
self.camera.fov=fov;
}
pub fn render(
&mut self,
view:&wgpu::TextureView,
device:&wgpu::Device,
queue:&wgpu::Queue,
frame_state:session::FrameState,
camera:glam::Mat4,
){
//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.trajectory.extrapolated_position(frame_state.time).map(Into::<f32>::into).to_array().into(),
frame_state.camera.simulate_move_angles(glam::IVec2::ZERO)
);
let camera_uniforms=self.camera.to_uniform_data(camera);
self.staging_belt
.write_buffer(
&mut encoder,

View File

@@ -1,2 +1,3 @@
pub mod model;
pub mod setup;
pub mod graphics;

View File

@@ -0,0 +1,112 @@
fn optional_features()->wgpu::Features{
wgpu::Features::TEXTURE_COMPRESSION_ASTC
|wgpu::Features::TEXTURE_COMPRESSION_ETC2
}
fn required_features()->wgpu::Features{
wgpu::Features::TEXTURE_COMPRESSION_BC
}
fn required_downlevel_capabilities()->wgpu::DownlevelCapabilities{
wgpu::DownlevelCapabilities{
flags:wgpu::DownlevelFlags::empty(),
shader_model:wgpu::ShaderModel::Sm5,
..wgpu::DownlevelCapabilities::default()
}
}
pub mod step1{
pub fn create_instance()->wgpu::Instance{
Default::default()
}
}
pub mod step2{
pub fn create_surface<'window>(instance:&wgpu::Instance,target:impl Into<wgpu::SurfaceTarget<'window>>)->Result<wgpu::Surface<'window>,wgpu::CreateSurfaceError>{
instance.create_surface(target)
}
}
pub mod step3{
pub async fn pick_adapter(instance:&wgpu::Instance,surface:&wgpu::Surface<'_>)->Option<wgpu::Adapter>{
let backends=wgpu::Backends::from_env().unwrap_or_default();
//TODO: prefer adapter that implements optional features
//let optional_features=optional_features();
let required_features=super::required_features();
let required_downlevel_capabilities=super::required_downlevel_capabilities();
//no helper function smh gotta write it myself
let adapters=instance.enumerate_adapters(backends).await;
let adapter=adapters.into_iter()
// reverse because we want to select adapters that appear first in ties,
// and max_by_key selects the last equal element in the iterator.
.rev()
.filter(|adapter|
adapter.is_surface_supported(surface)
&&adapter.features().contains(required_features)
&&{
let downlevel_capabilities=adapter.get_downlevel_capabilities();
downlevel_capabilities.shader_model>=required_downlevel_capabilities.shader_model
&&downlevel_capabilities.flags.contains(required_downlevel_capabilities.flags)
}
)
.max_by_key(|adapter|match adapter.get_info().device_type{
wgpu::DeviceType::IntegratedGpu=>3,
wgpu::DeviceType::DiscreteGpu=>4,
wgpu::DeviceType::VirtualGpu=>2,
wgpu::DeviceType::Other|wgpu::DeviceType::Cpu=>1,
})?;
let adapter_info=adapter.get_info();
println!("Using {} ({:?})", adapter_info.name, adapter_info.backend);
Some(adapter)
}
}
pub mod step4{
pub async fn request_device(adapter:&wgpu::Adapter)->(wgpu::Device,wgpu::Queue){
let optional_features=super::optional_features();
let required_features=super::required_features();
// Make sure we use the texture resolution limits from the adapter, so we can support images the size of the surface.
let needed_limits=crate::graphics::required_limits().using_resolution(adapter.limits());
let (device, queue)=adapter
.request_device(
&wgpu::DeviceDescriptor{
label:None,
required_features:(optional_features&adapter.features())|required_features,
required_limits:needed_limits,
memory_hints:wgpu::MemoryHints::Performance,
trace:wgpu::Trace::Off,
experimental_features:wgpu::ExperimentalFeatures::disabled(),
},
).await
.expect("Unable to find a suitable GPU adapter!");
(
device,
queue,
)
}
}
pub mod step5{
pub fn configure_surface(
adapter:&wgpu::Adapter,
device:&wgpu::Device,
surface:&wgpu::Surface<'_>,
(width,height):(u32,u32),
)->wgpu::SurfaceConfiguration{
let mut config=surface
.get_default_config(adapter, width, height)
.expect("Surface isn't supported by the adapter.");
let surface_view_format=config.format.add_srgb_suffix();
config.view_formats.push(surface_view_format);
config.present_mode=wgpu::PresentMode::AutoNoVsync;
surface.configure(device,&config);
config
}
}

View File

@@ -1,13 +1,13 @@
[package]
name = "strafesnet_physics"
version = "0.1.0"
version = "0.0.1"
edition = "2024"
[dependencies]
arrayvec = "0.7.6"
glam = "0.30.0"
glam.workspace = true
id = { version = "0.1.0", registry = "strafesnet" }
strafesnet_common = { path = "../../lib/common", registry = "strafesnet" }
strafesnet_common.workspace = true
[lints]
workspace = true

View File

@@ -57,6 +57,9 @@ impl InputState{
fn replace_mouse(&mut self,mouse:MouseState,next_mouse:MouseState){
(self.next_mouse,self.mouse)=(next_mouse,mouse);
}
fn get_control(&self,control:Controls)->bool{
self.controls.contains(control)
}
fn set_control(&mut self,control:Controls,state:bool){
self.controls.set(control,state)
}
@@ -265,6 +268,12 @@ pub struct PhysicsCamera{
impl PhysicsCamera{
const ANGLE_PITCH_LOWER_LIMIT:Angle32=Angle32::NEG_FRAC_PI_2;
const ANGLE_PITCH_UPPER_LIMIT:Angle32=Angle32::FRAC_PI_2;
fn set_sensitivity(&mut self,sensitivity:Ratio64Vec2){
// Calculate nearest mouse position in new sensitivity
self.clamped_mouse_pos.x=(self.clamped_mouse_pos.x as i128*self.sensitivity.x.num() as i128*sensitivity.x.den() as i128/(sensitivity.x.num() as i128*self.sensitivity.x.den() as i128)) as i32;
self.clamped_mouse_pos.y=(self.clamped_mouse_pos.y as i128*self.sensitivity.y.num() as i128*sensitivity.y.den() as i128/(sensitivity.y.num() as i128*self.sensitivity.y.den() as i128)) as i32;
self.sensitivity=sensitivity;
}
pub fn move_mouse(&mut self,mouse_delta:glam::IVec2){
let mut unclamped_mouse_pos=self.clamped_mouse_pos+mouse_delta;
unclamped_mouse_pos.y=unclamped_mouse_pos.y.clamp(
@@ -922,6 +931,9 @@ impl PhysicsState{
pub fn get_finish_time(&self)->Option<run::Time>{
self.run.get_finish_time()
}
fn is_no_clip_enabled(&self)->bool{
self.input_state.get_control(Controls::Sprint)
}
pub fn clear(&mut self){
self.touching.clear();
}
@@ -1195,6 +1207,11 @@ fn next_instruction_internal(state:&PhysicsState,data:&PhysicsData,time_limit:Ti
let trajectory=state.body.with_acceleration(state.acceleration(data));
//check for collision ends
state.touching.predict_collision_end(&mut collector,&data.models,&data.hitbox_mesh,&trajectory,state.time);
if state.is_no_clip_enabled(){
return collector.take();
}
//check for collision starts
let mut aabb=aabb::Aabb::default();
trajectory.grow_aabb(&mut aabb,state.time,collector.time());
@@ -1862,10 +1879,11 @@ fn atomic_input_instruction(state:&mut PhysicsState,data:&PhysicsData,ins:TimedI
state.input_state.set_next_mouse(m);
},
Instruction::Mouse(MouseInstruction::ReplaceMouse{m0,m1})=>{
state.camera.move_mouse(m0.pos-state.input_state.mouse.pos);
state.camera.move_mouse(state.input_state.mouse_delta());
state.camera.move_mouse(m0.pos-state.input_state.next_mouse.pos);
state.input_state.replace_mouse(m0,m1);
},
Instruction::Misc(MiscInstruction::SetSensitivity(sensitivity))=>state.camera.sensitivity=sensitivity,
Instruction::Misc(MiscInstruction::SetSensitivity(sensitivity))=>state.camera.set_sensitivity(sensitivity),
Instruction::SetControl(SetControlInstruction::SetMoveForward(s))=>state.input_state.set_control(Controls::MoveForward,s),
Instruction::SetControl(SetControlInstruction::SetMoveLeft(s))=>state.input_state.set_control(Controls::MoveLeft,s),
Instruction::SetControl(SetControlInstruction::SetMoveBack(s))=>state.input_state.set_control(Controls::MoveBackward,s),

View File

@@ -4,12 +4,12 @@ version = "0.1.0"
edition = "2024"
[dependencies]
glam = "0.30.0"
glam.workspace = true
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" }
strafesnet_common.workspace = true
strafesnet_physics.workspace = true
strafesnet_settings.workspace = true
strafesnet_snf.workspace = true
[lints]
workspace = true

View File

@@ -61,6 +61,14 @@ pub struct FrameState{
pub camera:physics::PhysicsCamera,
pub time:PhysicsTime,
}
impl FrameState{
pub fn pos(&self)->glam::Vec3{
self.trajectory.extrapolated_position(self.time).map(Into::<f32>::into).to_array().into()
}
pub fn angles(&self)->glam::Vec2{
self.camera.simulate_move_angles(glam::IVec2::ZERO)
}
}
pub struct Simulation{
timer:Timer<Scaled<SessionTimeInner,PhysicsTimeInner>>,

View File

@@ -6,8 +6,8 @@ edition = "2024"
[dependencies]
configparser = "3.0.2"
directories = "6.0.0"
glam = "0.30.0"
strafesnet_common = { path = "../../lib/common", registry = "strafesnet" }
glam.workspace = true
strafesnet_common.workspace = true
[lints]
workspace = true

View File

@@ -4,12 +4,12 @@ version = "0.1.0"
edition = "2024"
[dependencies]
glam = "0.30.0"
strafesnet_common = { path = "../lib/common", registry = "strafesnet" }
strafesnet_physics = { path = "../engine/physics", registry = "strafesnet" }
strafesnet_snf = { path = "../lib/snf", registry = "strafesnet" }
glam.workspace = true
strafesnet_common.workspace = true
strafesnet_physics.workspace = true
strafesnet_snf.workspace = true
# this is just for the primitive constructor
strafesnet_rbx_loader = { path = "../lib/rbx_loader", registry = "strafesnet" }
strafesnet_rbx_loader.workspace = true
[lints]
workspace = true

View File

@@ -10,9 +10,9 @@ authors = ["Rhys Lloyd <krakow20@gmail.com>"]
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
glam = "0.30.0"
strafesnet_common = { version = "0.7.0", path = "../common", registry = "strafesnet" }
strafesnet_deferred_loader = { version = "0.5.1", path = "../deferred_loader", registry = "strafesnet" }
glam.workspace = true
strafesnet_common.workspace = true
strafesnet_deferred_loader.workspace = true
vbsp = "0.9.1"
vbsp-entities-css = "0.6.0"
vmdl = "0.2.0"

View File

@@ -1,6 +1,6 @@
[package]
name = "strafesnet_common"
version = "0.7.0"
version = "0.8.2"
edition = "2024"
repository = "https://git.itzana.me/StrafesNET/strafe-project"
license = "MIT OR Apache-2.0"
@@ -12,10 +12,10 @@ authors = ["Rhys Lloyd <krakow20@gmail.com>"]
[dependencies]
arrayvec = "0.7.4"
bitflags = "2.6.0"
fixed_wide = { version = "0.2.0", path = "../fixed_wide", registry = "strafesnet", features = ["deferred-division","zeroes","wide-mul"] }
linear_ops = { version = "0.1.1", path = "../linear_ops", registry = "strafesnet", features = ["deferred-division","named-fields"] }
ratio_ops = { version = "0.1.0", path = "../ratio_ops", registry = "strafesnet" }
glam = "0.30.0"
fixed_wide = { workspace = true, features = ["deferred-division","zeroes","wide-mul"] }
linear_ops = { workspace = true, features = ["deferred-division","named-fields"] }
ratio_ops = { workspace = true }
glam.workspace = true
id = { version = "0.1.0", registry = "strafesnet" }
[lints]

View File

@@ -214,11 +214,11 @@ impl Ratio64{
}
#[inline]
pub const fn mul_int(&self,rhs:i64)->i64{
rhs*self.num/(self.den as i64)
(rhs as i128*self.num as i128/self.den as i128) as i64
}
#[inline]
pub const fn rhs_div_int(&self,rhs:i64)->i64{
rhs*(self.den as i64)/self.num
(rhs as i128*self.den as i128/self.num as i128) as i64
}
#[inline]
pub const fn mul_ref(&self,rhs:&Ratio64)->Ratio64{
@@ -263,7 +263,6 @@ fn integer_decode_f64(f: f64) -> (u64, i16, i8) {
pub enum Ratio64TryFromFloatError{
Nan,
Infinite,
Subnormal,
HighlyNegativeExponent(i16),
HighlyPositiveExponent(i16),
}
@@ -298,8 +297,10 @@ fn ratio64_from_mes((m,e,s):(u64,i16,i8))->Result<Ratio64,Ratio64TryFromFloatErr
Ok(Ratio64::new(num as i64,den as u64).unwrap())
}else if e<0{
// simple exact representation
Ok(Ratio64::new((m as i64)*(s as i64),1<<-e).unwrap())
}else if (64-m.leading_zeros() as i16)+e<64{
// integer
Ok(Ratio64::new((m as i64)*(s as i64)*(1<<e),1).unwrap())
}else{
Err(Ratio64TryFromFloatError::HighlyPositiveExponent(e))
@@ -331,6 +332,29 @@ impl TryFrom<f64> for Ratio64{
}
}
}
#[cfg(test)]
fn req(r0:Ratio64,r1:Ratio64){
println!("r0={r0:?} r1={r1:?}");
assert_eq!(r0.num(),r1.num(),"Nums not eq");
assert_eq!(r0.den(),r1.den(),"Dens not eq");
}
#[test]
fn test_ratio64_from_float(){
req(2.0.try_into().unwrap(),Ratio64::new(2,1).unwrap());
req(1.0.try_into().unwrap(),Ratio64::new(1,1).unwrap());
req(0.5.try_into().unwrap(),Ratio64::new(1,2).unwrap());
req(1.1.try_into().unwrap(),Ratio64::new(2476979795053773,2251799813685248).unwrap());
req(0.8.try_into().unwrap(),Ratio64::new(3602879701896397,4503599627370496).unwrap());
req(0.61.try_into().unwrap(),Ratio64::new(5494391545392005,9007199254740992).unwrap());
req(0.01.try_into().unwrap(),Ratio64::new(5764607523034235,576460752303423488).unwrap());
req(0.001.try_into().unwrap(),Ratio64::new(1152921504606847,1152921504606846976).unwrap());
req(0.00001.try_into().unwrap(),Ratio64::new(89605456633725,8960545663372499267).unwrap());
req(0.00000000001.try_into().unwrap(),Ratio64::new(35204848,3520484800000000213).unwrap());
req(0.000000000000000001.try_into().unwrap(),Ratio64::new(11,10999999999999999213).unwrap());
req(2222222222222.0.try_into().unwrap(),Ratio64::new(2222222222222,1).unwrap());
req(core::f64::consts::PI.try_into().unwrap(),Ratio64::new(884279719003555,281474976710656).unwrap());
}
impl std::ops::Mul<Ratio64> for Ratio64{
type Output=Ratio64;
#[inline]

View File

@@ -53,36 +53,23 @@ impl std::fmt::Display for Error{
impl std::error::Error for Error{}
#[derive(Clone,Copy,Debug)]
enum RunState{
pub enum RunState{
Created,
Started{timer:TimerFixed<Realtime<PhysicsTimeInner,TimeInner>,Unpaused>},
Finished{timer:TimerFixed<Realtime<PhysicsTimeInner,TimeInner>,Paused>},
}
#[derive(Clone,Copy,Debug)]
pub struct Run{
state:RunState,
flagged:Option<FlagReason>,
}
impl Run{
pub fn new()->Self{
Self{
state:RunState::Created,
flagged:None,
}
}
impl RunState{
pub fn time(&self,time:PhysicsTime)->Time{
match &self.state{
match &self{
RunState::Created=>Time::ZERO,
RunState::Started{timer}=>timer.time(time),
RunState::Finished{timer}=>timer.time(),
}
}
pub fn start(&mut self,time:PhysicsTime)->Result<(),Error>{
match &self.state{
match &self{
RunState::Created=>{
self.state=RunState::Started{
*self=RunState::Started{
timer:TimerFixed::new(time,Time::ZERO),
};
Ok(())
@@ -93,10 +80,10 @@ impl Run{
}
pub fn finish(&mut self,time:PhysicsTime)->Result<(),Error>{
//this uses Copy
match &self.state{
match &self{
RunState::Created=>Err(Error::NotStarted),
RunState::Started{timer}=>{
self.state=RunState::Finished{
*self=RunState::Finished{
timer:timer.into_paused(time),
};
Ok(())
@@ -104,12 +91,39 @@ impl Run{
RunState::Finished{..}=>Err(Error::AlreadyFinished),
}
}
}
#[derive(Clone,Copy,Debug)]
pub struct Run{
state:RunState,
flag_reason:Option<FlagReason>,
}
impl Run{
pub fn new()->Self{
Self{
state:RunState::Created,
flag_reason:None,
}
}
pub fn time(&self,time:PhysicsTime)->Time{
self.state.time(time)
}
pub fn start(&mut self,time:PhysicsTime)->Result<(),Error>{
self.state.start(time)
}
pub fn finish(&mut self,time:PhysicsTime)->Result<(),Error>{
self.state.finish(time)
}
pub fn flag(&mut self,flag_reason:FlagReason){
//don't replace the first reason the run was flagged
if self.flagged.is_none(){
self.flagged=Some(flag_reason);
if self.flag_reason.is_none(){
self.flag_reason=Some(flag_reason);
}
}
pub fn flag_reason(&self)->Option<FlagReason>{
self.flag_reason
}
pub fn get_finish_time(&self)->Option<Time>{
match &self.state{
RunState::Finished{timer}=>Some(timer.time()),

View File

@@ -10,7 +10,7 @@ authors = ["Rhys Lloyd <krakow20@gmail.com>"]
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
strafesnet_common = { version = "0.7.0", path = "../common", registry = "strafesnet" }
strafesnet_common.workspace = true
[lints]
workspace = true

View File

@@ -1,6 +1,6 @@
[package]
name = "fixed_wide"
version = "0.2.1"
version = "0.2.2"
edition = "2024"
repository = "https://git.itzana.me/StrafesNET/strafe-project"
license = "MIT OR Apache-2.0"
@@ -17,7 +17,7 @@ zeroes=["dep:arrayvec"]
bnum = "0.13.0"
arrayvec = { version = "0.7.6", optional = true }
paste = "1.0.15"
ratio_ops = { version = "0.1.0", path = "../ratio_ops", registry = "strafesnet", optional = true }
ratio_ops = { workspace = true, optional = true }
[lints]
workspace = true

View File

@@ -7,7 +7,7 @@ const BNUM_DIGIT_WIDTH:usize=64;
/// N is the number of u64s to use
/// F is the number of fractional bits (always N*32 lol)
pub struct Fixed<const N:usize,const F:usize>{
pub(crate)bits:BInt<{N}>,
bits:BInt<{N}>,
}
impl<const N:usize,const F:usize> Fixed<N,F>{
@@ -545,7 +545,7 @@ impl_shift_operator!( Fixed, Shr, shr, Self );
// wide operators. The result width is the sum of the input widths, i.e. none of the multiplication
#[expect(unused_macros)]
#[allow(unused_macros)]
macro_rules! impl_wide_operators{
($lhs:expr,$rhs:expr)=>{
impl core::ops::Mul<Fixed<$rhs,{$rhs*32}>> for Fixed<$lhs,{$lhs*32}>{

View File

@@ -14,12 +14,12 @@ fixed-wide=["dep:fixed_wide","dep:paste"]
deferred-division=["dep:ratio_ops"]
[dependencies]
ratio_ops = { version = "0.1.0", path = "../ratio_ops", registry = "strafesnet", optional = true }
fixed_wide = { version = "0.2.0", path = "../fixed_wide", registry = "strafesnet", optional = true }
ratio_ops = { workspace = true, optional = true }
fixed_wide = { workspace = true, optional = true }
paste = { version = "1.0.15", optional = true }
[dev-dependencies]
fixed_wide = { path = "../fixed_wide", registry = "strafesnet", features = ["wide-mul"] }
fixed_wide = { workspace = true, features = ["wide-mul"] }
[lints]
workspace = true

View File

@@ -11,13 +11,13 @@ authors = ["Rhys Lloyd <krakow20@gmail.com>"]
[dependencies]
bytemuck = "1.14.3"
glam = "0.30.0"
glam.workspace = true
regex = { version = "1.11.3", default-features = false, features = ["unicode-perl"] }
rbx_mesh = "0.5.0"
rbxassetid = { version = "0.1.0", path = "../rbxassetid", registry = "strafesnet" }
roblox_emulator = { version = "0.5.1", path = "../roblox_emulator", default-features = false, registry = "strafesnet" }
strafesnet_common = { version = "0.7.0", path = "../common", registry = "strafesnet" }
strafesnet_deferred_loader = { version = "0.5.1", path = "../deferred_loader", registry = "strafesnet" }
strafesnet_common.workspace = true
strafesnet_deferred_loader.workspace = true
rbx_binary = "2.0.1"
rbx_dom_weak = "4.1.0"
rbx_reflection = "6.1.0"

View File

@@ -9,6 +9,7 @@ use crate::loader::MeshWithSize;
#[derive(Debug)]
pub enum Error{
ZeroVertices,
RbxMesh(rbx_mesh::mesh::Error)
}
impl std::fmt::Display for Error{
@@ -143,5 +144,8 @@ pub fn convert(roblox_mesh_bytes:crate::data::RobloxMeshBytes)->Result<MeshWithS
for &point in &mesh.unique_pos{
aabb.grow(point);
}
if mesh.unique_vertices.is_empty(){
return Err(Error::ZeroVertices);
}
Ok(MeshWithSize{mesh,size:aabb.size()})
}

View File

@@ -867,6 +867,11 @@ impl PartialMap1<'_>{
deferred_model_deferred_attributes.model.mesh,
deferred_model_deferred_attributes.render
)?;
// If the mesh size is zero we can't auto-scale it, throw it out.
if mesh_size.x==integer::Fixed::ZERO||mesh_size.y==integer::Fixed::ZERO||mesh_size.z==integer::Fixed::ZERO{
print!("[rbx_loader] Mesh with zero size!");
return None;
}
Some(ModelDeferredAttributes{
mesh,
deferred_attributes:deferred_model_deferred_attributes.model.deferred_attributes,

View File

@@ -10,6 +10,7 @@ use strafesnet_common::integer::vec3;
#[derive(Debug)]
pub enum Error{
Block,
ZeroVertices,
MissingVertexId(u32),
Planar64Vec3(strafesnet_common::integer::Planar64TryFromFloatError),
RobloxPhysicsData(rbx_mesh::physics_data::Error),
@@ -104,7 +105,7 @@ fn build_mesh2(
if let Some(normal_id)=normal_agreement_checker.into_agreed_normal(){
polygon_groups_normal_id[normal_id as usize-1].push(face);
}else{
panic!("Empty face!");
print!("[union] Empty face!");
}
}
Ok(())
@@ -158,7 +159,7 @@ fn build_mesh5(
if let Some(normal_id)=normal_agreement_checker.into_agreed_normal(){
polygon_groups_normal_id[normal_id as usize-1].push(face);
}else{
panic!("Empty face!");
print!("[union] Empty face!");
}
}
Ok(())
@@ -265,6 +266,9 @@ pub fn convert(
graphics_groups,
physics_groups,
);
if mesh.unique_vertices.is_empty(){
return Err(Error::ZeroVertices);
}
Ok(MeshWithSize{
mesh,
size:vec3::ONE,

View File

@@ -12,7 +12,7 @@ default=["run-service"]
run-service=[]
[dependencies]
glam = "0.30.0"
glam.workspace = true
mlua = { version = "0.11.3", features = ["luau"] }
phf = { version = "0.13.1", features = ["macros"] }
rbx_dom_weak = "4.1.0"

View File

@@ -1,6 +1,6 @@
[package]
name = "strafesnet_snf"
version = "0.3.1"
version = "0.3.2"
edition = "2024"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
@@ -8,7 +8,7 @@ edition = "2024"
[dependencies]
binrw = "0.15.0"
id = { version = "0.1.0", registry = "strafesnet" }
strafesnet_common = { version = "0.7.0", path = "../common", registry = "strafesnet" }
strafesnet_common.workspace = true
[lints]
workspace = true

View File

@@ -18,10 +18,10 @@ rbx_dom_weak = "4.1.0"
rbx_reflection_database = "2.0.2"
rbx_xml = "2.0.1"
rbxassetid = { version = "0.1.0", registry = "strafesnet" }
strafesnet_bsp_loader = { version = "0.3.1", path = "../lib/bsp_loader", registry = "strafesnet" }
strafesnet_deferred_loader = { version = "0.5.1", path = "../lib/deferred_loader", registry = "strafesnet" }
strafesnet_rbx_loader = { version = "0.7.0", path = "../lib/rbx_loader", registry = "strafesnet" }
strafesnet_snf = { version = "0.3.1", path = "../lib/snf", registry = "strafesnet" }
strafesnet_bsp_loader.workspace = true
strafesnet_deferred_loader.workspace = true
strafesnet_rbx_loader.workspace = true
strafesnet_snf.workspace = true
thiserror = "2.0.11"
tokio = { version = "1.43.0", features = ["macros", "rt-multi-thread", "fs"] }
vbsp = "0.9.1"

View File

@@ -16,18 +16,18 @@ source = ["dep:strafesnet_deferred_loader", "dep:strafesnet_bsp_loader"]
roblox = ["dep:strafesnet_deferred_loader", "dep:strafesnet_rbx_loader"]
[dependencies]
glam = "0.30.0"
glam.workspace = true
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", 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 }
strafesnet_bsp_loader = { workspace = true, optional = true }
strafesnet_common.workspace = true
strafesnet_deferred_loader = { workspace = true, optional = true }
strafesnet_graphics.workspace = true
strafesnet_physics.workspace = true
strafesnet_rbx_loader = { workspace = true, optional = true }
strafesnet_session.workspace = true
strafesnet_settings.workspace = true
strafesnet_snf = { workspace = true, optional = true }
wgpu = "28.0.0"
winit = "0.30.7"

View File

@@ -37,7 +37,8 @@ pub fn new(
config.width=size.width.max(1);
config.height=size.height.max(1);
surface.configure(&device,&config);
graphics.resize(&device,&config,&user_settings);
let fov=user_settings.calculate_fov(1.0,&glam::uvec2(config.width,config.height)).as_vec2();
graphics.resize(&device,&config,fov);
println!("Resize took {:?}",t0.elapsed());
}
Instruction::Render(frame_state)=>{
@@ -56,7 +57,7 @@ pub fn new(
..wgpu::TextureViewDescriptor::default()
});
graphics.render(&view,&device,&queue,frame_state);
graphics.render(&view,&device,&queue,graphics::view_inv(frame_state.pos(),frame_state.angles()));
frame.present();
}

View File

@@ -9,5 +9,5 @@ mod graphics_worker;
const TITLE:&'static str=concat!("Strafe Client v",env!("CARGO_PKG_VERSION"));
fn main(){
setup::setup_and_start(TITLE);
pollster::block_on(setup::setup_and_start(TITLE));
}

View File

@@ -1,199 +1,38 @@
fn optional_features()->wgpu::Features{
wgpu::Features::TEXTURE_COMPRESSION_ASTC
|wgpu::Features::TEXTURE_COMPRESSION_ETC2
}
fn required_features()->wgpu::Features{
wgpu::Features::TEXTURE_COMPRESSION_BC
}
fn required_downlevel_capabilities()->wgpu::DownlevelCapabilities{
wgpu::DownlevelCapabilities{
flags:wgpu::DownlevelFlags::empty(),
shader_model:wgpu::ShaderModel::Sm5,
..wgpu::DownlevelCapabilities::default()
}
}
use strafesnet_graphics::setup;
struct SetupContextPartial1{
backends:wgpu::Backends,
instance:wgpu::Instance,
}
fn create_window(title:&str,event_loop:&winit::event_loop::EventLoop<()>)->Result<winit::window::Window,winit::error::OsError>{
let mut attr=winit::window::WindowAttributes::default();
attr=attr.with_title(title);
event_loop.create_window(attr)
}
fn create_instance()->SetupContextPartial1{
let backends=wgpu::Backends::from_env().unwrap_or_default();
SetupContextPartial1{
backends,
instance:Default::default(),
}
}
impl SetupContextPartial1{
fn create_surface<'a>(self,window:&'a winit::window::Window)->Result<SetupContextPartial2<'a>,wgpu::CreateSurfaceError>{
Ok(SetupContextPartial2{
backends:self.backends,
surface:self.instance.create_surface(window)?,
instance:self.instance,
})
}
}
struct SetupContextPartial2<'a>{
backends:wgpu::Backends,
instance:wgpu::Instance,
surface:wgpu::Surface<'a>,
}
impl<'a> SetupContextPartial2<'a>{
fn pick_adapter(self)->SetupContextPartial3<'a>{
let adapter;
//TODO: prefer adapter that implements optional features
//let optional_features=optional_features();
let required_features=required_features();
//no helper function smh gotta write it myself
let adapters=pollster::block_on(self.instance.enumerate_adapters(self.backends));
let mut chosen_adapter=None;
let mut chosen_adapter_score=0;
for adapter in adapters {
if !adapter.is_surface_supported(&self.surface) {
continue;
}
let score=match adapter.get_info().device_type{
wgpu::DeviceType::IntegratedGpu=>3,
wgpu::DeviceType::DiscreteGpu=>4,
wgpu::DeviceType::VirtualGpu=>2,
wgpu::DeviceType::Other|wgpu::DeviceType::Cpu=>1,
};
let adapter_features=adapter.features();
if chosen_adapter_score<score&&adapter_features.contains(required_features) {
chosen_adapter_score=score;
chosen_adapter=Some(adapter);
}
}
if let Some(maybe_chosen_adapter)=chosen_adapter{
adapter=maybe_chosen_adapter;
}else{
panic!("No suitable GPU adapters found on the system!");
}
let adapter_info=adapter.get_info();
println!("Using {} ({:?})", adapter_info.name, adapter_info.backend);
let required_downlevel_capabilities=required_downlevel_capabilities();
let downlevel_capabilities=adapter.get_downlevel_capabilities();
assert!(
downlevel_capabilities.shader_model >= required_downlevel_capabilities.shader_model,
"Adapter does not support the minimum shader model required to run this example: {:?}",
required_downlevel_capabilities.shader_model
);
assert!(
downlevel_capabilities
.flags
.contains(required_downlevel_capabilities.flags),
"Adapter does not support the downlevel capabilities required to run this example: {:?}",
required_downlevel_capabilities.flags - downlevel_capabilities.flags
);
SetupContextPartial3{
surface:self.surface,
adapter,
}
}
}
struct SetupContextPartial3<'a>{
surface:wgpu::Surface<'a>,
adapter:wgpu::Adapter,
}
impl<'a> SetupContextPartial3<'a>{
fn request_device(self)->SetupContextPartial4<'a>{
let optional_features=optional_features();
let required_features=required_features();
// Make sure we use the texture resolution limits from the adapter, so we can support images the size of the surface.
let needed_limits=strafesnet_graphics::graphics::required_limits().using_resolution(self.adapter.limits());
let (device, queue)=pollster::block_on(self.adapter
.request_device(
&wgpu::DeviceDescriptor{
label:None,
required_features:(optional_features&self.adapter.features())|required_features,
required_limits:needed_limits,
memory_hints:wgpu::MemoryHints::Performance,
trace:wgpu::Trace::Off,
experimental_features:wgpu::ExperimentalFeatures::disabled(),
},
))
.expect("Unable to find a suitable GPU adapter!");
SetupContextPartial4{
surface:self.surface,
adapter:self.adapter,
device,
queue,
}
}
}
struct SetupContextPartial4<'a>{
surface:wgpu::Surface<'a>,
adapter:wgpu::Adapter,
device:wgpu::Device,
queue:wgpu::Queue,
}
impl<'a> SetupContextPartial4<'a>{
fn configure_surface(self,size:&'a winit::dpi::PhysicalSize<u32>)->SetupContext<'a>{
let mut config=self.surface
.get_default_config(&self.adapter, size.width, size.height)
.expect("Surface isn't supported by the adapter.");
let surface_view_format=config.format.add_srgb_suffix();
config.view_formats.push(surface_view_format);
config.present_mode=wgpu::PresentMode::AutoNoVsync;
self.surface.configure(&self.device, &config);
SetupContext{
surface:self.surface,
device:self.device,
queue:self.queue,
config,
}
}
}
pub struct SetupContext<'a>{
pub surface:wgpu::Surface<'a>,
pub device:wgpu::Device,
pub queue:wgpu::Queue,
pub config:wgpu::SurfaceConfiguration,
}
pub fn setup_and_start(title:&str){
pub async fn setup_and_start(title:&str){
let event_loop=winit::event_loop::EventLoop::new().unwrap();
println!("Initializing the surface...");
let partial_1=create_instance();
let window=create_window(title,&event_loop).unwrap();
let partial_2=partial_1.create_surface(&window).unwrap();
println!("Initializing the surface...");
let partial_3=partial_2.pick_adapter();
let instance=setup::step1::create_instance();
let partial_4=partial_3.request_device();
let surface=setup::step2::create_surface(&instance,&window).unwrap();
let adapter=setup::step3::pick_adapter(&instance,&surface).await.expect("No suitable GPU adapters found on the system!");
let (device,queue)=setup::step4::request_device(&adapter).await;
let size=window.inner_size();
let setup_context=partial_4.configure_surface(&size);
let config=setup::step5::configure_surface(&adapter,&device,&surface,(size.width,size.height));
//dedicated thread to ping request redraw back and resize the window doesn't seem logical
//the thread that spawns the physics thread
let mut window_thread=crate::window::worker(
&window,
setup_context,
device,
queue,
surface,
config,
);
for arg in std::env::args().skip(1){

View File

@@ -239,7 +239,10 @@ impl WindowContext<'_>{
}
pub fn worker<'a>(
window:&'a winit::window::Window,
setup_context:crate::setup::SetupContext<'a>,
device:wgpu::Device,
queue:wgpu::Queue,
surface:wgpu::Surface<'a>,
config:wgpu::SurfaceConfiguration,
)->crate::compat_worker::QNWorker<'a,TimedInstruction<Instruction,SessionTime>>{
// WindowContextSetup::new
#[cfg(feature="user-install")]
@@ -249,12 +252,13 @@ pub fn worker<'a>(
let user_settings=directories.settings();
let mut graphics=strafesnet_graphics::graphics::GraphicsState::new(&setup_context.device,&setup_context.queue,&setup_context.config);
graphics.load_user_settings(&user_settings);
let mut graphics=strafesnet_graphics::graphics::GraphicsState::new(&device,&queue,&config);
//WindowContextSetup::into_context
let screen_size=glam::uvec2(setup_context.config.width,setup_context.config.height);
let graphics_thread=crate::graphics_worker::new(graphics,setup_context.config,setup_context.surface,setup_context.device,setup_context.queue);
let screen_size=glam::uvec2(config.width,config.height);
let fov=user_settings.calculate_fov(1.0,&screen_size).as_vec2();
graphics.resize(&device,&config,fov);
let graphics_thread=crate::graphics_worker::new(graphics,config,surface,device,queue);
let mut window_context=WindowContext{
manual_mouse_lock:false,
mouse_pos:glam::DVec2::ZERO,