Compare commits

...

9 Commits

11 changed files with 786 additions and 453 deletions

814
Cargo.lock generated

File diff suppressed because it is too large Load Diff

@ -18,8 +18,10 @@ rbx_binary = "0.7.1"
rbx_dom_weak = "2.5.0"
rbx_reflection_database = "0.2.7"
rbx_xml = "0.13.1"
wgpu = "0.18.0"
winit = { version = "0.29.2", features = ["rwh_05"] }
vbsp = "0.5.0"
vmdl = "0.1.1"
wgpu = "0.19.0"
winit = { version = "0.29.2" }
#[profile.release]
#lto = true

@ -72,9 +72,9 @@ fn generate_bvh_node(boxen:Vec<(usize,Aabb)>)->BvhNode{
sort_y.push((*i,center.y()));
sort_z.push((*i,center.z()));
}
sort_x.sort_by(|tup0,tup1|tup0.1.partial_cmp(&tup1.1).unwrap());
sort_y.sort_by(|tup0,tup1|tup0.1.partial_cmp(&tup1.1).unwrap());
sort_z.sort_by(|tup0,tup1|tup0.1.partial_cmp(&tup1.1).unwrap());
sort_x.sort_by(|tup0,tup1|tup0.1.cmp(&tup1.1));
sort_y.sort_by(|tup0,tup1|tup0.1.cmp(&tup1.1));
sort_z.sort_by(|tup0,tup1|tup0.1.cmp(&tup1.1));
let h=n/2;
let median_x=sort_x[h].1;
let median_y=sort_y[h].1;

@ -152,11 +152,14 @@ impl GraphicsState{
let mut texture_loading_threads=Vec::new();
let num_textures=indexed_models.textures.len();
for (i,texture_id) in indexed_models.textures.into_iter().enumerate(){
if let Ok(mut file) = std::fs::File::open(std::path::Path::new(&format!("textures/{}.dds",texture_id))){
let path=std::path::PathBuf::from(format!("textures/{}.dds",texture_id));
if let Ok(mut file) = std::fs::File::open(path.clone()){
double_map.insert(i as u32, texture_loading_threads.len() as u32);
texture_loading_threads.push((texture_id,std::thread::spawn(move ||{
ddsfile::Dds::read(&mut file).unwrap()
})));
}else{
//println!("missing texture path={:?}",path);
}
}
@ -200,6 +203,7 @@ impl GraphicsState{
label: Some(format!("Texture{}",texture_id).as_str()),
view_formats: &[],
},
wgpu::util::TextureDataOrder::LayerMajor,
&image.data,
);
texture.create_view(&wgpu::TextureViewDescriptor {
@ -697,6 +701,7 @@ impl GraphicsState{
label: Some("Skybox Texture"),
view_formats: &[],
},
wgpu::util::TextureDataOrder::LayerMajor,
&skybox_image.data,
);
@ -737,6 +742,7 @@ impl GraphicsState{
label: Some("Squid Texture"),
view_formats: &[],
},
wgpu::util::TextureDataOrder::LayerMajor,
&image.data,
);

@ -18,7 +18,7 @@ WorkerDescription{
pub fn new<'a>(
mut graphics:crate::graphics::GraphicsState,
mut config:wgpu::SurfaceConfiguration,
surface:wgpu::Surface,
surface:wgpu::Surface<'a>,
device:wgpu::Device,
queue:wgpu::Queue,
)->crate::compat_worker::INWorker<'a,Instruction>{

@ -459,26 +459,8 @@ pub enum Planar64TryFromFloatError{
Nan,
Infinite,
Subnormal,
HighlyNegativeExponent(i16),
HighlyPositiveExponent(i16),
}
#[inline]
fn planar64_from_mes((m,e,s):(u64,i16,i8))->Result<Planar64,Planar64TryFromFloatError>{
let e32=e+32;
if e32<0&&(m>>-e32)==0{//shifting m will underflow to 0
Ok(Planar64::ZERO)
// println!("m{} e{} s{}",m,e,s);
// println!("f={}",(m as f64)*(2.0f64.powf(e as f64))*(s as f64));
// Err(Planar64TryFromFloatError::HighlyNegativeExponent(e))
}else if (64-m.leading_zeros() as i16)+e32<64{//shifting m will not overflow
if e32<0{
Ok(Planar64((m as i64)*(s as i64)>>-e32))
}else{
Ok(Planar64((m as i64)*(s as i64)<<e32))
}
}else{//if shifting m will overflow (prev check failed)
Err(Planar64TryFromFloatError::HighlyPositiveExponent(e))
}
HighlyNegativeExponent,
HighlyPositiveExponent,
}
impl TryFrom<f32> for Planar64{
type Error=Planar64TryFromFloatError;
@ -489,7 +471,14 @@ impl TryFrom<f32> for Planar64{
std::num::FpCategory::Infinite=>Err(Self::Error::Infinite),
std::num::FpCategory::Zero=>Ok(Self::ZERO),
std::num::FpCategory::Subnormal
|std::num::FpCategory::Normal=>planar64_from_mes(integer_decode_f32(value)),
|std::num::FpCategory::Normal=>{
let planar=value*PLANAR64_ONE_FLOAT32;
if planar<(i64::MIN as f32)||(i64::MAX as f32)<planar{
Err(Self::Error::HighlyPositiveExponent)
}else{
Ok(Planar64(unsafe{planar.to_int_unchecked()}))
}
}
}
}
}
@ -502,7 +491,14 @@ impl TryFrom<f64> for Planar64{
std::num::FpCategory::Infinite=>Err(Self::Error::Infinite),
std::num::FpCategory::Zero=>Ok(Self::ZERO),
std::num::FpCategory::Subnormal
|std::num::FpCategory::Normal=>planar64_from_mes(integer_decode_f64(value)),
|std::num::FpCategory::Normal=>{
let planar=value*PLANAR64_ONE_FLOAT64;
if planar<(i64::MIN as f64)||(i64::MAX as f64)<planar{
Err(Self::Error::HighlyPositiveExponent)
}else{
Ok(Planar64(unsafe{planar.to_int_unchecked()}))
}
}
}
}
}

235
src/load_bsp.rs Normal file

@ -0,0 +1,235 @@
const VALVE_SCALE:f32=1.0/16.0;
fn valve_transform(v:[f32;3])->crate::integer::Planar64Vec3{
crate::integer::Planar64Vec3::try_from([v[0]*VALVE_SCALE,v[2]*VALVE_SCALE,-v[1]*VALVE_SCALE]).unwrap()
}
pub fn generate_indexed_models<R:std::io::Read+std::io::Seek>(input:&mut R)->Result<crate::model::IndexedModelInstances,vbsp::BspError>{
let mut s=Vec::new();
match input.read_to_end(&mut s){
Ok(_)=>(),
Err(e)=>println!("load_bsp::generate_indexed_models read_to_end failed: {:?}",e),
}
match vbsp::Bsp::read(s.as_slice()){
Ok(bsp)=>{
let mut spawn_point=crate::integer::Planar64Vec3::ZERO;
let vertices: Vec<_> = bsp
.vertices
.iter()
.map(|vertex|<[f32;3]>::from(vertex.position))
.collect();
let mut name_from_texture_id=Vec::new();
let mut texture_id_from_name=std::collections::HashMap::new();
let mut models=bsp.models().map(|world_model|{
//non-deduplicated
let mut spam_pos=Vec::new();
let mut spam_tex=Vec::new();
let mut spam_normal=Vec::new();
let mut spam_vertices=Vec::new();
let groups=world_model.faces()
.filter(|face| face.is_visible())//TODO: look at this
.map(|face|{
let face_texture=face.texture();
let face_texture_data=face_texture.texture_data();
let (texture_u,texture_v)=(glam::Vec3A::from_slice(&face_texture.texture_transforms_u[0..3]),glam::Vec3A::from_slice(&face_texture.texture_transforms_v[0..3]));
let texture_offset=glam::vec2(face_texture.texture_transforms_u[3],face_texture.texture_transforms_v[3]);
let texture_size=glam::vec2(face_texture_data.width as f32,face_texture_data.height as f32);
//texture
let texture_id=if let Some(&texture_id)=texture_id_from_name.get(face_texture_data.name()){
texture_id
}else{
let texture_id=name_from_texture_id.len() as u32;
texture_id_from_name.insert(face_texture_data.name().to_string(),texture_id);
name_from_texture_id.push(face_texture_data.name().to_string());
texture_id
};
//normal
let normal=face.normal();
let normal_idx=spam_normal.len() as u32;
spam_normal.push(valve_transform(<[f32;3]>::from(normal)));
let mut vertices:Vec<u32>=face.vertex_indexes().map(|vertex_index|{
let pos=glam::Vec3A::from_array(vertices[vertex_index as usize]);
let pos_idx=spam_pos.len();
spam_pos.push(valve_transform(vertices[vertex_index as usize]));
//calculate texture coordinates
let tex=(glam::vec2(pos.dot(texture_u),pos.dot(texture_v))+texture_offset)/texture_size;
let tex_idx=spam_tex.len() as u32;
spam_tex.push(tex);
let i=spam_vertices.len() as u32;
spam_vertices.push(crate::model::IndexedVertex{
pos: pos_idx as u32,
tex: tex_idx as u32,
normal: normal_idx,
color: 0,
});
i
}).collect();
vertices.reverse();
crate::model::IndexedGroup{
texture:Some(texture_id),
polys:vec![crate::model::IndexedPolygon{vertices}],
}
}).collect();
crate::model::IndexedModel{
unique_pos:spam_pos,
unique_tex:spam_tex,
unique_normal:spam_normal,
unique_color:vec![glam::Vec4::ONE],
unique_vertices:spam_vertices,
groups,
instances:vec![crate::model::ModelInstance{
attributes:crate::model::CollisionAttributes::Decoration,
transform:crate::integer::Planar64Affine3::new(
crate::integer::Planar64Mat3::default(),
valve_transform(<[f32;3]>::from(world_model.origin))
),
..Default::default()
}],
}
}).collect();
//dedupe prop models
let mut model_dedupe=std::collections::HashSet::new();
for prop in bsp.static_props(){
model_dedupe.insert(prop.model());
}
//generate unique meshes
let mut model_map=std::collections::HashMap::with_capacity(model_dedupe.len());
let mut prop_models=Vec::new();
for model_name in model_dedupe{
let model_name_lower=model_name.to_lowercase();
//.mdl, .vvd, .dx90.vtx
let mut path=std::path::PathBuf::from(model_name_lower.as_str());
let file_name=std::path::PathBuf::from(path.file_stem().unwrap());
path.pop();
path.push(file_name);
let mut vvd_path=path.clone();
let mut vtx_path=path.clone();
vvd_path.set_extension("vvd");
vtx_path.set_extension("dx90.vtx");
match (bsp.pack.get(model_name_lower.as_str()),bsp.pack.get(vvd_path.as_os_str().to_str().unwrap()),bsp.pack.get(vtx_path.as_os_str().to_str().unwrap())){
(Ok(Some(mdl_file)),Ok(Some(vvd_file)),Ok(Some(vtx_file)))=>{
match (vmdl::mdl::Mdl::read(mdl_file.as_ref()),vmdl::vvd::Vvd::read(vvd_file.as_ref()),vmdl::vtx::Vtx::read(vtx_file.as_ref())){
(Ok(mdl),Ok(vvd),Ok(vtx))=>{
let model=vmdl::Model::from_parts(mdl,vtx,vvd);
let texture_paths=model.texture_directories();
if texture_paths.len()!=1{
println!("WARNING: multiple texture paths");
}
let skin=model.skin_tables().nth(0).unwrap();
let mut spam_pos=Vec::with_capacity(model.vertices().len());
let mut spam_normal=Vec::with_capacity(model.vertices().len());
let mut spam_tex=Vec::with_capacity(model.vertices().len());
let mut spam_vertices=Vec::with_capacity(model.vertices().len());
for (i,vertex) in model.vertices().iter().enumerate(){
spam_pos.push(valve_transform(<[f32;3]>::from(vertex.position)));
spam_normal.push(valve_transform(<[f32;3]>::from(vertex.normal)));
spam_tex.push(glam::Vec2::from_array(vertex.texture_coordinates));
spam_vertices.push(crate::model::IndexedVertex{
pos:i as u32,
tex:i as u32,
normal:i as u32,
color:0,
});
}
let model_id=prop_models.len();
model_map.insert(model_name,model_id);
prop_models.push(crate::model::IndexedModel{
unique_pos:spam_pos,
unique_normal:spam_normal,
unique_tex:spam_tex,
unique_color:vec![glam::Vec4::ONE],
unique_vertices:spam_vertices,
groups:model.meshes().map(|mesh|{
let texture=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);
let texture_location=path.as_os_str().to_str().unwrap();
let texture_id=if let Some(&texture_id)=texture_id_from_name.get(texture_location){
texture_id
}else{
println!("texture! {}",texture_location);
let texture_id=name_from_texture_id.len() as u32;
texture_id_from_name.insert(texture_location.to_string(),texture_id);
name_from_texture_id.push(texture_location.to_string());
texture_id
};
Some(texture_id)
}else{
None
};
crate::model::IndexedGroup{
texture,
polys:{
//looking at the code, it would seem that the strips are pre-deindexed into triangle lists when calling this function
mesh.vertex_strip_indices().map(|strip|{
strip.collect::<Vec<usize>>().chunks(3).map(|tri|{
crate::model::IndexedPolygon{vertices:vec![tri[0] as u32,tri[1] as u32,tri[2] as u32]}
}).collect::<Vec<crate::model::IndexedPolygon>>()
}).flatten().collect()
},
}
}).collect(),
instances:Vec::new(),
});
},
_=>println!("model_name={} error",model_name),
}
},
_=>println!("no model name={}",model_name),
}
}
//generate model instances
for prop in bsp.static_props(){
let placement=prop.as_prop_placement();
if let Some(&model_index)=model_map.get(placement.model){
prop_models[model_index].instances.push(crate::model::ModelInstance{
transform:crate::integer::Planar64Affine3::new(
crate::integer::Planar64Mat3::try_from(
glam::Mat3A::from_diagonal(glam::Vec3::splat(placement.scale))
//TODO: figure this out
*glam::Mat3A::from_quat(glam::Quat::from_xyzw(
placement.rotation.v.x,//b
placement.rotation.v.y,//c
placement.rotation.v.z,//d
placement.rotation.s,//a
))
).unwrap(),
valve_transform(<[f32;3]>::from(placement.origin)),
),
attributes:crate::model::CollisionAttributes::Decoration,
..Default::default()
});
}else{
//println!("model not found {}",placement.model);
}
}
//actually add the prop models
prop_models.append(&mut models);
Ok(crate::model::IndexedModelInstances{
textures:name_from_texture_id,
models:prop_models,
spawn_point,
modes:Vec::new(),
})
},
Err(e)=>{
println!("rotten {:?}",e);
Err(e)
},
}
}

@ -14,12 +14,15 @@ fn class_is_a(class: &str, superclass: &str) -> bool {
return false
}
fn recursive_collect_superclass(objects: &mut std::vec::Vec<rbx_dom_weak::types::Ref>,dom: &rbx_dom_weak::WeakDom, instance: &rbx_dom_weak::Instance, superclass: &str){
for &referent in instance.children() {
if let Some(c) = dom.get_by_ref(referent) {
if class_is_a(c.class.as_str(), superclass) {
objects.push(c.referent());//copy ref
let mut stack=vec![instance];
while let Some(item)=stack.pop(){
for &referent in item.children(){
if let Some(c)=dom.get_by_ref(referent){
if class_is_a(c.class.as_str(),superclass){
objects.push(c.referent());//copy ref
}
stack.push(c);
}
recursive_collect_superclass(objects,dom,c,superclass);
}
}
}
@ -262,6 +265,18 @@ pub fn generate_indexed_models(dom:rbx_dom_weak::WeakDom) -> crate::model::Index
{
let model_transform=planar64_affine3_from_roblox(cf,size);
if model_transform.matrix3.determinant()==Planar64::ZERO{
let mut parent_ref=object.parent();
let mut full_path=object.name.clone();
while let Some(parent)=dom.get_by_ref(parent_ref){
full_path=format!("{}.{}",parent.name,full_path);
parent_ref=parent.parent();
}
println!("Zero determinant CFrame at location {}",full_path);
println!("matrix3:{}",model_transform.matrix3);
continue;
}
//push TempIndexedAttributes
let mut force_intersecting=false;
let mut temp_indexing_attributes=Vec::new();

@ -11,6 +11,7 @@ mod graphics;
mod settings;
mod primitives;
mod instruction;
mod load_bsp;
mod load_roblox;
mod face_crawler;
mod compat_worker;
@ -46,7 +47,7 @@ fn load_file(path: std::path::PathBuf)->Option<model::IndexedModelInstances>{
},
}
},
//b"VBSP"=>Some(load_bsp::generate_indexed_models(input)),
b"VBSP"=>load_bsp::generate_indexed_models(&mut input).ok(),
//b"SNFM"=>Some(sniffer::generate_indexed_models(input)),
//b"SNFB"=>Some(sniffer::load_bot(input)),
other=>{
@ -116,6 +117,5 @@ pub fn default_models()->model::IndexedModelInstances{
}
fn main(){
let context=setup::setup(format!("Strafe Client v{}",env!("CARGO_PKG_VERSION")).as_str());
context.start();//creates and runs a window context
setup::setup_and_start(format!("Strafe Client v{}",env!("CARGO_PKG_VERSION")));
}

@ -46,21 +46,21 @@ fn create_instance()->SetupContextPartial1{
}
}
impl SetupContextPartial1{
fn create_surface(self,window:&winit::window::Window)->Result<SetupContextPartial2,wgpu::CreateSurfaceError>{
fn create_surface<'a>(self,window:&'a winit::window::Window)->Result<SetupContextPartial2<'a>,wgpu::CreateSurfaceError>{
Ok(SetupContextPartial2{
backends:self.backends,
surface:unsafe{self.instance.create_surface(window)}?,
surface:self.instance.create_surface(window)?,
instance:self.instance,
})
}
}
struct SetupContextPartial2{
struct SetupContextPartial2<'a>{
backends:wgpu::Backends,
instance:wgpu::Instance,
surface:wgpu::Surface,
surface:wgpu::Surface<'a>,
}
impl SetupContextPartial2{
fn pick_adapter(self)->SetupContextPartial3{
impl<'a> SetupContextPartial2<'a>{
fn pick_adapter(self)->SetupContextPartial3<'a>{
let adapter;
//TODO: prefer adapter that implements optional features
@ -122,13 +122,13 @@ impl SetupContextPartial2{
}
}
}
struct SetupContextPartial3{
struct SetupContextPartial3<'a>{
instance:wgpu::Instance,
surface:wgpu::Surface,
surface:wgpu::Surface<'a>,
adapter:wgpu::Adapter,
}
impl SetupContextPartial3{
fn request_device(self)->SetupContextPartial4{
impl<'a> SetupContextPartial3<'a>{
fn request_device(self)->SetupContextPartial4<'a>{
let optional_features=optional_features();
let required_features=required_features();
@ -140,8 +140,8 @@ impl SetupContextPartial3{
.request_device(
&wgpu::DeviceDescriptor {
label: None,
features: (optional_features & self.adapter.features()) | required_features,
limits: needed_limits,
required_features: (optional_features & self.adapter.features()) | required_features,
required_limits: needed_limits,
},
trace_dir.ok().as_ref().map(std::path::Path::new),
))
@ -156,15 +156,15 @@ impl SetupContextPartial3{
}
}
}
struct SetupContextPartial4{
struct SetupContextPartial4<'a>{
instance:wgpu::Instance,
surface:wgpu::Surface,
surface:wgpu::Surface<'a>,
adapter:wgpu::Adapter,
device:wgpu::Device,
queue:wgpu::Queue,
}
impl SetupContextPartial4{
fn configure_surface(self,size:&winit::dpi::PhysicalSize<u32>)->SetupContext{
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.");
@ -182,65 +182,42 @@ impl SetupContextPartial4{
}
}
}
pub struct SetupContext{
pub struct SetupContext<'a>{
pub instance:wgpu::Instance,
pub surface:wgpu::Surface,
pub surface:wgpu::Surface<'a>,
pub device:wgpu::Device,
pub queue:wgpu::Queue,
pub config:wgpu::SurfaceConfiguration,
}
pub fn setup(title:&str)->SetupContextSetup{
pub fn setup_and_start(title:String){
let event_loop=winit::event_loop::EventLoop::new().unwrap();
let window=create_window(title,&event_loop).unwrap();
println!("Initializing the surface...");
let partial_1=create_instance();
let window=create_window(title.as_str(),&event_loop).unwrap();
let partial_2=partial_1.create_surface(&window).unwrap();
let partial_3=partial_2.pick_adapter();
let partial_4=partial_3.request_device();
SetupContextSetup{
window,
event_loop,
partial_context:partial_4,
}
}
let size=window.inner_size();
pub struct SetupContextSetup{
window:winit::window::Window,
event_loop:winit::event_loop::EventLoop<()>,
partial_context:SetupContextPartial4,
}
let setup_context=partial_4.configure_surface(&size);
impl SetupContextSetup{
fn into_split(self)->(winit::window::Window,winit::event_loop::EventLoop<()>,SetupContext){
let size=self.window.inner_size();
//Steal values and drop self
(
self.window,
self.event_loop,
self.partial_context.configure_surface(&size),
)
}
pub fn start(self){
let (window,event_loop,setup_context)=self.into_split();
//dedicated thread to ping request redraw back and resize the window doesn't seem logical
//dedicated thread to ping request redraw back and resize the window doesn't seem logical
let window=crate::window::WindowContextSetup::new(&setup_context,&window);
//the thread that spawns the physics thread
let window_thread=window.into_worker(setup_context);
let window=crate::window::WindowContextSetup::new(&setup_context,window);
//the thread that spawns the physics thread
let window_thread=window.into_worker(setup_context);
println!("Entering event loop...");
let root_time=std::time::Instant::now();
run_event_loop(event_loop,window_thread,root_time).unwrap();
}
println!("Entering event loop...");
let root_time=std::time::Instant::now();
run_event_loop(event_loop,window_thread,root_time).unwrap();
}
fn run_event_loop(

@ -15,7 +15,7 @@ struct WindowContext<'a>{
mouse:crate::physics::MouseState,//std::sync::Arc<std::sync::Mutex<>>
screen_size:glam::UVec2,
user_settings:crate::settings::UserSettings,
window:winit::window::Window,
window:&'a winit::window::Window,
physics_thread:crate::compat_worker::QNWorker<'a, TimedInstruction<crate::physics_worker::Instruction>>,
}
@ -158,15 +158,15 @@ impl WindowContext<'_>{
}
}
pub struct WindowContextSetup{
pub struct WindowContextSetup<'a>{
user_settings:crate::settings::UserSettings,
window:winit::window::Window,
window:&'a winit::window::Window,
physics:crate::physics::PhysicsState,
graphics:crate::graphics::GraphicsState,
}
impl WindowContextSetup{
pub fn new(context:&crate::setup::SetupContext,window:winit::window::Window)->Self{
impl<'a> WindowContextSetup<'a>{
pub fn new(context:&crate::setup::SetupContext,window:&'a winit::window::Window)->Self{
//wee
let user_settings=crate::settings::read_user_settings();
@ -194,7 +194,7 @@ impl WindowContextSetup{
}
}
fn into_context<'a>(self,setup_context:crate::setup::SetupContext)->WindowContext<'a>{
fn into_context(self,setup_context:crate::setup::SetupContext<'a>)->WindowContext<'a>{
let screen_size=glam::uvec2(setup_context.config.width,setup_context.config.height);
let graphics_thread=crate::graphics_worker::new(self.graphics,setup_context.config,setup_context.surface,setup_context.device,setup_context.queue);
WindowContext{
@ -208,7 +208,7 @@ impl WindowContextSetup{
}
}
pub fn into_worker<'a>(self,setup_context:crate::setup::SetupContext)->crate::compat_worker::QNWorker<'a,TimedInstruction<WindowInstruction>>{
pub fn into_worker(self,setup_context:crate::setup::SetupContext<'a>)->crate::compat_worker::QNWorker<'a,TimedInstruction<WindowInstruction>>{
let mut window_context=self.into_context(setup_context);
crate::compat_worker::QNWorker::new(move |ins:TimedInstruction<WindowInstruction>|{
match ins.instruction{