Compare commits

..

3 Commits

60 changed files with 1554 additions and 2116 deletions

705
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -38,7 +38,6 @@ unexpected_cfgs = "warn"
[workspace.dependencies] [workspace.dependencies]
glam = "0.32.0" glam = "0.32.0"
wgpu = "29.0.0"
# engine # engine
strafesnet_graphics = { path = "engine/graphics", registry = "strafesnet" } strafesnet_graphics = { path = "engine/graphics", registry = "strafesnet" }
@@ -50,8 +49,8 @@ strafesnet_settings = { path = "engine/settings", registry = "strafesnet" }
fixed_wide = { version = "0.2.2", path = "lib/fixed_wide", registry = "strafesnet" } fixed_wide = { version = "0.2.2", path = "lib/fixed_wide", registry = "strafesnet" }
linear_ops = { version = "0.1.1", path = "lib/linear_ops", 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" } ratio_ops = { version = "0.1.0", path = "lib/ratio_ops", registry = "strafesnet" }
strafesnet_bsp_loader = { version = "0.5.0", path = "lib/bsp_loader", registry = "strafesnet" } strafesnet_bsp_loader = { version = "0.4.0", path = "lib/bsp_loader", registry = "strafesnet" }
strafesnet_common = { version = "0.8.7", path = "lib/common", registry = "strafesnet" } strafesnet_common = { version = "0.8.6", path = "lib/common", registry = "strafesnet" }
strafesnet_deferred_loader = { version = "0.6.0", path = "lib/deferred_loader", registry = "strafesnet" } strafesnet_deferred_loader = { version = "0.6.0", path = "lib/deferred_loader", registry = "strafesnet" }
strafesnet_rbx_loader = { version = "0.10.2", path = "lib/rbx_loader", registry = "strafesnet" } strafesnet_rbx_loader = { version = "0.8.0", path = "lib/rbx_loader", registry = "strafesnet" }
strafesnet_snf = { version = "0.3.2", path = "lib/snf", registry = "strafesnet" } strafesnet_snf = { version = "0.3.2", path = "lib/snf", registry = "strafesnet" }

View File

@@ -1,6 +1,6 @@
[package] [package]
name = "strafesnet_graphics" name = "strafesnet_graphics"
version = "0.0.8" version = "0.0.4"
edition = "2024" edition = "2024"
[dependencies] [dependencies]
@@ -9,7 +9,7 @@ ddsfile = "0.5.1"
glam.workspace = true glam.workspace = true
id = { version = "0.1.0", registry = "strafesnet" } id = { version = "0.1.0", registry = "strafesnet" }
strafesnet_common.workspace = true strafesnet_common.workspace = true
wgpu.workspace = true wgpu = "28.0.0"
[lints] [lints]
workspace = true workspace = true

View File

@@ -44,11 +44,11 @@ struct ModelInstance{
transform:mat4x4<f32>, transform:mat4x4<f32>,
normal_transform:mat3x3<f32>, normal_transform:mat3x3<f32>,
color:vec4<f32>, color:vec4<f32>,
texture_color:vec4<f32>,
} }
//my fancy idea is to create a megatexture for each model that includes all the textures each intance will need //my fancy idea is to create a megatexture for each model that includes all the textures each intance will need
//the texture transform then maps the texture coordinates to the location of the specific texture //the texture transform then maps the texture coordinates to the location of the specific texture
//group 1 is the model //group 1 is the model
const MAX_MODEL_INSTANCES=512;
@group(2) @group(2)
@binding(0) @binding(0)
var<uniform> model_instances: array<ModelInstance, MAX_MODEL_INSTANCES>; var<uniform> model_instances: array<ModelInstance, MAX_MODEL_INSTANCES>;
@@ -66,7 +66,6 @@ struct EntityOutputTexture {
@location(3) view: vec3<f32>, @location(3) view: vec3<f32>,
@location(4) color: vec4<f32>, @location(4) color: vec4<f32>,
@location(5) @interpolate(flat) model_color: vec4<f32>, @location(5) @interpolate(flat) model_color: vec4<f32>,
@location(6) @interpolate(flat) texture_color: vec4<f32>,
}; };
@vertex @vertex
fn vs_entity_texture( fn vs_entity_texture(
@@ -82,7 +81,6 @@ fn vs_entity_texture(
result.texture = texture; result.texture = texture;
result.color = color; result.color = color;
result.model_color = model_instances[instance].color; result.model_color = model_instances[instance].color;
result.texture_color = model_instances[instance].texture_color;
result.view = position.xyz - camera.view_inv[3].xyz;//col(3) result.view = position.xyz - camera.view_inv[3].xyz;//col(3)
result.position = camera.proj * camera.view * position; result.position = camera.proj * camera.view * position;
return result; return result;
@@ -108,9 +106,7 @@ fn fs_entity_texture(vertex: EntityOutputTexture) -> @location(0) vec4<f32> {
let d = dot(normal, incident); let d = dot(normal, incident);
let reflected = incident - 2.0 * d * normal; let reflected = incident - 2.0 * d * normal;
let fragment_color = textureSample(model_texture, model_sampler, vertex.texture)*vertex.texture_color; let fragment_color = textureSample(model_texture, model_sampler, vertex.texture)*vertex.color;
let reflected_color = textureSample(cube_texture, cube_sampler, reflected).rgb; let reflected_color = textureSample(cube_texture, cube_sampler, reflected).rgb;
let entity_color = mix(vertex.model_color*vertex.color,vec4<f32>(fragment_color.rgb,1.0),fragment_color.a); return mix(vec4<f32>(vec3<f32>(0.05) + 0.2 * reflected_color,1.0),mix(vertex.model_color,vec4<f32>(fragment_color.rgb,1.0),fragment_color.a),0.5+0.5*abs(d));
let sky_color = vec4<f32>(vec3<f32>(0.05) + 0.2 * reflected_color,1.0);
return mix(sky_color,entity_color,0.5+0.5*abs(d));
} }

View File

@@ -3,7 +3,7 @@ use std::collections::{HashSet,HashMap};
use strafesnet_common::map; use strafesnet_common::map;
use strafesnet_common::model::{self, ColorId, NormalId, PolygonIter, PositionId, RenderConfigId, TextureCoordinateId, VertexId}; use strafesnet_common::model::{self, ColorId, NormalId, PolygonIter, PositionId, RenderConfigId, TextureCoordinateId, VertexId};
use wgpu::{util::DeviceExt,AstcBlock,AstcChannel}; use wgpu::{util::DeviceExt,AstcBlock,AstcChannel};
use crate::model::{self as model_graphics,IndexedGraphicsMeshOwnedRenderConfig,IndexedGraphicsMeshOwnedRenderConfigId,GraphicsMeshOwnedRenderConfig,GraphicsModelOwned,GraphicsVertex}; use crate::model::{self as model_graphics,IndexedGraphicsMeshOwnedRenderConfig,IndexedGraphicsMeshOwnedRenderConfigId,GraphicsMeshOwnedRenderConfig,GraphicsModelColor4,GraphicsModelOwned,GraphicsVertex};
struct Indices{ struct Indices{
count:u32, count:u32,
@@ -96,7 +96,7 @@ impl Default for GraphicsCamera{
} }
} }
const MODEL_BUFFER_SIZE:usize=4*4 + 12 + 4 + 4;//let size=std::mem::size_of::<ModelInstance>(); 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; const MODEL_BUFFER_SIZE_BYTES:usize=MODEL_BUFFER_SIZE*4;
fn get_instances_buffer_data(instances:&[GraphicsModelOwned])->Vec<f32>{ fn get_instances_buffer_data(instances:&[GraphicsModelOwned])->Vec<f32>{
let mut raw=Vec::with_capacity(MODEL_BUFFER_SIZE*instances.len()); let mut raw=Vec::with_capacity(MODEL_BUFFER_SIZE*instances.len());
@@ -111,9 +111,7 @@ fn get_instances_buffer_data(instances:&[GraphicsModelOwned])->Vec<f32>{
raw.extend_from_slice(AsRef::<[f32; 3]>::as_ref(&mi.normal_transform.z_axis)); raw.extend_from_slice(AsRef::<[f32; 3]>::as_ref(&mi.normal_transform.z_axis));
raw.extend_from_slice(&[0.0]); raw.extend_from_slice(&[0.0]);
//color //color
raw.extend_from_slice(AsRef::<[f32; 4]>::as_ref(&mi.color)); raw.extend_from_slice(AsRef::<[f32; 4]>::as_ref(&mi.color.get()));
//texture color
raw.extend_from_slice(AsRef::<[f32; 4]>::as_ref(&mi.texture_color));
} }
raw raw
} }
@@ -129,19 +127,18 @@ pub struct GraphicsState{
models:Vec<GraphicsModel>, models:Vec<GraphicsModel>,
depth_view:wgpu::TextureView, depth_view:wgpu::TextureView,
staging_belt:wgpu::util::StagingBelt, staging_belt:wgpu::util::StagingBelt,
model_instances_uniform_len:usize,
} }
impl GraphicsState{ impl GraphicsState{
const DEPTH_FORMAT:wgpu::TextureFormat=wgpu::TextureFormat::Depth24Plus; const DEPTH_FORMAT:wgpu::TextureFormat=wgpu::TextureFormat::Depth24Plus;
fn create_depth_texture( fn create_depth_texture(
size:glam::UVec2, config:&wgpu::SurfaceConfiguration,
device:&wgpu::Device, device:&wgpu::Device,
)->wgpu::TextureView{ )->wgpu::TextureView{
let depth_texture=device.create_texture(&wgpu::TextureDescriptor{ let depth_texture=device.create_texture(&wgpu::TextureDescriptor{
size:wgpu::Extent3d{ size:wgpu::Extent3d{
width:size.x, width:config.width,
height:size.y, height:config.height,
depth_or_array_layers:1, depth_or_array_layers:1,
}, },
mip_level_count:1, mip_level_count:1,
@@ -232,8 +229,7 @@ impl GraphicsState{
let instance=GraphicsModelOwned{ let instance=GraphicsModelOwned{
transform:model.transform.into(), 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(), normal_transform:glam::Mat3::from_cols_array_2d(&model.transform.matrix3.to_array().map(|row|row.map(Into::into))).inverse().transpose(),
color:model.color, color:GraphicsModelColor4::new(model.color),
texture_color:glam::Vec4::ONE,
}; };
//get or create owned mesh map //get or create owned mesh map
let owned_mesh_map=owned_mesh_id_from_mesh_id_render_config_id let owned_mesh_map=owned_mesh_id_from_mesh_id_render_config_id
@@ -263,15 +259,16 @@ impl GraphicsState{
owned_mesh_id owned_mesh_id
}); });
let owned_mesh=unique_render_config_models.get_mut(owned_mesh_id.get() as usize).unwrap(); let owned_mesh=unique_render_config_models.get_mut(owned_mesh_id.get() as usize).unwrap();
let model::PolygonGroup::PolygonList(polygon_list)=&mut owned_mesh.polys; match &mut owned_mesh.polys{
polygon_list.extend( model::PolygonGroup::PolygonList(polygon_list)=>polygon_list.extend(
graphics_group.groups.iter().flat_map(|polygon_group_id|{ graphics_group.groups.iter().flat_map(|polygon_group_id|{
mesh.polygon_groups[polygon_group_id.get() as usize].polys() mesh.polygon_groups[polygon_group_id.get() as usize].polys()
}) })
.map(|vertex_id_slice| .map(|vertex_id_slice|
vertex_id_slice.to_vec() vertex_id_slice.to_vec()
) )
); ),
}
} }
} }
owned_mesh_map owned_mesh_map
@@ -279,20 +276,10 @@ impl GraphicsState{
for owned_mesh_id in owned_mesh_map.values(){ 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 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]; let render_config=&map.render_configs[owned_mesh.render_config.get() as usize];
let no_texture=render_config.texture.is_none(); if model.color.w==0.0&&render_config.texture.is_none(){
let instance_clone=if no_texture{ continue;
// this model has no chance of affecting pixels on the screen }
if model.color.w==0.0{ owned_mesh.instances.push(instance.clone());
continue;
}
// hack texture_color to zero to make the texture invisible
let mut instance=instance.clone();
instance.texture_color=glam::Vec4::ZERO;
instance
}else{
instance.clone()
};
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 //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
@@ -318,7 +305,7 @@ impl GraphicsState{
//separate instances by color //separate instances by color
for (instance_id,instance) in model.instances.iter().enumerate(){ for (instance_id,instance) in model.instances.iter().enumerate(){
let model_instance_list=unique_color let model_instance_list=unique_color
.entry(instance.hashable_color()) .entry(instance.color)
.or_insert_with(||Vec::new()); .or_insert_with(||Vec::new());
//add model instance to list //add model instance to list
model_instance_list.push((model_id,instance_id)); model_instance_list.push((model_id,instance_id));
@@ -407,7 +394,6 @@ impl GraphicsState{
).collect() ).collect()
)); ));
} }
let (color,texture_color)=color.color();
//push model into dedup //push model into dedup
deduplicated_models.push(IndexedGraphicsMeshOwnedRenderConfig{ deduplicated_models.push(IndexedGraphicsMeshOwnedRenderConfig{
unique_pos, unique_pos,
@@ -420,8 +406,7 @@ impl GraphicsState{
instances:vec![GraphicsModelOwned{ instances:vec![GraphicsModelOwned{
transform:glam::Mat4::IDENTITY, transform:glam::Mat4::IDENTITY,
normal_transform:glam::Mat3::IDENTITY, normal_transform:glam::Mat3::IDENTITY,
color, color
texture_color,
}], }],
}); });
} }
@@ -479,14 +464,16 @@ impl GraphicsState{
//.into_iter() the modeldata vec so entities can be /moved/ to models.entities //.into_iter() the modeldata vec so entities can be /moved/ to models.entities
let mut model_count=0; let mut model_count=0;
let mut instance_count=0; let mut instance_count=0;
let uniform_buffer_binding_size=crate::setup::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()); self.models.reserve(models.len());
for model in models.into_iter(){ for model in models.into_iter(){
instance_count+=model.instances.len(); instance_count+=model.instances.len();
for instances_chunk in model.instances.rchunks(self.model_instances_uniform_len){ for instances_chunk in model.instances.rchunks(chunk_size){
model_count+=1; model_count+=1;
let mut model_uniforms=get_instances_buffer_data(instances_chunk); let mut model_uniforms=get_instances_buffer_data(instances_chunk);
//TEMP: fill with zeroes to pass validation //TEMP: fill with zeroes to pass validation
model_uniforms.resize(MODEL_BUFFER_SIZE*self.model_instances_uniform_len,0.0f32); model_uniforms.resize(MODEL_BUFFER_SIZE*512,0.0f32);
let model_buf=device.create_buffer_init(&wgpu::util::BufferInitDescriptor{ let model_buf=device.create_buffer_init(&wgpu::util::BufferInitDescriptor{
label:Some(format!("Model{} Buf",model_count).as_str()), label:Some(format!("Model{} Buf",model_count).as_str()),
contents:bytemuck::cast_slice(&model_uniforms), contents:bytemuck::cast_slice(&model_uniforms),
@@ -542,9 +529,7 @@ impl GraphicsState{
pub fn new( pub fn new(
device:&wgpu::Device, device:&wgpu::Device,
queue:&wgpu::Queue, queue:&wgpu::Queue,
size:glam::UVec2, config:&wgpu::SurfaceConfiguration,
view_format:wgpu::TextureFormat,
limits:wgpu::Limits,
)->Self{ )->Self{
let camera_bind_group_layout=device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor{ let camera_bind_group_layout=device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor{
label:None, label:None,
@@ -636,18 +621,10 @@ impl GraphicsState{
..Default::default() ..Default::default()
}); });
let model_instances_uniform_len=limits.max_uniform_buffer_binding_size as usize/MODEL_BUFFER_SIZE_BYTES;
// write dynamic value into shader
let shader=format!("
// This is equal to the CHUNK_SIZE constant from graphics.rs
const MAX_MODEL_INSTANCES={model_instances_uniform_len};
")+include_str!("../shaders/shader.wgsl");
// Create the render pipeline // Create the render pipeline
let shader=device.create_shader_module(wgpu::ShaderModuleDescriptor{ let shader=device.create_shader_module(wgpu::ShaderModuleDescriptor{
label:None, label:None,
source:wgpu::ShaderSource::Wgsl(Cow::Owned(shader)), source:wgpu::ShaderSource::Wgsl(Cow::Borrowed(include_str!("../shaders/shader.wgsl"))),
}); });
//load textures //load textures
@@ -763,17 +740,17 @@ impl GraphicsState{
let model_pipeline_layout=device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor{ let model_pipeline_layout=device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor{
label:None, label:None,
bind_group_layouts:&[ bind_group_layouts:&[
Some(&camera_bind_group_layout), &camera_bind_group_layout,
Some(&skybox_texture_bind_group_layout), &skybox_texture_bind_group_layout,
Some(&model_bind_group_layout), &model_bind_group_layout,
], ],
immediate_size:0, immediate_size:0,
}); });
let sky_pipeline_layout=device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor{ let sky_pipeline_layout=device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor{
label:None, label:None,
bind_group_layouts:&[ bind_group_layouts:&[
Some(&camera_bind_group_layout), &camera_bind_group_layout,
Some(&skybox_texture_bind_group_layout), &skybox_texture_bind_group_layout,
], ],
immediate_size:0, immediate_size:0,
}); });
@@ -791,7 +768,7 @@ impl GraphicsState{
fragment:Some(wgpu::FragmentState{ fragment:Some(wgpu::FragmentState{
module:&shader, module:&shader,
entry_point:Some("fs_sky"), entry_point:Some("fs_sky"),
targets:&[Some(view_format.into())], targets:&[Some(config.view_formats[0].into())],
compilation_options:wgpu::PipelineCompilationOptions::default(), compilation_options:wgpu::PipelineCompilationOptions::default(),
}), }),
primitive:wgpu::PrimitiveState{ primitive:wgpu::PrimitiveState{
@@ -800,8 +777,8 @@ impl GraphicsState{
}, },
depth_stencil:Some(wgpu::DepthStencilState{ depth_stencil:Some(wgpu::DepthStencilState{
format:Self::DEPTH_FORMAT, format:Self::DEPTH_FORMAT,
depth_write_enabled:Some(false), depth_write_enabled:false,
depth_compare:Some(wgpu::CompareFunction::LessEqual), depth_compare:wgpu::CompareFunction::LessEqual,
stencil:wgpu::StencilState::default(), stencil:wgpu::StencilState::default(),
bias:wgpu::DepthBiasState::default(), bias:wgpu::DepthBiasState::default(),
}), }),
@@ -825,7 +802,7 @@ impl GraphicsState{
fragment:Some(wgpu::FragmentState{ fragment:Some(wgpu::FragmentState{
module:&shader, module:&shader,
entry_point:Some("fs_entity_texture"), entry_point:Some("fs_entity_texture"),
targets:&[Some(view_format.into())], targets:&[Some(config.view_formats[0].into())],
compilation_options:wgpu::PipelineCompilationOptions::default(), compilation_options:wgpu::PipelineCompilationOptions::default(),
}), }),
primitive:wgpu::PrimitiveState{ primitive:wgpu::PrimitiveState{
@@ -835,8 +812,8 @@ impl GraphicsState{
}, },
depth_stencil:Some(wgpu::DepthStencilState{ depth_stencil:Some(wgpu::DepthStencilState{
format:Self::DEPTH_FORMAT, format:Self::DEPTH_FORMAT,
depth_write_enabled:Some(true), depth_write_enabled:true,
depth_compare:Some(wgpu::CompareFunction::LessEqual), depth_compare:wgpu::CompareFunction::LessEqual,
stencil:wgpu::StencilState::default(), stencil:wgpu::StencilState::default(),
bias:wgpu::DepthBiasState::default(), bias:wgpu::DepthBiasState::default(),
}), }),
@@ -878,7 +855,7 @@ impl GraphicsState{
label:Some("Sky Texture"), label:Some("Sky Texture"),
}); });
let depth_view=Self::create_depth_texture(size,device); let depth_view=Self::create_depth_texture(config,device);
Self{ Self{
pipelines:GraphicsPipelines{ pipelines:GraphicsPipelines{
@@ -897,34 +874,34 @@ impl GraphicsState{
bind_group_layouts:GraphicsBindGroupLayouts{model:model_bind_group_layout}, bind_group_layouts:GraphicsBindGroupLayouts{model:model_bind_group_layout},
samplers:GraphicsSamplers{repeat:repeat_sampler}, samplers:GraphicsSamplers{repeat:repeat_sampler},
temp_squid_texture_view:squid_texture_view, temp_squid_texture_view:squid_texture_view,
model_instances_uniform_len,
} }
} }
pub fn resize( pub fn resize(
&mut self, &mut self,
device:&wgpu::Device, device:&wgpu::Device,
size:glam::UVec2, config:&wgpu::SurfaceConfiguration,
fov:glam::Vec2, fov:glam::Vec2,
){ ){
self.depth_view=Self::create_depth_texture(size,device); self.depth_view=Self::create_depth_texture(config,device);
self.camera.screen_size=size; self.camera.screen_size=glam::uvec2(config.width,config.height);
self.camera.fov=fov; self.camera.fov=fov;
} }
pub fn encode_commands( pub fn render(
&mut self, &mut self,
encoder:&mut wgpu::CommandEncoder,
view:&wgpu::TextureView, view:&wgpu::TextureView,
device:&wgpu::Device,
queue:&wgpu::Queue,
camera:glam::Mat4, camera:glam::Mat4,
){ ){
//TODO:use scheduled frame times to create beautiful smoothing simulation physics extrapolation assuming no input //TODO:use scheduled frame times to create beautiful smoothing simulation physics extrapolation assuming no input
// TODO: find a way to call this directly after queue.submit() let mut encoder=device.create_command_encoder(&wgpu::CommandEncoderDescriptor{label:None});
self.staging_belt.recall();
// update rotation // update rotation
let camera_uniforms=self.camera.to_uniform_data(camera); let camera_uniforms=self.camera.to_uniform_data(camera);
self.staging_belt self.staging_belt
.write_buffer( .write_buffer(
encoder, &mut encoder,
&self.camera_buf, &self.camera_buf,
0, 0,
wgpu::BufferSize::new((camera_uniforms.len() * 4) as wgpu::BufferAddress).unwrap(), wgpu::BufferSize::new((camera_uniforms.len() * 4) as wgpu::BufferAddress).unwrap(),
@@ -992,5 +969,9 @@ impl GraphicsState{
rpass.set_pipeline(&self.pipelines.skybox); rpass.set_pipeline(&self.pipelines.skybox);
rpass.draw(0..3,0..1); rpass.draw(0..3,0..1);
} }
queue.submit(std::iter::once(encoder.finish()));
self.staging_belt.recall();
} }
} }

View File

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

View File

@@ -30,23 +30,19 @@ pub struct GraphicsMeshOwnedRenderConfig{
pub render_config:RenderConfigId, pub render_config:RenderConfigId,
pub instances:Vec<GraphicsModelOwned>, 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)] #[derive(Clone)]
pub struct GraphicsModelOwned{ pub struct GraphicsModelOwned{
pub transform:glam::Mat4, pub transform:glam::Mat4,
pub normal_transform:glam::Mat3, pub normal_transform:glam::Mat3,
pub color:glam::Vec4, pub color:GraphicsModelColor4,
pub texture_color:glam::Vec4,
}
impl GraphicsModelOwned{
pub fn hashable_color(&self)->HashableColor{
HashableColor([bytemuck::cast::<[f32;4],[u32;4]>(self.color.to_array()),bytemuck::cast::<[f32;4],[u32;4]>(self.texture_color.to_array())])
}
}
#[derive(Clone,Copy,Eq,Hash,PartialEq)]
pub struct HashableColor([[u32;4];2]);
impl HashableColor{
pub fn color(self)->(glam::Vec4,glam::Vec4){
let [color,texture_color]=bytemuck::cast::<[[u32;4];2],[[f32;4];2]>(self.0);
(glam::Vec4::from_array(color),glam::Vec4::from_array(texture_color))
}
} }

View File

@@ -5,6 +5,9 @@ fn optional_features()->wgpu::Features{
fn required_features()->wgpu::Features{ fn required_features()->wgpu::Features{
wgpu::Features::TEXTURE_COMPRESSION_BC wgpu::Features::TEXTURE_COMPRESSION_BC
} }
pub fn required_limits()->wgpu::Limits{
wgpu::Limits::defaults()
}
fn required_downlevel_capabilities()->wgpu::DownlevelCapabilities{ fn required_downlevel_capabilities()->wgpu::DownlevelCapabilities{
wgpu::DownlevelCapabilities{ wgpu::DownlevelCapabilities{
flags:wgpu::DownlevelFlags::empty(), flags:wgpu::DownlevelFlags::empty(),
@@ -61,12 +64,12 @@ pub mod step3{
} }
pub mod step4{ pub mod step4{
pub async fn request_device(adapter:&wgpu::Adapter,limits:wgpu::Limits)->Result<(wgpu::Device,wgpu::Queue),wgpu::RequestDeviceError>{ pub async fn request_device(adapter:&wgpu::Adapter)->Result<(wgpu::Device,wgpu::Queue),wgpu::RequestDeviceError>{
let optional_features=super::optional_features(); let optional_features=super::optional_features();
let required_features=super::required_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. // Make sure we use the texture resolution limits from the adapter, so we can support images the size of the surface.
let needed_limits=limits.using_resolution(adapter.limits()); let needed_limits=super::required_limits().using_resolution(adapter.limits());
let (device, queue)=adapter let (device, queue)=adapter
.request_device( .request_device(
@@ -88,7 +91,6 @@ pub mod step4{
} }
pub mod step5{ pub mod step5{
use crate::surface::Surface;
#[derive(Debug)] #[derive(Debug)]
pub struct ErrorSurfaceNotSupported; pub struct ErrorSurfaceNotSupported;
impl std::fmt::Display for ErrorSurfaceNotSupported{ impl std::fmt::Display for ErrorSurfaceNotSupported{
@@ -96,12 +98,12 @@ pub mod step5{
write!(f,"Surface isn't supported by the adapter.") write!(f,"Surface isn't supported by the adapter.")
} }
} }
pub fn configure_surface<'window>( pub fn configure_surface(
adapter:&wgpu::Adapter, adapter:&wgpu::Adapter,
device:&wgpu::Device, device:&wgpu::Device,
surface:wgpu::Surface<'window>, surface:&wgpu::Surface<'_>,
(width,height):(u32,u32), (width,height):(u32,u32),
)->Result<Surface<'window>,ErrorSurfaceNotSupported>{ )->Result<wgpu::SurfaceConfiguration,ErrorSurfaceNotSupported>{
let mut config=surface let mut config=surface
.get_default_config(adapter, width, height) .get_default_config(adapter, width, height)
.ok_or(ErrorSurfaceNotSupported)?; .ok_or(ErrorSurfaceNotSupported)?;
@@ -111,6 +113,6 @@ pub mod step5{
config.present_mode=wgpu::PresentMode::AutoNoVsync; config.present_mode=wgpu::PresentMode::AutoNoVsync;
surface.configure(device,&config); surface.configure(device,&config);
Ok(Surface::new(surface,config)) Ok(config)
} }
} }

View File

@@ -1,80 +0,0 @@
/// A texture view which can be targeted by draw calls in the command buffer, and then presented to the surface texture.
pub struct Frame{
surface_texture:wgpu::SurfaceTexture,
view:wgpu::TextureView,
}
impl Frame{
pub const fn view(&self)->&wgpu::TextureView{
&self.view
}
pub fn present(self){
self.surface_texture.present();
}
}
#[derive(Debug)]
pub enum FrameError{
Skip,
DeviceLost,
}
impl core::fmt::Display for FrameError{
fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{
write!(f,"{self:?}")
}
}
impl core::error::Error for FrameError{}
/// A render surface configuration, containing information such as resolution and pixel format
pub struct Surface<'window>{
surface:wgpu::Surface<'window>,
config:wgpu::SurfaceConfiguration,
}
impl<'window> Surface<'window>{
pub(crate) fn new(
surface:wgpu::Surface<'window>,
config:wgpu::SurfaceConfiguration,
)->Self{
Self{surface,config}
}
#[must_use]
pub fn new_frame(&self,device:&wgpu::Device)->Result<Frame,FrameError>{
let frame=match self.surface.get_current_texture(){
wgpu::CurrentSurfaceTexture::Success(surface_texture)=>surface_texture,
wgpu::CurrentSurfaceTexture::Suboptimal(surface_texture)=>{
self.surface.configure(device,&self.config);
surface_texture
},
wgpu::CurrentSurfaceTexture::Outdated=>{
self.surface.configure(device,&self.config);
match self.surface.get_current_texture(){
wgpu::CurrentSurfaceTexture::Success(surface_texture)=>surface_texture,
_=>panic!("Failed to acquire next surface texture!"),
}
}
wgpu::CurrentSurfaceTexture::Timeout
|wgpu::CurrentSurfaceTexture::Occluded=>return Err(FrameError::Skip),
wgpu::CurrentSurfaceTexture::Lost=>return Err(FrameError::DeviceLost),
wgpu::CurrentSurfaceTexture::Validation=>unreachable!(),
};
let view=frame.texture.create_view(&wgpu::TextureViewDescriptor{
format:Some(self.config.view_formats[0]),
..wgpu::TextureViewDescriptor::default()
});
Ok(Frame{
surface_texture:frame,
view,
})
}
pub const fn size(&self)->glam::UVec2{
glam::uvec2(self.config.width,self.config.height)
}
pub fn view_format(&self)->wgpu::TextureFormat{
self.config.view_formats[0]
}
pub fn configure(&mut self,device:&wgpu::Device,size:glam::UVec2){
self.config.width=size.x.max(1);
self.config.height=size.y.max(1);
self.surface.configure(device,&self.config);
}
}

View File

@@ -7,7 +7,6 @@ edition = "2024"
arrayvec = "0.7.6" arrayvec = "0.7.6"
glam.workspace = true glam.workspace = true
id = { version = "0.1.0", registry = "strafesnet" } id = { version = "0.1.0", registry = "strafesnet" }
mlua = { version = "0.11.5", features = ["luau"] }
strafesnet_common.workspace = true strafesnet_common.workspace = true
[lints] [lints]

View File

@@ -86,12 +86,12 @@ impl<T> Trajectory<T>
pub fn extrapolated_position(&self,time:Time<T>)->Planar64Vec3{ pub fn extrapolated_position(&self,time:Time<T>)->Planar64Vec3{
let dt=time-self.time; let dt=time-self.time;
self.position self.position
+(self.velocity*dt).map(|elem|elem.divide().clamp_64()) +(self.velocity*dt).map(|elem|elem.divide().clamp_1())
+self.acceleration.map(|elem|(dt*dt*elem/2).divide().clamp_64()) +self.acceleration.map(|elem|(dt*dt*elem/2).divide().clamp_1())
} }
pub fn extrapolated_velocity(&self,time:Time<T>)->Planar64Vec3{ pub fn extrapolated_velocity(&self,time:Time<T>)->Planar64Vec3{
let dt=time-self.time; let dt=time-self.time;
self.velocity+(self.acceleration*dt).map(|elem|elem.divide().clamp_64()) self.velocity+(self.acceleration*dt).map(|elem|elem.divide().clamp_1())
} }
pub fn extrapolated_body(&self,time:Time<T>)->Body<T>{ pub fn extrapolated_body(&self,time:Time<T>)->Body<T>{
Body::new( Body::new(

View File

@@ -1,7 +1,5 @@
use crate::model::{into_giga_time,GigaTime}; use crate::model::{into_giga_time,GigaTime};
use strafesnet_common::integer::fixed_types::{F64_32,F128_64,F256_128}; use strafesnet_common::integer::{Fixed,Ratio,vec3::Vector3,Planar64Vec3};
use strafesnet_common::integer::vec3::Vector3;
use strafesnet_common::integer::{Ratio,Planar64Vec3};
use crate::physics::{Time,Trajectory}; use crate::physics::{Time,Trajectory};
use crate::mesh_query::{FEV,DirectedEdge,MeshQuery,MeshTopology}; use crate::mesh_query::{FEV,DirectedEdge,MeshQuery,MeshTopology};
@@ -67,16 +65,16 @@ where
} }
} }
impl<F:Copy,M:MeshQuery<Normal=Vector3<F>,Offset=F256_128,Position=Planar64Vec3,Direction=Planar64Vec3>> FEV<M> impl<F:Copy,M:MeshQuery<Normal=Vector3<F>,Offset=Fixed<4,128>,Position=Planar64Vec3,Direction=Planar64Vec3>> FEV<M>
where where
// This is hardcoded for MinkowskiMesh lol // This is hardcoded for MinkowskiMesh lol
M::Face:Copy, M::Face:Copy,
M::Edge:Copy, M::Edge:Copy,
M::DirectedEdge:Copy, M::DirectedEdge:Copy,
M::Vert:Copy, M::Vert:Copy,
F:core::ops::Mul<F64_32,Output=F256_128>, F:core::ops::Mul<Fixed<1,32>,Output=Fixed<4,128>>,
<F as core::ops::Mul<F64_32>>::Output:core::iter::Sum, <F as core::ops::Mul<Fixed<1,32>>>::Output:core::iter::Sum,
M::Offset:core::ops::Sub<<F as std::ops::Mul<F64_32>>::Output>, M::Offset:core::ops::Sub<<F as std::ops::Mul<Fixed<1,32>>>::Output>,
{ {
fn next_transition(&self,mesh:&M,trajectory:&Trajectory,lower_bound:Bound<GigaTime>,mut upper_bound:Bound<GigaTime>)->Transition<M>{ fn next_transition(&self,mesh:&M,trajectory:&Trajectory,lower_bound:Bound<GigaTime>,mut upper_bound:Bound<GigaTime>)->Transition<M>{
//conflicting derivative means it crosses in the wrong direction. //conflicting derivative means it crosses in the wrong direction.
@@ -90,7 +88,7 @@ impl<F:Copy,M:MeshQuery<Normal=Vector3<F>,Offset=F256_128,Position=Planar64Vec3,
let (n,d)=mesh.face_nd(face_id); let (n,d)=mesh.face_nd(face_id);
//TODO: use higher precision d value? //TODO: use higher precision d value?
//use the mesh transform translation instead of baking it into the d value. //use the mesh transform translation instead of baking it into the d value.
for dt in F256_128::zeroes2((n.dot(trajectory.position)-d)*2,n.dot(trajectory.velocity)*2,n.dot(trajectory.acceleration)){ for dt in Fixed::<4,128>::zeroes2((n.dot(trajectory.position)-d)*2,n.dot(trajectory.velocity)*2,n.dot(trajectory.acceleration)){
if low(&lower_bound,&dt)&&upp(&dt,&upper_bound)&&n.dot(trajectory.extrapolated_velocity_ratio_dt(dt)).is_negative(){ if low(&lower_bound,&dt)&&upp(&dt,&upper_bound)&&n.dot(trajectory.extrapolated_velocity_ratio_dt(dt)).is_negative(){
upper_bound=Bound::Included(dt); upper_bound=Bound::Included(dt);
best_transition=Transition::Hit(face_id,dt); best_transition=Transition::Hit(face_id,dt);
@@ -105,7 +103,7 @@ impl<F:Copy,M:MeshQuery<Normal=Vector3<F>,Offset=F256_128,Position=Planar64Vec3,
//WARNING: d is moved out of the *2 block because of adding two vertices! //WARNING: d is moved out of the *2 block because of adding two vertices!
//WARNING: precision is swept under the rug! //WARNING: precision is swept under the rug!
//wrap for speed //wrap for speed
for dt in F256_128::zeroes2(n.dot(trajectory.position*2-(mesh.vert(v0)+mesh.vert(v1))).wrap_256(),n.dot(trajectory.velocity).wrap_256()*2,n.dot(trajectory.acceleration).wrap_256()){ for dt in Fixed::<4,128>::zeroes2(n.dot(trajectory.position*2-(mesh.vert(v0)+mesh.vert(v1))).wrap_4(),n.dot(trajectory.velocity).wrap_4()*2,n.dot(trajectory.acceleration).wrap_4()){
if low(&lower_bound,&dt)&&upp(&dt,&upper_bound)&&n.dot(trajectory.extrapolated_velocity_ratio_dt(dt)).is_negative(){ if low(&lower_bound,&dt)&&upp(&dt,&upper_bound)&&n.dot(trajectory.extrapolated_velocity_ratio_dt(dt)).is_negative(){
upper_bound=Bound::Included(dt); upper_bound=Bound::Included(dt);
best_transition=Transition::Next(FEV::Edge(directed_edge_id.as_undirected()),dt); best_transition=Transition::Next(FEV::Edge(directed_edge_id.as_undirected()),dt);
@@ -128,7 +126,7 @@ impl<F:Copy,M:MeshQuery<Normal=Vector3<F>,Offset=F256_128,Position=Planar64Vec3,
let n=face_n.cross(edge_n)*((i as i64)*2-1); let n=face_n.cross(edge_n)*((i as i64)*2-1);
//WARNING yada yada d *2 //WARNING yada yada d *2
//wrap for speed //wrap for speed
for dt in F256_128::zeroes2(n.dot(delta_pos).wrap_256(),n.dot(trajectory.velocity).wrap_256()*2,n.dot(trajectory.acceleration).wrap_256()){ for dt in Fixed::<4,128>::zeroes2(n.dot(delta_pos).wrap_4(),n.dot(trajectory.velocity).wrap_4()*2,n.dot(trajectory.acceleration).wrap_4()){
if low(&lower_bound,&dt)&&upp(&dt,&upper_bound)&&n.dot(trajectory.extrapolated_velocity_ratio_dt(dt)).is_negative(){ if low(&lower_bound,&dt)&&upp(&dt,&upper_bound)&&n.dot(trajectory.extrapolated_velocity_ratio_dt(dt)).is_negative(){
upper_bound=Bound::Included(dt); upper_bound=Bound::Included(dt);
best_transition=Transition::Next(FEV::Face(edge_face_id),dt); best_transition=Transition::Next(FEV::Face(edge_face_id),dt);
@@ -140,9 +138,9 @@ impl<F:Copy,M:MeshQuery<Normal=Vector3<F>,Offset=F256_128,Position=Planar64Vec3,
for (i,&vert_id) in edge_verts.as_ref().iter().enumerate(){ for (i,&vert_id) in edge_verts.as_ref().iter().enumerate(){
//vertex normal gets parity from vert index //vertex normal gets parity from vert index
let n=edge_n*(1-2*(i as i64)); let n=edge_n*(1-2*(i as i64));
for dt in F128_64::zeroes2((n.dot(trajectory.position-mesh.vert(vert_id)))*2,n.dot(trajectory.velocity)*2,n.dot(trajectory.acceleration)){ for dt in Fixed::<2,64>::zeroes2((n.dot(trajectory.position-mesh.vert(vert_id)))*2,n.dot(trajectory.velocity)*2,n.dot(trajectory.acceleration)){
if low(&lower_bound,&dt)&&upp(&dt,&upper_bound)&&n.dot(trajectory.extrapolated_velocity_ratio_dt(dt)).is_negative(){ if low(&lower_bound,&dt)&&upp(&dt,&upper_bound)&&n.dot(trajectory.extrapolated_velocity_ratio_dt(dt)).is_negative(){
let dt=Ratio::new(dt.num.widen_256(),dt.den.widen_256()); let dt=Ratio::new(dt.num.widen_4(),dt.den.widen_4());
upper_bound=Bound::Included(dt); upper_bound=Bound::Included(dt);
best_transition=Transition::Next(FEV::Vert(vert_id),dt); best_transition=Transition::Next(FEV::Vert(vert_id),dt);
break; break;
@@ -156,9 +154,9 @@ impl<F:Copy,M:MeshQuery<Normal=Vector3<F>,Offset=F256_128,Position=Planar64Vec3,
mesh.for_each_vert_edge(vert_id,|directed_edge_id|{ mesh.for_each_vert_edge(vert_id,|directed_edge_id|{
//edge is directed away from vertex, but we want the dot product to turn out negative //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); let n=-mesh.directed_edge_n(directed_edge_id);
for dt in F128_64::zeroes2((n.dot(trajectory.position-mesh.vert(vert_id)))*2,n.dot(trajectory.velocity)*2,n.dot(trajectory.acceleration)){ for dt in Fixed::<2,64>::zeroes2((n.dot(trajectory.position-mesh.vert(vert_id)))*2,n.dot(trajectory.velocity)*2,n.dot(trajectory.acceleration)){
if low(&lower_bound,&dt)&&upp(&dt,&upper_bound)&&n.dot(trajectory.extrapolated_velocity_ratio_dt(dt)).is_negative(){ if low(&lower_bound,&dt)&&upp(&dt,&upper_bound)&&n.dot(trajectory.extrapolated_velocity_ratio_dt(dt)).is_negative(){
let dt=Ratio::new(dt.num.widen_256(),dt.den.widen_256()); let dt=Ratio::new(dt.num.widen_4(),dt.den.widen_4());
upper_bound=Bound::Included(dt); upper_bound=Bound::Included(dt);
best_transition=Transition::Next(FEV::Edge(directed_edge_id.as_undirected()),dt); best_transition=Transition::Next(FEV::Edge(directed_edge_id.as_undirected()),dt);
break; break;

View File

@@ -5,7 +5,6 @@ mod minkowski;
mod model; mod model;
mod push_solve; mod push_solve;
mod minimum_difference; mod minimum_difference;
mod minimum_difference_lua;
pub mod physics; pub mod physics;

View File

@@ -1,4 +1,3 @@
#[derive(Debug)]
pub enum FEV<M:MeshTopology>{ pub enum FEV<M:MeshTopology>{
Vert(M::Vert), Vert(M::Vert),
Edge(M::Edge), Edge(M::Edge),

View File

@@ -1,4 +1,3 @@
use strafesnet_common::integer::fixed_types::{F128_64,F192_96,F256_128};
use strafesnet_common::integer::vec3; use strafesnet_common::integer::vec3;
use strafesnet_common::integer::vec3::Vector3; use strafesnet_common::integer::vec3::Vector3;
use strafesnet_common::integer::{Fixed,Planar64,Planar64Vec3}; use strafesnet_common::integer::{Fixed,Planar64,Planar64Vec3};
@@ -11,7 +10,7 @@ use crate::minkowski::{MinkowskiMesh,MinkowskiVert};
// written by Trey Reynolds in 2021 // written by Trey Reynolds in 2021
type Simplex<const N:usize,Vert>=[Vert;N]; type Simplex<const N:usize,Vert>=[Vert;N];
#[derive(Clone,Copy,Debug)] #[derive(Clone,Copy)]
enum Simplex1_3<Vert>{ enum Simplex1_3<Vert>{
Simplex1(Simplex<1,Vert>), Simplex1(Simplex<1,Vert>),
Simplex2(Simplex<2,Vert>), Simplex2(Simplex<2,Vert>),
@@ -101,35 +100,35 @@ const fn choose_any_direction()->Planar64Vec3{
vec3::X vec3::X
} }
fn narrow_dir2(dir:Vector3<F128_64>)->Planar64Vec3{ fn narrow_dir2(dir:Vector3<Fixed<2,64>>)->Planar64Vec3{
if dir==vec3::zero(){ if dir==vec3::zero(){
return dir.narrow_64().unwrap(); return dir.narrow_1().unwrap();
} }
let x=dir.x.as_bits().unsigned_abs().bit_width(); let x=dir.x.as_bits().unsigned_abs().bits();
let y=dir.y.as_bits().unsigned_abs().bit_width(); let y=dir.y.as_bits().unsigned_abs().bits();
let z=dir.z.as_bits().unsigned_abs().bit_width(); let z=dir.z.as_bits().unsigned_abs().bits();
let big=x.max(y).max(z); let big=x.max(y).max(z);
const MAX_BITS:u32=64+31; const MAX_BITS:u32=64+31;
if MAX_BITS<big{ if MAX_BITS<big{
dir>>(big-MAX_BITS) dir>>(big-MAX_BITS)
}else{ }else{
dir dir
}.narrow_64().unwrap() }.narrow_1().unwrap()
} }
fn narrow_dir3(dir:Vector3<F192_96>)->Planar64Vec3{ fn narrow_dir3(dir:Vector3<Fixed<3,96>>)->Planar64Vec3{
if dir==vec3::zero(){ if dir==vec3::zero(){
return dir.narrow_64().unwrap(); return dir.narrow_1().unwrap();
} }
let x=dir.x.as_bits().unsigned_abs().bit_width(); let x=dir.x.as_bits().unsigned_abs().bits();
let y=dir.y.as_bits().unsigned_abs().bit_width(); let y=dir.y.as_bits().unsigned_abs().bits();
let z=dir.z.as_bits().unsigned_abs().bit_width(); let z=dir.z.as_bits().unsigned_abs().bits();
let big=x.max(y).max(z); let big=x.max(y).max(z);
const MAX_BITS:u32=96+31; const MAX_BITS:u32=96+31;
if MAX_BITS<big{ if MAX_BITS<big{
dir>>(big-MAX_BITS) dir>>(big-MAX_BITS)
}else{ }else{
dir dir
}.narrow_64().unwrap() }.narrow_1().unwrap()
} }
fn reduce1<M:MeshQuery<Position=Planar64Vec3>>( fn reduce1<M:MeshQuery<Position=Planar64Vec3>>(
@@ -139,15 +138,12 @@ fn reduce1<M:MeshQuery<Position=Planar64Vec3>>(
)->Reduced<M::Vert> )->Reduced<M::Vert>
where M::Vert:Copy, where M::Vert:Copy,
{ {
println!("reduce1");
// --debug.profilebegin("reduceSimplex0") // --debug.profilebegin("reduceSimplex0")
// local a = a1 - a0 // local a = a1 - a0
let p0=mesh.vert(v0); let p0=mesh.vert(v0);
println!("p0={p0}");
// local p = -a // local p = -a
let p=-(p0+point); let p=-(p0+point);
println!("p={p}");
// local direction = p // local direction = p
let mut dir=p; let mut dir=p;
@@ -174,7 +170,6 @@ fn reduce2<M:MeshQuery<Position=Planar64Vec3>>(
where where
M::Vert:Copy M::Vert:Copy
{ {
println!("reduce2");
// --debug.profilebegin("reduceSimplex1") // --debug.profilebegin("reduceSimplex1")
// local a = a1 - a0 // local a = a1 - a0
// local b = b1 - b0 // local b = b1 - b0
@@ -235,7 +230,6 @@ fn reduce3<M:MeshQuery<Position=Planar64Vec3>>(
where where
M::Vert:Copy M::Vert:Copy
{ {
println!("reduce3");
// --debug.profilebegin("reduceSimplex2") // --debug.profilebegin("reduceSimplex2")
// local a = a1 - a0 // local a = a1 - a0
// local b = b1 - b0 // local b = b1 - b0
@@ -348,7 +342,6 @@ fn reduce4<M:MeshQuery<Position=Planar64Vec3>>(
where where
M::Vert:Copy M::Vert:Copy
{ {
println!("reduce4");
// --debug.profilebegin("reduceSimplex3") // --debug.profilebegin("reduceSimplex3")
// local a = a1 - a0 // local a = a1 - a0
// local b = b1 - b0 // local b = b1 - b0
@@ -572,8 +565,8 @@ trait Contains{
// convenience type to check if a point is within some threshold of a plane. // convenience type to check if a point is within some threshold of a plane.
struct ThickPlane{ struct ThickPlane{
point:Planar64Vec3, point:Planar64Vec3,
normal:Vector3<F128_64>, normal:Vector3<Fixed<2,64>>,
epsilon:F192_96, epsilon:Fixed<3,96>,
} }
impl ThickPlane{ impl ThickPlane{
fn new<M:MeshQuery<Position=Planar64Vec3>>(mesh:&M,[v0,v1,v2]:Simplex<3,M::Vert>)->Self{ fn new<M:MeshQuery<Position=Planar64Vec3>>(mesh:&M,[v0,v1,v2]:Simplex<3,M::Vert>)->Self{
@@ -584,7 +577,7 @@ impl ThickPlane{
let normal=(p1-p0).cross(p2-p0); let normal=(p1-p0).cross(p2-p0);
// Allow ~ 2*sqrt(3) units of thickness on the plane // Allow ~ 2*sqrt(3) units of thickness on the plane
// This is to account for the variance of two voxels across the longest diagonal // This is to account for the variance of two voxels across the longest diagonal
let epsilon=(normal.length()*(Planar64::EPSILON*3)).wrap_192(); let epsilon=(normal.length()*(Planar64::EPSILON*3)).wrap_3();
Self{point,normal,epsilon} Self{point,normal,epsilon}
} }
} }
@@ -597,7 +590,7 @@ impl Contains for ThickPlane{
struct ThickLine{ struct ThickLine{
point:Planar64Vec3, point:Planar64Vec3,
dir:Planar64Vec3, dir:Planar64Vec3,
epsilon:F256_128, epsilon:Fixed<4,128>,
} }
impl ThickLine{ impl ThickLine{
fn new<M:MeshQuery<Position=Planar64Vec3>>(mesh:&M,[v0,v1]:Simplex<2,M::Vert>)->Self{ fn new<M:MeshQuery<Position=Planar64Vec3>>(mesh:&M,[v0,v1]:Simplex<2,M::Vert>)->Self{
@@ -607,7 +600,7 @@ impl ThickLine{
let dir=p1-p0; let dir=p1-p0;
// Allow ~ 2*sqrt(3) units of thickness on the plane // Allow ~ 2*sqrt(3) units of thickness on the plane
// This is to account for the variance of two voxels across the longest diagonal // This is to account for the variance of two voxels across the longest diagonal
let epsilon=(dir.length_squared()*(Planar64::EPSILON*3)).widen_256(); let epsilon=(dir.length_squared()*(Planar64::EPSILON*3)).widen_4();
Self{point,dir,epsilon} Self{point,dir,epsilon}
} }
} }
@@ -620,7 +613,7 @@ impl Contains for ThickLine{
struct EVFinder<'a,M,C>{ struct EVFinder<'a,M,C>{
mesh:&'a M, mesh:&'a M,
constraint:C, constraint:C,
best_distance_squared:F128_64, best_distance_squared:Fixed<2,64>,
} }
impl<M:MeshQuery<Position=Planar64Vec3>,C:Contains> EVFinder<'_,M,C> impl<M:MeshQuery<Position=Planar64Vec3>,C:Contains> EVFinder<'_,M,C>
@@ -664,7 +657,7 @@ impl<M:MeshQuery<Position=Planar64Vec3>,C:Contains> EVFinder<'_,M,C>
let distance_squared={ let distance_squared={
let c=diff.cross(edge_n); let c=diff.cross(edge_n);
//wrap for speed //wrap for speed
(c.dot(c)/edge_nn).divide().wrap_128() (c.dot(c)/edge_nn).divide().wrap_2()
}; };
if distance_squared<=self.best_distance_squared{ if distance_squared<=self.best_distance_squared{
best_transition=EV::Edge(directed_edge_id.as_undirected()); best_transition=EV::Edge(directed_edge_id.as_undirected());
@@ -740,7 +733,6 @@ fn crawl_to_closest_fev<'a>(mesh:&MinkowskiMesh<'a>,simplex:Simplex<3,MinkowskiV
//if test point is behind face, the face is invalid //if test point is behind face, the face is invalid
// TODO: find out why I thought of this backwards // TODO: find out why I thought of this backwards
if !(face_n.dot(point)-d).is_positive(){ if !(face_n.dot(point)-d).is_positive(){
println!("behind");
continue; continue;
} }
//edge-face boundary nd, n facing out of the face towards the edge //edge-face boundary nd, n facing out of the face towards the edge
@@ -750,8 +742,6 @@ fn crawl_to_closest_fev<'a>(mesh:&MinkowskiMesh<'a>,simplex:Simplex<3,MinkowskiV
if !boundary_d.is_positive(){ if !boundary_d.is_positive(){
//both faces cannot pass this condition, return early if one does. //both faces cannot pass this condition, return early if one does.
return FEV::Face(face_id); return FEV::Face(face_id);
}else{
println!("boundary_d is positive");
} }
} }
FEV::Edge(edge_id) FEV::Edge(edge_id)
@@ -760,21 +750,11 @@ fn crawl_to_closest_fev<'a>(mesh:&MinkowskiMesh<'a>,simplex:Simplex<3,MinkowskiV
} }
pub fn closest_fev_not_inside<'a>(mesh:&MinkowskiMesh<'a>,point:Planar64Vec3)->Option<FEV<MinkowskiMesh<'a>>>{ pub fn closest_fev_not_inside<'a>(mesh:&MinkowskiMesh<'a>,point:Planar64Vec3)->Option<FEV<MinkowskiMesh<'a>>>{
println!("=== LUA ===");
let (hits,_details)=crate::minimum_difference_lua::minimum_difference_details(mesh,point).unwrap();
println!("=== RUST ===");
let closest_fev_not_inside=closest_fev_not_inside_inner(mesh,point);
assert_eq!(hits,closest_fev_not_inside.is_none(),"algorithms disagree");
closest_fev_not_inside
}
pub fn closest_fev_not_inside_inner<'a>(mesh:&MinkowskiMesh<'a>,point:Planar64Vec3)->Option<FEV<MinkowskiMesh<'a>>>{
const ENABLE_FAST_FAIL:bool=false; const ENABLE_FAST_FAIL:bool=false;
// TODO: remove mesh negation // TODO: remove mesh negation
minimum_difference::<ENABLE_FAST_FAIL,_,_>(&-mesh,point, minimum_difference::<ENABLE_FAST_FAIL,_,_>(&-mesh,point,
// on_exact // on_exact
|is_intersecting,simplex|{ |is_intersecting,simplex|{
println!("on_exact simplex={simplex:?}");
if is_intersecting{ if is_intersecting{
return None; return None;
} }
@@ -785,11 +765,7 @@ pub fn closest_fev_not_inside_inner<'a>(mesh:&MinkowskiMesh<'a>,point:Planar64Ve
Simplex1_3::Simplex2([v0,v1])=>{ Simplex1_3::Simplex2([v0,v1])=>{
// invert // invert
let (v0,v1)=(-v0,-v1); let (v0,v1)=(-v0,-v1);
let ev=crawl_to_closest_ev(mesh,[v0,v1],point); crawl_to_closest_ev(mesh,[v0,v1],point).into()
if !matches!(ev,EV::Edge(_)){
println!("I can't believe it's not an edge!");
}
ev.into()
}, },
Simplex1_3::Simplex3([v0,v1,v2])=>{ Simplex1_3::Simplex3([v0,v1,v2])=>{
// invert // invert
@@ -797,11 +773,7 @@ pub fn closest_fev_not_inside_inner<'a>(mesh:&MinkowskiMesh<'a>,point:Planar64Ve
// Shimmy to the side until you find a face that contains the closest point // Shimmy to the side until you find a face that contains the closest point
// it's ALWAYS representable as a face, but this algorithm may // it's ALWAYS representable as a face, but this algorithm may
// return E or V in edge cases but I don't think that will break the face crawler // return E or V in edge cases but I don't think that will break the face crawler
let fev=crawl_to_closest_fev(mesh,[v0,v1,v2],point); crawl_to_closest_fev(mesh,[v0,v1,v2],point)
if !matches!(fev,FEV::Face(_)){
println!("I can't believe it's not a face!");
}
fev
}, },
}) })
}, },
@@ -859,7 +831,6 @@ fn minimum_difference<const ENABLE_FAST_FAIL:bool,T,M:MeshQuery<Position=Planar6
if initial_axis==vec3::zero(){ if initial_axis==vec3::zero(){
initial_axis=choose_any_direction(); initial_axis=choose_any_direction();
} }
println!("initial_axis={initial_axis}");
let last_point=mesh.farthest_vert(-initial_axis); let last_point=mesh.farthest_vert(-initial_axis);
// this represents the 'a' value in the commented code // this represents the 'a' value in the commented code
let mut last_pos=mesh.vert(last_point); let mut last_pos=mesh.vert(last_point);
@@ -868,8 +839,6 @@ fn minimum_difference<const ENABLE_FAST_FAIL:bool,T,M:MeshQuery<Position=Planar6
// exitRadius = testIntersection and 0 or exitRadius or 1/0 // exitRadius = testIntersection and 0 or exitRadius or 1/0
// for _ = 1, 100 do // for _ = 1, 100 do
loop{ loop{
println!("direction={direction}");
// new_point_p = queryP(-direction) // new_point_p = queryP(-direction)
// new_point_q = queryQ(direction) // new_point_q = queryQ(direction)
// local next_point = new_point_q - new_point_p // local next_point = new_point_q - new_point_p
@@ -877,11 +846,7 @@ fn minimum_difference<const ENABLE_FAST_FAIL:bool,T,M:MeshQuery<Position=Planar6
let next_pos=mesh.vert(next_point); let next_pos=mesh.vert(next_point);
// if -direction:Dot(next_point) > (exitRadius + radiusP + radiusQ)*direction.magnitude then // if -direction:Dot(next_point) > (exitRadius + radiusP + radiusQ)*direction.magnitude then
let d=direction.dot(next_pos+point); if ENABLE_FAST_FAIL&&direction.dot(next_pos+point).is_negative(){
let fast_fail=d.is_negative();
println!("ENABLE_FAST_FAIL={ENABLE_FAST_FAIL} fast_fail={fast_fail} next_point={} dot={d}",next_pos+point);
if ENABLE_FAST_FAIL&&fast_fail{
println!("on_fast_fail");
return on_fast_fail(); return on_fast_fail();
} }
@@ -890,11 +855,8 @@ fn minimum_difference<const ENABLE_FAST_FAIL:bool,T,M:MeshQuery<Position=Planar6
// if // if
// direction:Dot(next_point - a) <= 0 or // direction:Dot(next_point - a) <= 0 or
// absDet(next_point, a, b, c) < 1e-6 // absDet(next_point, a, b, c) < 1e-6
let d1=direction.dot(next_pos-last_pos); if !direction.dot(next_pos-last_pos).is_positive()
let cond2=simplex_big.det_is_zero(mesh); ||simplex_big.det_is_zero(mesh){
println!("d1={d1:?} cond2={cond2}");
if !d1.is_positive()||cond2{
println!("on_exact");
// Found enough information to compute the exact closest point. // Found enough information to compute the exact closest point.
// local norm = direction.unit // local norm = direction.unit
// local dist = a:Dot(norm) // local dist = a:Dot(norm)
@@ -907,7 +869,6 @@ fn minimum_difference<const ENABLE_FAST_FAIL:bool,T,M:MeshQuery<Position=Planar6
match simplex_big.reduce(mesh,point){ match simplex_big.reduce(mesh,point){
// if a and b and c and d then // if a and b and c and d then
Reduce::Escape(simplex)=>{ Reduce::Escape(simplex)=>{
println!("on_escape");
// Enough information to conclude that the meshes are intersecting. // Enough information to conclude that the meshes are intersecting.
// Topology information is computed if needed. // Topology information is computed if needed.
return on_escape(simplex); return on_escape(simplex);

View File

@@ -1,174 +0,0 @@
use mlua::{Lua,FromLuaMulti,IntoLuaMulti,Function,Result as LuaResult,Vector};
use strafesnet_common::integer::{Planar64,Planar64Vec3,FixedFromFloatError};
use crate::mesh_query::MeshQuery;
use crate::minkowski::MinkowskiMesh;
pub fn contains_point(
mesh:&MinkowskiMesh,
point:Planar64Vec3,
)->LuaResult<bool>{
Ok(minimum_difference(mesh,point,true)?.hits)
}
pub fn minimum_difference_details(
mesh:&MinkowskiMesh,
point:Planar64Vec3,
)->LuaResult<(bool,Option<Details>)>{
let md=minimum_difference(mesh,point,false)?;
Ok((md.hits,md.details))
}
fn p64v3(v:Vector)->Result<Planar64Vec3,FixedFromFloatError>{
Ok(Planar64Vec3::new([
v.x().try_into()?,
v.y().try_into()?,
v.z().try_into()?,
]))
}
fn vec(v:Planar64Vec3)->Vector{
Vector::new(v.x.into(),v.y.into(),v.z.into())
}
struct MinimumDifference{
hits:bool,
details:Option<Details>
}
pub struct Details{
pub distance:Planar64,
pub p_pos:Planar64Vec3,
pub p_norm:Planar64Vec3,
pub q_pos:Planar64Vec3,
pub q_norm:Planar64Vec3,
}
impl FromLuaMulti for MinimumDifference{
fn from_lua_multi(mut values:mlua::MultiValue,_lua:&Lua)->LuaResult<Self>{
match values.make_contiguous(){
&mut [
mlua::Value::Boolean(hits),
mlua::Value::Nil,
mlua::Value::Nil,
mlua::Value::Nil,
mlua::Value::Nil,
mlua::Value::Nil,
]=>Ok(Self{hits,details:None}),
&mut [
mlua::Value::Boolean(hits),
mlua::Value::Number(distance),
mlua::Value::Vector(p_pos),
mlua::Value::Vector(p_norm),
mlua::Value::Vector(q_pos),
mlua::Value::Vector(q_norm),
]=>Ok(Self{
hits,
details:Some(Details{
distance:distance.try_into().unwrap(),
p_pos:p64v3(p_pos).unwrap(),
p_norm:p64v3(p_norm).unwrap(),
q_pos:p64v3(q_pos).unwrap(),
q_norm:p64v3(q_norm).unwrap(),
}),
}),
&mut [
mlua::Value::Boolean(hits),
mlua::Value::Integer(distance),
mlua::Value::Vector(p_pos),
mlua::Value::Vector(p_norm),
mlua::Value::Vector(q_pos),
mlua::Value::Vector(q_norm),
]=>Ok(Self{
hits,
details:Some(Details{
distance:distance.into(),
p_pos:p64v3(p_pos).unwrap(),
p_norm:p64v3(p_norm).unwrap(),
q_pos:p64v3(q_pos).unwrap(),
q_norm:p64v3(q_norm).unwrap(),
}),
}),
values=>Err(mlua::Error::runtime(format!("Invalid return values: {values:?}"))),
}
}
}
struct Args{
query_p:Function,
radius_p:f64,
query_q:Function,
radius_q:f64,
test_intersection:bool,
}
impl Args{
fn new(
lua:&Lua,
mesh:&'static MinkowskiMesh<'static>,
point:Planar64Vec3,
test_intersection:bool,
)->LuaResult<Self>{
let radius_p=0.0;
let radius_q=0.0;
// Query the farthest point on the mesh in the given direction.
let query_p=lua.create_function(move|_,dir:Option<Vector>|{
let Some(dir)=dir else{
return Ok(vec(mesh.mesh0.hint_point()));
};
let dir=p64v3(dir).unwrap();
let vert_id=mesh.mesh0.farthest_vert(dir);
let dir=mesh.mesh0.vert(vert_id);
Ok(vec(dir))
})?;
// query_q is different since it includes the test point offset.
let query_q=lua.create_function(move|_,dir:Option<Vector>|{
let Some(dir)=dir else{
return Ok(vec(mesh.mesh1.hint_point()+point));
};
let dir=p64v3(dir).unwrap();
let vert_id=mesh.mesh1.farthest_vert(dir);
let dir=mesh.mesh1.vert(vert_id)+point;
Ok(vec(dir))
})?;
Ok(Args{
query_p,
radius_p,
query_q,
radius_q,
test_intersection,
})
}
}
impl IntoLuaMulti for Args{
fn into_lua_multi(self,lua:&Lua)->LuaResult<mlua::MultiValue>{
use mlua::IntoLua;
Ok(mlua::MultiValue::from_vec(vec![
self.query_p.into_lua(lua)?,
self.radius_p.into_lua(lua)?,
self.query_q.into_lua(lua)?,
self.radius_q.into_lua(lua)?,
mlua::Value::Nil,
self.test_intersection.into_lua(lua)?,
]))
}
}
fn minimum_difference(
mesh:&MinkowskiMesh,
point:Planar64Vec3,
test_intersection:bool,
)->LuaResult<MinimumDifference>{
let ctx=init_lua()?;
// SAFETY: mesh lifetime must outlive args usages
let mesh=unsafe{core::mem::transmute(mesh)};
let args=Args::new(&ctx.lua,mesh,point,test_intersection)?;
ctx.f.call(args)
}
struct Ctx{
lua:Lua,
f:Function,
}
fn init_lua()->LuaResult<Ctx>{
static SOURCE:std::sync::LazyLock<String>=std::sync::LazyLock::new(||std::fs::read_to_string("../../Trey-MinimumDifference.lua").unwrap());
let lua=Lua::new();
lua.sandbox(true)?;
let lib_f=lua.load(SOURCE.as_str()).set_name("Trey-MinimumDifference").into_function()?;
let lib:mlua::Table=lib_f.call(())?;
let f=lib.raw_get("difference")?;
Ok(Ctx{lua,f})
}

View File

@@ -1,8 +1,6 @@
use core::ops::{Bound,RangeBounds}; use core::ops::{Bound,RangeBounds};
use strafesnet_common::integer::fixed_types::{F192_96,F256_128,F512_256}; use strafesnet_common::integer::{Planar64Vec3,Ratio,Fixed,vec3::Vector3};
use strafesnet_common::integer::vec3::Vector3;
use strafesnet_common::integer::{Planar64Vec3,Ratio,Fixed};
use crate::model::into_giga_time; use crate::model::into_giga_time;
use crate::model::{SubmeshVertId,SubmeshEdgeId,SubmeshDirectedEdgeId,SubmeshFaceId,TransformedMesh,GigaTime}; use crate::model::{SubmeshVertId,SubmeshEdgeId,SubmeshDirectedEdgeId,SubmeshFaceId,TransformedMesh,GigaTime};
use crate::mesh_query::{MeshQuery,MeshTopology,DirectedEdge,UndirectedEdge}; use crate::mesh_query::{MeshQuery,MeshTopology,DirectedEdge,UndirectedEdge};
@@ -80,8 +78,8 @@ pub enum MinkowskiFace{
#[derive(Debug)] #[derive(Debug)]
pub struct MinkowskiMesh<'a>{ pub struct MinkowskiMesh<'a>{
pub mesh0:TransformedMesh<'a>, mesh0:TransformedMesh<'a>,
pub mesh1:TransformedMesh<'a>, mesh1:TransformedMesh<'a>,
} }
// TODO: remove this // TODO: remove this
@@ -99,9 +97,6 @@ impl MinkowskiMesh<'_>{
mesh1, mesh1,
} }
} }
pub fn closest_point(&self,point:Planar64Vec3)->Option<crate::mesh_query::FEV<Self>>{
crate::minimum_difference::closest_fev_not_inside(self,point)
}
pub fn predict_collision_in(&self,trajectory:&Trajectory,range:impl RangeBounds<Time>)->Option<(MinkowskiFace,GigaTime)>{ pub fn predict_collision_in(&self,trajectory:&Trajectory,range:impl RangeBounds<Time>)->Option<(MinkowskiFace,GigaTime)>{
let start_position=match range.start_bound(){ let start_position=match range.start_bound(){
Bound::Included(time)=>trajectory.extrapolated_position(*time), Bound::Included(time)=>trajectory.extrapolated_position(*time),
@@ -147,7 +142,7 @@ impl MinkowskiMesh<'_>{
//WARNING! d outside of *2 //WARNING! d outside of *2
//WARNING: truncated precision //WARNING: truncated precision
//wrap for speed //wrap for speed
for dt in F256_128::zeroes2(((n.dot(trajectory.position))*2-d).wrap_256(),n.dot(trajectory.velocity).wrap_256()*2,n.dot(trajectory.acceleration).wrap_256()){ for dt in Fixed::<4,128>::zeroes2(((n.dot(trajectory.position))*2-d).wrap_4(),n.dot(trajectory.velocity).wrap_4()*2,n.dot(trajectory.acceleration).wrap_4()){
if low(&start_time,&dt)&&upp(&dt,&best_time)&&n.dot(trajectory.extrapolated_velocity_ratio_dt(dt)).is_negative(){ if low(&start_time,&dt)&&upp(&dt,&best_time)&&n.dot(trajectory.extrapolated_velocity_ratio_dt(dt)).is_negative(){
best_time=Bound::Included(dt); best_time=Bound::Included(dt);
best_edge=Some((directed_edge_id,dt)); best_edge=Some((directed_edge_id,dt));
@@ -164,8 +159,8 @@ impl MinkowskiMesh<'_>{
impl MeshQuery for MinkowskiMesh<'_>{ impl MeshQuery for MinkowskiMesh<'_>{
type Direction=Planar64Vec3; type Direction=Planar64Vec3;
type Position=Planar64Vec3; type Position=Planar64Vec3;
type Normal=Vector3<F192_96>; type Normal=Vector3<Fixed<3,96>>;
type Offset=F256_128; type Offset=Fixed<4,128>;
// TODO: relative d // TODO: relative d
fn face_nd(&self,face_id:MinkowskiFace)->(Self::Normal,Self::Offset){ fn face_nd(&self,face_id:MinkowskiFace)->(Self::Normal,Self::Offset){
match face_id{ match face_id{
@@ -181,7 +176,7 @@ impl MeshQuery for MinkowskiMesh<'_>{
let n=edge0_n.cross(edge1_n); let n=edge0_n.cross(edge1_n);
let e0d=n.dot(self.mesh0.vert(e0v0)+self.mesh0.vert(e0v1)); let e0d=n.dot(self.mesh0.vert(e0v0)+self.mesh0.vert(e0v1));
let e1d=n.dot(self.mesh1.vert(e1v0)+self.mesh1.vert(e1v1)); let e1d=n.dot(self.mesh1.vert(e1v0)+self.mesh1.vert(e1v1));
((n*(parity as i64*4-2)).widen_192(),((e0d-e1d)*(parity as i64*2-1)).widen_256()) ((n*(parity as i64*4-2)).widen_3(),((e0d-e1d)*(parity as i64*2-1)).widen_4())
}, },
MinkowskiFace::FaceVert(f0,v1)=>{ MinkowskiFace::FaceVert(f0,v1)=>{
let (n,d)=self.mesh0.face_nd(f0); let (n,d)=self.mesh0.face_nd(f0);
@@ -245,7 +240,7 @@ impl MeshTopology for MinkowskiMesh<'_>{
for face_n in &v1f_n{ for face_n in &v1f_n{
//add reflected mesh1 faces //add reflected mesh1 faces
//wrap for speed //wrap for speed
face_normals.push(*face_n-(n*face_n.dot(n)*2/nn).divide().wrap_192()); face_normals.push(*face_n-(n*face_n.dot(n)*2/nn).divide().wrap_3());
} }
if is_empty_volume(&face_normals){ if is_empty_volume(&face_normals){
f(MinkowskiDirectedEdge::EdgeVert(directed_edge_id,v1)); f(MinkowskiDirectedEdge::EdgeVert(directed_edge_id,v1));
@@ -260,7 +255,7 @@ impl MeshTopology for MinkowskiMesh<'_>{
// make a set of faces from mesh1's perspective // make a set of faces from mesh1's perspective
for face_n in &v0f_n{ for face_n in &v0f_n{
//wrap for speed //wrap for speed
face_normals.push(*face_n-(n*face_n.dot(n)*2/nn).divide().wrap_192()); face_normals.push(*face_n-(n*face_n.dot(n)*2/nn).divide().wrap_3());
} }
if is_empty_volume(&face_normals){ if is_empty_volume(&face_normals){
f(MinkowskiDirectedEdge::VertEdge(v0,directed_edge_id)); f(MinkowskiDirectedEdge::VertEdge(v0,directed_edge_id));
@@ -284,7 +279,7 @@ impl MeshTopology for MinkowskiMesh<'_>{
let &[e1f0,e1f1]=self.mesh1.edge_faces(e1).as_ref(); let &[e1f0,e1f1]=self.mesh1.edge_faces(e1).as_ref();
AsRefHelper([(e1f1,false),(e1f0,true)].map(|(edge_face_id1,face_parity)|{ AsRefHelper([(e1f1,false),(e1f0,true)].map(|(edge_face_id1,face_parity)|{
let mut best_edge=None; let mut best_edge=None;
let mut best_d:Ratio<F512_256,F512_256>=Ratio::new(Fixed::ZERO,Fixed::ONE); let mut best_d:Ratio<Fixed<8,256>,Fixed<8,256>>=Ratio::new(Fixed::ZERO,Fixed::ONE);
let edge_face1_n=self.mesh1.face_nd(edge_face_id1).0; let edge_face1_n=self.mesh1.face_nd(edge_face_id1).0;
let edge_face1_nn=edge_face1_n.dot(edge_face1_n); let edge_face1_nn=edge_face1_n.dot(edge_face1_n);
for &directed_edge_id0 in &v0e{ for &directed_edge_id0 in &v0e{
@@ -318,7 +313,7 @@ impl MeshTopology for MinkowskiMesh<'_>{
let &[e0f0,e0f1]=self.mesh0.edge_faces(e0).as_ref(); let &[e0f0,e0f1]=self.mesh0.edge_faces(e0).as_ref();
AsRefHelper([(e0f0,true),(e0f1,false)].map(|(edge_face_id0,face_parity)|{ AsRefHelper([(e0f0,true),(e0f1,false)].map(|(edge_face_id0,face_parity)|{
let mut best_edge=None; let mut best_edge=None;
let mut best_d:Ratio<F512_256,F512_256>=Ratio::new(Fixed::ZERO,Fixed::ONE); let mut best_d:Ratio<Fixed<8,256>,Fixed<8,256>>=Ratio::new(Fixed::ZERO,Fixed::ONE);
let edge_face0_n=self.mesh0.face_nd(edge_face_id0).0; let edge_face0_n=self.mesh0.face_nd(edge_face_id0).0;
let edge_face0_nn=edge_face0_n.dot(edge_face0_n); let edge_face0_nn=edge_face0_n.dot(edge_face0_n);
for &directed_edge_id1 in &v1e{ for &directed_edge_id1 in &v1e{
@@ -380,7 +375,7 @@ impl MeshTopology for MinkowskiMesh<'_>{
} }
} }
fn is_empty_volume(normals:&[Vector3<F192_96>])->bool{ fn is_empty_volume(normals:&[Vector3<Fixed<3,96>>])->bool{
let len=normals.len(); let len=normals.len();
for i in 0..len-1{ for i in 0..len-1{
for j in i+1..len{ for j in i+1..len{
@@ -407,6 +402,6 @@ fn is_empty_volume(normals:&[Vector3<F192_96>])->bool{
#[test] #[test]
fn test_is_empty_volume(){ fn test_is_empty_volume(){
use strafesnet_common::integer::vec3; use strafesnet_common::integer::vec3;
assert!(!is_empty_volume(&[vec3::X.widen_192(),vec3::Y.widen_192(),vec3::Z.widen_192()])); assert!(!is_empty_volume(&[vec3::X.widen_3(),vec3::Y.widen_3(),vec3::Z.widen_3()]));
assert!(is_empty_volume(&[vec3::X.widen_192(),vec3::Y.widen_192(),vec3::Z.widen_192(),vec3::NEG_X.widen_192()])); assert!(is_empty_volume(&[vec3::X.widen_3(),vec3::Y.widen_3(),vec3::Z.widen_3(),vec3::NEG_X.widen_3()]));
} }

View File

@@ -1,5 +1,4 @@
use std::collections::{HashSet,HashMap}; use std::collections::{HashSet,HashMap};
use strafesnet_common::integer::fixed_types::{F128_64,F192_96,F256_128};
use strafesnet_common::integer::vec3::Vector3; use strafesnet_common::integer::vec3::Vector3;
use strafesnet_common::model::{self,MeshId,PolygonIter}; use strafesnet_common::model::{self,MeshId,PolygonIter};
use strafesnet_common::integer::{self,vec3,Fixed,Planar64,Planar64Vec3,Ratio}; use strafesnet_common::integer::{self,vec3,Fixed,Planar64,Planar64Vec3,Ratio};
@@ -350,8 +349,8 @@ impl TryFrom<&model::Mesh> for PhysicsMesh{
} }
//assume face hash is stable, and there are no flush faces... //assume face hash is stable, and there are no flush faces...
let face=Face{ let face=Face{
normal:(normal/len as i64).divide().narrow_64().unwrap(), normal:(normal/len as i64).divide().narrow_1().unwrap(),
dot:(dot/(len*len) as i64).narrow_64().unwrap(), dot:(dot/(len*len) as i64).narrow_1().unwrap(),
}; };
let face_id=*face_id_from_face.entry(face).or_insert_with(||{ let face_id=*face_id_from_face.entry(face).or_insert_with(||{
let face_id=MeshFaceId::new(faces.len() as u32); let face_id=MeshFaceId::new(faces.len() as u32);
@@ -462,8 +461,8 @@ impl MeshTopology for PhysicsMeshView<'_>{
#[derive(Debug)] #[derive(Debug)]
pub struct PhysicsMeshTransform{ pub struct PhysicsMeshTransform{
pub vertex:integer::Planar64Affine3, pub vertex:integer::Planar64Affine3,
pub normal:integer::mat3::Matrix3<F128_64>, pub normal:integer::mat3::Matrix3<Fixed<2,64>>,
pub det:F192_96, pub det:Fixed<3,96>,
} }
impl PhysicsMeshTransform{ impl PhysicsMeshTransform{
pub fn new(transform:integer::Planar64Affine3)->Self{ pub fn new(transform:integer::Planar64Affine3)->Self{
@@ -478,7 +477,7 @@ impl PhysicsMeshTransform{
#[derive(Debug,Clone,Copy)] #[derive(Debug,Clone,Copy)]
pub struct TransformedMesh<'a>{ pub struct TransformedMesh<'a>{
view:PhysicsMeshView<'a>, view:PhysicsMeshView<'a>,
pub transform:&'a PhysicsMeshTransform, transform:&'a PhysicsMeshTransform,
} }
impl TransformedMesh<'_>{ impl TransformedMesh<'_>{
pub const fn new<'a>( pub const fn new<'a>(
@@ -490,15 +489,15 @@ impl TransformedMesh<'_>{
transform, transform,
} }
} }
pub fn verts<'a>(&'a self)->impl Iterator<Item=Vector3<F128_64>>+'a{ pub fn verts<'a>(&'a self)->impl Iterator<Item=Vector3<Fixed<2,64>>>+'a{
self.view.data.verts.iter().map(|&Vert(pos)|self.transform.vertex.transform_point3(pos)) self.view.data.verts.iter().map(|&Vert(pos)|self.transform.vertex.transform_point3(pos))
} }
} }
impl MeshQuery for TransformedMesh<'_>{ impl MeshQuery for TransformedMesh<'_>{
type Direction=Planar64Vec3; type Direction=Planar64Vec3;
type Position=Planar64Vec3; type Position=Planar64Vec3;
type Normal=Vector3<F192_96>; type Normal=Vector3<Fixed<3,96>>;
type Offset=F256_128; type Offset=Fixed<4,128>;
fn face_nd(&self,face_id:SubmeshFaceId)->(Self::Normal,Self::Offset){ fn face_nd(&self,face_id:SubmeshFaceId)->(Self::Normal,Self::Offset){
let (n,d)=self.view.face_nd(face_id); let (n,d)=self.view.face_nd(face_id);
let transformed_n=self.transform.normal*n; let transformed_n=self.transform.normal*n;
@@ -507,7 +506,7 @@ impl MeshQuery for TransformedMesh<'_>{
} }
fn vert(&self,vert_id:SubmeshVertId)->Planar64Vec3{ fn vert(&self,vert_id:SubmeshVertId)->Planar64Vec3{
// wrap for speed // wrap for speed
self.transform.vertex.transform_point3(self.view.vert(vert_id)).wrap_64() self.transform.vertex.transform_point3(self.view.vert(vert_id)).wrap_1()
} }
fn hint_point(&self)->Planar64Vec3{ fn hint_point(&self)->Planar64Vec3{
self.transform.vertex.translation self.transform.vertex.translation
@@ -554,8 +553,8 @@ impl MeshTopology for TransformedMesh<'_>{
fn edge_verts(&self,edge_id:Self::Edge)->impl AsRef<[Self::Vert;2]>{ fn edge_verts(&self,edge_id:Self::Edge)->impl AsRef<[Self::Vert;2]>{
self.view.edge_verts(edge_id) self.view.edge_verts(edge_id)
} }
fn for_each_face_vert(&self,_face_id:Self::Face,_f:impl FnMut(Self::Vert)){ fn for_each_face_vert(&self,face_id:Self::Face,f:impl FnMut(Self::Vert)){
unimplemented!() self.view.for_each_face_vert(face_id,f)
} }
#[inline] #[inline]
fn for_each_face_edge(&self,face_id:Self::Face,f:impl FnMut(Self::DirectedEdge)){ fn for_each_face_edge(&self,face_id:Self::Face,f:impl FnMut(Self::DirectedEdge)){
@@ -563,8 +562,8 @@ impl MeshTopology for TransformedMesh<'_>{
} }
} }
pub type GigaTime=Ratio<F256_128,F256_128>; pub type GigaTime=Ratio<Fixed<4,128>,Fixed<4,128>>;
pub fn into_giga_time(time:Time,relative_to:Time)->GigaTime{ pub fn into_giga_time(time:Time,relative_to:Time)->GigaTime{
let r=(time-relative_to).to_ratio(); let r=(time-relative_to).to_ratio();
Ratio::new(r.num.widen_256(),r.den.widen_256()) Ratio::new(r.num.widen_4(),r.den.widen_4())
} }

View File

@@ -129,8 +129,8 @@ impl TransientAcceleration{
}else{ }else{
//normal friction acceleration is clippedAcceleration.dot(normal)*friction //normal friction acceleration is clippedAcceleration.dot(normal)*friction
TransientAcceleration::Reachable{ TransientAcceleration::Reachable{
acceleration:target_diff.with_length(accel).divide().wrap_64(), acceleration:target_diff.with_length(accel).divide().wrap_1(),
time:time+Time::from((target_diff.length()/accel).divide().clamp_64()) time:time+Time::from((target_diff.length()/accel).divide().clamp_1())
} }
} }
} }
@@ -428,7 +428,7 @@ impl HitboxMesh{
let transform=PhysicsMeshTransform::new(transform); let transform=PhysicsMeshTransform::new(transform);
let transformed_mesh=TransformedMesh::new(mesh.complete_mesh_view(),&transform); let transformed_mesh=TransformedMesh::new(mesh.complete_mesh_view(),&transform);
for vert in transformed_mesh.verts(){ for vert in transformed_mesh.verts(){
aabb.grow(vert.narrow_64().unwrap()); aabb.grow(vert.narrow_1().unwrap());
} }
Self{ Self{
halfsize:aabb.size()>>1, halfsize:aabb.size()>>1,
@@ -481,12 +481,12 @@ impl StyleHelper for StyleModifiers{
} }
fn get_y_control_dir(&self,camera:&PhysicsCamera,controls:Controls)->Planar64Vec3{ fn get_y_control_dir(&self,camera:&PhysicsCamera,controls:Controls)->Planar64Vec3{
(camera.rotation_y()*self.get_control_dir(controls)).wrap_64() (camera.rotation_y()*self.get_control_dir(controls)).wrap_1()
} }
fn get_propulsion_control_dir(&self,camera:&PhysicsCamera,controls:Controls)->Planar64Vec3{ fn get_propulsion_control_dir(&self,camera:&PhysicsCamera,controls:Controls)->Planar64Vec3{
//don't interpolate this! discrete mouse movement, constant acceleration //don't interpolate this! discrete mouse movement, constant acceleration
(camera.rotation()*self.get_control_dir(controls)).wrap_64() (camera.rotation()*self.get_control_dir(controls)).wrap_1()
} }
fn calculate_mesh(&self)->HitboxMesh{ fn calculate_mesh(&self)->HitboxMesh{
let mesh=match self.hitbox.mesh{ let mesh=match self.hitbox.mesh{
@@ -1003,12 +1003,6 @@ impl PhysicsData{
hitbox_mesh:StyleModifiers::default().calculate_mesh(), hitbox_mesh:StyleModifiers::default().calculate_mesh(),
} }
} }
pub fn closest_point(&self,mesh_id:u32,point:Planar64Vec3)->Option<crate::mesh_query::FEV<MinkowskiMesh<'_>>>{
let model_mesh=self.models.mesh(ConvexMeshId{model_id:PhysicsModelId::Contact(ContactModelId(mesh_id)),submesh_id:PhysicsSubmeshId::new(0)});
println!("transform={:?}",model_mesh.transform.vertex.matrix3);
let minkowski=MinkowskiMesh::minkowski_sum(model_mesh,self.hitbox_mesh.transformed_mesh());
minkowski.closest_point(point)
}
pub fn new(map:&map::CompleteMap)->Self{ pub fn new(map:&map::CompleteMap)->Self{
let modes=map.modes.clone().denormalize(); let modes=map.modes.clone().denormalize();
let mut used_contact_attributes=Vec::new(); let mut used_contact_attributes=Vec::new();
@@ -1124,7 +1118,7 @@ impl PhysicsData{
let mut aabb=aabb::Aabb::default(); let mut aabb=aabb::Aabb::default();
let transformed_mesh=TransformedMesh::new(view,transform); let transformed_mesh=TransformedMesh::new(view,transform);
for v in transformed_mesh.verts(){ for v in transformed_mesh.verts(){
aabb.grow(v.narrow_64().unwrap()); aabb.grow(v.narrow_1().unwrap());
} }
(ConvexMeshId{ (ConvexMeshId{
model_id, model_id,
@@ -1257,7 +1251,7 @@ fn contact_normal(
let minkowski=MinkowskiMesh::minkowski_sum(model_mesh,hitbox_mesh.transformed_mesh()); let minkowski=MinkowskiMesh::minkowski_sum(model_mesh,hitbox_mesh.transformed_mesh());
// TODO: normalize to i64::MAX>>1 // TODO: normalize to i64::MAX>>1
// wrap for speed // wrap for speed
minkowski.face_nd(face_id).0.wrap_64() minkowski.face_nd(face_id).0.wrap_1()
} }
fn recalculate_touching( fn recalculate_touching(
@@ -1397,7 +1391,7 @@ fn teleport_to_spawn(
const EPSILON:Planar64=Planar64::raw((1<<32)/16); const EPSILON:Planar64=Planar64::raw((1<<32)/16);
let transform=models.get_model_transform(spawn_model_id).ok_or(TeleportToSpawnError::NoModel)?; let transform=models.get_model_transform(spawn_model_id).ok_or(TeleportToSpawnError::NoModel)?;
//TODO: transform.vertex.matrix3.col(1)+transform.vertex.translation //TODO: transform.vertex.matrix3.col(1)+transform.vertex.translation
let point=transform.vertex.transform_point3(vec3::Y).clamp_64()+Planar64Vec3::new([Planar64::ZERO,style.hitbox.halfsize.y+EPSILON,Planar64::ZERO]); let point=transform.vertex.transform_point3(vec3::Y).clamp_1()+Planar64Vec3::new([Planar64::ZERO,style.hitbox.halfsize.y+EPSILON,Planar64::ZERO]);
teleport(point,move_state,body,touching,run,mode_state,Some(mode),models,hitbox_mesh,bvh,style,camera,input_state,time); teleport(point,move_state,body,touching,run,mode_state,Some(mode),models,hitbox_mesh,bvh,style,camera,input_state,time);
Ok(()) Ok(())
} }
@@ -1561,7 +1555,7 @@ fn collision_start_contact(
Some(gameplay_attributes::ContactingBehaviour::Surf)=>(), Some(gameplay_attributes::ContactingBehaviour::Surf)=>(),
Some(gameplay_attributes::ContactingBehaviour::Cling)=>println!("Unimplemented!"), Some(gameplay_attributes::ContactingBehaviour::Cling)=>println!("Unimplemented!"),
&Some(gameplay_attributes::ContactingBehaviour::Elastic(elasticity))=>{ &Some(gameplay_attributes::ContactingBehaviour::Elastic(elasticity))=>{
let reflected_velocity=body.velocity+((body.velocity-incident_velocity)*Planar64::raw(elasticity as i64+1)).wrap_64(); let reflected_velocity=body.velocity+((body.velocity-incident_velocity)*Planar64::raw(elasticity as i64+1)).wrap_1();
set_velocity(body,touching,models,hitbox_mesh,reflected_velocity); set_velocity(body,touching,models,hitbox_mesh,reflected_velocity);
}, },
Some(gameplay_attributes::ContactingBehaviour::Ladder(contacting_ladder))=> Some(gameplay_attributes::ContactingBehaviour::Ladder(contacting_ladder))=>
@@ -1815,7 +1809,7 @@ fn atomic_internal_instruction(state:&mut PhysicsState,data:&PhysicsData,ins:Tim
// manually advance time // manually advance time
let extrapolated_body=state.body.with_acceleration(state.acceleration(data)).extrapolated_body(state.time); let extrapolated_body=state.body.with_acceleration(state.acceleration(data)).extrapolated_body(state.time);
let camera_mat=state.camera.simulate_move_rotation_y(state.input_state.lerp_delta(state.time).x); let camera_mat=state.camera.simulate_move_rotation_y(state.input_state.lerp_delta(state.time).x);
if let Some(ticked_velocity)=strafe_settings.tick_velocity(extrapolated_body.velocity,(camera_mat*control_dir).with_length(Planar64::ONE).divide().wrap_64()){ if let Some(ticked_velocity)=strafe_settings.tick_velocity(extrapolated_body.velocity,(camera_mat*control_dir).with_length(Planar64::ONE).divide().wrap_1()){
state.body=extrapolated_body; state.body=extrapolated_body;
//this is wrong but will work ig //this is wrong but will work ig
//need to note which push planes activate in push solve and keep those //need to note which push planes activate in push solve and keep those
@@ -2026,24 +2020,6 @@ mod test{
),Some(Time::from_secs(2))); ),Some(Time::from_secs(2)));
} }
#[test] #[test]
fn test_collision_zero_time_exact(){
test_collision(Trajectory::new(
int3(0,3,0),
int3(0,-1,0),
vec3::zero(),
Time::ZERO
),Some(Time::from_secs(0)));
}
#[test]
fn test_collision_zero_time_epsilon(){
test_collision(Trajectory::new(
int3(0,3,0)+(vec3::Y>>32),
int3(0,-1,0),
vec3::zero(),
Time::ZERO
),Some(Time::from_secs(0)));
}
#[test]
fn test_collision_small_mv(){ fn test_collision_small_mv(){
test_collision(Trajectory::new( test_collision(Trajectory::new(
int3(0,5,0), int3(0,5,0),

View File

@@ -1,4 +1,3 @@
use strafesnet_common::integer::fixed_types::{F64_32,F128_64,F192_96,F256_128,F320_160};
use strafesnet_common::integer::vec3::{self,Vector3}; use strafesnet_common::integer::vec3::{self,Vector3};
use strafesnet_common::integer::{Fixed,Planar64Vec3,Ratio}; use strafesnet_common::integer::{Fixed,Planar64Vec3,Ratio};
use strafesnet_common::ray::Ray; use strafesnet_common::ray::Ray;
@@ -13,7 +12,7 @@ use strafesnet_common::ray::Ray;
type Conts<'a>=arrayvec::ArrayVec<&'a Contact,4>; type Conts<'a>=arrayvec::ArrayVec<&'a Contact,4>;
// hack to allow comparing ratios to zero // hack to allow comparing ratios to zero
const RATIO_ZERO:Ratio<F64_32,F64_32>=Ratio::new(Fixed::ZERO,Fixed::EPSILON); const RATIO_ZERO:Ratio<Fixed<1,32>,Fixed<1,32>>=Ratio::new(Fixed::ZERO,Fixed::EPSILON);
/// Information about a contact restriction /// Information about a contact restriction
#[derive(Debug,PartialEq)] #[derive(Debug,PartialEq)]
@@ -30,17 +29,17 @@ impl Contact{
normal:self.normal, normal:self.normal,
} }
} }
fn relative_dot(&self,direction:Planar64Vec3)->F128_64{ fn relative_dot(&self,direction:Planar64Vec3)->Fixed<2,64>{
(direction-self.velocity).dot(self.normal) (direction-self.velocity).dot(self.normal)
} }
/// Calculate the time of intersection. (previously get_touch_time) /// Calculate the time of intersection. (previously get_touch_time)
fn solve(&self,ray:&Ray)->Ratio<F128_64,F128_64>{ 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) (self.position-ray.origin).dot(self.normal)/(ray.direction-self.velocity).dot(self.normal)
} }
} }
//note that this is horrible with fixed point arithmetic //note that this is horrible with fixed point arithmetic
fn solve1(c0:&Contact)->Option<Ratio<Vector3<F192_96>,F128_64>>{ fn solve1(c0:&Contact)->Option<Ratio<Vector3<Fixed<3,96>>,Fixed<2,64>>>{
let det=c0.normal.dot(c0.velocity); let det=c0.normal.dot(c0.velocity);
if det.abs()==Fixed::ZERO{ if det.abs()==Fixed::ZERO{
return None; return None;
@@ -48,7 +47,7 @@ fn solve1(c0:&Contact)->Option<Ratio<Vector3<F192_96>,F128_64>>{
let d0=c0.normal.dot(c0.position); let d0=c0.normal.dot(c0.position);
Some(c0.normal*d0/det) Some(c0.normal*d0/det)
} }
fn solve2(c0:&Contact,c1:&Contact)->Option<Ratio<Vector3<F320_160>,F256_128>>{ fn solve2(c0:&Contact,c1:&Contact)->Option<Ratio<Vector3<Fixed<5,160>>,Fixed<4,128>>>{
let u0_u1=c0.velocity.cross(c1.velocity); let u0_u1=c0.velocity.cross(c1.velocity);
let n0_n1=c0.normal.cross(c1.normal); let n0_n1=c0.normal.cross(c1.normal);
let det=u0_u1.dot(n0_n1); let det=u0_u1.dot(n0_n1);
@@ -59,7 +58,7 @@ fn solve2(c0:&Contact,c1:&Contact)->Option<Ratio<Vector3<F320_160>,F256_128>>{
let d1=c1.normal.dot(c1.position); let d1=c1.normal.dot(c1.position);
Some((c1.normal.cross(u0_u1)*d0+u0_u1.cross(c0.normal)*d1)/det) 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<F256_128>,F192_96>>{ fn solve3(c0:&Contact,c1:&Contact,c2:&Contact)->Option<Ratio<Vector3<Fixed<4,128>>,Fixed<3,96>>>{
let n0_n1=c0.normal.cross(c1.normal); let n0_n1=c0.normal.cross(c1.normal);
let det=c2.normal.dot(n0_n1); let det=c2.normal.dot(n0_n1);
if det.abs()==Fixed::ZERO{ if det.abs()==Fixed::ZERO{
@@ -71,7 +70,7 @@ fn solve3(c0:&Contact,c1:&Contact,c2:&Contact)->Option<Ratio<Vector3<F256_128>,F
Some((c1.normal.cross(c2.normal)*d0+c2.normal.cross(c0.normal)*d1+c0.normal.cross(c1.normal)*d2)/det) 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<F128_64,F128_64>;1]>{ fn decompose1(point:Planar64Vec3,u0:Planar64Vec3)->Option<[Ratio<Fixed<2,64>,Fixed<2,64>>;1]>{
let det=u0.dot(u0); let det=u0.dot(u0);
if det==Fixed::ZERO{ if det==Fixed::ZERO{
return None; return None;
@@ -79,7 +78,7 @@ fn decompose1(point:Planar64Vec3,u0:Planar64Vec3)->Option<[Ratio<F128_64,F128_64
let s0=u0.dot(point)/det; let s0=u0.dot(point)/det;
Some([s0]) Some([s0])
} }
fn decompose2(point:Planar64Vec3,u0:Planar64Vec3,u1:Planar64Vec3)->Option<[Ratio<F256_128,F256_128>;2]>{ fn decompose2(point:Planar64Vec3,u0:Planar64Vec3,u1:Planar64Vec3)->Option<[Ratio<Fixed<4,128>,Fixed<4,128>>;2]>{
let u0_u1=u0.cross(u1); let u0_u1=u0.cross(u1);
let det=u0_u1.dot(u0_u1); let det=u0_u1.dot(u0_u1);
if det==Fixed::ZERO{ if det==Fixed::ZERO{
@@ -89,7 +88,7 @@ fn decompose2(point:Planar64Vec3,u0:Planar64Vec3,u1:Planar64Vec3)->Option<[Ratio
let s1=u0_u1.dot(u0.cross(point))/det; let s1=u0_u1.dot(u0.cross(point))/det;
Some([s0,s1]) Some([s0,s1])
} }
fn decompose3(point:Planar64Vec3,u0:Planar64Vec3,u1:Planar64Vec3,u2:Planar64Vec3)->Option<[Ratio<F192_96,F192_96>;3]>{ 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); let det=u0.cross(u1).dot(u2);
if det==Fixed::ZERO{ if det==Fixed::ZERO{
return None; return None;
@@ -152,18 +151,18 @@ const fn get_push_ray_0(point:Planar64Vec3)->Ray{
} }
fn get_push_ray_1(point:Planar64Vec3,c0:&Contact)->Option<Ray>{ fn get_push_ray_1(point:Planar64Vec3,c0:&Contact)->Option<Ray>{
//wrap for speed //wrap for speed
let direction=solve1(c0)?.divide().wrap_64(); let direction=solve1(c0)?.divide().wrap_1();
let [s0]=decompose1(direction,c0.velocity)?; let [s0]=decompose1(direction,c0.velocity)?;
if s0.lt_ratio(RATIO_ZERO){ if s0.lt_ratio(RATIO_ZERO){
return None; return None;
} }
let origin=point+solve1( let origin=point+solve1(
&c0.relative_to(point), &c0.relative_to(point),
)?.divide().wrap_64(); )?.divide().wrap_1();
Some(Ray{origin,direction}) Some(Ray{origin,direction})
} }
fn get_push_ray_2(point:Planar64Vec3,c0:&Contact,c1:&Contact)->Option<Ray>{ fn get_push_ray_2(point:Planar64Vec3,c0:&Contact,c1:&Contact)->Option<Ray>{
let direction=solve2(c0,c1)?.divide().wrap_64(); let direction=solve2(c0,c1)?.divide().wrap_1();
let [s0,s1]=decompose2(direction,c0.velocity,c1.velocity)?; let [s0,s1]=decompose2(direction,c0.velocity,c1.velocity)?;
if s0.lt_ratio(RATIO_ZERO)||s1.lt_ratio(RATIO_ZERO){ if s0.lt_ratio(RATIO_ZERO)||s1.lt_ratio(RATIO_ZERO){
return None; return None;
@@ -171,11 +170,11 @@ fn get_push_ray_2(point:Planar64Vec3,c0:&Contact,c1:&Contact)->Option<Ray>{
let origin=point+solve2( let origin=point+solve2(
&c0.relative_to(point), &c0.relative_to(point),
&c1.relative_to(point), &c1.relative_to(point),
)?.divide().wrap_64(); )?.divide().wrap_1();
Some(Ray{origin,direction}) Some(Ray{origin,direction})
} }
fn get_push_ray_3(point:Planar64Vec3,c0:&Contact,c1:&Contact,c2:&Contact)->Option<Ray>{ fn get_push_ray_3(point:Planar64Vec3,c0:&Contact,c1:&Contact,c2:&Contact)->Option<Ray>{
let direction=solve3(c0,c1,c2)?.divide().wrap_64(); let direction=solve3(c0,c1,c2)?.divide().wrap_1();
let [s0,s1,s2]=decompose3(direction,c0.velocity,c1.velocity,c2.velocity)?; 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){ if s0.lt_ratio(RATIO_ZERO)||s1.lt_ratio(RATIO_ZERO)||s2.lt_ratio(RATIO_ZERO){
return None; return None;
@@ -184,7 +183,7 @@ fn get_push_ray_3(point:Planar64Vec3,c0:&Contact,c1:&Contact,c2:&Contact)->Optio
&c0.relative_to(point), &c0.relative_to(point),
&c1.relative_to(point), &c1.relative_to(point),
&c2.relative_to(point), &c2.relative_to(point),
)?.divide().wrap_64(); )?.divide().wrap_1();
Some(Ray{origin,direction}) Some(Ray{origin,direction})
} }
@@ -273,10 +272,10 @@ fn get_best_push_ray_and_conts<'a>(
} }
} }
fn get_first_touch<'a>(contacts:&'a [Contact],ray:&Ray,conts:&Conts)->Option<(Ratio<F128_64,F128_64>,&'a Contact)>{ fn get_first_touch<'a>(contacts:&'a [Contact],ray:&Ray,conts:&Conts)->Option<(Ratio<Fixed<2,64>,Fixed<2,64>>,&'a Contact)>{
contacts.iter() contacts.iter()
.filter(|&contact| .filter(|&contact|
!conts.iter().any(|&c|core::ptr::eq(c,contact)) !conts.iter().any(|&c|std::ptr::eq(c,contact))
&&contact.relative_dot(ray.direction).is_negative() &&contact.relative_dot(ray.direction).is_negative()
) )
.map(|contact|(contact.solve(ray),contact)) .map(|contact|(contact.solve(ray),contact))

View File

@@ -76,21 +76,3 @@ fn physics_bug_3()->Result<(),ReplayError>{
Ok(()) Ok(())
} }
#[test]
fn physics_bug_26()->Result<(),ReplayError>{
println!("loading map file..");
let data=read_entire_file("../tools/bhop_maps/5692124338.snfm")?;
let map=strafesnet_snf::read_map(data)?.into_complete_map()?;
// create recording
println!("generating models..");
let physics_data=PhysicsData::new(&map);
println!("reproducing bug...");
//teleport to bug
let fev=physics_data.closest_point(1020,strafesnet_common::integer::vec3::try_from_f32_array([76.889,363.188,-309.263]).unwrap()).unwrap();
println!("{fev:?}");
Ok(())
}

View File

@@ -1,6 +1,6 @@
[package] [package]
name = "strafesnet_bsp_loader" name = "strafesnet_bsp_loader"
version = "0.5.0" version = "0.4.0"
edition = "2024" edition = "2024"
repository = "https://git.itzana.me/StrafesNET/strafe-project" repository = "https://git.itzana.me/StrafesNET/strafe-project"
license = "MIT OR Apache-2.0" license = "MIT OR Apache-2.0"

View File

@@ -1,22 +1,21 @@
use strafesnet_common::integer::Planar64;
use strafesnet_common::{model,integer}; use strafesnet_common::{model,integer};
use strafesnet_common::integer::fixed_types::F192_96; use strafesnet_common::integer::{vec3::Vector3,Fixed,Ratio};
use strafesnet_common::integer::vec3::Vector3;
use strafesnet_common::integer::{Planar64,Planar64Vec3,Ratio};
use crate::{valve_transform_normal,valve_transform_dist}; use crate::{valve_transform_normal,valve_transform_dist};
#[derive(Hash,Eq,PartialEq)] #[derive(Hash,Eq,PartialEq)]
struct Face{ struct Face{
normal:Planar64Vec3, normal:integer::Planar64Vec3,
dot:Planar64, dot:Planar64,
} }
#[derive(Debug)] #[derive(Debug)]
struct Faces{ struct Faces{
faces:Vec<Vec<Planar64Vec3>>, faces:Vec<Vec<integer::Planar64Vec3>>,
} }
fn solve3(c0:&Face,c1:&Face,c2:&Face)->Option<Ratio<Vector3<F192_96>,F192_96>>{ fn solve3(c0:&Face,c1:&Face,c2:&Face)->Option<Ratio<Vector3<Fixed<3,96>>,Fixed<3,96>>>{
let n0_n1=c0.normal.cross(c1.normal); let n0_n1=c0.normal.cross(c1.normal);
let det=c2.normal.dot(n0_n1); let det=c2.normal.dot(n0_n1);
if det.abs().is_zero(){ if det.abs().is_zero(){
@@ -83,12 +82,12 @@ fn planes_to_faces(face_list:std::collections::HashSet<Face>)->Result<Faces,Plan
// test if any *other* faces occlude the intersection // test if any *other* faces occlude the intersection
for new_face in &face_list{ for new_face in &face_list{
// new face occludes intersection point // new face occludes intersection point
if (new_face.dot.widen_128()/Planar64::ONE).lt_ratio(new_face.normal.dot(intersection.num)/intersection.den){ if (new_face.dot.widen_2()/Planar64::ONE).lt_ratio(new_face.normal.dot(intersection.num)/intersection.den){
// replace one of the faces with the new face // replace one of the faces with the new face
// dont' try to replace face0 because we are exploring that face in particular // dont' try to replace face0 because we are exploring that face in particular
if let Some(new_intersection)=solve3(face0,new_face,face2){ if let Some(new_intersection)=solve3(face0,new_face,face2){
// face1 does not occlude (or intersect) the new intersection // face1 does not occlude (or intersect) the new intersection
if (face1.dot.widen_128()/Planar64::ONE).gt_ratio(face1.normal.dot(new_intersection.num)/new_intersection.den){ if (face1.dot.widen_2()/Planar64::ONE).gt_ratio(face1.normal.dot(new_intersection.num)/new_intersection.den){
face1=new_face; face1=new_face;
intersection=new_intersection; intersection=new_intersection;
continue 'find; continue 'find;
@@ -96,7 +95,7 @@ fn planes_to_faces(face_list:std::collections::HashSet<Face>)->Result<Faces,Plan
} }
if let Some(new_intersection)=solve3(face0,face1,new_face){ if let Some(new_intersection)=solve3(face0,face1,new_face){
// face2 does not occlude (or intersect) the new intersection // face2 does not occlude (or intersect) the new intersection
if (face2.dot.widen_128()/Planar64::ONE).gt_ratio(face2.normal.dot(new_intersection.num)/new_intersection.den){ if (face2.dot.widen_2()/Planar64::ONE).gt_ratio(face2.normal.dot(new_intersection.num)/new_intersection.den){
face2=new_face; face2=new_face;
intersection=new_intersection; intersection=new_intersection;
continue 'find; continue 'find;
@@ -121,7 +120,7 @@ fn planes_to_faces(face_list:std::collections::HashSet<Face>)->Result<Faces,Plan
continue; continue;
} }
// new_face occludes intersection meaning intersection is not on convex solid and face0 is degenrate // new_face occludes intersection meaning intersection is not on convex solid and face0 is degenrate
if (new_face.dot.widen_128()/Planar64::ONE).lt_ratio(new_face.normal.dot(intersection.num)/intersection.den){ if (new_face.dot.widen_2()/Planar64::ONE).lt_ratio(new_face.normal.dot(intersection.num)/intersection.den){
// abort! reject face0 entirely // abort! reject face0 entirely
continue 'face; continue 'face;
} }
@@ -139,7 +138,7 @@ fn planes_to_faces(face_list:std::collections::HashSet<Face>)->Result<Faces,Plan
loop{ loop{
// push point onto vertices // push point onto vertices
// problem: this may push a vertex that does not fit in the fixed point range and is thus meaningless // problem: this may push a vertex that does not fit in the fixed point range and is thus meaningless
face.push(intersection.divide().narrow_64().unwrap()); face.push(intersection.divide().narrow_1().unwrap());
// we looped back around to face1, we're done! // we looped back around to face1, we're done!
if core::ptr::eq(face1,face2){ if core::ptr::eq(face1,face2){
@@ -205,7 +204,7 @@ impl std::fmt::Display for BrushToMeshError{
} }
impl core::error::Error for BrushToMeshError{} impl core::error::Error for BrushToMeshError{}
pub fn faces_to_mesh(faces:Vec<Vec<Planar64Vec3>>)->model::Mesh{ pub fn faces_to_mesh(faces:Vec<Vec<integer::Planar64Vec3>>)->model::Mesh{
// generate the mesh // generate the mesh
let mut mb=model::MeshBuilder::new(); let mut mb=model::MeshBuilder::new();
let color=mb.acquire_color_id(glam::Vec4::ONE); let color=mb.acquire_color_id(glam::Vec4::ONE);

View File

@@ -1,5 +1,8 @@
pub mod bsp; use strafesnet_deferred_loader::deferred_loader::{LoadFailureMode,MeshDeferredLoader,RenderConfigDeferredLoader};
pub mod mesh; use strafesnet_deferred_loader::loader::Loader;
mod bsp;
mod mesh;
mod brush; mod brush;
const VALVE_SCALE:f32=1.0/16.0; const VALVE_SCALE:f32=1.0/16.0;
@@ -25,6 +28,27 @@ impl std::fmt::Display for ReadError{
} }
impl std::error::Error for ReadError{} impl std::error::Error for ReadError{}
#[derive(Debug)]
pub enum LoadError<M,T>{
Mesh(M),
Texture(T),
}
impl<M,T> std::fmt::Display for LoadError<M,T>
where
M:std::fmt::Debug,
T:std::fmt::Debug,
{
fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{
write!(f,"{self:?}")
}
}
impl<M,T> std::error::Error for LoadError<M,T>
where
M:std::fmt::Debug,
T:std::fmt::Debug,
{}
pub struct Bsp{ pub struct Bsp{
bsp:vbsp::Bsp, bsp:vbsp::Bsp,
case_folded_file_names:std::collections::HashMap<String,String>, case_folded_file_names:std::collections::HashMap<String,String>,
@@ -59,6 +83,36 @@ impl Bsp{
None=>Ok(None), None=>Ok(None),
} }
} }
pub fn to_snf<'dom,'mesh,'texture,M,T>(
&'dom self,
failure_mode:LoadFailureMode,
mut mesh_loader:M,
mut texture_loader:T,
)->Result<strafesnet_common::map::CompleteMap,LoadError<M::Error,T::Error>>
where
'dom:'mesh+'texture,
M:Loader<Resource=strafesnet_common::model::Mesh,Index<'mesh>=&'mesh str>+'mesh,
T:Loader<Resource=strafesnet_deferred_loader::texture::Texture,Index<'texture>=std::borrow::Cow<'texture,str>>+'texture,
{
let mut texture_deferred_loader=RenderConfigDeferredLoader::new();
let mut mesh_deferred_loader=MeshDeferredLoader::new();
let map_step1=bsp::convert(
self,
&mut texture_deferred_loader,
&mut mesh_deferred_loader,
);
let prop_meshes=mesh_deferred_loader.into_meshes(&mut mesh_loader,failure_mode).map_err(LoadError::Mesh)?;
let map_step2=map_step1.add_prop_meshes(prop_meshes);
let render_configs=texture_deferred_loader.into_render_configs(&mut texture_loader,failure_mode).map_err(LoadError::Texture)?;
let map=map_step2.add_render_configs_and_textures(render_configs);
Ok(map)
}
} }
pub struct Vpk{ pub struct Vpk{
vpk:vpk::VPK, vpk:vpk::VPK,

View File

@@ -5,8 +5,6 @@ use strafesnet_deferred_loader::deferred_loader::RenderConfigDeferredLoader;
use crate::valve_transform; use crate::valve_transform;
pub use model::Mesh;
fn ingest_vertex(mb:&mut model::MeshBuilder,vertex:&vmdl::vvd::Vertex,color:model::ColorId)->model::VertexId{ fn ingest_vertex(mb:&mut model::MeshBuilder,vertex:&vmdl::vvd::Vertex,color:model::ColorId)->model::VertexId{
let pos=mb.acquire_pos_id(valve_transform(vertex.position.into())); let pos=mb.acquire_pos_id(valve_transform(vertex.position.into()));
let normal=mb.acquire_normal_id(valve_transform(vertex.normal.into())); let normal=mb.acquire_normal_id(valve_transform(vertex.normal.into()));
@@ -19,7 +17,7 @@ fn ingest_vertex(mb:&mut model::MeshBuilder,vertex:&vmdl::vvd::Vertex,color:mode
}) })
} }
pub fn convert_mesh(model:vmdl::Model,deferred_loader:&mut RenderConfigDeferredLoader<Cow<str>>)->Mesh{ pub fn convert_mesh(model:vmdl::Model,deferred_loader:&mut RenderConfigDeferredLoader<Cow<str>>)->model::Mesh{
let texture_paths=model.texture_directories(); let texture_paths=model.texture_directories();
if texture_paths.len()!=1{ if texture_paths.len()!=1{
println!("WARNING: multiple texture paths"); println!("WARNING: multiple texture paths");

View File

@@ -1,6 +1,6 @@
[package] [package]
name = "strafesnet_common" name = "strafesnet_common"
version = "0.8.7" version = "0.8.6"
edition = "2024" edition = "2024"
repository = "https://git.itzana.me/StrafesNET/strafe-project" repository = "https://git.itzana.me/StrafesNET/strafe-project"
license = "MIT OR Apache-2.0" license = "MIT OR Apache-2.0"

View File

@@ -62,7 +62,7 @@ impl Aabb{
self.min.map_zip(self.max,|(min,max)|min.midpoint(max)) self.min.map_zip(self.max,|(min,max)|min.midpoint(max))
} }
#[inline] #[inline]
pub fn area_weight(&self)->fixed_wide::types::F128_64{ pub fn area_weight(&self)->fixed_wide::fixed::Fixed<2,64>{
let d=self.max-self.min; let d=self.max-self.min;
d.x*d.y+d.y*d.z+d.z*d.x d.x*d.y+d.y*d.z+d.z*d.x
} }

View File

@@ -3,7 +3,7 @@ use std::collections::BTreeMap;
use crate::aabb::Aabb; use crate::aabb::Aabb;
use crate::ray::Ray; use crate::ray::Ray;
use crate::integer::{Ratio,Planar64,Planar64Vec3}; use crate::integer::{Ratio,Planar64};
use crate::instruction::{InstructionCollector,TimedInstruction}; use crate::instruction::{InstructionCollector,TimedInstruction};
//da algaritum //da algaritum
@@ -140,34 +140,33 @@ impl<L> BvhNode<L>{
}, },
} }
} }
fn populate_nodes<'a,T,IntersectLeaf,IntersectAabb>( fn populate_nodes<'a,T,F>(
&'a self, &'a self,
start_point:Planar64Vec3, collector:&mut InstructionCollector<&'a L,Ratio<Planar64,Planar64>>,
collector:&mut InstructionCollector<&'a L,T>, nodes:&mut BTreeMap<Ratio<Planar64,Planar64>,&'a BvhNode<L>>,
nodes:&mut BTreeMap<T,&'a BvhNode<L>>, ray:&Ray,
start_time:T, start_time:Ratio<Planar64,Planar64>,
intersect_leaf:&IntersectLeaf, f:&F,
intersect_aabb:&IntersectAabb,
) )
where where
T:Ord+Copy, T:Ord+Copy,
IntersectLeaf:Fn(&L)->Option<T>, Ratio<Planar64,Planar64>:From<T>,
IntersectAabb:Fn(&Aabb)->Option<T>, F:Fn(&L,&Ray)->Option<T>,
{ {
match &self.content{ match &self.content{
RecursiveContent::Leaf(leaf)=>if let Some(time)=intersect_leaf(leaf){ RecursiveContent::Leaf(leaf)=>if let Some(time)=f(leaf,ray){
let ins=TimedInstruction{time,instruction:leaf}; let ins=TimedInstruction{time:time.into(),instruction:leaf};
if start_time<ins.time&&ins.time<collector.time(){ if start_time.lt_ratio(ins.time)&&ins.time.lt_ratio(collector.time()){
collector.collect(Some(ins)); collector.collect(Some(ins));
} }
}, },
RecursiveContent::Branch(children)=>for child in children{ RecursiveContent::Branch(children)=>for child in children{
if child.aabb.contains(start_point){ if child.aabb.contains(ray.origin){
child.populate_nodes(start_point,collector,nodes,start_time,intersect_leaf,intersect_aabb); child.populate_nodes(collector,nodes,ray,start_time,f);
}else{ }else{
// Am I an upcoming superstar? // Am I an upcoming superstar?
if let Some(t)=intersect_aabb(&child.aabb){ if let Some(t)=intersect_aabb(ray,&child.aabb){
if start_time<t&&t<collector.time(){ if start_time.lt_ratio(t)&&t.lt_ratio(collector.time()){
nodes.insert(t,child); nodes.insert(t,child);
} }
} }
@@ -175,29 +174,27 @@ impl<L> BvhNode<L>{
}, },
} }
} }
/// Traverse the BVH using the given sampling functions. pub fn sample_ray<T,F>(
/// Nodes are tested in order of T returned by IntersectAabb.
/// The algorithm ends when T for the next node to test is
/// greater than the current best collected T from IntersectLeaf.
pub fn traverse<T,IntersectLeaf,IntersectAabb>(
&self, &self,
start_point:Planar64Vec3, ray:&Ray,
start_time:T, start_time:T,
time_limit:T, time_limit:T,
intersect_leaf:IntersectLeaf, f:F,
intersect_aabb:IntersectAabb,
)->Option<(T,&L)> )->Option<(T,&L)>
where where
T:Ord+Copy, T:Ord+Copy,
IntersectLeaf:Fn(&L)->Option<T>, T:From<Ratio<Planar64,Planar64>>,
IntersectAabb:Fn(&Aabb)->Option<T>, Ratio<Planar64,Planar64>:From<T>,
F:Fn(&L,&Ray)->Option<T>,
{ {
// source of nondeterminism when Aabb boundaries are coplanar // source of nondeterminism when Aabb boundaries are coplanar
let mut nodes=BTreeMap::new(); let mut nodes=BTreeMap::new();
let start_time=start_time.into();
let time_limit=time_limit.into();
let mut collector=InstructionCollector::new(time_limit); let mut collector=InstructionCollector::new(time_limit);
// break open all nodes that contain ray.origin and populate nodes with future intersection times // break open all nodes that contain ray.origin and populate nodes with future intersection times
self.populate_nodes(start_point,&mut collector,&mut nodes,start_time,&intersect_leaf,&intersect_aabb); self.populate_nodes(&mut collector,&mut nodes,ray,start_time,&f);
// swim through nodes one at a time // swim through nodes one at a time
while let Some((t,node))=nodes.pop_first(){ while let Some((t,node))=nodes.pop_first(){
@@ -205,18 +202,18 @@ impl<L> BvhNode<L>{
break; break;
} }
match &node.content{ match &node.content{
RecursiveContent::Leaf(leaf)=>if let Some(time)=intersect_leaf(leaf){ RecursiveContent::Leaf(leaf)=>if let Some(time)=f(leaf,ray){
let ins=TimedInstruction{time:time.into(),instruction:leaf}; let ins=TimedInstruction{time:time.into(),instruction:leaf};
// this lower bound can also be omitted // this lower bound can also be omitted
// but it causes type inference errors lol // but it causes type inference errors lol
if start_time<ins.time&&ins.time<collector.time(){ if start_time.lt_ratio(ins.time)&&ins.time.lt_ratio(collector.time()){
collector.collect(Some(ins)); collector.collect(Some(ins));
} }
}, },
// break open the node and predict collisions with the child nodes // break open the node and predict collisions with the child nodes
RecursiveContent::Branch(children)=>for child in children{ RecursiveContent::Branch(children)=>for child in children{
// Am I an upcoming superstar? // Am I an upcoming superstar?
if let Some(t)=intersect_aabb(&child.aabb){ if let Some(t)=intersect_aabb(ray,&child.aabb){
// we don't need to check the lower bound // we don't need to check the lower bound
// because child aabbs are guaranteed to be within the parent bounds. // because child aabbs are guaranteed to be within the parent bounds.
if t<collector.time(){ if t<collector.time(){

View File

@@ -66,7 +66,7 @@ impl JumpImpulse{
_mass:Planar64, _mass:Planar64,
)->Planar64Vec3{ )->Planar64Vec3{
match self{ match self{
&JumpImpulse::Time(time)=>velocity-(*gravity*time).map(|t|t.divide().clamp_64()), &JumpImpulse::Time(time)=>velocity-(*gravity*time).map(|t|t.divide().clamp_1()),
&JumpImpulse::Height(height)=>{ &JumpImpulse::Height(height)=>{
//height==-v.y*v.y/(2*g.y); //height==-v.y*v.y/(2*g.y);
//use energy to determine max height //use energy to determine max height
@@ -74,10 +74,10 @@ impl JumpImpulse{
let g=gg.sqrt(); let g=gg.sqrt();
let v_g=gravity.dot(velocity); let v_g=gravity.dot(velocity);
//do it backwards //do it backwards
let radicand=v_g*v_g+(g*height*2).widen_256(); let radicand=v_g*v_g+(g*height*2).widen_4();
velocity-(*gravity*(radicand.sqrt().wrap_128()+v_g)/gg).divide().clamp_64() velocity-(*gravity*(radicand.sqrt().wrap_2()+v_g)/gg).divide().clamp_1()
}, },
&JumpImpulse::Linear(jump_speed)=>velocity+(jump_dir*jump_speed/jump_dir.length()).divide().clamp_64(), &JumpImpulse::Linear(jump_speed)=>velocity+(jump_dir*jump_speed/jump_dir.length()).divide().clamp_1(),
&JumpImpulse::Energy(_energy)=>{ &JumpImpulse::Energy(_energy)=>{
//calculate energy //calculate energy
//let e=gravity.dot(velocity); //let e=gravity.dot(velocity);
@@ -91,10 +91,10 @@ impl JumpImpulse{
pub fn get_jump_deltav(&self,gravity:&Planar64Vec3,mass:Planar64)->Planar64{ pub fn get_jump_deltav(&self,gravity:&Planar64Vec3,mass:Planar64)->Planar64{
//gravity.length() is actually the proper calculation because the jump is always opposite the gravity direction //gravity.length() is actually the proper calculation because the jump is always opposite the gravity direction
match self{ match self{
&JumpImpulse::Time(time)=>(gravity.length().wrap_64()*time/2).divide().clamp_64(), &JumpImpulse::Time(time)=>(gravity.length().wrap_1()*time/2).divide().clamp_1(),
&JumpImpulse::Height(height)=>(gravity.length()*height*2).sqrt().wrap_64(), &JumpImpulse::Height(height)=>(gravity.length()*height*2).sqrt().wrap_1(),
&JumpImpulse::Linear(deltav)=>deltav, &JumpImpulse::Linear(deltav)=>deltav,
&JumpImpulse::Energy(energy)=>(energy.sqrt()*2/mass.sqrt()).divide().clamp_64(), &JumpImpulse::Energy(energy)=>(energy.sqrt()*2/mass.sqrt()).divide().clamp_1(),
} }
} }
} }
@@ -126,10 +126,10 @@ impl JumpSettings{
None=>rel_velocity, None=>rel_velocity,
}; };
let j=boost_vel.dot(jump_dir); let j=boost_vel.dot(jump_dir);
let js=jump_speed.widen_128(); let js=jump_speed.widen_2();
if j<js{ if j<js{
//weak booster: just do a regular jump //weak booster: just do a regular jump
boost_vel+jump_dir.with_length(js-j).divide().wrap_64() boost_vel+jump_dir.with_length(js-j).divide().wrap_1()
}else{ }else{
//activate booster normally, jump does nothing //activate booster normally, jump does nothing
boost_vel boost_vel
@@ -142,13 +142,13 @@ impl JumpSettings{
None=>rel_velocity, None=>rel_velocity,
}; };
let j=boost_vel.dot(jump_dir); let j=boost_vel.dot(jump_dir);
let js=jump_speed.widen_128(); let js=jump_speed.widen_2();
if j<js{ if j<js{
//speed in direction of jump cannot be lower than amount //speed in direction of jump cannot be lower than amount
boost_vel+jump_dir.with_length(js-j).divide().wrap_64() boost_vel+jump_dir.with_length(js-j).divide().wrap_1()
}else{ }else{
//boost and jump add together //boost and jump add together
boost_vel+jump_dir.with_length(js).divide().wrap_64() boost_vel+jump_dir.with_length(js).divide().wrap_1()
} }
} }
(false,JumpCalculation::Max)=>{ (false,JumpCalculation::Max)=>{
@@ -159,10 +159,10 @@ impl JumpSettings{
None=>rel_velocity, None=>rel_velocity,
}; };
let boost_dot=boost_vel.dot(jump_dir); let boost_dot=boost_vel.dot(jump_dir);
let js=jump_speed.widen_128(); let js=jump_speed.widen_2();
if boost_dot<js{ if boost_dot<js{
//weak boost is extended to jump speed //weak boost is extended to jump speed
boost_vel+jump_dir.with_length(js-boost_dot).divide().wrap_64() boost_vel+jump_dir.with_length(js-boost_dot).divide().wrap_1()
}else{ }else{
//activate booster normally, jump does nothing //activate booster normally, jump does nothing
boost_vel boost_vel
@@ -174,7 +174,7 @@ impl JumpSettings{
Some(booster)=>booster.boost(rel_velocity), Some(booster)=>booster.boost(rel_velocity),
None=>rel_velocity, None=>rel_velocity,
}; };
boost_vel+jump_dir.with_length(jump_speed).divide().wrap_64() boost_vel+jump_dir.with_length(jump_speed).divide().wrap_1()
}, },
} }
} }
@@ -267,9 +267,9 @@ pub struct StrafeSettings{
impl StrafeSettings{ 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 d=velocity.dot(control_dir);
let mv=self.mv.widen_128(); let mv=self.mv.widen_2();
match d<mv{ match d<mv{
true=>Some(velocity+(control_dir*self.air_accel_limit.map_or(mv-d,|limit|limit.widen_128().min(mv-d))).wrap_64()), true=>Some(velocity+(control_dir*self.air_accel_limit.map_or(mv-d,|limit|limit.widen_2().min(mv-d))).wrap_1()),
false=>None, false=>None,
} }
} }
@@ -290,7 +290,7 @@ pub struct PropulsionSettings{
} }
impl PropulsionSettings{ impl PropulsionSettings{
pub fn acceleration(&self,control_dir:Planar64Vec3)->Planar64Vec3{ pub fn acceleration(&self,control_dir:Planar64Vec3)->Planar64Vec3{
(control_dir*self.magnitude).clamp_64() (control_dir*self.magnitude).clamp_1()
} }
} }
@@ -310,13 +310,13 @@ pub struct WalkSettings{
impl WalkSettings{ impl WalkSettings{
pub fn accel(&self,target_diff:Planar64Vec3,gravity:Planar64Vec3)->Planar64{ pub fn accel(&self,target_diff:Planar64Vec3,gravity:Planar64Vec3)->Planar64{
//TODO: fallible walk accel //TODO: fallible walk accel
let diff_len=target_diff.length().wrap_64(); let diff_len=target_diff.length().wrap_1();
let friction=if diff_len<self.accelerate.topspeed{ let friction=if diff_len<self.accelerate.topspeed{
self.static_friction self.static_friction
}else{ }else{
self.kinetic_friction self.kinetic_friction
}; };
self.accelerate.accel.min((-gravity.y*friction).clamp_64()) self.accelerate.accel.min((-gravity.y*friction).clamp_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(){ if control_dir==crate::integer::vec3::zero(){
@@ -332,7 +332,7 @@ impl WalkSettings{
if cr==crate::integer::vec3::zero(){ if cr==crate::integer::vec3::zero(){
crate::integer::vec3::zero() crate::integer::vec3::zero()
}else{ }else{
(cr.cross(normal)*self.accelerate.topspeed/((nn*(nnmm-dd)).sqrt())).divide().clamp_64() (cr.cross(normal)*self.accelerate.topspeed/((nn*(nnmm-dd)).sqrt())).divide().clamp_1()
} }
}else{ }else{
crate::integer::vec3::zero() crate::integer::vec3::zero()
@@ -341,7 +341,7 @@ impl WalkSettings{
pub fn is_slope_walkable(&self,normal:Planar64Vec3,up:Planar64Vec3)->bool{ pub fn is_slope_walkable(&self,normal:Planar64Vec3,up:Planar64Vec3)->bool{
//normal is not guaranteed to be unit length //normal is not guaranteed to be unit length
let ny=normal.dot(up); let ny=normal.dot(up);
let h=normal.length().wrap_64(); let h=normal.length().wrap_1();
//remember this is a normal vector //remember this is a normal vector
ny.is_positive()&&h*self.surf_dot<ny ny.is_positive()&&h*self.surf_dot<ny
} }
@@ -368,13 +368,13 @@ impl LadderSettings{
let nnmm=nn*mm; let nnmm=nn*mm;
let d=normal.dot(control_dir); let d=normal.dot(control_dir);
let mut dd=d*d; let mut dd=d*d;
if (self.dot*self.dot*nnmm).clamp_256()<dd{ if (self.dot*self.dot*nnmm).clamp_4()<dd{
if d.is_negative(){ if d.is_negative(){
control_dir=Planar64Vec3::new([Planar64::ZERO,mm.clamp_64(),Planar64::ZERO]); control_dir=Planar64Vec3::new([Planar64::ZERO,mm.clamp_1(),Planar64::ZERO]);
}else{ }else{
control_dir=Planar64Vec3::new([Planar64::ZERO,-mm.clamp_64(),Planar64::ZERO]); control_dir=Planar64Vec3::new([Planar64::ZERO,-mm.clamp_1(),Planar64::ZERO]);
} }
dd=(normal.y*normal.y).widen_256(); dd=(normal.y*normal.y).widen_4();
} }
//n=d if you are standing on top of a ladder and press E. //n=d if you are standing on top of a ladder and press E.
//two fixes: //two fixes:
@@ -385,7 +385,7 @@ impl LadderSettings{
if cr==crate::integer::vec3::zero(){ if cr==crate::integer::vec3::zero(){
crate::integer::vec3::zero() crate::integer::vec3::zero()
}else{ }else{
(cr.cross(normal)*self.accelerate.topspeed/((nn*(nnmm-dd)).sqrt())).divide().clamp_64() (cr.cross(normal)*self.accelerate.topspeed/((nn*(nnmm-dd)).sqrt())).divide().clamp_1()
} }
}else{ }else{
crate::integer::vec3::zero() crate::integer::vec3::zero()
@@ -417,7 +417,7 @@ impl Hitbox{
} }
pub fn source()->Self{ pub fn source()->Self{
Self{ Self{
halfsize:((int3(33,73,33)>>1)*VALVE_SCALE).narrow_64().unwrap(), halfsize:((int3(33,73,33)>>1)*VALVE_SCALE).narrow_1().unwrap(),
mesh:HitboxMesh::Box, mesh:HitboxMesh::Box,
} }
} }
@@ -538,11 +538,11 @@ impl StyleModifiers{
tick_rate:Ratio64::new(100,AbsoluteTime::ONE_SECOND.get() as u64).unwrap(), tick_rate:Ratio64::new(100,AbsoluteTime::ONE_SECOND.get() as u64).unwrap(),
}), }),
jump:Some(JumpSettings{ jump:Some(JumpSettings{
impulse:JumpImpulse::Height((int(52)*VALVE_SCALE).narrow_64().unwrap()), impulse:JumpImpulse::Height((int(52)*VALVE_SCALE).narrow_1().unwrap()),
calculation:JumpCalculation::JumpThenBoost, calculation:JumpCalculation::JumpThenBoost,
limit_minimum:true, limit_minimum:true,
}), }),
gravity:(int3(0,-800,0)*VALVE_SCALE).narrow_64().unwrap(), gravity:(int3(0,-800,0)*VALVE_SCALE).narrow_1().unwrap(),
mass:int(1), mass:int(1),
rocket:None, rocket:None,
walk:Some(WalkSettings{ walk:Some(WalkSettings{
@@ -565,7 +565,7 @@ impl StyleModifiers{
magnitude:int(12),//? magnitude:int(12),//?
}), }),
hitbox:Hitbox::source(), hitbox:Hitbox::source(),
camera_offset:((int3(0,64,0)-(int3(0,73,0)>>1))*VALVE_SCALE).narrow_64().unwrap(), camera_offset:((int3(0,64,0)-(int3(0,73,0)>>1))*VALVE_SCALE).narrow_1().unwrap(),
} }
} }
pub fn source_surf()->Self{ pub fn source_surf()->Self{
@@ -574,16 +574,16 @@ impl StyleModifiers{
controls_mask_state:Controls::all(), controls_mask_state:Controls::all(),
strafe:Some(StrafeSettings{ strafe:Some(StrafeSettings{
enable:ControlsActivation::full_2d(), enable:ControlsActivation::full_2d(),
air_accel_limit:Some((int(150)*66*VALVE_SCALE).narrow_64().unwrap()), air_accel_limit:Some((int(150)*66*VALVE_SCALE).narrow_1().unwrap()),
mv:Planar64::raw(30<<28), mv:Planar64::raw(30<<28),
tick_rate:Ratio64::new(66,AbsoluteTime::ONE_SECOND.get() as u64).unwrap(), tick_rate:Ratio64::new(66,AbsoluteTime::ONE_SECOND.get() as u64).unwrap(),
}), }),
jump:Some(JumpSettings{ jump:Some(JumpSettings{
impulse:JumpImpulse::Height((int(52)*VALVE_SCALE).narrow_64().unwrap()), impulse:JumpImpulse::Height((int(52)*VALVE_SCALE).narrow_1().unwrap()),
calculation:JumpCalculation::JumpThenBoost, calculation:JumpCalculation::JumpThenBoost,
limit_minimum:true, limit_minimum:true,
}), }),
gravity:(int3(0,-800,0)*VALVE_SCALE).narrow_64().unwrap(), gravity:(int3(0,-800,0)*VALVE_SCALE).narrow_1().unwrap(),
mass:int(1), mass:int(1),
rocket:None, rocket:None,
walk:Some(WalkSettings{ walk:Some(WalkSettings{
@@ -606,7 +606,7 @@ impl StyleModifiers{
magnitude:int(12),//? magnitude:int(12),//?
}), }),
hitbox:Hitbox::source(), hitbox:Hitbox::source(),
camera_offset:((int3(0,64,0)-(int3(0,73,0)>>1))*VALVE_SCALE).narrow_64().unwrap(), camera_offset:((int3(0,64,0)-(int3(0,73,0)>>1))*VALVE_SCALE).narrow_1().unwrap(),
} }
} }
} }

View File

@@ -1,12 +1,6 @@
pub use fixed_wide::fixed::*; pub use fixed_wide::fixed::*;
pub use ratio_ops::ratio::{Ratio,Divide,Parity}; pub use ratio_ops::ratio::{Ratio,Divide,Parity};
pub mod fixed_types{
pub use fixed_wide::types::*;
}
use fixed_wide::types::F128_64;
//integer units //integer units
/// specific example of a "default" time type /// specific example of a "default" time type
@@ -74,7 +68,7 @@ impl<T> Time<T>{
impl<T> From<Planar64> for Time<T>{ impl<T> From<Planar64> for Time<T>{
#[inline] #[inline]
fn from(value:Planar64)->Self{ fn from(value:Planar64)->Self{
Self::raw((value*Planar64::raw(1_000_000_000)).clamp_64().to_raw()) Self::raw((value*Planar64::raw(1_000_000_000)).clamp_1().to_raw())
} }
} }
impl<T> From<Time<T>> for Ratio<Planar64,Planar64>{ impl<T> From<Time<T>> for Ratio<Planar64,Planar64>{
@@ -140,10 +134,10 @@ impl_time_additive_assign_operator!(core::ops::AddAssign,add_assign);
impl_time_additive_assign_operator!(core::ops::SubAssign,sub_assign); impl_time_additive_assign_operator!(core::ops::SubAssign,sub_assign);
impl_time_additive_assign_operator!(core::ops::RemAssign,rem_assign); impl_time_additive_assign_operator!(core::ops::RemAssign,rem_assign);
impl<T> std::ops::Mul for Time<T>{ impl<T> std::ops::Mul for Time<T>{
type Output=Ratio<F128_64,F128_64>; type Output=Ratio<Fixed<2,64>,Fixed<2,64>>;
#[inline] #[inline]
fn mul(self,rhs:Self)->Self::Output{ fn mul(self,rhs:Self)->Self::Output{
Ratio::new(Fixed::raw(self.0)*Fixed::raw(rhs.0),Fixed::from_u64(1_000_000_000u64.pow(2))) Ratio::new(Fixed::raw(self.0)*Fixed::raw(rhs.0),Fixed::raw_digit(1_000_000_000i64.pow(2)))
} }
} }
macro_rules! impl_time_i64_rhs_operator { macro_rules! impl_time_i64_rhs_operator {
@@ -162,7 +156,7 @@ impl_time_i64_rhs_operator!(Mul,mul);
impl_time_i64_rhs_operator!(Shr,shr); impl_time_i64_rhs_operator!(Shr,shr);
impl_time_i64_rhs_operator!(Shl,shl); impl_time_i64_rhs_operator!(Shl,shl);
impl<T> core::ops::Mul<Time<T>> for Planar64{ impl<T> core::ops::Mul<Time<T>> for Planar64{
type Output=Ratio<F128_64,Planar64>; type Output=Ratio<Fixed<2,64>,Planar64>;
#[inline] #[inline]
fn mul(self,rhs:Time<T>)->Self::Output{ fn mul(self,rhs:Time<T>)->Self::Output{
Ratio::new(self*Fixed::raw(rhs.0),Planar64::raw(1_000_000_000)) Ratio::new(self*Fixed::raw(rhs.0),Planar64::raw(1_000_000_000))
@@ -183,7 +177,6 @@ impl<T> From<Time<T>> for f64{
#[cfg(test)] #[cfg(test)]
mod test_time{ mod test_time{
use super::*; use super::*;
use fixed_wide::types::F64_32;
type Time=AbsoluteTime; type Time=AbsoluteTime;
#[test] #[test]
fn time_from_planar64(){ fn time_from_planar64(){
@@ -198,13 +191,13 @@ mod test_time{
#[test] #[test]
fn time_squared(){ fn time_squared(){
let a=Time::from_secs(2); let a=Time::from_secs(2);
assert_eq!(a*a,Ratio::new(F128_64::from_u64(1_000_000_000u64.pow(2))*4,F128_64::from_u64(1_000_000_000u64.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] #[test]
fn time_times_planar64(){ fn time_times_planar64(){
let a=Time::from_secs(2); let a=Time::from_secs(2);
let b=Planar64::from(2); let b=Planar64::from(2);
assert_eq!(b*a,Ratio::new(F128_64::from_u64(1_000_000_000*(1<<32))<<2,F64_32::from_u64(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)));
} }
} }
@@ -572,8 +565,8 @@ fn angle_sin_cos(){
println!("cordic s={} c={}",(s/h).divide(),(c/h).divide()); println!("cordic s={} c={}",(s/h).divide(),(c/h).divide());
let (fs,fc)=f.sin_cos(); let (fs,fc)=f.sin_cos();
println!("float s={} c={}",fs,fc); println!("float s={} c={}",fs,fc);
assert!(close_enough((c/h).divide().wrap_64(),Planar64::raw((fc*((1u64<<32) as f64)) as i64))); assert!(close_enough((c/h).divide().wrap_1(),Planar64::raw((fc*((1u64<<32) as f64)) as i64)));
assert!(close_enough((s/h).divide().wrap_64(),Planar64::raw((fs*((1u64<<32) as f64)) as i64))); assert!(close_enough((s/h).divide().wrap_1(),Planar64::raw((fs*((1u64<<32) as f64)) as i64)));
} }
test_angle(1.0); test_angle(1.0);
test_angle(std::f64::consts::PI/4.0); test_angle(std::f64::consts::PI/4.0);
@@ -605,7 +598,7 @@ impl TryFrom<[f32;3]> for Unit32Vec3{
*/ */
pub type Planar64TryFromFloatError=FixedFromFloatError; pub type Planar64TryFromFloatError=FixedFromFloatError;
pub type Planar64=fixed_wide::types::F64_32; pub type Planar64=fixed_wide::types::I32F32;
pub type Planar64Vec3=linear_ops::types::Vector3<Planar64>; pub type Planar64Vec3=linear_ops::types::Vector3<Planar64>;
pub type Planar64Mat3=linear_ops::types::Matrix3<Planar64>; pub type Planar64Mat3=linear_ops::types::Matrix3<Planar64>;
pub mod vec3{ pub mod vec3{
@@ -684,8 +677,8 @@ pub mod mat3{
let (yc,ys)=y.cos_sin(); let (yc,ys)=y.cos_sin();
Planar64Mat3::from_cols([ Planar64Mat3::from_cols([
Planar64Vec3::new([xc,Planar64::ZERO,-xs]), Planar64Vec3::new([xc,Planar64::ZERO,-xs]),
Planar64Vec3::new([(xs*ys).wrap_64(),yc,(xc*ys).wrap_64()]), Planar64Vec3::new([(xs*ys).wrap_1(),yc,(xc*ys).wrap_1()]),
Planar64Vec3::new([(xs*yc).wrap_64(),-ys,(xc*yc).wrap_64()]), Planar64Vec3::new([(xs*yc).wrap_1(),-ys,(xc*yc).wrap_1()]),
]) ])
} }
#[inline] #[inline]
@@ -726,8 +719,8 @@ impl Planar64Affine3{
} }
} }
#[inline] #[inline]
pub fn transform_point3(&self,point:Planar64Vec3)->vec3::Vector3<F128_64>{ pub fn transform_point3(&self,point:Planar64Vec3)->vec3::Vector3<Fixed<2,64>>{
self.translation.widen_128()+self.matrix3*point self.translation.widen_2()+self.matrix3*point
} }
} }
impl Into<glam::Mat4> for Planar64Affine3{ impl Into<glam::Mat4> for Planar64Affine3{

View File

@@ -14,7 +14,7 @@ wide-mul=[]
zeroes=["dep:arrayvec"] zeroes=["dep:arrayvec"]
[dependencies] [dependencies]
bnum = "0.14.3" bnum = "0.13.0"
arrayvec = { version = "0.7.6", optional = true } arrayvec = { version = "0.7.6", optional = true }
paste = "1.0.15" paste = "1.0.15"
ratio_ops = { workspace = true, optional = true } ratio_ops = { workspace = true, optional = true }

View File

@@ -1,58 +1,54 @@
use bnum::{Int,cast::As,n}; use bnum::{BInt,cast::As};
pub(crate)const BNUM_DIGIT_WIDTH:usize=8; const BNUM_DIGIT_WIDTH:usize=64;
const DIGIT_SHIFT:u32=BNUM_DIGIT_WIDTH.ilog2();
#[derive(Clone,Copy,Default,Hash,PartialEq,Eq,PartialOrd,Ord)] #[derive(Clone,Copy,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) /// 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 u8s to use /// N is the number of u64s to use
/// F is the number of fractional bits (currently always N*8/2) /// F is the number of fractional bits (always N*32 lol)
pub struct Fixed<const N:usize,const F:usize>{ pub struct Fixed<const N:usize,const F:usize>{
bits:Int<N>, bits:BInt<{N}>,
} }
impl<const N:usize,const F:usize> Fixed<N,F>{ impl<const N:usize,const F:usize> Fixed<N,F>{
pub const MAX:Self=Self::from_bits(Int::<N>::MAX); pub const MAX:Self=Self::from_bits(BInt::<N>::MAX);
pub const MIN:Self=Self::from_bits(Int::<N>::MIN); pub const MIN:Self=Self::from_bits(BInt::<N>::MIN);
pub const ZERO:Self=Self::from_bits(n!(0)); pub const ZERO:Self=Self::from_bits(BInt::<N>::ZERO);
pub const EPSILON:Self=Self::from_bits(n!(1)); pub const EPSILON:Self=Self::from_bits(BInt::<N>::ONE);
pub const NEG_EPSILON:Self=Self::from_bits(n!(-1)); pub const NEG_EPSILON:Self=Self::from_bits(BInt::<N>::NEG_ONE);
pub const ONE:Self=Self::from_bits(n!(1).shl(F as u32)); pub const ONE:Self=Self::from_bits(BInt::<N>::ONE.shl(F as u32));
pub const TWO:Self=Self::from_bits(n!(2).shl(F as u32)); pub const TWO:Self=Self::from_bits(BInt::<N>::TWO.shl(F as u32));
pub const HALF:Self=Self::from_bits(n!(1).shl(F as u32-1)); pub const HALF:Self=Self::from_bits(BInt::<N>::ONE.shl(F as u32-1));
pub const NEG_ONE:Self=Self::from_bits(n!(-1).shl(F as u32)); pub const NEG_ONE:Self=Self::from_bits(BInt::<N>::NEG_ONE.shl(F as u32));
pub const NEG_TWO:Self=Self::from_bits(n!(-2).shl(F as u32)); pub const NEG_TWO:Self=Self::from_bits(BInt::<N>::NEG_TWO.shl(F as u32));
pub const NEG_HALF:Self=Self::from_bits(n!(-1).shl(F as u32-1)); pub const NEG_HALF:Self=Self::from_bits(BInt::<N>::NEG_ONE.shl(F as u32-1));
} }
impl<const N:usize,const F:usize> Fixed<N,F>{ impl<const N:usize,const F:usize> Fixed<N,F>{
#[inline] #[inline]
pub const fn from_bits(bits:Int::<N>)->Self{ pub const fn from_bits(bits:BInt::<N>)->Self{
Self{ Self{
bits, bits,
} }
} }
#[inline] #[inline]
pub const fn to_bits(self)->Int<N>{ pub const fn to_bits(self)->BInt<N>{
self.bits self.bits
} }
#[inline] #[inline]
pub const fn as_bits(&self)->&Int<N>{ pub const fn as_bits(&self)->&BInt<N>{
&self.bits &self.bits
} }
#[inline] #[inline]
pub const fn as_bits_mut(&mut self)->&mut Int<N>{ pub const fn as_bits_mut(&mut self)->&mut BInt<N>{
&mut self.bits &mut self.bits
} }
#[inline] #[inline]
pub const fn from_u64(value:u64)->Self{ pub const fn raw_digit(value:i64)->Self{
let mut digits=Self::ZERO; let mut digits=[0u64;N];
let bytes=value.to_ne_bytes(); digits[0]=value.abs() as u64;
let mut digit=0; //sign bit
while digit<N&&digit<bytes.len(){ digits[N-1]|=(value&i64::MIN) as u64;
digits.as_bits_mut().as_bytes_mut()[digit]=bytes[digit]; Self::from_bits(BInt::from_bits(bnum::BUint::from_digits(digits)))
digit+=1;
}
digits
} }
#[inline] #[inline]
pub const fn is_zero(self)->bool{ pub const fn is_zero(self)->bool{
@@ -103,25 +99,26 @@ impl<const N:usize,const F:usize> Fixed<N,F>{
} }
} }
} }
impl<const F:usize> Fixed<{64/BNUM_DIGIT_WIDTH},F>{ impl<const F:usize> Fixed<1,F>{
/// My old code called this function everywhere so let's provide it /// My old code called this function everywhere so let's provide it
#[inline] #[inline]
pub const fn raw(value:i64)->Self{ pub const fn raw(value:i64)->Self{
Self::from_bits(Int::from_bytes(value.to_ne_bytes())) Self::from_bits(BInt::from_bits(bnum::BUint::from_digit(value as u64)))
} }
#[inline] #[inline]
pub const fn to_raw(self)->i64{ pub const fn to_raw(self)->i64{
i64::from_le_bytes(self.to_bits().to_bytes()) let &[digit]=self.to_bits().to_bits().digits();
digit as i64
} }
} }
macro_rules! impl_from{ macro_rules! impl_from {
($($from:ty),*)=>{ ($($from:ty),*)=>{
$( $(
impl<const N:usize,const F:usize> From<$from> for Fixed<N,F>{ impl<const N:usize,const F:usize> From<$from> for Fixed<N,F>{
#[inline] #[inline]
fn from(value:$from)->Self{ fn from(value:$from)->Self{
Self::from_bits(value.as_::<Int::<{N}>>()<<F as u32) Self::from_bits(BInt::<{N}>::from(value)<<F as u32)
} }
} }
)* )*
@@ -150,153 +147,84 @@ impl<const N:usize,const F:usize> std::iter::Sum for Fixed<N,F>{
} }
} }
macro_rules! impl_into_float{ const fn signed_shift(lhs:u64,rhs:i32)->u64{
($output:ty,$unsigned:ty,$mantissa_msb:expr,$bias:expr) => { if rhs.is_negative(){
lhs>>-rhs
}else{
lhs<<rhs
}
}
macro_rules! impl_into_float {
( $output: ty, $unsigned:ty, $exponent_bits:expr, $mantissa_bits:expr ) => {
impl<const N:usize,const F:usize> Into<$output> for Fixed<N,F>{ impl<const N:usize,const F:usize> Into<$output> for Fixed<N,F>{
#[inline] #[inline]
fn into(self)->$output{ fn into(self)->$output{
let unsigned=self.bits.unsigned_abs(); const DIGIT_SHIFT:u32=6;//Log2[64]
// most_significant_bit is the "index" of the most significant bit. // SBBB BBBB
// 0b0000_0000.msb()==0 (but we just special case return 0.0) // 1001 1110 0000 0000
// 0b0000_0001.msb()==0
// 0b1000_0000.msb()==7
let Some(most_significant_bit)=unsigned.bit_width().checked_sub(1)else{
return 0.0;
};
// sign
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();
// exp let most_significant_bit=unsigned.bits();
let msb=most_significant_bit as $unsigned; let exp=if unsigned.is_zero(){
let msb_offset=msb+$bias-F as $unsigned; 0
let exp=msb_offset<<$mantissa_msb;
// mant
let digits=unsigned.to_bytes();
// Copy digits into mantissa
let mut m_bytes=[0u8;_];
let mant_unmasked;
const MOD8:usize=((1<<DIGIT_SHIFT)-1);
const MANT_REM:usize=$mantissa_msb&MOD8;
const NEG_MANT_REM:usize=(1<<DIGIT_SHIFT)-MANT_REM;
if $mantissa_msb<most_significant_bit{
// lsb of mantissa is higher than lsb of fixed point
// Copy bytes (f64)
// CASE 0:
// F64_32 [00000000,00011111,11111111,11111111,11111111,11111111,11111111,11111111,...]
// u64 [00011111,11111111,11111111,11111111,11111111,11111111,11111111,00000000]
// msb%8=4
// i_m=1
// CASE 1:
// F64_32 [00000000,00111111,11111111,11111111,11111111,11111111,11111111,11111110,...]
// u64 [00111111,11111111,11111111,11111111,11111111,11111111,11111110,00000000]
// CASE 2:
// F64_32 [00000000,01111111,11111111,11111111,11111111,11111111,11111111,11111100,...]
// u64 [01111111,11111111,11111111,11111111,11111111,11111111,11111100,00000000]
// CASE 3:
// F64_32 [00000000,11111111,11111111,11111111,11111111,11111111,11111111,11111000,...]
// u64 [11111111,11111111,11111111,11111111,11111111,11111111,11111000,00000000]
// CASE 4:
// F64_32 [00000001,11111111,11111111,11111111,11111111,11111111,11111111,11110000,...]
// u64 [00000001,11111111,11111111,11111111,11111111,11111111,11111111,11110000]
// CASE 5:
// F64_32 [00000011,11111111,11111111,11111111,11111111,11111111,11111111,11100000,...]
// u64 [00000011,11111111,11111111,11111111,11111111,11111111,11111111,11100000]
// CASE 6:
// F64_32 [00000111,11111111,11111111,11111111,11111111,11111111,11111111,11000000,...]
// u64 [00000111,11111111,11111111,11111111,11111111,11111111,11111111,11000000]
// CASE 7:
// F64_32 [00001111,11111111,11111111,11111111,11111111,11111111,11111111,10000000,...]
// u64 [00001111,11111111,11111111,11111111,11111111,11111111,11111111,10000000]
//
// Copy bytes (f32)
// CASE 0:
// F64_32 [00000000,01111111,11111111,11111111,...]
// u32 [01111111,11111111,11111111,00000000]
// msb%8=4
// i_m=1
// CASE 1:
// F64_32 [00000000,11111111,11111111,11111110,...]
// u32 [11111111,11111111,11111110,00000000]
// CASE 2:
// F64_32 [00000001,11111111,11111111,11111100,...]
// u32 [00000001,11111111,11111111,11111100]
// CASE 3:
// F64_32 [00000011,11111111,11111111,11111000,...]
// u32 [00000011,11111111,11111111,11111000]
// CASE 4:
// F64_32 [00000111,11111111,11111111,11110000,...]
// u32 [00000111,11111111,11111111,11110000]
// CASE 5:
// F64_32 [00001111,11111111,11111111,11100000,...]
// u32 [00001111,11111111,11111111,11100000]
// CASE 6:
// F64_32 [00011111,11111111,11111111,11000000,...]
// u32 [00011111,11111111,11111111,11000000]
// CASE 7:
// F64_32 [00111111,11111111,11111111,10000000,...]
// u32 [00111111,11111111,11111111,10000000]
let right_shift=most_significant_bit as usize-$mantissa_msb;
let mut i_m=((most_significant_bit as usize&MOD8)+NEG_MANT_REM)>>DIGIT_SHIFT;
let mut i_d=right_shift>>DIGIT_SHIFT;
while i_m<m_bytes.len()&&i_d<N{
m_bytes[i_m]=digits[i_d];
i_m+=1;
i_d+=1;
}
let unsigned=<$unsigned>::from_le_bytes(m_bytes);
let right_shift=((right_shift+MANT_REM)&MOD8)+NEG_MANT_REM;
mant_unmasked=unsigned>>right_shift;
}else{ }else{
// lsb of mantissa is lower than lsb of fixed point let msb=most_significant_bit as $unsigned;
// [0,0,0,0,0b0100_0000,0,0,0] let _127=((1 as $unsigned)<<($exponent_bits-1))-1;
// [0,0b0001_0000,0,0,0,0,0,0]<<e let msb_offset=msb+_127-1-F as $unsigned;
let left_shift=$mantissa_msb-most_significant_bit as usize; msb_offset<<($mantissa_bits-1)
let mut i_m=left_shift>>DIGIT_SHIFT; };
let mut i_d=0; let digits=unsigned.digits();
while i_m<m_bytes.len()&&i_d<N{ let digit_index=most_significant_bit.saturating_sub(1)>>DIGIT_SHIFT;
m_bytes[i_m]=digits[i_d]; let digit=digits[digit_index as usize];
i_m+=1; //How many bits does the mantissa take from this digit
i_d+=1; let take_bits=most_significant_bit-(digit_index<<DIGIT_SHIFT);
} let rest_of_mantissa=$mantissa_bits as i32-(take_bits as i32);
mant_unmasked=<$unsigned>::from_le_bytes(m_bytes)<<(left_shift&MOD8); let mut unmasked_mant=signed_shift(digit,rest_of_mantissa) as $unsigned;
if 0<rest_of_mantissa&&digit_index!=0{
//take the next digit down and shove some of its bits onto the bottom of the mantissa
let digit=digits[digit_index as usize-1];
let take_bits=most_significant_bit-((digit_index-1)<<DIGIT_SHIFT);
let rest_of_mantissa=$mantissa_bits as i32-(take_bits as i32);
let unmasked_mant2=signed_shift(digit,rest_of_mantissa) as $unsigned;
unmasked_mant|=unmasked_mant2;
} }
let mant=unmasked_mant&((1 as $unsigned)<<($mantissa_bits-1))-1;
let mant=mant_unmasked&(((1 as $unsigned)<<$mantissa_msb)-1);
let bits=sign|exp|mant; let bits=sign|exp|mant;
<$output>::from_bits(bits) <$output>::from_bits(bits)
} }
} }
} }
} }
impl_into_float!(f32,u32,23,127); impl_into_float!(f32,u32,8,24);
impl_into_float!(f64,u64,52,1023); impl_into_float!(f64,u64,11,53);
#[inline] #[inline]
fn integer_decode_f32(f: f32) -> (u32, u8, bool) { fn integer_decode_f32(f: f32) -> (u64, i16, bool) {
let bits: u32 = f.to_bits(); let bits: u32 = f.to_bits();
let sign: bool = bits & (1<<31) != 0; let sign: bool = bits & (1<<31) != 0;
let exponent = (bits >> 23) & 0xff; let mut exponent: i16 = ((bits >> 23) & 0xff) as i16;
let mantissa = if exponent == 0 { let mantissa = if exponent == 0 {
(bits & 0x7fffff) << 1 (bits & 0x7fffff) << 1
} else { } else {
(bits & 0x7fffff) | 0x800000 (bits & 0x7fffff) | 0x800000
}; };
(mantissa, exponent as u8, sign) // Exponent bias + mantissa shift
exponent -= 127 + 23;
(mantissa as u64, exponent, sign)
} }
#[inline] #[inline]
fn integer_decode_f64(f: f64) -> (u64, u16, bool) { fn integer_decode_f64(f: f64) -> (u64, i16, bool) {
let bits: u64 = f.to_bits(); let bits: u64 = f.to_bits();
let sign: bool = bits & (1u64<<63) != 0; let sign: bool = bits & (1u64<<63) != 0;
let exponent = (bits >> 52) & 0x7ff; let mut exponent: i16 = ((bits >> 52) & 0x7ff) as i16;
let mantissa = if exponent == 0 { let mantissa = if exponent == 0 {
(bits & 0xfffffffffffff) << 1 (bits & 0xfffffffffffff) << 1
} else { } else {
(bits & 0xfffffffffffff) | 0x10000000000000 (bits & 0xfffffffffffff) | 0x10000000000000
}; };
(mantissa, exponent as u16, sign) // Exponent bias + mantissa shift
exponent -= 1023 + 52;
(mantissa, exponent, sign)
} }
#[derive(Debug,Eq,PartialEq)] #[derive(Debug,Eq,PartialEq)]
pub enum FixedFromFloatError{ pub enum FixedFromFloatError{
@@ -318,12 +246,13 @@ impl core::fmt::Display for FixedFromFloatError{
write!(f,"{self:?}") write!(f,"{self:?}")
} }
} }
macro_rules! impl_from_float{ macro_rules! impl_from_float {
($decode:ident,$input:ty,$mantissa_bits:expr,$bias:expr)=>{ ( $decode:ident, $input: ty, $mantissa_bits:expr ) => {
impl<const N:usize,const F:usize> TryFrom<$input> for Fixed<N,F>{ impl<const N:usize,const F:usize> TryFrom<$input> for Fixed<N,F>{
type Error=FixedFromFloatError; type Error=FixedFromFloatError;
#[inline] #[inline]
fn try_from(value:$input)->Result<Self,Self::Error>{ fn try_from(value:$input)->Result<Self,Self::Error>{
const DIGIT_SHIFT:u32=6;
match value.classify(){ match value.classify(){
std::num::FpCategory::Nan=>Err(FixedFromFloatError::Nan), std::num::FpCategory::Nan=>Err(FixedFromFloatError::Nan),
std::num::FpCategory::Infinite=>Err(FixedFromFloatError::Infinite), std::num::FpCategory::Infinite=>Err(FixedFromFloatError::Infinite),
@@ -332,62 +261,25 @@ macro_rules! impl_from_float{
|std::num::FpCategory::Normal |std::num::FpCategory::Normal
=>{ =>{
let (m,e,s)=$decode(value); let (m,e,s)=$decode(value);
let mut digits=[0u8;N]; let mut digits=[0u64;N];
let msb_biased=e as usize+F+1; let most_significant_bit=e as i32+$mantissa_bits as i32+F as i32;
if msb_biased<$bias{ if most_significant_bit<0{
return Err(FixedFromFloatError::Underflow); return Err(FixedFromFloatError::Underflow);
};
if N*BNUM_DIGIT_WIDTH+$bias<=msb_biased{
return Err(FixedFromFloatError::Overflow);
} }
let lsb=msb_biased as isize-$bias-$mantissa_bits; let digit_index=most_significant_bit>>DIGIT_SHIFT;
// underflow is ok, we only need to know the alignment let digit=digits.get_mut(digit_index as usize).ok_or(FixedFromFloatError::Overflow)?;
let lsb_alignment=lsb&((1<<DIGIT_SHIFT)-1); let take_bits=most_significant_bit-(digit_index<<DIGIT_SHIFT);
// the 53 bit mantissa has room to shift by 0-7 bits let rest_of_mantissa=-($mantissa_bits as i32-(take_bits as i32));
let aligned_mantissa=m<<lsb_alignment; *digit=signed_shift(m,rest_of_mantissa);
let m_bytes=aligned_mantissa.to_le_bytes(); if rest_of_mantissa<0&&digit_index!=0{
let digit_index=lsb>>DIGIT_SHIFT; //we don't care if some float bits are partially truncated
let mut i_m; if let Some(digit)=digits.get_mut((digit_index-1) as usize){
let mut i_d; let take_bits=most_significant_bit-((digit_index-1)<<DIGIT_SHIFT);
if digit_index<0{ let rest_of_mantissa=-($mantissa_bits as i32-(take_bits as i32));
// lsb of mantissa is lower than lsb of fixed point *digit=signed_shift(m,rest_of_mantissa);
// [0,0,0,0]<<e }
// [0,0,0,1,0,0,0,0]
i_m=-digit_index as usize;
i_d=0;
}else{
// lsb of mantissa is higher than lsb of fixed point
// [0,0,0,0]<<e
// [0,0,0,1,0,0,0,0]
i_m=0;
i_d=digit_index as usize;
} }
while i_m<m_bytes.len()&&i_d<N{ let bits=BInt::from_bits(bnum::BUint::from_digits(digits));
digits[i_d]=m_bytes[i_m];
i_m+=1;
i_d+=1;
}
/* AI idea is not bad
// Calculate i_m and i_d without branching
// is_less is 1 if lsb < bias + mantissa_bits (mantissa is "above" fixed point)
let is_less = (lsb < ($bias + $mantissa_bits)) as usize;
// If lsb < bias, we skip i_m bytes in m_bytes, i_d is 0
// If lsb >= bias, i_m is 0, we skip i_d bytes in digits
i_m = (((($bias + $mantissa_bits) - lsb) >> DIGIT_SHIFT) & is_less);
i_d = ((lsb.wrapping_sub($bias + $mantissa_bits) >> DIGIT_SHIFT) & !is_less);
// Calculate how many bytes to copy safely
let m_bytes_len = m_bytes.len();
let count = (m_bytes_len.saturating_sub(i_m)).min(N.saturating_sub(i_d));
if count > 0 {
digits[i_d..i_d + count].copy_from_slice(&m_bytes[i_m..i_m + count]);
}
*/
let bits=Int::from_bytes(digits);
Ok(if s{ Ok(if s{
Self::from_bits(bits.overflowing_neg().0) Self::from_bits(bits.overflowing_neg().0)
}else{ }else{
@@ -399,14 +291,14 @@ macro_rules! impl_from_float{
} }
} }
} }
impl_from_float!(integer_decode_f32,f32,24,127); impl_from_float!(integer_decode_f32,f32,24);
impl_from_float!(integer_decode_f64,f64,53,1023); impl_from_float!(integer_decode_f64,f64,53);
impl<const N:usize,const F:usize> core::fmt::Debug for Fixed<N,F>{ impl<const N:usize,const F:usize> core::fmt::Debug for Fixed<N,F>{
#[inline] #[inline]
fn fmt(&self,f:&mut core::fmt::Formatter)->Result<(),core::fmt::Error>{ fn fmt(&self,f:&mut core::fmt::Formatter)->Result<(),core::fmt::Error>{
let integral=self.as_bits().unsigned_abs()>>F; let integral=self.as_bits().unsigned_abs()>>F;
let fractional=self.as_bits().unsigned_abs()&((n!(1)<<F)-n!(1)); let fractional=self.as_bits().unsigned_abs()&((bnum::BUint::<N>::ONE<<F)-bnum::BUint::<N>::ONE);
let leading_zeroes=(fractional.leading_zeros() as usize).saturating_sub(N*BNUM_DIGIT_WIDTH-F)>>2; let leading_zeroes=(fractional.leading_zeros() as usize).saturating_sub(N*BNUM_DIGIT_WIDTH-F)>>2;
if self.is_negative(){ if self.is_negative(){
core::write!(f,"-")?; core::write!(f,"-")?;
@@ -428,14 +320,14 @@ impl<const N:usize,const F:usize> core::fmt::Display for Fixed<N,F>{
} }
macro_rules! impl_additive_operator { macro_rules! impl_additive_operator {
( $trait: ident, $method: ident, $output: ty ) => { ( $struct: ident, $trait: ident, $method: ident, $output: ty ) => {
impl<const N:usize,const F:usize> Fixed<N,F>{ impl<const N:usize,const F:usize> $struct<N,F>{
#[inline] #[inline]
pub const fn $method(self, other: Self) -> Self { pub const fn $method(self, other: Self) -> Self {
Self::from_bits(self.bits.$method(other.bits)) Self::from_bits(self.bits.$method(other.bits))
} }
} }
impl<const N:usize,const F:usize> core::ops::$trait for Fixed<N,F>{ impl<const N:usize,const F:usize> core::ops::$trait for $struct<N,F>{
type Output = $output; type Output = $output;
#[inline] #[inline]
fn $method(self, other: Self) -> Self::Output { fn $method(self, other: Self) -> Self::Output {
@@ -445,8 +337,8 @@ macro_rules! impl_additive_operator {
}; };
} }
macro_rules! impl_additive_assign_operator { macro_rules! impl_additive_assign_operator {
( $trait: ident, $method: ident ) => { ( $struct: ident, $trait: ident, $method: ident ) => {
impl<const N:usize,const F:usize> core::ops::$trait for Fixed<N,F>{ impl<const N:usize,const F:usize> core::ops::$trait for $struct<N,F>{
#[inline] #[inline]
fn $method(&mut self, other: Self) { fn $method(&mut self, other: Self) {
self.bits.$method(other.bits); self.bits.$method(other.bits);
@@ -456,28 +348,28 @@ macro_rules! impl_additive_assign_operator {
} }
// Impl arithmetic pperators // Impl arithmetic pperators
impl_additive_assign_operator!( AddAssign, add_assign ); impl_additive_assign_operator!( Fixed, AddAssign, add_assign );
impl_additive_operator!( Add, add, Self ); impl_additive_operator!( Fixed, Add, add, Self );
impl_additive_assign_operator!( SubAssign, sub_assign ); impl_additive_assign_operator!( Fixed, SubAssign, sub_assign );
impl_additive_operator!( Sub, sub, Self ); impl_additive_operator!( Fixed, Sub, sub, Self );
impl_additive_assign_operator!( RemAssign, rem_assign ); impl_additive_assign_operator!( Fixed, RemAssign, rem_assign );
impl_additive_operator!( Rem, rem, Self ); impl_additive_operator!( Fixed, Rem, rem, Self );
// Impl bitwise operators // Impl bitwise operators
impl_additive_assign_operator!( BitAndAssign, bitand_assign ); impl_additive_assign_operator!( Fixed, BitAndAssign, bitand_assign );
impl_additive_operator!( BitAnd, bitand, Self ); impl_additive_operator!( Fixed, BitAnd, bitand, Self );
impl_additive_assign_operator!( BitOrAssign, bitor_assign ); impl_additive_assign_operator!( Fixed, BitOrAssign, bitor_assign );
impl_additive_operator!( BitOr, bitor, Self ); impl_additive_operator!( Fixed, BitOr, bitor, Self );
impl_additive_assign_operator!( BitXorAssign, bitxor_assign ); impl_additive_assign_operator!( Fixed, BitXorAssign, bitxor_assign );
impl_additive_operator!( BitXor, bitxor, Self ); impl_additive_operator!( Fixed, BitXor, bitxor, Self );
// non-wide operators. The result is the same width as the inputs. // non-wide operators. The result is the same width as the inputs.
// This macro is not used in the default configuration. // This macro is not used in the default configuration.
#[expect(unused_macros)] #[expect(unused_macros)]
macro_rules! impl_multiplicative_operator_not_const_generic { macro_rules! impl_multiplicative_operator_not_const_generic {
( ($trait: ident, $method: ident, $output: ty ), $width:expr ) => { ( ($struct: ident, $trait: ident, $method: ident, $output: ty ), $width:expr ) => {
impl<const F:usize> core::ops::$trait for Fixed<{$width/BNUM_DIGIT_WIDTH},F>{ impl<const F:usize> core::ops::$trait for $struct<$width,F>{
type Output = $output; type Output = $output;
#[inline] #[inline]
fn $method(self, other: Self) -> Self::Output { fn $method(self, other: Self) -> Self::Output {
@@ -489,8 +381,8 @@ macro_rules! impl_multiplicative_operator_not_const_generic {
}; };
} }
macro_rules! impl_multiplicative_assign_operator_not_const_generic { macro_rules! impl_multiplicative_assign_operator_not_const_generic {
( ($trait: ident, $method: ident, $non_assign_method: ident ), $width:expr ) => { ( ($struct: ident, $trait: ident, $method: ident, $non_assign_method: ident ), $width:expr ) => {
impl<const F:usize> core::ops::$trait for Fixed<{$width/BNUM_DIGIT_WIDTH},F>{ impl<const F:usize> core::ops::$trait for $struct<$width,F>{
#[inline] #[inline]
fn $method(&mut self, other: Self) { fn $method(&mut self, other: Self) {
paste::item!{ paste::item!{
@@ -502,13 +394,13 @@ macro_rules! impl_multiplicative_assign_operator_not_const_generic {
} }
macro_rules! impl_multiply_operator_not_const_generic { macro_rules! impl_multiply_operator_not_const_generic {
( ($trait: ident, $method: ident, $output: ty ), $width:expr ) => { ( ($struct: ident, $trait: ident, $method: ident, $output: ty ), $width:expr ) => {
impl<const F:usize> Fixed<{$width/BNUM_DIGIT_WIDTH},F>{ impl<const F:usize> $struct<$width,F>{
paste::item!{ paste::item!{
#[inline] #[inline]
pub fn [<fixed_ $method>](self, rhs: Self) -> Self { pub fn [<fixed_ $method>](self, rhs: Self) -> Self {
let (low,high)=self.bits.unsigned_abs().widening_mul(rhs.bits.unsigned_abs()); let (low,high)=self.bits.unsigned_abs().widening_mul(rhs.bits.unsigned_abs());
let out:Int::<{$width*2/BNUM_DIGIT_WIDTH}>=unsafe{core::mem::transmute([low,high])}; let out:BInt::<{$width*2}>=unsafe{core::mem::transmute([low,high])};
if self.is_negative()==rhs.is_negative(){ if self.is_negative()==rhs.is_negative(){
Self::from_bits(out.shr(F as u32).as_()) Self::from_bits(out.shr(F as u32).as_())
}else{ }else{
@@ -518,34 +410,34 @@ macro_rules! impl_multiply_operator_not_const_generic {
} }
} }
#[cfg(not(feature="wide-mul"))] #[cfg(not(feature="wide-mul"))]
impl_multiplicative_operator_not_const_generic!(($trait,$method,$output),$width); impl_multiplicative_operator_not_const_generic!(($struct, $trait, $method, $output ), $width);
#[cfg(feature="deferred-division")] #[cfg(feature="deferred-division")]
impl ratio_ops::ratio::Divide<i64> for Fixed<{$width/BNUM_DIGIT_WIDTH},{$width>>1}>{ impl ratio_ops::ratio::Divide<i64> for Fixed<$width,{$width*32}>{
type Output=Self; type Output=Self;
#[inline] #[inline]
fn divide(self, other: i64)->Self::Output{ fn divide(self, other: i64)->Self::Output{
Self::from_bits(self.bits.div_euclid(other.as_())) Self::from_bits(self.bits.div_euclid(BInt::from(other)))
} }
} }
} }
} }
macro_rules! impl_divide_operator_not_const_generic { macro_rules! impl_divide_operator_not_const_generic {
( ($trait: ident, $method: ident, $output: ty ), $width:expr ) => { ( ($struct: ident, $trait: ident, $method: ident, $output: ty ), $width:expr ) => {
impl<const F:usize> Fixed<{$width/BNUM_DIGIT_WIDTH},F>{ impl<const F:usize> $struct<$width,F>{
paste::item!{ paste::item!{
#[inline] #[inline]
pub fn [<fixed_ $method>](self,other:Self)->Self{ pub fn [<fixed_ $method>](self,other:Self)->Self{
//this only needs to be $width+F as u32/64+1 but MUH CONST GENERICS!!!!! //this only needs to be $width+F as u32/64+1 but MUH CONST GENERICS!!!!!
let lhs=self.bits.as_::<Int::<{$width*2/BNUM_DIGIT_WIDTH}>>().shl(F as u32); let lhs=self.bits.as_::<BInt::<{$width*2}>>().shl(F as u32);
let rhs=other.bits.as_::<Int::<{$width*2/BNUM_DIGIT_WIDTH}>>(); let rhs=other.bits.as_::<BInt::<{$width*2}>>();
Self::from_bits(lhs.div_euclid(rhs).as_()) Self::from_bits(lhs.div_euclid(rhs).as_())
} }
} }
} }
#[cfg(all(not(feature="wide-mul"),not(feature="deferred-division")))] #[cfg(all(not(feature="wide-mul"),not(feature="deferred-division")))]
impl_multiplicative_operator_not_const_generic!(($trait,$method,$output),$width); impl_multiplicative_operator_not_const_generic!(($struct, $trait, $method, $output ), $width);
#[cfg(all(not(feature="wide-mul"),feature="deferred-division"))] #[cfg(all(not(feature="wide-mul"),feature="deferred-division"))]
impl<const F:usize> ratio_ops::ratio::Divide for Fixed<{$width/BNUM_DIGIT_WIDTH},F>{ impl<const F:usize> ratio_ops::ratio::Divide for $struct<$width,F>{
type Output = $output; type Output = $output;
#[inline] #[inline]
fn divide(self, other: Self) -> Self::Output { fn divide(self, other: Self) -> Self::Output {
@@ -558,28 +450,28 @@ macro_rules! impl_divide_operator_not_const_generic {
} }
macro_rules! impl_multiplicative_operator { macro_rules! impl_multiplicative_operator {
( $trait: ident, $method: ident, $inner_method: ident, $output: ty ) => { ( $struct: ident, $trait: ident, $method: ident, $inner_method: ident, $output: ty ) => {
impl<const N:usize,const F:usize,U> core::ops::$trait<U> for Fixed<N,F> impl<const N:usize,const F:usize,U> core::ops::$trait<U> for $struct<N,F>
where where
Int::<N>:bnum::cast::CastFrom<U>+core::ops::$trait, BInt::<N>:From<U>+core::ops::$trait,
{ {
type Output = $output; type Output = $output;
#[inline] #[inline]
fn $method(self,other:U)->Self::Output{ fn $method(self,other:U)->Self::Output{
Self::from_bits(self.bits.$inner_method(other.as_())) Self::from_bits(self.bits.$inner_method(BInt::<N>::from(other)))
} }
} }
}; };
} }
macro_rules! impl_multiplicative_assign_operator { macro_rules! impl_multiplicative_assign_operator {
( $trait: ident, $method: ident, $not_assign_method: ident ) => { ( $struct: ident, $trait: ident, $method: ident, $not_assign_method: ident ) => {
impl<const N:usize,const F:usize,U> core::ops::$trait<U> for Fixed<N,F> impl<const N:usize,const F:usize,U> core::ops::$trait<U> for $struct<N,F>
where where
Int::<N>:bnum::cast::CastFrom<U>+core::ops::$trait, BInt::<N>:From<U>+core::ops::$trait,
{ {
#[inline] #[inline]
fn $method(&mut self,other:U){ fn $method(&mut self,other:U){
self.bits=self.bits.$not_assign_method(other.as_()); self.bits=self.bits.$not_assign_method(BInt::<N>::from(other));
} }
} }
}; };
@@ -599,18 +491,18 @@ macro_rules! macro_repeated{
macro_rules! macro_16 { macro_rules! macro_16 {
( $macro: ident, $any:tt ) => { ( $macro: ident, $any:tt ) => {
macro_repeated!($macro,$any,64,128,192,256,320,384,448,512,576,640,704,768,832,896,960,1024); 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, (MulAssign, mul_assign, mul) ); macro_16!( impl_multiplicative_assign_operator_not_const_generic, (Fixed, MulAssign, mul_assign, mul) );
macro_16!( impl_multiply_operator_not_const_generic, (Mul, mul, Self) ); macro_16!( impl_multiply_operator_not_const_generic, (Fixed, Mul, mul, Self) );
macro_16!( impl_multiplicative_assign_operator_not_const_generic, (DivAssign, div_assign, div_euclid) ); macro_16!( impl_multiplicative_assign_operator_not_const_generic, (Fixed, DivAssign, div_assign, div) );
macro_16!( impl_divide_operator_not_const_generic, (Div, div_euclid, Self) ); macro_16!( impl_divide_operator_not_const_generic, (Fixed, Div, div, Self) );
impl_multiplicative_assign_operator!( MulAssign, mul_assign, mul ); impl_multiplicative_assign_operator!( Fixed, MulAssign, mul_assign, mul );
impl_multiplicative_operator!( Mul, mul, mul, Self ); impl_multiplicative_operator!( Fixed, Mul, mul, mul, Self );
impl_multiplicative_assign_operator!( DivAssign, div_assign, div_euclid ); impl_multiplicative_assign_operator!( Fixed, DivAssign, div_assign, div_euclid );
impl_multiplicative_operator!( Div, div, div_euclid, Self ); impl_multiplicative_operator!( Fixed, Div, div, div_euclid, Self );
#[cfg(feature="deferred-division")] #[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>>; type Output=ratio_ops::ratio::Ratio<Fixed<LHS_N,LHS_F>,Fixed<RHS_N,RHS_F>>;
@@ -626,8 +518,8 @@ impl<const N:usize,const F:usize> ratio_ops::ratio::Parity for Fixed<N,F>{
} }
} }
macro_rules! impl_shift_operator { macro_rules! impl_shift_operator {
( $trait: ident, $method: ident, $output: ty ) => { ( $struct: ident, $trait: ident, $method: ident, $output: ty ) => {
impl<const N:usize,const F:usize> core::ops::$trait<u32> for Fixed<N,F>{ impl<const N:usize,const F:usize> core::ops::$trait<u32> for $struct<N,F>{
type Output = $output; type Output = $output;
#[inline] #[inline]
fn $method(self, other: u32) -> Self::Output { fn $method(self, other: u32) -> Self::Output {
@@ -637,8 +529,8 @@ macro_rules! impl_shift_operator {
}; };
} }
macro_rules! impl_shift_assign_operator { macro_rules! impl_shift_assign_operator {
( $trait: ident, $method: ident ) => { ( $struct: ident, $trait: ident, $method: ident ) => {
impl<const N:usize,const F:usize> core::ops::$trait<u32> for Fixed<N,F>{ impl<const N:usize,const F:usize> core::ops::$trait<u32> for $struct<N,F>{
#[inline] #[inline]
fn $method(&mut self, other: u32) { fn $method(&mut self, other: u32) {
self.bits.$method(other); self.bits.$method(other);
@@ -646,40 +538,40 @@ macro_rules! impl_shift_assign_operator {
} }
}; };
} }
impl_shift_assign_operator!( ShlAssign, shl_assign ); impl_shift_assign_operator!( Fixed, ShlAssign, shl_assign );
impl_shift_operator!( Shl, shl, Self ); impl_shift_operator!( Fixed, Shl, shl, Self );
impl_shift_assign_operator!( ShrAssign, shr_assign ); impl_shift_assign_operator!( Fixed, ShrAssign, shr_assign );
impl_shift_operator!( Shr, shr, Self ); 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 // wide operators. The result width is the sum of the input widths, i.e. none of the multiplication
#[allow(unused_macros)] #[allow(unused_macros)]
macro_rules! impl_wide_operators{ macro_rules! impl_wide_operators{
($lhs:expr,$rhs:expr)=>{ ($lhs:expr,$rhs:expr)=>{
impl core::ops::Mul<Fixed<{$rhs/BNUM_DIGIT_WIDTH},{$rhs>>1}>> for Fixed<{$lhs/BNUM_DIGIT_WIDTH},{$lhs>>1}>{ impl core::ops::Mul<Fixed<$rhs,{$rhs*32}>> for Fixed<$lhs,{$lhs*32}>{
type Output=Fixed<{($lhs+$rhs)/BNUM_DIGIT_WIDTH},{($lhs+$rhs)>>1}>; type Output=Fixed<{$lhs+$rhs},{($lhs+$rhs)*32}>;
#[inline] #[inline]
fn mul(self, other: Fixed<{$rhs/BNUM_DIGIT_WIDTH},{$rhs>>1}>)->Self::Output{ fn mul(self, other: Fixed<$rhs,{$rhs*32}>)->Self::Output{
paste::item!{ paste::item!{
self.[<wide_mul_ $lhs _ $rhs>](other) self.[<wide_mul_ $lhs _ $rhs>](other)
} }
} }
} }
#[cfg(not(feature="deferred-division"))] #[cfg(not(feature="deferred-division"))]
impl core::ops::Div<Fixed<{$rhs/BNUM_DIGIT_WIDTH},{$rhs>>1}>> for Fixed<{$lhs/BNUM_DIGIT_WIDTH},{$lhs>>1}>{ impl core::ops::Div<Fixed<$rhs,{$rhs*32}>> for Fixed<$lhs,{$lhs*32}>{
type Output=Fixed<{($lhs+$rhs)/BNUM_DIGIT_WIDTH},{($lhs+$rhs)>>1}>; type Output=Fixed<{$lhs+$rhs},{($lhs+$rhs)*32}>;
#[inline] #[inline]
fn div(self, other: Fixed<{$rhs/BNUM_DIGIT_WIDTH},{$rhs>>1}>)->Self::Output{ fn div(self, other: Fixed<$rhs,{$rhs*32}>)->Self::Output{
paste::item!{ paste::item!{
self.[<wide_div_ $lhs _ $rhs>](other) self.[<wide_div_ $lhs _ $rhs>](other)
} }
} }
} }
#[cfg(feature="deferred-division")] #[cfg(feature="deferred-division")]
impl ratio_ops::ratio::Divide<Fixed<{$rhs/BNUM_DIGIT_WIDTH},{$rhs>>1}>> for Fixed<{$lhs/BNUM_DIGIT_WIDTH},{$lhs>>1}>{ impl ratio_ops::ratio::Divide<Fixed<$rhs,{$rhs*32}>> for Fixed<$lhs,{$lhs*32}>{
type Output=Fixed<{($lhs+$rhs)/BNUM_DIGIT_WIDTH},{($lhs+$rhs)>>1}>; type Output=Fixed<{$lhs+$rhs},{($lhs+$rhs)*32}>;
#[inline] #[inline]
fn divide(self, other: Fixed<{$rhs/BNUM_DIGIT_WIDTH},{$rhs>>1}>)->Self::Output{ fn divide(self, other: Fixed<$rhs,{$rhs*32}>)->Self::Output{
paste::item!{ paste::item!{
self.[<wide_div_ $lhs _ $rhs>](other) self.[<wide_div_ $lhs _ $rhs>](other)
} }
@@ -696,24 +588,24 @@ macro_rules! impl_wide_not_const_generic{
(), (),
($lhs:expr,$rhs:expr) ($lhs:expr,$rhs:expr)
)=>{ )=>{
impl Fixed<{$lhs/BNUM_DIGIT_WIDTH},{$lhs>>1}> impl Fixed<$lhs,{$lhs*32}>
{ {
paste::item!{ paste::item!{
#[inline] #[inline]
pub fn [<wide_mul_ $lhs _ $rhs>](self,rhs:Fixed<{$rhs/BNUM_DIGIT_WIDTH},{$rhs>>1}>)->Fixed<{($lhs+$rhs)/BNUM_DIGIT_WIDTH},{($lhs+$rhs)>>1}>{ pub fn [<wide_mul_ $lhs _ $rhs>](self,rhs:Fixed<$rhs,{$rhs*32}>)->Fixed<{$lhs+$rhs},{($lhs+$rhs)*32}>{
let lhs=self.bits.as_::<Int<{($lhs+$rhs)/BNUM_DIGIT_WIDTH}>>(); let lhs=self.bits.as_::<BInt<{$lhs+$rhs}>>();
let rhs=rhs.bits.as_::<Int<{($lhs+$rhs)/BNUM_DIGIT_WIDTH}>>(); let rhs=rhs.bits.as_::<BInt<{$lhs+$rhs}>>();
Fixed::from_bits(lhs*rhs) Fixed::from_bits(lhs*rhs)
} }
/// This operation cannot represent the fraction exactly, /// This operation cannot represent the fraction exactly,
/// but it shapes the output to have precision for the /// but it shapes the output to have precision for the
/// largest and smallest possible fractions. /// largest and smallest possible fractions.
#[inline] #[inline]
pub fn [<wide_div_ $lhs _ $rhs>](self,rhs:Fixed<{$rhs/BNUM_DIGIT_WIDTH},{$rhs>>1}>)->Fixed<{($lhs+$rhs)/BNUM_DIGIT_WIDTH},{($lhs+$rhs)>>1}>{ pub fn [<wide_div_ $lhs _ $rhs>](self,rhs:Fixed<$rhs,{$rhs*32}>)->Fixed<{$lhs+$rhs},{($lhs+$rhs)*32}>{
// (lhs/2^LHS_FRAC)/(rhs/2^RHS_FRAC) // (lhs/2^LHS_FRAC)/(rhs/2^RHS_FRAC)
let lhs=self.bits.as_::<Int<{($lhs+$rhs)/BNUM_DIGIT_WIDTH}>>().shl($rhs); let lhs=self.bits.as_::<BInt<{$lhs+$rhs}>>().shl($rhs*64);
let rhs=rhs.bits.as_::<Int<{($lhs+$rhs)/BNUM_DIGIT_WIDTH}>>(); let rhs=rhs.bits.as_::<BInt<{$lhs+$rhs}>>();
Fixed::from_bits(lhs.div_euclid(rhs)) Fixed::from_bits(lhs/rhs)
} }
} }
} }
@@ -726,13 +618,13 @@ macro_rules! impl_wide_same_size_not_const_generic{
(), (),
$width:expr $width:expr
)=>{ )=>{
impl Fixed<{$width/BNUM_DIGIT_WIDTH},{$width>>1}> impl Fixed<$width,{$width*32}>
{ {
paste::item!{ paste::item!{
#[inline] #[inline]
pub fn [<wide_mul_ $width _ $width>](self,rhs:Fixed<{$width/BNUM_DIGIT_WIDTH},{$width>>1}>)->Fixed<{$width*2/BNUM_DIGIT_WIDTH},{$width*2>>1}>{ pub fn [<wide_mul_ $width _ $width>](self,rhs:Fixed<$width,{$width*32}>)->Fixed<{$width*2},{$width*2*32}>{
let (low,high)=self.bits.unsigned_abs().widening_mul(rhs.bits.unsigned_abs()); let (low,high)=self.bits.unsigned_abs().widening_mul(rhs.bits.unsigned_abs());
let out:Int::<{$width*2/BNUM_DIGIT_WIDTH}>=unsafe{core::mem::transmute([low,high])}; let out:BInt::<{$width*2}>=unsafe{core::mem::transmute([low,high])};
if self.is_negative()==rhs.is_negative(){ if self.is_negative()==rhs.is_negative(){
Fixed::from_bits(out) Fixed::from_bits(out)
}else{ }else{
@@ -745,11 +637,11 @@ macro_rules! impl_wide_same_size_not_const_generic{
/// but it shapes the output to have precision for the /// but it shapes the output to have precision for the
/// largest and smallest possible fractions. /// largest and smallest possible fractions.
#[inline] #[inline]
pub fn [<wide_div_ $width _ $width>](self,rhs:Fixed<{$width/BNUM_DIGIT_WIDTH},{$width>>1}>)->Fixed<{$width*2/BNUM_DIGIT_WIDTH},{$width*2>>1}>{ pub fn [<wide_div_ $width _ $width>](self,rhs:Fixed<$width,{$width*32}>)->Fixed<{$width*2},{$width*2*32}>{
// (lhs/2^LHS_FRAC)/(rhs/2^RHS_FRAC) // (lhs/2^LHS_FRAC)/(rhs/2^RHS_FRAC)
let lhs=self.bits.as_::<Int<{$width*2/BNUM_DIGIT_WIDTH}>>().shl($width); let lhs=self.bits.as_::<BInt<{$width*2}>>().shl($width*64);
let rhs=rhs.bits.as_::<Int<{$width*2/BNUM_DIGIT_WIDTH}>>(); let rhs=rhs.bits.as_::<BInt<{$width*2}>>();
Fixed::from_bits(lhs.div_euclid(rhs)) Fixed::from_bits(lhs/rhs)
} }
} }
} }
@@ -761,25 +653,25 @@ macro_rules! impl_wide_same_size_not_const_generic{
//const generics sidestepped wahoo //const generics sidestepped wahoo
macro_repeated!( macro_repeated!(
impl_wide_not_const_generic,(), impl_wide_not_const_generic,(),
(128,64),(192,64),(256,64),(320,64),(384,64),(448,64),(512,64),(576,64),(640,64),(704,64),(768,64),(832,64),(896,64),(960,64), (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),
(64,128), (192,128),(256,128),(320,128),(384,128),(448,128),(512,128),(576,128),(640,128),(704,128),(768,128),(832,128),(896,128), (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),
(64,192),(128,192), (256,192),(320,192),(384,192),(448,192),(512,192),(576,192),(640,192),(704,192),(768,192),(832,192), (1,3),(2,3), (4,3),(5,3),(6,3),(7,3),(8,3),(9,3),(10,3),(11,3),(12,3),(13,3),
(64,256),(128,256),(192,256), (320,256),(384,256),(448,256),(512,256),(576,256),(640,256),(704,256),(768,256), (1,4),(2,4),(3,4), (5,4),(6,4),(7,4),(8,4),(9,4),(10,4),(11,4),(12,4),
(64,320),(128,320),(192,320),(256,320), (384,320),(448,320),(512,320),(576,320),(640,320),(704,320), (1,5),(2,5),(3,5),(4,5), (6,5),(7,5),(8,5),(9,5),(10,5),(11,5),
(64,384),(128,384),(192,384),(256,384),(320,384), (448,384),(512,384),(576,384),(640,384), (1,6),(2,6),(3,6),(4,6),(5,6), (7,6),(8,6),(9,6),(10,6),
(64,448),(128,448),(192,448),(256,448),(320,448),(384,448), (512,448),(576,448), (1,7),(2,7),(3,7),(4,7),(5,7),(6,7), (8,7),(9,7),
(64,512),(128,512),(192,512),(256,512),(320,512),(384,512),(448,512), (576,512), (1,8),(2,8),(3,8),(4,8),(5,8),(6,8),(7,8), (9,8),
(64,576),(128,576),(192,576),(256,576),(320,576),(384,576),(448,576), (1,9),(2,9),(3,9),(4,9),(5,9),(6,9),(7,9),
(64,640),(128,640),(192,640),(256,640),(320,640),(384,640), (1,10),(2,10),(3,10),(4,10),(5,10),(6,10),
(64,704),(128,704),(192,704),(256,704),(320,704), (1,11),(2,11),(3,11),(4,11),(5,11),
(64,768),(128,768),(192,768),(256,768), (1,12),(2,12),(3,12),(4,12),
(64,832),(128,832),(192,832), (1,13),(2,13),(3,13),
(64,896),(128,896), (1,14),(2,14),
(64,960) (1,15)
); );
macro_repeated!( macro_repeated!(
impl_wide_same_size_not_const_generic,(), impl_wide_same_size_not_const_generic,(),
64,128,192,256,320,384,448,512 1,2,3,4,5,6,7,8
); );
#[derive(Debug,Eq,PartialEq)] #[derive(Debug,Eq,PartialEq)]
@@ -810,43 +702,43 @@ macro_rules! impl_narrow_not_const_generic{
($lhs:expr,$rhs:expr) ($lhs:expr,$rhs:expr)
)=>{ )=>{
paste::item!{ paste::item!{
impl Fixed<{$lhs/BNUM_DIGIT_WIDTH},{$lhs>>1}> impl Fixed<$lhs,{$lhs*32}>
{ {
#[inline] #[inline]
pub fn [<wrap_ $rhs>](self)->Fixed<{$rhs/BNUM_DIGIT_WIDTH},{$rhs>>1}>{ pub fn [<wrap_ $rhs>](self)->Fixed<$rhs,{$rhs*32}>{
Fixed::from_bits(bnum::cast::As::as_::<Int::<{$rhs/BNUM_DIGIT_WIDTH}>>(self.bits.shr(($lhs-$rhs)>>1))) Fixed::from_bits(bnum::cast::As::as_::<BInt::<$rhs>>(self.bits.shr(($lhs-$rhs)*32)))
} }
#[inline] #[inline]
pub fn [<narrow_ $rhs>](self)->Result<Fixed<{$rhs/BNUM_DIGIT_WIDTH},{$rhs>>1}>,NarrowError>{ pub fn [<narrow_ $rhs>](self)->Result<Fixed<$rhs,{$rhs*32}>,NarrowError>{
if Fixed::<{$rhs/BNUM_DIGIT_WIDTH},{$rhs>>1}>::MAX.[<widen_ $lhs>]().bits<self.bits{ if Fixed::<$rhs,{$rhs*32}>::MAX.[<widen_ $lhs>]().bits<self.bits{
return Err(NarrowError::Overflow); return Err(NarrowError::Overflow);
} }
if self.bits<Fixed::<{$rhs/BNUM_DIGIT_WIDTH},{$rhs>>1}>::MIN.[<widen_ $lhs>]().bits{ if self.bits<Fixed::<$rhs,{$rhs*32}>::MIN.[<widen_ $lhs>]().bits{
return Err(NarrowError::Underflow); return Err(NarrowError::Underflow);
} }
Ok(self.[<wrap_ $rhs>]()) Ok(self.[<wrap_ $rhs>]())
} }
#[inline] #[inline]
pub fn [<clamp_ $rhs>](self)->Fixed<{$rhs/BNUM_DIGIT_WIDTH},{$rhs>>1}>{ pub fn [<clamp_ $rhs>](self)->Fixed<$rhs,{$rhs*32}>{
self.[<narrow_ $rhs>]().clamp() self.[<narrow_ $rhs>]().clamp()
} }
} }
impl Wrap<Fixed<{$rhs/BNUM_DIGIT_WIDTH},{$rhs>>1}>> for Fixed<{$lhs/BNUM_DIGIT_WIDTH},{$lhs>>1}>{ impl Wrap<Fixed<$rhs,{$rhs*32}>> for Fixed<$lhs,{$lhs*32}>{
#[inline] #[inline]
fn wrap(self)->Fixed<{$rhs/BNUM_DIGIT_WIDTH},{$rhs>>1}>{ fn wrap(self)->Fixed<$rhs,{$rhs*32}>{
self.[<wrap_ $rhs>]() self.[<wrap_ $rhs>]()
} }
} }
impl TryInto<Fixed<{$rhs/BNUM_DIGIT_WIDTH},{$rhs>>1}>> for Fixed<{$lhs/BNUM_DIGIT_WIDTH},{$lhs>>1}>{ impl TryInto<Fixed<$rhs,{$rhs*32}>> for Fixed<$lhs,{$lhs*32}>{
type Error=NarrowError; type Error=NarrowError;
#[inline] #[inline]
fn try_into(self)->Result<Fixed<{$rhs/BNUM_DIGIT_WIDTH},{$rhs>>1}>,Self::Error>{ fn try_into(self)->Result<Fixed<$rhs,{$rhs*32}>,Self::Error>{
self.[<narrow_ $rhs>]() self.[<narrow_ $rhs>]()
} }
} }
impl Clamp<Fixed<{$rhs/BNUM_DIGIT_WIDTH},{$rhs>>1}>> for Fixed<{$lhs/BNUM_DIGIT_WIDTH},{$lhs>>1}>{ impl Clamp<Fixed<$rhs,{$rhs*32}>> for Fixed<$lhs,{$lhs*32}>{
#[inline] #[inline]
fn clamp(self)->Fixed<{$rhs/BNUM_DIGIT_WIDTH},{$rhs>>1}>{ fn clamp(self)->Fixed<$rhs,{$rhs*32}>{
self.[<clamp_ $rhs>]() self.[<clamp_ $rhs>]()
} }
} }
@@ -859,16 +751,16 @@ macro_rules! impl_widen_not_const_generic{
($lhs:expr,$rhs:expr) ($lhs:expr,$rhs:expr)
)=>{ )=>{
paste::item!{ paste::item!{
impl Fixed<{$lhs/BNUM_DIGIT_WIDTH},{$lhs>>1}> impl Fixed<$lhs,{$lhs*32}>
{ {
#[inline] #[inline]
pub fn [<widen_ $rhs>](self)->Fixed<{$rhs/BNUM_DIGIT_WIDTH},{$rhs>>1}>{ pub fn [<widen_ $rhs>](self)->Fixed<$rhs,{$rhs*32}>{
Fixed::from_bits(bnum::cast::As::as_::<Int::<{$rhs/BNUM_DIGIT_WIDTH}>>(self.bits).shl(($rhs-$lhs)>>1)) Fixed::from_bits(bnum::cast::As::as_::<BInt::<$rhs>>(self.bits).shl(($rhs-$lhs)*32))
} }
} }
impl Into<Fixed<{$rhs/BNUM_DIGIT_WIDTH},{$rhs>>1}>> for Fixed<{$lhs/BNUM_DIGIT_WIDTH},{$lhs>>1}>{ impl Into<Fixed<$rhs,{$rhs*32}>> for Fixed<$lhs,{$lhs*32}>{
#[inline] #[inline]
fn into(self)->Fixed<{$rhs/BNUM_DIGIT_WIDTH},{$rhs>>1}>{ fn into(self)->Fixed<$rhs,{$rhs*32}>{
self.[<widen_ $rhs>]() self.[<widen_ $rhs>]()
} }
} }
@@ -880,45 +772,45 @@ macro_rules! impl_widen_not_const_generic{
macro_repeated!( macro_repeated!(
impl_narrow_not_const_generic,(), impl_narrow_not_const_generic,(),
(128,64),(192,64),(256,64),(320,64),(384,64),(448,64),(512,64),(576,64),(640,64),(704,64),(768,64),(832,64),(896,64),(960,64),(1024,64),(1088,64), (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),
(192,128),(256,128),(320,128),(384,128),(448,128),(512,128),(576,128),(640,128),(704,128),(768,128),(832,128),(896,128),(960,128),(1024,128), (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),
(256,192),(320,192),(384,192),(448,192),(512,192),(576,192),(640,192),(704,192),(768,192),(832,192),(896,192),(960,192),(1024,192), (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),
(320,256),(384,256),(448,256),(512,256),(576,256),(640,256),(704,256),(768,256),(832,256),(896,256),(960,256),(1024,256), (5,4),(6,4),(7,4),(8,4),(9,4),(10,4),(11,4),(12,4),(13,4),(14,4),(15,4),(16,4),
(384,320),(448,320),(512,320),(576,320),(640,320),(704,320),(768,320),(832,320),(896,320),(960,320),(1024,320), (6,5),(7,5),(8,5),(9,5),(10,5),(11,5),(12,5),(13,5),(14,5),(15,5),(16,5),
(448,384),(512,384),(576,384),(640,384),(704,384),(768,384),(832,384),(896,384),(960,384),(1024,384), (7,6),(8,6),(9,6),(10,6),(11,6),(12,6),(13,6),(14,6),(15,6),(16,6),
(512,448),(576,448),(640,448),(704,448),(768,448),(832,448),(896,448),(960,448),(1024,448), (8,7),(9,7),(10,7),(11,7),(12,7),(13,7),(14,7),(15,7),(16,7),
(576,512),(640,512),(704,512),(768,512),(832,512),(896,512),(960,512),(1024,512), (9,8),(10,8),(11,8),(12,8),(13,8),(14,8),(15,8),(16,8),
(640,576),(704,576),(768,576),(832,576),(896,576),(960,576),(1024,576), (10,9),(11,9),(12,9),(13,9),(14,9),(15,9),(16,9),
(704,640),(768,640),(832,640),(896,640),(960,640),(1024,640), (11,10),(12,10),(13,10),(14,10),(15,10),(16,10),
(768,704),(832,704),(896,704),(960,704),(1024,704), (12,11),(13,11),(14,11),(15,11),(16,11),
(832,768),(896,768),(960,768),(1024,768), (13,12),(14,12),(15,12),(16,12),
(896,832),(960,832),(1024,832), (14,13),(15,13),(16,13),
(960,896),(1024,896), (15,14),(16,14),
(1024,960) (16,15)
); );
macro_repeated!( macro_repeated!(
impl_widen_not_const_generic,(), impl_widen_not_const_generic,(),
(64,128), (1,2),
(64,192),(128,192), (1,3),(2,3),
(64,256),(128,256),(192,256), (1,4),(2,4),(3,4),
(64,320),(128,320),(192,320),(256,320), (1,5),(2,5),(3,5),(4,5),
(64,384),(128,384),(192,384),(256,384),(320,384), (1,6),(2,6),(3,6),(4,6),(5,6),
(64,448),(128,448),(192,448),(256,448),(320,448),(384,448), (1,7),(2,7),(3,7),(4,7),(5,7),(6,7),
(64,512),(128,512),(192,512),(256,512),(320,512),(384,512),(448,512), (1,8),(2,8),(3,8),(4,8),(5,8),(6,8),(7,8),
(64,576),(128,576),(192,576),(256,576),(320,576),(384,576),(448,576),(512,576), (1,9),(2,9),(3,9),(4,9),(5,9),(6,9),(7,9),(8,9),
(64,640),(128,640),(192,640),(256,640),(320,640),(384,640),(448,640),(512,640),(576,640), (1,10),(2,10),(3,10),(4,10),(5,10),(6,10),(7,10),(8,10),(9,10),
(64,704),(128,704),(192,704),(256,704),(320,704),(384,704),(448,704),(512,704),(576,704),(640,704), (1,11),(2,11),(3,11),(4,11),(5,11),(6,11),(7,11),(8,11),(9,11),(10,11),
(64,768),(128,768),(192,768),(256,768),(320,768),(384,768),(448,768),(512,768),(576,768),(640,768),(704,768), (1,12),(2,12),(3,12),(4,12),(5,12),(6,12),(7,12),(8,12),(9,12),(10,12),(11,12),
(64,832),(128,832),(192,832),(256,832),(320,832),(384,832),(448,832),(512,832),(576,832),(640,832),(704,832),(768,832), (1,13),(2,13),(3,13),(4,13),(5,13),(6,13),(7,13),(8,13),(9,13),(10,13),(11,13),(12,13),
(64,896),(128,896),(192,896),(256,896),(320,896),(384,896),(448,896),(512,896),(576,896),(640,896),(704,896),(768,896),(832,896), (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),
(64,960),(128,960),(192,960),(256,960),(320,960),(384,960),(448,960),(512,960),(576,960),(640,960),(704,960),(768,960),(832,960),(896,960), (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),
(64,1024),(128,1024),(192,1024),(256,1024),(320,1024),(384,1024),(448,1024),(512,1024),(576,1024),(640,1024),(704,1024),(768,1024),(832,1024),(896,1024),(960,1024), (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),
(64,1088) (1,17)
); );
macro_rules! impl_not_const_generic{ macro_rules! impl_not_const_generic{
($n:expr,$_2n:expr)=>{ ($n:expr,$_2n:expr)=>{
impl Fixed<{$n/BNUM_DIGIT_WIDTH},{$n>>1}>{ impl Fixed<$n,{$n*32}>{
paste::item!{ paste::item!{
#[inline] #[inline]
pub fn sqrt_unchecked(self)->Self{ pub fn sqrt_unchecked(self)->Self{
@@ -928,18 +820,18 @@ macro_rules! impl_not_const_generic{
//2. divide by 2 via >>1 (sqrt-ish) //2. divide by 2 via >>1 (sqrt-ish)
//3. add on fractional offset //3. add on fractional offset
//Voila //Voila
let used_bits=self.bits.unsigned_abs().bit_width() as i32-1-($n>>1) as i32; let used_bits=self.bits.bits() as i32-1-($n*32) as i32;
let max_shift=((used_bits>>1)+($n>>1) as i32) as u32; let max_shift=((used_bits>>1)+($n*32) as i32) as u32;
let mut result=Self::ZERO; let mut result=Self::ZERO;
//resize self to match the wide mul output //resize self to match the wide mul output
let wide_self=self.[<widen_ $_2n>](); let wide_self=self.[<widen_ $_2n>]();
//descend down the bits and check if flipping each bit would push the square over the input value //descend down the bits and check if flipping each bit would push the square over the input value
for shift in (0..=max_shift).rev(){ for shift in (0..=max_shift).rev(){
result.as_bits_mut().set_bit(shift,true); result.as_bits_mut().as_bits_mut().set_bit(shift,true);
if wide_self<result.[<wide_mul_ $n _ $n>](result){ if wide_self<result.[<wide_mul_ $n _ $n>](result){
// put it back lol // put it back lol
result.as_bits_mut().set_bit(shift,false); result.as_bits_mut().as_bits_mut().set_bit(shift,false);
} }
} }
result result
@@ -964,11 +856,11 @@ macro_rules! impl_not_const_generic{
} }
} }
} }
impl_not_const_generic!(64,128); impl_not_const_generic!(1,2);
impl_not_const_generic!(128,256); impl_not_const_generic!(2,4);
impl_not_const_generic!(192,384); impl_not_const_generic!(3,6);
impl_not_const_generic!(256,512); impl_not_const_generic!(4,8);
impl_not_const_generic!(320,640); impl_not_const_generic!(5,10);
impl_not_const_generic!(384,768); impl_not_const_generic!(6,12);
impl_not_const_generic!(448,896); impl_not_const_generic!(7,14);
impl_not_const_generic!(512,1024); impl_not_const_generic!(8,16);

View File

@@ -1,273 +1,208 @@
use crate::fixed::Fixed; use crate::types::I32F32;
use crate::types::{F64_32,F128_64,F192_96,F512_256}; use crate::types::I256F256;
#[test] #[test]
fn you_can_add_numbers(){ fn you_can_add_numbers(){
let a=F512_256::from((3i128*2).pow(4)); let a=I256F256::from((3i128*2).pow(4));
assert_eq!(a+a,F512_256::from((3i128*2).pow(4)*2)); assert_eq!(a+a,I256F256::from((3i128*2).pow(4)*2));
}
macro_rules! test_bit_by_bit{
($n:expr,$float:ty,$mantissa_bits:expr)=>{{
const MANT:u64=(1<<$mantissa_bits)-1;
// all bits in range
for i in 0..$n-$mantissa_bits{
let a=Fixed::<{$n/8},{$n>>1}>::from_bits(bnum::cast::As::as_::<bnum::Int::<{$n/8}>>(MANT).shl(i));
let b=(MANT as $float)*(2.0 as $float).powi(i as i32-{$n>>1});
let f:$float=a.into();
assert_eq!(f,b,"F{}_{} Into float {i}",$n,$n>>1);
assert_eq!(a,b.try_into().unwrap(),"F{}_{} From float {i}",$n,$n>>1);
}
// underflow
for i in 0u32..$mantissa_bits{
let a=Fixed::<{$n/8},{$n>>1}>::from_bits(bnum::cast::As::as_::<bnum::Int::<{$n/8}>>(MANT>>i));
let b=((MANT>>i) as $float)*(2.0 as $float).powi(-{$n>>1});
let f:$float=a.into();
assert_eq!(f,b,"Underflow F{}_{} Into float {i}",$n,$n>>1);
assert_eq!(a,b.try_into().unwrap(),"Underflow F{}_{} From float {i}",$n,$n>>1);
}
}};
}
#[test]
fn test_many(){
test_bit_by_bit!(64,f32,24);
test_bit_by_bit!(128,f32,24);
// f32 is reaching its limits here
// test_bit_by_bit!(256,f32,24);
// test_bit_by_bit!(512,f32,24);
test_bit_by_bit!(64,f64,53);
test_bit_by_bit!(128,f64,53);
test_bit_by_bit!(256,f64,53);
test_bit_by_bit!(512,f64,53);
} }
#[test] #[test]
fn to_f32(){ fn to_f32(){
let a=F64_32::ZERO; let a=I256F256::from(1)>>2;
let f:f32=a.into();
assert_eq!(f,0.0f32);
let a=F64_32::from(1)>>2;
let f:f32=a.into(); let f:f32=a.into();
assert_eq!(f,0.25f32); assert_eq!(f,0.25f32);
let f:f32=(-a).into(); let f:f32=(-a).into();
assert_eq!(f,-0.25f32); assert_eq!(f,-0.25f32);
let a=F64_32::MIN; let a=I256F256::from(0);
let f:f32=a.into();
assert_eq!(f,i32::MIN as f32);
let a=F512_256::from(1)>>2;
let f:f32=a.into();
assert_eq!(f,0.25f32);
let f:f32=(-a).into();
assert_eq!(f,-0.25f32);
let a=F512_256::from(0);
let f:f32=(-a).into(); let f:f32=(-a).into();
assert_eq!(f,0f32); assert_eq!(f,0f32);
let a=F512_256::from(237946589723468975i64)<<16; let a=I256F256::from(237946589723468975i64)<<16;
let f:f32=a.into(); let f:f32=a.into();
assert_eq!(f,237946589723468975f32*2.0f32.powi(16)); assert_eq!(f,237946589723468975f32*2.0f32.powi(16));
} }
#[test] #[test]
fn to_f64(){ fn to_f64(){
let a=F64_32::ZERO; let a=I256F256::from(1)>>2;
let f:f64=a.into();
assert_eq!(f,0.0f64);
let a=F64_32::from(1)>>2;
let f:f64=a.into(); let f:f64=a.into();
assert_eq!(f,0.25f64); assert_eq!(f,0.25f64);
let f:f64=(-a).into(); let f:f64=(-a).into();
assert_eq!(f,-0.25f64); assert_eq!(f,-0.25f64);
let a=F64_32::MIN; let a=I256F256::from(0);
let f:f64=a.into();
assert_eq!(f,i32::MIN as f64);
let a=F512_256::from(1)>>2;
let f:f64=a.into();
assert_eq!(f,0.25f64);
let f:f64=(-a).into();
assert_eq!(f,-0.25f64);
let a=F512_256::from(0);
let f:f64=(-a).into(); let f:f64=(-a).into();
assert_eq!(f,0f64); assert_eq!(f,0f64);
let a=F512_256::from(237946589723468975i64)<<16; let a=I256F256::from(237946589723468975i64)<<16;
let f:f64=a.into(); let f:f64=a.into();
assert_eq!(f,237946589723468975f64*2.0f64.powi(16)); assert_eq!(f,237946589723468975f64*2.0f64.powi(16));
} }
#[test] #[test]
fn from_f32(){ fn from_f32(){
let a=F64_32::ZERO; let a=I256F256::from(1)>>2;
let b:Result<F64_32,_>=0.0f32.try_into(); let b:Result<I256F256,_>=0.25f32.try_into();
assert_eq!(b,Ok(a)); assert_eq!(b,Ok(a));
let a=F512_256::from(1)>>2; let a=I256F256::from(-1)>>2;
let b:Result<F512_256,_>=0.25f32.try_into(); let b:Result<I256F256,_>=(-0.25f32).try_into();
assert_eq!(b,Ok(a)); assert_eq!(b,Ok(a));
let a=F512_256::from(-1)>>2; let a=I256F256::from(0);
let b:Result<F512_256,_>=(-0.25f32).try_into(); let b:Result<I256F256,_>=0.try_into();
assert_eq!(b,Ok(a)); assert_eq!(b,Ok(a));
let a=F512_256::from(0); let a=I256F256::from(0b101011110101001010101010000000000000000000000000000i64)<<16;
let b:Result<F512_256,_>=0.try_into(); let b:Result<I256F256,_>=(0b101011110101001010101010000000000000000000000000000u64 as f32*2.0f32.powi(16)).try_into();
assert_eq!(b,Ok(a));
let a=F512_256::from(0b101011110101001010101010000000000000000000000000000i64)<<16;
let b:Result<F512_256,_>=(0b101011110101001010101010000000000000000000000000000u64 as f32*2.0f32.powi(16)).try_into();
assert_eq!(b,Ok(a)); assert_eq!(b,Ok(a));
//I32F32::MAX into f32 is truncated into this value //I32F32::MAX into f32 is truncated into this value
let a=F64_32::raw(0b111111111111111111111111000000000000000000000000000000000000000i64); let a=I32F32::raw(0b111111111111111111111111000000000000000000000000000000000000000i64);
let b:Result<F64_32,_>=Into::<f32>::into(F64_32::MAX).try_into(); let b:Result<I32F32,_>=Into::<f32>::into(I32F32::MAX).try_into();
assert_eq!(b,Ok(a)); assert_eq!(b,Ok(a));
//I32F32::MIN hits a special case since it's not representable as a positive signed integer //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 //TODO: don't return an overflow because this is technically possible
let a=F64_32::MIN; let _a=I32F32::MIN;
let f:f32=a.into(); let b:Result<I32F32,_>=Into::<f32>::into(I32F32::MIN).try_into();
let b:Result<F64_32,_>=f.try_into();
assert_eq!(b,Err(crate::fixed::FixedFromFloatError::Overflow)); assert_eq!(b,Err(crate::fixed::FixedFromFloatError::Overflow));
//16 is within the 24 bits of float precision //16 is within the 24 bits of float precision
let a=-F64_32::MIN.widen_128(); let b:Result<I32F32,_>=Into::<f32>::into(-I32F32::MIN.widen_2()).try_into();
let f:f32=a.into();
let b:Result<F64_32,_>=f.try_into();
assert_eq!(b,Err(crate::fixed::FixedFromFloatError::Overflow)); assert_eq!(b,Err(crate::fixed::FixedFromFloatError::Overflow));
let b:Result<F64_32,_>=f32::MIN_POSITIVE.try_into(); let b:Result<I32F32,_>=f32::MIN_POSITIVE.try_into();
assert_eq!(b,Err(crate::fixed::FixedFromFloatError::Underflow)); assert_eq!(b,Err(crate::fixed::FixedFromFloatError::Underflow));
//test many cases //test many cases
for i in 0..64{ for i in 0..64{
let a=F128_64::from_u64(0b111111111111111111111111000000000000000000000000000000000000000u64)<<i; let a=crate::fixed::Fixed::<2,64>::raw_digit(0b111111111111111111111111000000000000000000000000000000000000000i64)<<i;
let f:f32=a.into(); let f:f32=a.into();
let b:Result<F128_64,_>=f.try_into(); let b:Result<crate::fixed::Fixed<2,64>,_>=f.try_into();
assert_eq!(b,Ok(a)); assert_eq!(b,Ok(a));
} }
} }
#[test] #[test]
fn from_f64(){ fn from_f64(){
let a=F64_32::ZERO; let a=I256F256::from(1)>>2;
let b:Result<F64_32,_>=0.0f64.try_into(); let b:Result<I256F256,_>=0.25f64.try_into();
assert_eq!(b,Ok(a)); assert_eq!(b,Ok(a));
let a=F512_256::from(1)>>2; let a=I256F256::from(-1)>>2;
let b:Result<F512_256,_>=0.25f64.try_into(); let b:Result<I256F256,_>=(-0.25f64).try_into();
assert_eq!(b,Ok(a)); assert_eq!(b,Ok(a));
let a=F512_256::from(-1)>>2; let a=I256F256::from(0);
let b:Result<F512_256,_>=(-0.25f64).try_into(); let b:Result<I256F256,_>=0.try_into();
assert_eq!(b,Ok(a)); assert_eq!(b,Ok(a));
let a=F512_256::from(0); let a=I256F256::from(0b101011110101001010101010000000000000000000000000000i64)<<16;
let b:Result<F512_256,_>=0.try_into(); let b:Result<I256F256,_>=(0b101011110101001010101010000000000000000000000000000u64 as f64*2.0f64.powi(16)).try_into();
assert_eq!(b,Ok(a));
let a=F512_256::from(0b101011110101001010101010000000000000000000000000000i64)<<16;
let b:Result<F512_256,_>=(0b101011110101001010101010000000000000000000000000000u64 as f64*2.0f64.powi(16)).try_into();
assert_eq!(b,Ok(a)); assert_eq!(b,Ok(a));
} }
#[test] #[test]
fn you_can_shr_numbers(){ fn you_can_shr_numbers(){
let a=F64_32::from(4); let a=I32F32::from(4);
assert_eq!(a>>1,F64_32::from(2)); assert_eq!(a>>1,I32F32::from(2));
} }
#[test] #[test]
fn test_wide_mul(){ fn test_wide_mul(){
let a=F64_32::ONE; let a=I32F32::ONE;
let aa=a.wide_mul_64_64(a); let aa=a.wide_mul_1_1(a);
assert_eq!(aa,F128_64::ONE); assert_eq!(aa,crate::types::I64F64::ONE);
} }
#[test] #[test]
fn test_wide_div(){ fn test_wide_div(){
let a=F64_32::ONE*4; let a=I32F32::ONE*4;
let b=F64_32::ONE*2; let b=I32F32::ONE*2;
let wide_a=a.wide_mul_64_64(F64_32::ONE); let wide_a=a.wide_mul_1_1(I32F32::ONE);
let wide_b=b.wide_mul_64_64(F64_32::ONE); let wide_b=b.wide_mul_1_1(I32F32::ONE);
let ab=a.wide_div_64_64(b); let ab=a.wide_div_1_1(b);
assert_eq!(ab,F128_64::ONE*2); assert_eq!(ab,crate::types::I64F64::ONE*2);
let wab=wide_a.wide_div_128_64(b); let wab=wide_a.wide_div_2_1(b);
assert_eq!(wab,F192_96::ONE*2); assert_eq!(wab,crate::fixed::Fixed::<3,96>::ONE*2);
let awb=a.wide_div_64_128(wide_b); let awb=a.wide_div_1_2(wide_b);
assert_eq!(awb,F192_96::ONE*2); assert_eq!(awb,crate::fixed::Fixed::<3,96>::ONE*2);
} }
#[test] #[test]
fn test_wide_mul_repeated() { fn test_wide_mul_repeated() {
let a=F64_32::from(2); let a=I32F32::from(2);
let b=F64_32::from(3); let b=I32F32::from(3);
let w1=a.wide_mul_64_64(b); let w1=a.wide_mul_1_1(b);
let w2=w1.wide_mul_128_128(w1); let w2=w1.wide_mul_2_2(w1);
let w3=w2.wide_mul_256_256(w2); let w3=w2.wide_mul_4_4(w2);
assert_eq!(w3,F512_256::from((3i128*2).pow(4))); assert_eq!(w3,I256F256::from((3i128*2).pow(4)));
} }
#[test] #[test]
fn test_bint(){ fn test_bint(){
let a=F64_32::ONE; let a=I32F32::ONE;
assert_eq!(a*2,F64_32::from(2)); assert_eq!(a*2,I32F32::from(2));
} }
#[test] #[test]
fn test_wrap(){ fn test_wrap(){
assert_eq!(F64_32::ONE,F512_256::ONE.wrap_64()); assert_eq!(I32F32::ONE,I256F256::ONE.wrap_1());
assert_eq!(F64_32::NEG_ONE,F512_256::NEG_ONE.wrap_64()); assert_eq!(I32F32::NEG_ONE,I256F256::NEG_ONE.wrap_1());
} }
#[test] #[test]
fn test_narrow(){ fn test_narrow(){
assert_eq!(Ok(F64_32::ONE),F512_256::ONE.narrow_64()); assert_eq!(Ok(I32F32::ONE),I256F256::ONE.narrow_1());
assert_eq!(Ok(F64_32::NEG_ONE),F512_256::NEG_ONE.narrow_64()); assert_eq!(Ok(I32F32::NEG_ONE),I256F256::NEG_ONE.narrow_1());
} }
#[test] #[test]
fn test_widen(){ fn test_widen(){
assert_eq!(F64_32::ONE.widen_512(),F512_256::ONE); assert_eq!(I32F32::ONE.widen_8(),I256F256::ONE);
assert_eq!(F64_32::NEG_ONE.widen_512(),F512_256::NEG_ONE); assert_eq!(I32F32::NEG_ONE.widen_8(),I256F256::NEG_ONE);
} }
#[test] #[test]
fn test_clamp(){ fn test_clamp(){
assert_eq!(F64_32::ONE,F512_256::ONE.clamp_64()); assert_eq!(I32F32::ONE,I256F256::ONE.clamp_1());
assert_eq!(F64_32::NEG_ONE,F512_256::NEG_ONE.clamp_64()); assert_eq!(I32F32::NEG_ONE,I256F256::NEG_ONE.clamp_1());
} }
#[test] #[test]
fn test_sqrt(){ fn test_sqrt(){
let a=F64_32::ONE*4; let a=I32F32::ONE*4;
assert_eq!(a.sqrt(),F64_32::from(2)); assert_eq!(a.sqrt(),I32F32::from(2));
} }
#[test] #[test]
fn test_sqrt_zero(){ fn test_sqrt_zero(){
let a=F64_32::ZERO; let a=I32F32::ZERO;
assert_eq!(a.sqrt(),F64_32::ZERO); assert_eq!(a.sqrt(),I32F32::ZERO);
} }
#[test] #[test]
fn test_sqrt_low(){ fn test_sqrt_low(){
let a=F64_32::HALF; let a=I32F32::HALF;
let b=a.fixed_mul(a); let b=a.fixed_mul(a);
assert_eq!(b.sqrt(),a); assert_eq!(b.sqrt(),a);
} }
fn find_equiv_sqrt_via_f64(n:F64_32)->F64_32{ fn find_equiv_sqrt_via_f64(n:I32F32)->I32F32{
//GIMME THEM BITS BOY //GIMME THEM BITS BOY
let ibits=i64::from_le_bytes(n.to_bits().to_bytes()); let &[bits]=n.to_bits().to_bits().digits();
let ibits=bits as i64;
let f=(ibits as f64)/((1u64<<32) as f64); let f=(ibits as f64)/((1u64<<32) as f64);
let f_ans=f.sqrt(); let f_ans=f.sqrt();
let i=(f_ans*((1u64<<32) as f64)) as u64; let i=(f_ans*((1u64<<32) as f64)) as i64;
let r=F64_32::from_u64(i); let r=I32F32::from_bits(bnum::BInt::<1>::from(i));
//mimic the behaviour of the algorithm, //mimic the behaviour of the algorithm,
//return the result if it truncates to the exact answer //return the result if it truncates to the exact answer
if (r+F64_32::EPSILON).wide_mul_64_64(r+F64_32::EPSILON)==n.wide_mul_64_64(F64_32::ONE){ if (r+I32F32::EPSILON).wide_mul_1_1(r+I32F32::EPSILON)==n.wide_mul_1_1(I32F32::ONE){
return r+F64_32::EPSILON; return r+I32F32::EPSILON;
} }
if (r-F64_32::EPSILON).wide_mul_64_64(r-F64_32::EPSILON)==n.wide_mul_64_64(F64_32::ONE){ if (r-I32F32::EPSILON).wide_mul_1_1(r-I32F32::EPSILON)==n.wide_mul_1_1(I32F32::ONE){
return r-F64_32::EPSILON; return r-I32F32::EPSILON;
} }
return r; return r;
} }
fn test_exact(n:F64_32){ fn test_exact(n:I32F32){
assert_eq!(n.sqrt(),find_equiv_sqrt_via_f64(n)); assert_eq!(n.sqrt(),find_equiv_sqrt_via_f64(n));
} }
#[test] #[test]
fn test_sqrt_exact(){ fn test_sqrt_exact(){
//43 //43
for i in 0..((i64::MAX as f32).ln() as u32){ for i in 0..((i64::MAX as f32).ln() as u32){
let n=F64_32::from_u64((i as f32).exp() as u64); let n=I32F32::from_bits(bnum::BInt::<1>::from((i as f32).exp() as i64));
test_exact(n); test_exact(n);
} }
} }
#[test] #[test]
fn test_sqrt_max(){ fn test_sqrt_max(){
let a=F64_32::MAX; let a=I32F32::MAX;
test_exact(a); test_exact(a);
} }
#[test] #[test]
@@ -275,9 +210,9 @@ fn test_sqrt_max(){
fn test_zeroes_normal(){ fn test_zeroes_normal(){
// (x-1)*(x+1) // (x-1)*(x+1)
// x^2-1 // x^2-1
let zeroes=F64_32::zeroes2(F64_32::NEG_ONE,F64_32::ZERO,F64_32::ONE); 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=F64_32::zeroes2(F64_32::NEG_ONE*3,F64_32::ONE*2,F64_32::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] #[test]
@@ -285,25 +220,25 @@ fn test_zeroes_normal(){
fn test_zeroes_deferred_division(){ fn test_zeroes_deferred_division(){
// (x-1)*(x+1) // (x-1)*(x+1)
// x^2-1 // x^2-1
let zeroes=F64_32::zeroes2(F64_32::NEG_ONE,F64_32::ZERO,F64_32::ONE); let zeroes=I32F32::zeroes2(I32F32::NEG_ONE,I32F32::ZERO,I32F32::ONE);
assert_eq!( assert_eq!(
zeroes, zeroes,
arrayvec::ArrayVec::from_iter([ arrayvec::ArrayVec::from_iter([
ratio_ops::ratio::Ratio::new(F64_32::ONE*2,F64_32::NEG_ONE*2), ratio_ops::ratio::Ratio::new(I32F32::ONE*2,I32F32::NEG_ONE*2),
ratio_ops::ratio::Ratio::new(F64_32::ONE*2,F64_32::ONE*2), ratio_ops::ratio::Ratio::new(I32F32::ONE*2,I32F32::ONE*2),
]) ])
); );
} }
#[test] #[test]
fn test_debug(){ fn test_debug(){
assert_eq!(format!("{:?}",F64_32::EPSILON),"0.00000001"); assert_eq!(format!("{:?}",I32F32::EPSILON),"0.00000001");
assert_eq!(format!("{:?}",F64_32::ONE),"1.00000000"); assert_eq!(format!("{:?}",I32F32::ONE),"1.00000000");
assert_eq!(format!("{:?}",F64_32::TWO),"2.00000000"); assert_eq!(format!("{:?}",I32F32::TWO),"2.00000000");
assert_eq!(format!("{:?}",F64_32::MAX),"7fffffff.ffffffff"); assert_eq!(format!("{:?}",I32F32::MAX),"7fffffff.ffffffff");
assert_eq!(format!("{:?}",F64_32::try_from(core::f64::consts::PI).unwrap()),"3.243f6a88"); assert_eq!(format!("{:?}",I32F32::try_from(core::f64::consts::PI).unwrap()),"3.243f6a88");
assert_eq!(format!("{:?}",F64_32::NEG_EPSILON),"-0.00000001"); assert_eq!(format!("{:?}",I32F32::NEG_EPSILON),"-0.00000001");
assert_eq!(format!("{:?}",F64_32::NEG_ONE),"-1.00000000"); assert_eq!(format!("{:?}",I32F32::NEG_ONE),"-1.00000000");
assert_eq!(format!("{:?}",F64_32::NEG_TWO),"-2.00000000"); assert_eq!(format!("{:?}",I32F32::NEG_TWO),"-2.00000000");
assert_eq!(format!("{:?}",F64_32::MIN),"-80000000.00000000"); assert_eq!(format!("{:?}",I32F32::MIN),"-80000000.00000000");
} }

View File

@@ -1,7 +1,4 @@
use crate::fixed::BNUM_DIGIT_WIDTH; pub type I32F32=crate::fixed::Fixed<1,32>;
pub type F64_32=crate::fixed::Fixed<{64/BNUM_DIGIT_WIDTH},32>; pub type I64F64=crate::fixed::Fixed<2,64>;
pub type F128_64=crate::fixed::Fixed<{128/BNUM_DIGIT_WIDTH},64>; pub type I128F128=crate::fixed::Fixed<4,128>;
pub type F192_96=crate::fixed::Fixed<{192/BNUM_DIGIT_WIDTH},96>; pub type I256F256=crate::fixed::Fixed<8,256>;
pub type F256_128=crate::fixed::Fixed<{256/BNUM_DIGIT_WIDTH},128>;
pub type F320_160=crate::fixed::Fixed<{320/BNUM_DIGIT_WIDTH},160>;
pub type F512_256=crate::fixed::Fixed<{512/BNUM_DIGIT_WIDTH},256>;

View File

@@ -1,19 +1,18 @@
use crate::fixed::Fixed; use crate::fixed::Fixed;
use crate::fixed::BNUM_DIGIT_WIDTH;
use arrayvec::ArrayVec; use arrayvec::ArrayVec;
use std::cmp::Ordering; use std::cmp::Ordering;
macro_rules! impl_zeroes{ macro_rules! impl_zeroes{
($n:expr)=>{ ($n:expr)=>{
impl Fixed<{$n/BNUM_DIGIT_WIDTH},{$n>>1}>{ impl Fixed<$n,{$n*32}>{
#[inline] #[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){ let a2pos=match a2.cmp(&Self::ZERO){
Ordering::Greater=>true, Ordering::Greater=>true,
Ordering::Equal=>return ArrayVec::from_iter(Self::zeroes1(a0,a1)), Ordering::Equal=>return ArrayVec::from_iter(Self::zeroes1(a0,a1).into_iter()),
Ordering::Less=>false, Ordering::Less=>false,
}; };
let radicand=a1*a1-((a2*a0)<<2); let radicand=a1*a1-a2*a0*4;
match radicand.cmp(&<Self as core::ops::Mul>::Output::ZERO){ match radicand.cmp(&<Self as core::ops::Mul>::Output::ZERO){
Ordering::Greater=>{ Ordering::Greater=>{
// using wrap because sqrt always halves the number of leading digits. // using wrap because sqrt always halves the number of leading digits.
@@ -22,21 +21,21 @@ macro_rules! impl_zeroes{
let planar_radicand=radicand.sqrt().[<wrap_ $n>](); let planar_radicand=radicand.sqrt().[<wrap_ $n>]();
} }
//sort roots ascending and avoid taking the difference of large numbers //sort roots ascending and avoid taking the difference of large numbers
let zeroes=match (a2pos,a1.is_positive()){ let zeroes=match (a2pos,Self::ZERO<a1){
(true, true )=>[(-a1-planar_radicand)/(a2<<1),(a0<<1)/(-a1-planar_radicand)], (true, true )=>[(-a1-planar_radicand)/(a2*2),(a0*2)/(-a1-planar_radicand)],
(true, false)=>[(a0<<1)/(-a1+planar_radicand),(-a1+planar_radicand)/(a2<<1)], (true, false)=>[(a0*2)/(-a1+planar_radicand),(-a1+planar_radicand)/(a2*2)],
(false,true )=>[(a0<<1)/(-a1-planar_radicand),(-a1-planar_radicand)/(a2<<1)], (false,true )=>[(a0*2)/(-a1-planar_radicand),(-a1-planar_radicand)/(a2*2)],
(false,false)=>[(-a1+planar_radicand)/(a2<<1),(a0<<1)/(-a1+planar_radicand)], (false,false)=>[(-a1+planar_radicand)/(a2*2),(a0*2)/(-a1+planar_radicand)],
}; };
ArrayVec::from_iter(zeroes) ArrayVec::from_iter(zeroes)
}, },
Ordering::Equal=>ArrayVec::from_iter([(a1)/(-a2<<1)]), Ordering::Equal=>ArrayVec::from_iter([(a1)/(a2*-2)]),
Ordering::Less=>ArrayVec::new_const(), Ordering::Less=>ArrayVec::new_const(),
} }
} }
#[inline] #[inline]
pub fn zeroes1(a0:Self,a1:Self)->ArrayVec<<Self as core::ops::Div>::Output,1>{ pub fn zeroes1(a0:Self,a1:Self)->ArrayVec<<Self as core::ops::Div>::Output,1>{
if a1.is_zero(){ if a1==Self::ZERO{
ArrayVec::new_const() ArrayVec::new_const()
}else{ }else{
ArrayVec::from_iter([(-a0)/(a1)]) ArrayVec::from_iter([(-a0)/(a1)])
@@ -45,10 +44,10 @@ macro_rules! impl_zeroes{
} }
}; };
} }
impl_zeroes!(64); impl_zeroes!(1);
impl_zeroes!(128); impl_zeroes!(2);
impl_zeroes!(192); impl_zeroes!(3);
impl_zeroes!(256); impl_zeroes!(4);
//sqrt doubles twice! //sqrt doubles twice!
//impl_zeroes!(5); //impl_zeroes!(5);
//impl_zeroes!(6); //impl_zeroes!(6);

View File

@@ -5,17 +5,17 @@ macro_rules! impl_fixed_wide_vector_not_const_generic {
(), (),
$n:expr $n:expr
) => { ) => {
impl<const N:usize> Vector<N,fixed_wide::fixed::Fixed<{$n>>3},{$n>>1}>>{ impl<const N:usize> Vector<N,fixed_wide::fixed::Fixed<$n,{$n*32}>>{
#[inline] #[inline]
pub fn length(self)-><fixed_wide::fixed::Fixed::<{$n>>3},{$n>>1}> 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() self.length_squared().sqrt_unchecked()
} }
#[inline] #[inline]
pub fn with_length<U,V>(self,length:U)-><Vector<N,V> as core::ops::Div<<fixed_wide::fixed::Fixed::<{$n>>3},{$n>>1}> 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 where
fixed_wide::fixed::Fixed<{$n>>3},{$n>>1}>:core::ops::Mul<U,Output=V>, fixed_wide::fixed::Fixed<$n,{$n*32}>:core::ops::Mul<U,Output=V>,
U:Copy, U:Copy,
V:core::ops::Div<<fixed_wide::fixed::Fixed::<{$n>>3},{$n>>1}> 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() self*length/self.length()
} }
@@ -27,7 +27,7 @@ macro_rules! impl_fixed_wide_vector_not_const_generic {
#[macro_export(local_inner_macros)] #[macro_export(local_inner_macros)]
macro_rules! macro_4 { macro_rules! macro_4 {
( $macro: ident, $any:tt ) => { ( $macro: ident, $any:tt ) => {
$crate::macro_repeated!($macro,$any,64,128,192,256); $crate::macro_repeated!($macro,$any,1,2,3,4);
} }
} }
@@ -39,40 +39,40 @@ macro_rules! impl_fixed_wide_vector {
// I LOVE NOT BEING ABLE TO USE CONST GENERICS // I LOVE NOT BEING ABLE TO USE CONST GENERICS
$crate::macro_repeated!( $crate::macro_repeated!(
impl_narrow_not_const_generic,(), impl_narrow_not_const_generic,(),
(128,64),(192,64),(256,64),(320,64),(384,64),(448,64),(512,64),(576,64),(640,64),(704,64),(768,64),(832,64),(896,64),(960,64),(1024,64),(1088,64), (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),
(192,128),(256,128),(320,128),(384,128),(448,128),(512,128),(576,128),(640,128),(704,128),(768,128),(832,128),(896,128),(960,128),(1024,128), (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),
(256,192),(320,192),(384,192),(448,192),(512,192),(576,192),(640,192),(704,192),(768,192),(832,192),(896,192),(960,192),(1024,192), (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),
(320,256),(384,256),(448,256),(512,256),(576,256),(640,256),(704,256),(768,256),(832,256),(896,256),(960,256),(1024,256), (5,4),(6,4),(7,4),(8,4),(9,4),(10,4),(11,4),(12,4),(13,4),(14,4),(15,4),(16,4),
(384,320),(448,320),(512,320),(576,320),(640,320),(704,320),(768,320),(832,320),(896,320),(960,320),(1024,320), (6,5),(7,5),(8,5),(9,5),(10,5),(11,5),(12,5),(13,5),(14,5),(15,5),(16,5),
(448,384),(512,384),(576,384),(640,384),(704,384),(768,384),(832,384),(896,384),(960,384),(1024,384), (7,6),(8,6),(9,6),(10,6),(11,6),(12,6),(13,6),(14,6),(15,6),(16,6),
(512,448),(576,448),(640,448),(704,448),(768,448),(832,448),(896,448),(960,448),(1024,448), (8,7),(9,7),(10,7),(11,7),(12,7),(13,7),(14,7),(15,7),(16,7),
(576,512),(640,512),(704,512),(768,512),(832,512),(896,512),(960,512),(1024,512), (9,8),(10,8),(11,8),(12,8),(13,8),(14,8),(15,8),(16,8),
(640,576),(704,576),(768,576),(832,576),(896,576),(960,576),(1024,576), (10,9),(11,9),(12,9),(13,9),(14,9),(15,9),(16,9),
(704,640),(768,640),(832,640),(896,640),(960,640),(1024,640), (11,10),(12,10),(13,10),(14,10),(15,10),(16,10),
(768,704),(832,704),(896,704),(960,704),(1024,704), (12,11),(13,11),(14,11),(15,11),(16,11),
(832,768),(896,768),(960,768),(1024,768), (13,12),(14,12),(15,12),(16,12),
(896,832),(960,832),(1024,832), (14,13),(15,13),(16,13),
(960,896),(1024,896), (15,14),(16,14),
(1024,960) (16,15)
); );
$crate::macro_repeated!( $crate::macro_repeated!(
impl_widen_not_const_generic,(), impl_widen_not_const_generic,(),
(64,128), (1,2),
(64,192),(128,192), (1,3),(2,3),
(64,256),(128,256),(192,256), (1,4),(2,4),(3,4),
(64,320),(128,320),(192,320),(256,320), (1,5),(2,5),(3,5),(4,5),
(64,384),(128,384),(192,384),(256,384),(320,384), (1,6),(2,6),(3,6),(4,6),(5,6),
(64,448),(128,448),(192,448),(256,448),(320,448),(384,448), (1,7),(2,7),(3,7),(4,7),(5,7),(6,7),
(64,512),(128,512),(192,512),(256,512),(320,512),(384,512),(448,512), (1,8),(2,8),(3,8),(4,8),(5,8),(6,8),(7,8),
(64,576),(128,576),(192,576),(256,576),(320,576),(384,576),(448,576),(512,576), (1,9),(2,9),(3,9),(4,9),(5,9),(6,9),(7,9),(8,9),
(64,640),(128,640),(192,640),(256,640),(320,640),(384,640),(448,640),(512,640),(576,640), (1,10),(2,10),(3,10),(4,10),(5,10),(6,10),(7,10),(8,10),(9,10),
(64,704),(128,704),(192,704),(256,704),(320,704),(384,704),(448,704),(512,704),(576,704),(640,704), (1,11),(2,11),(3,11),(4,11),(5,11),(6,11),(7,11),(8,11),(9,11),(10,11),
(64,768),(128,768),(192,768),(256,768),(320,768),(384,768),(448,768),(512,768),(576,768),(640,768),(704,768), (1,12),(2,12),(3,12),(4,12),(5,12),(6,12),(7,12),(8,12),(9,12),(10,12),(11,12),
(64,832),(128,832),(192,832),(256,832),(320,832),(384,832),(448,832),(512,832),(576,832),(640,832),(704,832),(768,832), (1,13),(2,13),(3,13),(4,13),(5,13),(6,13),(7,13),(8,13),(9,13),(10,13),(11,13),(12,13),
(64,896),(128,896),(192,896),(256,896),(320,896),(384,896),(448,896),(512,896),(576,896),(640,896),(704,896),(768,896),(832,896), (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),
(64,960),(128,960),(192,960),(256,960),(320,960),(384,960),(448,960),(512,960),(576,960),(640,960),(704,960),(768,960),(832,960),(896,960), (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),
(64,1024),(128,1024),(192,1024),(256,1024),(320,1024),(384,1024),(448,1024),(512,1024),(576,1024),(640,1024),(704,1024),(768,1024),(832,1024),(896,1024),(960,1024), (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),
(64,1088) (1,17)
); );
impl<const N:usize,T:fixed_wide::fixed::Wrap<U>,U> fixed_wide::fixed::Wrap<Vector<N,U>> for Vector<N,T> impl<const N:usize,T:fixed_wide::fixed::Wrap<U>,U> fixed_wide::fixed::Wrap<Vector<N,U>> for Vector<N,T>
{ {
@@ -98,17 +98,17 @@ macro_rules! impl_narrow_not_const_generic{
($lhs:expr,$rhs:expr) ($lhs:expr,$rhs:expr)
)=>{ )=>{
paste::item!{ paste::item!{
impl<const N:usize> Vector<N,fixed_wide::fixed::Fixed<{$lhs>>3},{$lhs>>1}>>{ impl<const N:usize> Vector<N,fixed_wide::fixed::Fixed<$lhs,{$lhs*32}>>{
#[inline] #[inline]
pub fn [<wrap_ $rhs>](self)->Vector<N,fixed_wide::fixed::Fixed<{$rhs>>3},{$rhs>>1}>>{ pub fn [<wrap_ $rhs>](self)->Vector<N,fixed_wide::fixed::Fixed<$rhs,{$rhs*32}>>{
self.map(|t|t.[<wrap_ $rhs>]()) self.map(|t|t.[<wrap_ $rhs>]())
} }
#[inline] #[inline]
pub fn [<narrow_ $rhs>](self)->Vector<N,Result<fixed_wide::fixed::Fixed<{$rhs>>3},{$rhs>>1}>,fixed_wide::fixed::NarrowError>>{ pub fn [<narrow_ $rhs>](self)->Vector<N,Result<fixed_wide::fixed::Fixed<$rhs,{$rhs*32}>,fixed_wide::fixed::NarrowError>>{
self.map(|t|t.[<narrow_ $rhs>]()) self.map(|t|t.[<narrow_ $rhs>]())
} }
#[inline] #[inline]
pub fn [<clamp_ $rhs>](self)->Vector<N,fixed_wide::fixed::Fixed<{$rhs>>3},{$rhs>>1}>>{ pub fn [<clamp_ $rhs>](self)->Vector<N,fixed_wide::fixed::Fixed<$rhs,{$rhs*32}>>{
self.map(|t|t.[<clamp_ $rhs>]()) self.map(|t|t.[<clamp_ $rhs>]())
} }
} }
@@ -123,9 +123,9 @@ macro_rules! impl_widen_not_const_generic{
($lhs:expr,$rhs:expr) ($lhs:expr,$rhs:expr)
)=>{ )=>{
paste::item!{ paste::item!{
impl<const N:usize> Vector<N,fixed_wide::fixed::Fixed<{$lhs>>3},{$lhs>>1}>>{ impl<const N:usize> Vector<N,fixed_wide::fixed::Fixed<$lhs,{$lhs*32}>>{
#[inline] #[inline]
pub fn [<widen_ $rhs>](self)->Vector<N,fixed_wide::fixed::Fixed<{$rhs>>3},{$rhs>>1}>>{ pub fn [<widen_ $rhs>](self)->Vector<N,fixed_wide::fixed::Fixed<$rhs,{$rhs*32}>>{
self.map(|t|t.[<widen_ $rhs>]()) self.map(|t|t.[<widen_ $rhs>]())
} }
} }

View File

@@ -1,9 +1,9 @@
use crate::types::{Matrix3,Matrix3x2,Matrix3x4,Matrix4x2,Vector3}; use crate::types::{Matrix3,Matrix3x2,Matrix3x4,Matrix4x2,Vector3};
type Planar64=fixed_wide::types::F64_32; type Planar64=fixed_wide::types::I32F32;
type Planar64Wide1=fixed_wide::types::F128_64; type Planar64Wide1=fixed_wide::types::I64F64;
//type Planar64Wide2=fixed_wide::types::I128F128; //type Planar64Wide2=fixed_wide::types::I128F128;
type Planar64Wide3=fixed_wide::types::F512_256; type Planar64Wide3=fixed_wide::types::I256F256;
#[test] #[test]
fn wide_vec3(){ fn wide_vec3(){
@@ -72,7 +72,7 @@ fn wide_matrix_det(){
]); ]);
// In[2]:= Det[{{1, 2, 3}, {4, 5, 7}, {6, 8, 9}}] // In[2]:= Det[{{1, 2, 3}, {4, 5, 7}, {6, 8, 9}}]
// Out[2]= 7 // Out[2]= 7
assert_eq!(m.det(),fixed_wide::types::F192_96::from(7)); assert_eq!(m.det(),fixed_wide::fixed::Fixed::<3,96>::from(7));
} }
#[test] #[test]

View File

@@ -1,6 +1,6 @@
[package] [package]
name = "strafesnet_rbx_loader" name = "strafesnet_rbx_loader"
version = "0.10.2" version = "0.8.0"
edition = "2024" edition = "2024"
repository = "https://git.itzana.me/StrafesNET/strafe-project" repository = "https://git.itzana.me/StrafesNET/strafe-project"
license = "MIT OR Apache-2.0" license = "MIT OR Apache-2.0"
@@ -13,7 +13,7 @@ authors = ["Rhys Lloyd <krakow20@gmail.com>"]
bytemuck = "1.14.3" bytemuck = "1.14.3"
glam.workspace = true glam.workspace = true
regex = { version = "1.11.3", default-features = false, features = ["unicode-perl"] } regex = { version = "1.11.3", default-features = false, features = ["unicode-perl"] }
rbx_mesh = "0.6.0" rbx_mesh = "0.5.0"
rbxassetid = { version = "0.1.0", path = "../rbxassetid", registry = "strafesnet" } rbxassetid = { version = "0.1.0", path = "../rbxassetid", registry = "strafesnet" }
roblox_emulator = { version = "0.5.1", path = "../roblox_emulator", default-features = false, registry = "strafesnet" } roblox_emulator = { version = "0.5.1", path = "../roblox_emulator", default-features = false, registry = "strafesnet" }
strafesnet_common.workspace = true strafesnet_common.workspace = true

View File

@@ -25,8 +25,6 @@ pub struct RecoverableErrors{
pub unsupported_class:HashSet<String>, pub unsupported_class:HashSet<String>,
/// A decal has an invalid / missing property. /// A decal has an invalid / missing property.
pub decal_property:Vec<InstancePath>, pub decal_property:Vec<InstancePath>,
/// A SurfaceAppearance has an invalid / missing property.
pub surface_appearance_property:Vec<InstancePath>,
/// A decal has an invalid normal_id. /// A decal has an invalid normal_id.
pub normal_id:Vec<NormalIdError>, pub normal_id:Vec<NormalIdError>,
/// A texture has an invalid / missing property. /// A texture has an invalid / missing property.
@@ -60,7 +58,6 @@ impl RecoverableErrors{
self.meshpart_content.len()+ self.meshpart_content.len()+
self.unsupported_class.len()+ self.unsupported_class.len()+
self.decal_property.len()+ self.decal_property.len()+
self.surface_appearance_property.len()+
self.normal_id.len()+ self.normal_id.len()+
self.texture_property.len()+ self.texture_property.len()+
self.mode_id_parse_int.len()+ self.mode_id_parse_int.len()+
@@ -148,7 +145,6 @@ impl core::fmt::Display for RecoverableErrors{
} }
} }
write_instance_path_error!(f,self,decal_property,"Decal is","Decals are","missing a property"); write_instance_path_error!(f,self,decal_property,"Decal is","Decals are","missing a property");
write_instance_path_error!(f,self,surface_appearance_property,"SurfaceAppearance is","SurfaceAppearances are","missing a property");
write_bespoke_error!(f,self,normal_id,"Decal","Decals","NormalId is invalid",path,normal_id); write_bespoke_error!(f,self,normal_id,"Decal","Decals","NormalId is invalid",path,normal_id);
write_instance_path_error!(f,self,texture_property,"Texture is","Textures are","missing a property"); write_instance_path_error!(f,self,texture_property,"Texture is","Textures are","missing a property");
write_bespoke_error!(f,self,mode_id_parse_int,"ModeId","ModeIds","failed to parse",context,error); write_bespoke_error!(f,self,mode_id_parse_int,"ModeId","ModeIds","failed to parse",context,error);

View File

@@ -1,15 +1,33 @@
use rbx_dom_weak::WeakDom; use rbx_dom_weak::WeakDom;
use roblox_emulator::context::Context; use roblox_emulator::context::Context;
use strafesnet_common::map::CompleteMap;
use strafesnet_deferred_loader::deferred_loader::{LoadFailureMode,MeshDeferredLoader,RenderConfigDeferredLoader};
use strafesnet_deferred_loader::loader::Loader;
use strafesnet_deferred_loader::texture::Texture;
pub use error::RecoverableErrors; pub use error::RecoverableErrors;
pub use roblox_emulator::runner::Error as RunnerError; pub use roblox_emulator::runner::Error as RunnerError;
pub mod rbx; use crate::loader::{MeshIndex,MeshWithSize};
pub mod mesh;
mod rbx;
mod mesh;
mod error; mod error;
pub mod union; mod union;
pub mod primitives; pub mod primitives;
pub mod data{
pub struct RobloxMeshBytes(Vec<u8>);
impl RobloxMeshBytes{
pub fn new(bytes:Vec<u8>)->Self{
Self(bytes)
}
pub(crate) fn cursor(self)->std::io::Cursor<Vec<u8>>{
std::io::Cursor::new(self.0)
}
}
}
pub struct Model{ pub struct Model{
dom:WeakDom, dom:WeakDom,
} }
@@ -17,6 +35,19 @@ impl Model{
pub fn new(dom:WeakDom)->Self{ pub fn new(dom:WeakDom)->Self{
Self{dom} Self{dom}
} }
pub fn to_snf<'dom,'mesh,'texture,M,T>(
&'dom self,
failure_mode:LoadFailureMode,
mesh_loader:M,
texture_loader:T,
)->Result<(CompleteMap,RecoverableErrors),LoadError<M::Error,T::Error>>
where
'dom:'mesh+'texture,
M:Loader<Resource=MeshWithSize,Index<'mesh>=MeshIndex<'mesh>>+'mesh,
T:Loader<Resource=Texture,Index<'texture>=&'texture str>+'texture,
{
to_snf(self.as_ref(),failure_mode,mesh_loader,texture_loader)
}
} }
impl AsRef<WeakDom> for Model{ impl AsRef<WeakDom> for Model{
fn as_ref(&self)->&WeakDom{ fn as_ref(&self)->&WeakDom{
@@ -47,6 +78,19 @@ impl Place{
} }
Ok(errors) Ok(errors)
} }
pub fn to_snf<'dom,'mesh,'texture,M,T>(
&'dom self,
failure_mode:LoadFailureMode,
mesh_loader:M,
texture_loader:T,
)->Result<(CompleteMap,RecoverableErrors),LoadError<M::Error,T::Error>>
where
'dom:'mesh+'texture,
M:Loader<Resource=MeshWithSize,Index<'mesh>=MeshIndex<'mesh>>+'mesh,
T:Loader<Resource=Texture,Index<'texture>=&'texture str>+'texture,
{
to_snf(self.as_ref(),failure_mode,mesh_loader,texture_loader)
}
} }
impl AsRef<WeakDom> for Place{ impl AsRef<WeakDom> for Place{
fn as_ref(&self)->&WeakDom{ fn as_ref(&self)->&WeakDom{
@@ -61,3 +105,52 @@ impl From<Model> for Place{
} }
} }
} }
#[derive(Debug)]
pub enum LoadError<M,T>{
Mesh(M),
Texture(T),
}
impl<M,T> std::fmt::Display for LoadError<M,T>
where
M:std::fmt::Debug,
T:std::fmt::Debug,
{
fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{
write!(f,"{self:?}")
}
}
impl<M,T> std::error::Error for LoadError<M,T>
where
M:std::fmt::Debug,
T:std::fmt::Debug,
{}
fn to_snf<'dom,'mesh,'texture,M,T>(
dom:&'dom WeakDom,
failure_mode:LoadFailureMode,
mut mesh_loader:M,
mut texture_loader:T,
)->Result<(CompleteMap,RecoverableErrors),LoadError<M::Error,T::Error>>
where
'dom:'mesh+'texture,
M:Loader<Resource=MeshWithSize,Index<'mesh>=MeshIndex<'mesh>>+'mesh,
T:Loader<Resource=Texture,Index<'texture>=&'texture str>+'texture,
{
let mut texture_deferred_loader=RenderConfigDeferredLoader::new();
let mut mesh_deferred_loader=MeshDeferredLoader::new();
let map_step1=rbx::convert(
dom,
&mut texture_deferred_loader,
&mut mesh_deferred_loader,
);
let meshpart_meshes=mesh_deferred_loader.into_meshes(&mut mesh_loader,failure_mode).map_err(LoadError::Mesh)?;
let map_step2=map_step1.add_meshpart_meshes_and_calculate_attributes(meshpart_meshes);
let render_configs=texture_deferred_loader.into_render_configs(&mut texture_loader,failure_mode).map_err(LoadError::Texture)?;
Ok(map_step2.add_render_configs_and_textures(render_configs))
}

View File

@@ -5,73 +5,11 @@ use strafesnet_common::aabb::Aabb;
use strafesnet_common::integer::vec3; use strafesnet_common::integer::vec3;
use strafesnet_common::model::{self,ColorId,IndexedVertex,PolygonGroup,PolygonList,RenderConfigId,VertexId}; use strafesnet_common::model::{self,ColorId,IndexedVertex,PolygonGroup,PolygonList,RenderConfigId,VertexId};
use crate::rbx::RobloxPartDescription; use crate::loader::MeshWithSize;
#[derive(Hash,Eq,PartialEq)]
pub enum MeshType<'a>{
FileMesh,
Union{
mesh_data:&'a [u8],
physics_data:&'a [u8],
size_float_bits:[u32;3],
part_texture_description:RobloxPartDescription,
},
}
#[derive(Hash,Eq,PartialEq)]
pub struct MeshIndex<'a>{
pub mesh_type:MeshType<'a>,
pub content:&'a str,
}
impl MeshIndex<'_>{
pub fn file_mesh(content:&str)->MeshIndex<'_>{
MeshIndex{
mesh_type:MeshType::FileMesh,
content,
}
}
pub fn union<'a>(
content:&'a str,
mesh_data:&'a [u8],
physics_data:&'a [u8],
size:&rbx_dom_weak::types::Vector3,
part_texture_description:RobloxPartDescription,
)->MeshIndex<'a>{
MeshIndex{
mesh_type:MeshType::Union{
mesh_data,
physics_data,
size_float_bits:[size.x.to_bits(),size.y.to_bits(),size.z.to_bits()],
part_texture_description,
},
content,
}
}
}
#[derive(Clone)]
pub struct MeshWithSize{
mesh:model::Mesh,
size:strafesnet_common::integer::Planar64Vec3,
}
impl MeshWithSize{
pub(crate) const fn new(
mesh:model::Mesh,
size:strafesnet_common::integer::Planar64Vec3,
)->Self{
Self{mesh,size}
}
pub const fn mesh(&self)->&model::Mesh{
&self.mesh
}
pub const fn size(&self)->strafesnet_common::integer::Planar64Vec3{
self.size
}
}
#[derive(Debug)] #[derive(Debug)]
pub enum Error{ pub enum Error{
NoPolygons, RbxMesh(rbx_mesh::mesh::Error)
RbxMesh(rbx_mesh::mesh::Error),
} }
impl std::fmt::Display for Error{ impl std::fmt::Display for Error{
fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{ fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{
@@ -122,10 +60,6 @@ fn ingest_vertices_truncated2(
))).collect() ))).collect()
} }
fn new_polygon_list_checked(list:Vec<model::IndexedVertexList>)->Option<PolygonList>{
(!list.is_empty()).then_some(PolygonList::new(list))
}
fn ingest_faces2_lods3( fn ingest_faces2_lods3(
polygon_groups:&mut Vec<PolygonGroup>, polygon_groups:&mut Vec<PolygonGroup>,
vertex_id_map:&HashMap<rbx_mesh::mesh::VertexId2,VertexId>, vertex_id_map:&HashMap<rbx_mesh::mesh::VertexId2,VertexId>,
@@ -133,21 +67,21 @@ fn ingest_faces2_lods3(
lods:&[rbx_mesh::mesh::Lod3], lods:&[rbx_mesh::mesh::Lod3],
){ ){
//faces have to be split into polygon groups based on lod //faces have to be split into polygon groups based on lod
polygon_groups.extend(lods.windows(2).filter_map(|lod_pair| polygon_groups.extend(lods.windows(2).map(|lod_pair|
Some(PolygonGroup::PolygonList(new_polygon_list_checked(faces[lod_pair[0].0 as usize..lod_pair[1].0 as usize].iter().filter_map(|rbx_mesh::mesh::Face2(v0,v1,v2)| PolygonGroup::PolygonList(PolygonList::new(faces[lod_pair[0].0 as usize..lod_pair[1].0 as usize].iter().filter_map(|rbx_mesh::mesh::Face2(v0,v1,v2)|
Some(vec![*vertex_id_map.get(&v0)?,*vertex_id_map.get(&v1)?,*vertex_id_map.get(&v2)?]) Some(vec![*vertex_id_map.get(&v0)?,*vertex_id_map.get(&v1)?,*vertex_id_map.get(&v2)?])
).collect())?)) ).collect()))
)) ))
} }
pub fn convert(roblox_mesh_bytes:&[u8])->Result<MeshWithSize,Error>{ pub fn convert(roblox_mesh_bytes:crate::data::RobloxMeshBytes)->Result<MeshWithSize,Error>{
//generate that mesh boi //generate that mesh boi
let mut polygon_groups=Vec::new(); let mut polygon_groups=Vec::new();
let mut mb=model::MeshBuilder::new(); let mut mb=model::MeshBuilder::new();
match rbx_mesh::read_versioned(std::io::Cursor::new(roblox_mesh_bytes)).map_err(Error::RbxMesh)?{ match rbx_mesh::read_versioned(roblox_mesh_bytes.cursor()).map_err(Error::RbxMesh)?{
rbx_mesh::mesh::Mesh::V1(mesh)=>{ rbx_mesh::mesh::Mesh::V1(mesh)=>{
let color_id=mb.acquire_color_id(glam::Vec4::ONE); let color_id=mb.acquire_color_id(glam::Vec4::ONE);
let polygon_list=new_polygon_list_checked(mesh.vertices.chunks_exact(3).filter_map(|trip|{ polygon_groups.push(PolygonGroup::PolygonList(PolygonList::new(mesh.vertices.chunks_exact(3).filter_map(|trip|{
let mut ingest_vertex1=|vertex:&rbx_mesh::mesh::Vertex1|{ let mut ingest_vertex1=|vertex:&rbx_mesh::mesh::Vertex1|{
let vertex=IndexedVertex{ let vertex=IndexedVertex{
pos:mb.acquire_pos_id(vec3::try_from_f32_array(vertex.pos).ok()?), pos:mb.acquire_pos_id(vec3::try_from_f32_array(vertex.pos).ok()?),
@@ -158,10 +92,7 @@ pub fn convert(roblox_mesh_bytes:&[u8])->Result<MeshWithSize,Error>{
Some(mb.acquire_vertex_id(vertex)) Some(mb.acquire_vertex_id(vertex))
}; };
Some(vec![ingest_vertex1(&trip[0])?,ingest_vertex1(&trip[1])?,ingest_vertex1(&trip[2])?]) Some(vec![ingest_vertex1(&trip[0])?,ingest_vertex1(&trip[1])?,ingest_vertex1(&trip[2])?])
}).collect()); }).collect())));
if let Some(polygon_list)=polygon_list{
polygon_groups.push(PolygonGroup::PolygonList(polygon_list));
}
}, },
rbx_mesh::mesh::Mesh::V2(mesh)=>{ rbx_mesh::mesh::Mesh::V2(mesh)=>{
let vertex_id_map=match mesh.header.sizeof_vertex{ let vertex_id_map=match mesh.header.sizeof_vertex{
@@ -173,12 +104,9 @@ pub fn convert(roblox_mesh_bytes:&[u8])->Result<MeshWithSize,Error>{
rbx_mesh::mesh::SizeOfVertex2::Full=>ingest_vertices2(mesh.vertices,&mut mb), rbx_mesh::mesh::SizeOfVertex2::Full=>ingest_vertices2(mesh.vertices,&mut mb),
}; };
//one big happy group for all the faces //one big happy group for all the faces
let polygon_list=new_polygon_list_checked(mesh.faces.into_iter().filter_map(|face| polygon_groups.push(PolygonGroup::PolygonList(PolygonList::new(mesh.faces.into_iter().filter_map(|face|
Some(vec![*vertex_id_map.get(&face.0)?,*vertex_id_map.get(&face.1)?,*vertex_id_map.get(&face.2)?]) Some(vec![*vertex_id_map.get(&face.0)?,*vertex_id_map.get(&face.1)?,*vertex_id_map.get(&face.2)?])
).collect()); ).collect())));
if let Some(polygon_list)=polygon_list{
polygon_groups.push(PolygonGroup::PolygonList(polygon_list));
}
}, },
rbx_mesh::mesh::Mesh::V3(mesh)=>{ rbx_mesh::mesh::Mesh::V3(mesh)=>{
let vertex_id_map=match mesh.header.sizeof_vertex{ let vertex_id_map=match mesh.header.sizeof_vertex{
@@ -199,9 +127,6 @@ pub fn convert(roblox_mesh_bytes:&[u8])->Result<MeshWithSize,Error>{
ingest_faces2_lods3(&mut polygon_groups,&vertex_id_map,&mesh.faces,&mesh.lods); ingest_faces2_lods3(&mut polygon_groups,&vertex_id_map,&mesh.faces,&mesh.lods);
}, },
} }
if polygon_groups.is_empty(){
return Err(Error::NoPolygons);
}
let mesh=mb.build( let mesh=mb.build(
polygon_groups, polygon_groups,
//these should probably be moved to the model... //these should probably be moved to the model...

View File

@@ -519,7 +519,7 @@ pub fn unit_cylinder(face_descriptions:CubeFaceDescription)->Mesh{
(glam::vec2(-x as f32,y as f32).normalize()+1.0)/2.0 (glam::vec2(-x as f32,y as f32).normalize()+1.0)/2.0
) )
); );
let pos=mb.acquire_pos_id($end+vec3::int(0,-x,y).with_length(Planar64::ONE).divide().wrap_64()); let pos=mb.acquire_pos_id($end+vec3::int(0,-x,y).with_length(Planar64::ONE).divide().wrap_1());
mb.acquire_vertex_id(IndexedVertex{pos,tex,normal,color}) mb.acquire_vertex_id(IndexedVertex{pos,tex,normal,color})
}).collect(); }).collect();
@@ -560,9 +560,9 @@ pub fn unit_cylinder(face_descriptions:CubeFaceDescription)->Mesh{
let mut polygon_list=Vec::with_capacity(CubeFaceDescription::FACES); let mut polygon_list=Vec::with_capacity(CubeFaceDescription::FACES);
for $loop in -GON..GON{ for $loop in -GON..GON{
// lo Z // lo Z
let lz_dir=$lo_dir.with_length(Planar64::ONE).divide().wrap_64(); let lz_dir=$lo_dir.with_length(Planar64::ONE).divide().wrap_1();
// hi Z // hi Z
let hz_dir=$hi_dir.with_length(Planar64::ONE).divide().wrap_64(); let hz_dir=$hi_dir.with_length(Planar64::ONE).divide().wrap_1();
// pos // pos
let lx_lz_pos=mb.acquire_pos_id(vec3::NEG_X+lz_dir); let lx_lz_pos=mb.acquire_pos_id(vec3::NEG_X+lz_dir);

View File

@@ -1,6 +1,6 @@
use std::collections::HashMap; use std::collections::HashMap;
use crate::error::{RecoverableErrors,CFrameError,CFrameErrorType,DuplicateStageError,InstancePath,NormalIdError,Planar64ConvertError,ParseIntContext,ShapeError}; use crate::error::{RecoverableErrors,CFrameError,CFrameErrorType,DuplicateStageError,InstancePath,NormalIdError,Planar64ConvertError,ParseIntContext,ShapeError};
use crate::mesh::{MeshWithSize,MeshIndex}; use crate::loader::{MeshWithSize,MeshIndex};
use crate::primitives::{self,CubeFace,CubeFaceDescription,WedgeFaceDescription,CornerWedgeFaceDescription,FaceDescription,Primitives}; use crate::primitives::{self,CubeFace,CubeFaceDescription,WedgeFaceDescription,CornerWedgeFaceDescription,FaceDescription,Primitives};
use strafesnet_common::map; use strafesnet_common::map;
use strafesnet_common::model; use strafesnet_common::model;
@@ -31,11 +31,11 @@ fn planar64_affine3_from_roblox(cf:&rbx_dom_weak::types::CFrame,size:&rbx_dom_we
Ok(Planar64Affine3::new( Ok(Planar64Affine3::new(
Planar64Mat3::from_cols([ Planar64Mat3::from_cols([
(vec3::try_from_f32_array([cf.orientation.x.x,cf.orientation.y.x,cf.orientation.z.x])? (vec3::try_from_f32_array([cf.orientation.x.x,cf.orientation.y.x,cf.orientation.z.x])?
*integer::try_from_f32(size.x/2.0)?).narrow_64().unwrap(),//.map_err(Planar64ConvertError::Narrow)? *integer::try_from_f32(size.x/2.0)?).narrow_1().unwrap(),//.map_err(Planar64ConvertError::Narrow)?
(vec3::try_from_f32_array([cf.orientation.x.y,cf.orientation.y.y,cf.orientation.z.y])? (vec3::try_from_f32_array([cf.orientation.x.y,cf.orientation.y.y,cf.orientation.z.y])?
*integer::try_from_f32(size.y/2.0)?).narrow_64().unwrap(),//.map_err(Planar64ConvertError::Narrow)? *integer::try_from_f32(size.y/2.0)?).narrow_1().unwrap(),//.map_err(Planar64ConvertError::Narrow)?
(vec3::try_from_f32_array([cf.orientation.x.z,cf.orientation.y.z,cf.orientation.z.z])? (vec3::try_from_f32_array([cf.orientation.x.z,cf.orientation.y.z,cf.orientation.z.z])?
*integer::try_from_f32(size.z/2.0)?).narrow_64().unwrap(),//.map_err(Planar64ConvertError::Narrow)? *integer::try_from_f32(size.z/2.0)?).narrow_1().unwrap(),//.map_err(Planar64ConvertError::Narrow)?
]), ]),
vec3::try_from_f32_array([cf.position.x,cf.position.y,cf.position.z])? vec3::try_from_f32_array([cf.position.x,cf.position.y,cf.position.z])?
)) ))
@@ -508,43 +508,6 @@ fn get_texture_description<'a>(
} }
part_texture_description part_texture_description
} }
fn get_surface_appearance<'a>(
recoverable_errors:&mut RecoverableErrors,
db:&rbx_reflection::ReflectionDatabase,
dom:&'a rbx_dom_weak::WeakDom,
object:&rbx_dom_weak::Instance,
)->Option<&'a str>{
//use the biggest one and cut it down later...
let decal=&db.classes["SurfaceAppearance"];
let surface_appearances=object.children().iter().filter_map(|&referent|{
let instance=dom.get_by_ref(referent)?;
db.classes.get(instance.class.as_str()).is_some_and(|class|
db.has_superclass(class,decal)
).then_some(instance)
});
for surface_appearance in surface_appearances{
// SurfaceAppearance should always have these properties,
// but it is not guaranteed by the rbx_dom_weak data structure.
let (
Some(rbx_dom_weak::types::Variant::Content(color_map)),
// Some(rbx_dom_weak::types::Variant::Content(_metalness_map)),
// Some(rbx_dom_weak::types::Variant::Content(_normal_map)),
// Some(rbx_dom_weak::types::Variant::Content(_roughness_map)),
)=(
surface_appearance.properties.get(&static_ustr("ColorMapContent")),
// surface_appearance.properties.get(&static_ustr("MetalnessMapContent")),
// surface_appearance.properties.get(&static_ustr("NormalMapContent")),
// surface_appearance.properties.get(&static_ustr("RoughnessMapContent")),
)else{
recoverable_errors.surface_appearance_property.push(InstancePath::new(dom,surface_appearance));
continue;
};
if let Some(texture_id)=get_content_url(color_map){
return Some(texture_id);
}
}
None
}
enum Shape{ enum Shape{
Primitive(Primitives), Primitive(Primitives),
MeshPart, MeshPart,
@@ -743,8 +706,7 @@ pub fn convert<'a>(
"" ""
} }
}; };
// load SurfaceAppearance and then fall back to mesh texture let texture_asset_id=get_content_url(texture_content);
let texture_asset_id=get_surface_appearance(&mut recoverable_errors,db,dom,object).or_else(||get_content_url(texture_content));
( (
MeshAvailability::DeferredMesh(render_config_deferred_loader.acquire_render_config_id(texture_asset_id)), MeshAvailability::DeferredMesh(render_config_deferred_loader.acquire_render_config_id(texture_asset_id)),
mesh_deferred_loader.acquire_mesh_id(MeshIndex::file_mesh(mesh_asset_id)), mesh_deferred_loader.acquire_mesh_id(MeshIndex::file_mesh(mesh_asset_id)),
@@ -821,11 +783,11 @@ fn acquire_mesh_id_from_render_config_id(
render:RenderConfigId, render:RenderConfigId,
)->Option<MeshIdWithSize>{ )->Option<MeshIdWithSize>{
//ignore meshes that fail to load completely for now //ignore meshes that fail to load completely for now
loaded_meshes.get(&old_mesh_id).map(|mesh_with_size|MeshIdWithSize{ loaded_meshes.get(&old_mesh_id).map(|&MeshWithSize{ref mesh,size}|MeshIdWithSize{
mesh:*mesh_id_from_render_config_id.entry(old_mesh_id).or_insert_with(||HashMap::new()) mesh:*mesh_id_from_render_config_id.entry(old_mesh_id).or_insert_with(||HashMap::new())
.entry(render).or_insert_with(||{ .entry(render).or_insert_with(||{
let mesh_id=model::MeshId::new(primitive_meshes.len() as u32); let mesh_id=model::MeshId::new(primitive_meshes.len() as u32);
let mut mesh_clone=mesh_with_size.mesh().clone(); let mut mesh_clone=mesh.clone();
//set the render group lool //set the render group lool
if let Some(graphics_group)=mesh_clone.graphics_groups.first_mut(){ if let Some(graphics_group)=mesh_clone.graphics_groups.first_mut(){
graphics_group.render=render; graphics_group.render=render;
@@ -833,7 +795,7 @@ fn acquire_mesh_id_from_render_config_id(
primitive_meshes.push(mesh_clone); primitive_meshes.push(mesh_clone);
mesh_id mesh_id
}), }),
size:mesh_with_size.size(), size,
}) })
} }
fn acquire_union_id_from_render_config_id( fn acquire_union_id_from_render_config_id(
@@ -844,11 +806,11 @@ fn acquire_union_id_from_render_config_id(
part_texture_description:RobloxPartDescription, part_texture_description:RobloxPartDescription,
)->Option<MeshIdWithSize>{ )->Option<MeshIdWithSize>{
//ignore uniones that fail to load completely for now //ignore uniones that fail to load completely for now
loaded_meshes.get(&old_union_id).map(|mesh_with_size|MeshIdWithSize{ loaded_meshes.get(&old_union_id).map(|&MeshWithSize{ref mesh,size}|MeshIdWithSize{
mesh:*union_id_from_render_config_id.entry(old_union_id).or_insert_with(||HashMap::new()) mesh:*union_id_from_render_config_id.entry(old_union_id).or_insert_with(||HashMap::new())
.entry(part_texture_description.clone()).or_insert_with(||{ .entry(part_texture_description.clone()).or_insert_with(||{
let union_id=model::MeshId::new(primitive_meshes.len() as u32); let union_id=model::MeshId::new(primitive_meshes.len() as u32);
let mut union_clone=mesh_with_size.mesh().clone(); let mut union_clone=mesh.clone();
//set the render groups //set the render groups
for (graphics_group,maybe_face_texture_description) in union_clone.graphics_groups.iter_mut().zip(part_texture_description.0){ for (graphics_group,maybe_face_texture_description) in union_clone.graphics_groups.iter_mut().zip(part_texture_description.0){
if let Some(face_texture_description)=maybe_face_texture_description{ if let Some(face_texture_description)=maybe_face_texture_description{
@@ -858,7 +820,7 @@ fn acquire_union_id_from_render_config_id(
primitive_meshes.push(union_clone); primitive_meshes.push(union_clone);
union_id union_id
}), }),
size:mesh_with_size.size(), size,
}) })
} }
pub struct PartialMap1<'a>{ pub struct PartialMap1<'a>{
@@ -905,19 +867,24 @@ impl PartialMap1<'_>{
deferred_model_deferred_attributes.model.mesh, deferred_model_deferred_attributes.model.mesh,
deferred_model_deferred_attributes.render deferred_model_deferred_attributes.render
)?; )?;
let mut model=deferred_model_deferred_attributes.model; // If the mesh size is zero we can't auto-scale it, throw it out.
model.mesh=mesh; if mesh_size.x==integer::Fixed::ZERO||mesh_size.y==integer::Fixed::ZERO||mesh_size.z==integer::Fixed::ZERO{
// avoid devide by zero but introduce more edge cases. not sure what the correct thing to do here is. print!("[rbx_loader] Mesh with zero size!");
if mesh_size.x!=integer::Fixed::ZERO{ return None;
model.transform.matrix3.x_axis=(model.transform.matrix3.x_axis*2/mesh_size.x).divide().narrow_64().unwrap();
} }
if mesh_size.y!=integer::Fixed::ZERO{ Some(ModelDeferredAttributes{
model.transform.matrix3.y_axis=(model.transform.matrix3.y_axis*2/mesh_size.y).divide().narrow_64().unwrap(); mesh,
} deferred_attributes:deferred_model_deferred_attributes.model.deferred_attributes,
if mesh_size.z!=integer::Fixed::ZERO{ color:deferred_model_deferred_attributes.model.color,
model.transform.matrix3.z_axis=(model.transform.matrix3.z_axis*2/mesh_size.z).divide().narrow_64().unwrap(); transform:Planar64Affine3::new(
} Planar64Mat3::from_cols([
Some(model) (deferred_model_deferred_attributes.model.transform.matrix3.x_axis*2/mesh_size.x).divide().narrow_1().unwrap(),
(deferred_model_deferred_attributes.model.transform.matrix3.y_axis*2/mesh_size.y).divide().narrow_1().unwrap(),
(deferred_model_deferred_attributes.model.transform.matrix3.z_axis*2/mesh_size.z).divide().narrow_1().unwrap(),
]),
deferred_model_deferred_attributes.model.transform.translation
),
})
}).chain(self.deferred_unions_deferred_attributes.into_iter().flat_map(|deferred_union_deferred_attributes|{ }).chain(self.deferred_unions_deferred_attributes.into_iter().flat_map(|deferred_union_deferred_attributes|{
//meshes need to be cloned from loaded_meshes with a new id when they are used with a new render_id //meshes need to be cloned from loaded_meshes with a new id when they are used with a new render_id
//insert into primitive_meshes //insert into primitive_meshes
@@ -928,19 +895,19 @@ impl PartialMap1<'_>{
deferred_union_deferred_attributes.model.mesh, deferred_union_deferred_attributes.model.mesh,
deferred_union_deferred_attributes.render deferred_union_deferred_attributes.render
)?; )?;
let mut model=deferred_union_deferred_attributes.model; Some(ModelDeferredAttributes{
model.mesh=mesh; mesh,
// avoid devide by zero but introduce more edge cases. not sure what the correct thing to do here is. deferred_attributes:deferred_union_deferred_attributes.model.deferred_attributes,
if size.x!=integer::Fixed::ZERO{ color:deferred_union_deferred_attributes.model.color,
model.transform.matrix3.x_axis=(model.transform.matrix3.x_axis*2/size.x).divide().narrow_64().unwrap(); transform:Planar64Affine3::new(
} Planar64Mat3::from_cols([
if size.y!=integer::Fixed::ZERO{ (deferred_union_deferred_attributes.model.transform.matrix3.x_axis*2/size.x).divide().narrow_1().unwrap(),
model.transform.matrix3.y_axis=(model.transform.matrix3.y_axis*2/size.y).divide().narrow_64().unwrap(); (deferred_union_deferred_attributes.model.transform.matrix3.y_axis*2/size.y).divide().narrow_1().unwrap(),
} (deferred_union_deferred_attributes.model.transform.matrix3.z_axis*2/size.z).divide().narrow_1().unwrap(),
if size.z!=integer::Fixed::ZERO{ ]),
model.transform.matrix3.z_axis=(model.transform.matrix3.z_axis*2/size.z).divide().narrow_64().unwrap(); deferred_union_deferred_attributes.model.transform.translation
} ),
Some(model) })
})) }))
.chain(self.primitive_models_deferred_attributes.into_iter()) .chain(self.primitive_models_deferred_attributes.into_iter())
.filter_map(|model_deferred_attributes|{ .filter_map(|model_deferred_attributes|{

View File

@@ -1,4 +1,4 @@
use crate::mesh::MeshWithSize; use crate::loader::MeshWithSize;
use crate::rbx::RobloxPartDescription; use crate::rbx::RobloxPartDescription;
use crate::primitives::{CUBE_DEFAULT_VERTICES,CUBE_DEFAULT_POLYS,FaceDescription}; use crate::primitives::{CUBE_DEFAULT_VERTICES,CUBE_DEFAULT_POLYS,FaceDescription};
@@ -164,10 +164,6 @@ fn build_mesh5(
Ok(()) Ok(())
} }
fn new_polygon_list_checked(list:Vec<model::IndexedVertexList>)->Option<PolygonList>{
(!list.is_empty()).then_some(PolygonList::new(list))
}
const NORMAL_FACES:usize=6; const NORMAL_FACES:usize=6;
impl std::error::Error for Error{} impl std::error::Error for Error{}
pub fn convert( pub fn convert(
@@ -176,12 +172,12 @@ pub fn convert(
size:glam::Vec3, size:glam::Vec3,
RobloxPartDescription(part_texture_description):RobloxPartDescription, RobloxPartDescription(part_texture_description):RobloxPartDescription,
)->Result<MeshWithSize,Error>{ )->Result<MeshWithSize,Error>{
let mut polygon_groups_normal_id:[_;NORMAL_FACES]=[vec![],vec![],vec![],vec![],vec![],vec![]];
// build graphics and physics meshes // build graphics and physics meshes
let mut mb=MeshBuilder::new(); let mut mb=MeshBuilder::new();
// graphics // graphics
let (polygon_groups_normal_id,graphics_groups)=if !roblox_mesh_data.is_empty(){ let graphics_groups=if !roblox_mesh_data.is_empty(){
let mut polygon_groups_normal_id:[_;NORMAL_FACES]=[vec![],vec![],vec![],vec![],vec![],vec![]];
// create per-face texture coordinate affine transforms // create per-face texture coordinate affine transforms
let cube_face_description=part_texture_description.map(|opt|opt.map(|mut t|{ let cube_face_description=part_texture_description.map(|opt|opt.map(|mut t|{
t.transform.set_size(1.0,1.0); t.transform.set_size(1.0,1.0);
@@ -197,26 +193,20 @@ pub fn convert(
rbx_mesh::mesh_data::MeshData::CSGMDL(rbx_mesh::mesh_data::CSGMDL::V4(mesh_data4))=>build_mesh2(&mut mb,&mut polygon_groups_normal_id,&cube_face_description,mesh_data4.mesh)?, rbx_mesh::mesh_data::MeshData::CSGMDL(rbx_mesh::mesh_data::CSGMDL::V4(mesh_data4))=>build_mesh2(&mut mb,&mut polygon_groups_normal_id,&cube_face_description,mesh_data4.mesh)?,
rbx_mesh::mesh_data::MeshData::CSGMDL(rbx_mesh::mesh_data::CSGMDL::V5(mesh_data4))=>build_mesh5(&mut mb,&mut polygon_groups_normal_id,&cube_face_description,mesh_data4)?, rbx_mesh::mesh_data::MeshData::CSGMDL(rbx_mesh::mesh_data::CSGMDL::V5(mesh_data4))=>build_mesh5(&mut mb,&mut polygon_groups_normal_id,&cube_face_description,mesh_data4)?,
}; };
let graphics_groups=polygon_groups_normal_id (0..NORMAL_FACES).map(|polygon_group_id|{
.iter() model::IndexedGraphicsGroup{
.enumerate() render:cube_face_description[polygon_group_id].as_ref().map_or(RenderConfigId::new(0),|face_description|face_description.render),
.filter(|&(_,group)|!group.is_empty()) groups:vec![PolygonGroupId::new(polygon_group_id as u32)]
.enumerate() }
.map(|(polygon_group_id,(normal_id,_))|{ }).collect()
model::IndexedGraphicsGroup{
render:cube_face_description[normal_id].as_ref().map_or(RenderConfigId::new(0),|face_description|face_description.render),
groups:vec![PolygonGroupId::new(polygon_group_id as u32)]
}
}).collect();
(polygon_groups_normal_id,graphics_groups)
}else{ }else{
([vec![],vec![],vec![],vec![],vec![],vec![]],Vec::new()) Vec::new()
}; };
//physics //physics
let polygon_groups_normal_it=polygon_groups_normal_id.into_iter().filter_map(|faces| let polygon_groups_normal_it=polygon_groups_normal_id.into_iter().map(|faces|
// graphics polygon groups (to be rendered) // graphics polygon groups (to be rendered)
Some(PolygonGroup::PolygonList(new_polygon_list_checked(faces)?)) Ok(PolygonGroup::PolygonList(PolygonList::new(faces)))
); );
let polygon_groups:Vec<PolygonGroup>=if !roblox_physics_data.is_empty(){ let polygon_groups:Vec<PolygonGroup>=if !roblox_physics_data.is_empty(){
let physics_data=rbx_mesh::read_physics_data_versioned( let physics_data=rbx_mesh::read_physics_data_versioned(
@@ -235,12 +225,12 @@ pub fn convert(
rbx_mesh::physics_data::PhysicsData::CSGPHS(rbx_mesh::physics_data::CSGPHS::V7(meshes)) rbx_mesh::physics_data::PhysicsData::CSGPHS(rbx_mesh::physics_data::CSGPHS::V7(meshes))
=>meshes.meshes, =>meshes.meshes,
}; };
let physics_convex_meshes_it=physics_convex_meshes.into_iter().filter_map(|mesh|{ let physics_convex_meshes_it=physics_convex_meshes.into_iter().map(|mesh|{
// this can be factored out of the loop but I am lazy // this can be factored out of the loop but I am lazy
let color=mb.acquire_color_id(glam::Vec4::ONE); let color=mb.acquire_color_id(glam::Vec4::ONE);
let tex=mb.acquire_tex_id(glam::Vec2::ZERO); let tex=mb.acquire_tex_id(glam::Vec2::ZERO);
// physics polygon groups (to do physics) // physics polygon groups (to do physics)
let polygons=mesh.faces.into_iter().map(|[PhysicsDataVertexId(vertex_id0),PhysicsDataVertexId(vertex_id1),PhysicsDataVertexId(vertex_id2)]|{ Ok(PolygonGroup::PolygonList(PolygonList::new(mesh.faces.into_iter().map(|[PhysicsDataVertexId(vertex_id0),PhysicsDataVertexId(vertex_id1),PhysicsDataVertexId(vertex_id2)]|{
let face=[ let face=[
mesh.vertices.get(vertex_id0 as usize).ok_or(Error::MissingVertexId(vertex_id0))?, mesh.vertices.get(vertex_id0 as usize).ok_or(Error::MissingVertexId(vertex_id0))?,
mesh.vertices.get(vertex_id1 as usize).ok_or(Error::MissingVertexId(vertex_id1))?, mesh.vertices.get(vertex_id1 as usize).ok_or(Error::MissingVertexId(vertex_id1))?,
@@ -253,14 +243,9 @@ pub fn convert(
let pos=mb.acquire_pos_id(vec3::try_from_f32_array(vertex_pos.to_array()).map_err(Error::Planar64Vec3)?); let pos=mb.acquire_pos_id(vec3::try_from_f32_array(vertex_pos.to_array()).map_err(Error::Planar64Vec3)?);
Ok(mb.acquire_vertex_id(IndexedVertex{pos,tex,normal,color})) Ok(mb.acquire_vertex_id(IndexedVertex{pos,tex,normal,color}))
}).collect() }).collect()
}).collect::<Result<_,_>>(); }).collect::<Result<_,_>>()?)))
let polygon_list=match polygons{
Ok(polygons)=>new_polygon_list_checked(polygons)?,
Err(e)=>return Some(Err(e)),
};
Some(Ok(PolygonGroup::PolygonList(polygon_list)))
}); });
polygon_groups_normal_it.map(Ok).chain(physics_convex_meshes_it).collect::<Result<_,_>>()? polygon_groups_normal_it.chain(physics_convex_meshes_it).collect::<Result<_,_>>()?
}else{ }else{
// generate a unit cube as default physics // generate a unit cube as default physics
let pos_list=CUBE_DEFAULT_VERTICES.map(|pos|mb.acquire_pos_id(pos>>1)); let pos_list=CUBE_DEFAULT_VERTICES.map(|pos|mb.acquire_pos_id(pos>>1));
@@ -270,9 +255,9 @@ pub fn convert(
let polygon_group=PolygonGroup::PolygonList(PolygonList::new(CUBE_DEFAULT_POLYS.map(|poly|poly.map(|[pos_id,_]| let polygon_group=PolygonGroup::PolygonList(PolygonList::new(CUBE_DEFAULT_POLYS.map(|poly|poly.map(|[pos_id,_]|
mb.acquire_vertex_id(IndexedVertex{pos:pos_list[pos_id as usize],tex,normal,color}) mb.acquire_vertex_id(IndexedVertex{pos:pos_list[pos_id as usize],tex,normal,color})
).to_vec()).to_vec())); ).to_vec()).to_vec()));
polygon_groups_normal_it.chain([polygon_group]).collect() polygon_groups_normal_it.chain([Ok(polygon_group)]).collect::<Result<_,_>>()?
}; };
let physics_groups=(graphics_groups.len()..polygon_groups.len()).map(|id|model::IndexedPhysicsGroup{ let physics_groups=(NORMAL_FACES..polygon_groups.len()).map(|id|model::IndexedPhysicsGroup{
groups:vec![PolygonGroupId::new(id as u32)] groups:vec![PolygonGroupId::new(id as u32)]
}).collect(); }).collect();
let mesh=mb.build( let mesh=mb.build(
@@ -280,5 +265,8 @@ pub fn convert(
graphics_groups, graphics_groups,
physics_groups, physics_groups,
); );
Ok(MeshWithSize::new(mesh,vec3::ONE)) Ok(MeshWithSize{
mesh,
size:vec3::ONE,
})
} }

View File

@@ -1,6 +1,6 @@
[package] [package]
name = "roblox_emulator" name = "roblox_emulator"
version = "0.5.3" version = "0.5.2"
edition = "2024" edition = "2024"
repository = "https://git.itzana.me/StrafesNET/strafe-project" repository = "https://git.itzana.me/StrafesNET/strafe-project"
license = "MIT OR Apache-2.0" license = "MIT OR Apache-2.0"

View File

@@ -101,19 +101,7 @@ impl Runnable<'_>{
.set_name(name).into_function().map_err(Error::RustLua)?; .set_name(name).into_function().map_err(Error::RustLua)?;
// TODO: set_environment without losing the ability to print from Lua // TODO: set_environment without losing the ability to print from Lua
let thread=self.lua.create_thread(f).map_err(Error::RustLua)?; let thread=self.lua.create_thread(f).map_err(Error::RustLua)?;
// set timeout
let start=std::time::Instant::now();
self.lua.set_interrupt(move|lua|{
if std::time::Duration::from_secs(1)<start.elapsed(){
lua.remove_interrupt();
return Err(mlua::Error::runtime("timeout"));
}
Ok(mlua::VmState::Continue)
});
thread.resume::<mlua::MultiValue>(()).map_err(|error|Error::Lua{source,error})?; thread.resume::<mlua::MultiValue>(()).map_err(|error|Error::Lua{source,error})?;
self.lua.remove_interrupt();
// wait() is called from inside Lua and goes to a rust function that schedules the thread and then yields // 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 // No need to schedule the thread here
Ok(()) Ok(())

View File

@@ -386,7 +386,7 @@ pub fn write_map<W:BinWriterExt>(mut writer:W,map:strafesnet_common::map::Comple
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=Aabb::default(); let mut aabb=Aabb::default();
for &pos in &mesh.unique_pos{ for &pos in &mesh.unique_pos{
aabb.grow(model.transform.transform_point3(pos).narrow_64().unwrap()); aabb.grow(model.transform.transform_point3(pos).narrow_1().unwrap());
} }
Ok(((model::ModelId::new(model_id as u32),model.into()),aabb)) Ok(((model::ModelId::new(model_id as u32),model.into()),aabb))
}).collect::<Result<Vec<_>,_>>()?; }).collect::<Result<Vec<_>,_>>()?;

View File

@@ -1,38 +1,13 @@
[package] [package]
name = "map-tool" name = "map-tool"
version = "3.0.1" version = "2.0.0"
edition = "2024" edition = "2024"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[features] [features]
default = ["cli","source","roblox"] default = ["cli"]
roblox = [ cli = ["dep:clap", "tokio/macros", "tokio/rt-multi-thread", "tokio/fs", "dep:futures"]
"dep:strafesnet_rbx_loader",
"dep:rbx_asset",
"dep:rbx_binary",
"dep:rbx_dom_weak",
"dep:rbx_reflection_database",
"dep:rbx_xml",
"dep:rbxassetid",
]
source = [
"dep:strafesnet_bsp_loader",
"dep:vbsp",
"dep:vbsp-entities-css",
"dep:vmdl",
"dep:vmt-parser",
"dep:vpk",
"dep:vtf",
"tokio/sync",
]
cli = [
"dep:clap",
"dep:futures",
"tokio/macros",
"tokio/rt-multi-thread",
"tokio/fs",
]
[lib] [lib]
name = "map_tool" name = "map_tool"
@@ -42,35 +17,30 @@ name = "map-tool"
required-features = ["cli"] required-features = ["cli"]
[dependencies] [dependencies]
strafesnet_deferred_loader.workspace = true
strafesnet_snf.workspace = true
anyhow = "1.0.75" anyhow = "1.0.75"
clap = { version = "4.4.2", features = ["derive"], optional = true } clap = { version = "4.4.2", features = ["derive"], optional = true }
flate2 = "1.0.27" flate2 = "1.0.27"
futures = { version = "0.3.31", optional = true } futures = { version = "0.3.31", optional = true }
image = { version = "0.25.2", features = ["png", "jpeg"], default-features = false } image = "0.25.2"
image_dds = { version = "0.7.1", features = ["ddsfile","encode"], default-features = false } image_dds = "0.7.1"
rbx_asset = { version = "0.5.0", registry = "strafesnet" }
rbx_binary = "2.0.1"
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.workspace = true
strafesnet_deferred_loader.workspace = true
strafesnet_rbx_loader.workspace = true
strafesnet_snf.workspace = true
thiserror = "2.0.11" thiserror = "2.0.11"
tokio = { version = "1.43.0", features = ["time"] } tokio = { version = "1.43.0", features = ["time"] }
vbsp = "0.9.1"
# roblox vbsp-entities-css = "0.6.0"
strafesnet_rbx_loader = { workspace = true, optional = true } vmdl = "0.2.0"
rbx_asset = { version = "0.5.0", registry = "strafesnet", optional = true } vmt-parser = "0.2.0"
rbx_binary = { version = "2.0.1", optional = true } vpk = "0.3.0"
rbx_dom_weak = { version = "4.1.0", optional = true } vtf = "0.3.0"
rbx_reflection_database = { version = "2.0.2", optional = true }
rbx_xml = { version = "2.0.1", optional = true }
rbxassetid = { version = "0.1.0", registry = "strafesnet", optional = true }
# source
strafesnet_bsp_loader = { workspace = true, optional = true }
vbsp = { version = "0.9.1", optional = true }
vbsp-entities-css = { version = "0.6.0", optional = true }
vmdl = { version = "0.2.0", optional = true }
vmt-parser = { version = "0.2.0", optional = true }
vpk = { version = "0.3.0", optional = true }
vtf = { version = "0.3.0", optional = true }
#[profile.release] #[profile.release]
#lto = true #lto = true

View File

@@ -1,4 +1,2 @@
#[cfg(feature="roblox")]
pub mod roblox; pub mod roblox;
#[cfg(feature="source")]
pub mod source; pub mod source;

View File

@@ -12,10 +12,8 @@ struct Cli{
#[derive(Subcommand)] #[derive(Subcommand)]
enum Commands{ enum Commands{
#[command(flatten)] #[command(flatten)]
#[cfg(feature="roblox")]
Roblox(map_tool::roblox::Commands), Roblox(map_tool::roblox::Commands),
#[command(flatten)] #[command(flatten)]
#[cfg(feature="source")]
Source(map_tool::source::Commands), Source(map_tool::source::Commands),
} }
@@ -23,9 +21,7 @@ enum Commands{
async fn main()->AResult<()>{ async fn main()->AResult<()>{
let cli=Cli::parse(); let cli=Cli::parse();
match cli.command{ match cli.command{
#[cfg(feature="roblox")]
Commands::Roblox(commands)=>commands.run().await, Commands::Roblox(commands)=>commands.run().await,
#[cfg(feature="source")]
Commands::Source(commands)=>commands.run().await, Commands::Source(commands)=>commands.run().await,
} }
} }

View File

@@ -1,10 +1,8 @@
use std::path::PathBuf; use std::path::PathBuf;
use std::io::Cursor;
use anyhow::Result as AResult; use anyhow::Result as AResult;
use rbxassetid::RobloxAssetId; use rbxassetid::RobloxAssetId;
use strafesnet_deferred_loader::deferred_loader::{LoadFailureMode,MeshDeferredLoader,RenderConfigDeferredLoader};
use super::{convert_texture_to_dds,get_unique_assets}; use super::{convert_to_snf,convert_texture_to_dds,get_unique_assets_from_file};
use super::{ConvertTextureError,UniqueAssets}; use super::{ConvertTextureError,UniqueAssets};
const DOWNLOAD_LIMIT:usize=16; const DOWNLOAD_LIMIT:usize=16;
@@ -13,7 +11,6 @@ const DOWNLOAD_LIMIT:usize=16;
pub enum Commands{ pub enum Commands{
RobloxToSNF(RobloxToSNFSubcommand), RobloxToSNF(RobloxToSNFSubcommand),
DownloadAssets(DownloadAssetsSubcommand), DownloadAssets(DownloadAssetsSubcommand),
ExtractUnionData(ExtractUnionData),
} }
#[derive(clap::Args)] #[derive(clap::Args)]
@@ -34,13 +31,6 @@ pub struct DownloadAssetsSubcommand{
#[arg(long,group="cookie",required=true)] #[arg(long,group="cookie",required=true)]
cookie_file:Option<PathBuf>, cookie_file:Option<PathBuf>,
} }
#[derive(clap::Args)]
pub struct ExtractUnionData{
#[arg(long)]
output_folder:PathBuf,
#[arg(long)]
input_file:PathBuf,
}
impl Commands{ impl Commands{
pub async fn run(self)->AResult<()>{ pub async fn run(self)->AResult<()>{
@@ -54,7 +44,6 @@ impl Commands{
subcommand.cookie_file, subcommand.cookie_file,
).await?, ).await?,
).await, ).await,
Commands::ExtractUnionData(subcommand)=>extract_union_data(subcommand.input_file,subcommand.output_folder).await,
} }
} }
} }
@@ -197,7 +186,7 @@ async fn download_assets(paths:Vec<PathBuf>,cookie:rbx_asset::cookie::Cookie)->A
let send=send_assets.clone(); let send=send_assets.clone();
tokio::spawn(async move{ tokio::spawn(async move{
let result=match tokio::fs::read(&path).await{ let result=match tokio::fs::read(&path).await{
Ok(data)=>super::load_dom(&data).map(|dom|get_unique_assets(&dom)).map_err(|e|format!("{e:?}")), Ok(data)=>get_unique_assets_from_file(&data).map_err(|e|format!("{e:?}")),
Err(e)=>Err(format!("{e:?}")), Err(e)=>Err(format!("{e:?}")),
}; };
_=send.send(result).await; _=send.send(result).await;
@@ -296,88 +285,3 @@ async fn roblox_to_snf(paths:Vec<PathBuf>,output_folder:PathBuf)->AResult<()>{
println!("elapsed={:?}", start.elapsed()); println!("elapsed={:?}", start.elapsed());
Ok(()) Ok(())
} }
#[expect(dead_code)]
#[derive(Debug)]
pub enum ConvertError{
IO(std::io::Error),
SNFMap(strafesnet_snf::map::Error),
RobloxLoadMesh(super::loader::MeshError),
RobloxLoadTexture(super::loader::TextureError),
}
impl std::fmt::Display for ConvertError{
fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{
write!(f,"{self:?}")
}
}
impl std::error::Error for ConvertError{}
pub struct ConvertOutput{
pub snf:Vec<u8>,
pub script_errors:Vec<strafesnet_rbx_loader::RunnerError>,
pub convert_errors:strafesnet_rbx_loader::RecoverableErrors,
}
pub fn convert_to_snf(dom:rbx_dom_weak::WeakDom)->Result<ConvertOutput,ConvertError>{
const FAILURE_MODE:LoadFailureMode=LoadFailureMode::DefaultToNone;
// run scripts
let model=strafesnet_rbx_loader::Model::new(dom);
let mut place=strafesnet_rbx_loader::Place::from(model);
let script_errors=place.run_scripts().unwrap_or_else(|e|vec![e]);
// convert
let mut texture_deferred_loader=RenderConfigDeferredLoader::new();
let mut mesh_deferred_loader=MeshDeferredLoader::new();
let map_step1=strafesnet_rbx_loader::rbx::convert(
place.as_ref(),
&mut texture_deferred_loader,
&mut mesh_deferred_loader,
);
let mut mesh_loader=super::loader::MeshLoader::new();
let meshpart_meshes=mesh_deferred_loader.into_meshes(&mut mesh_loader,FAILURE_MODE).map_err(ConvertError::RobloxLoadMesh)?;
let map_step2=map_step1.add_meshpart_meshes_and_calculate_attributes(meshpart_meshes);
let mut texture_loader=super::loader::TextureLoader::new();
let render_configs=texture_deferred_loader.into_render_configs(&mut texture_loader,FAILURE_MODE).map_err(ConvertError::RobloxLoadTexture)?;
let (map,convert_errors)=map_step2.add_render_configs_and_textures(render_configs);
let mut snf_buf=Vec::new();
strafesnet_snf::map::write_map(Cursor::new(&mut snf_buf),map).map_err(ConvertError::SNFMap)?;
Ok(ConvertOutput{
snf:snf_buf,
script_errors,
convert_errors,
})
}
async fn extract_union_data(input_file:PathBuf,output_folder:PathBuf)->AResult<()>{
let data=tokio::fs::read(&input_file).await?;
let dom=rbx_binary::from_reader(Cursor::new(data))?;
let &[referent]=dom.root().children()else{
panic!("Expected one child");
};
let Some(instance)=dom.get_by_ref(referent)else{
panic!("Missing instance");
};
let output_file=output_folder.join(input_file.file_stem().unwrap());
if let Some(rbx_dom_weak::types::Variant::BinaryString(data))=instance.properties.get(&rbx_dom_weak::ustr("PhysicsData")){
let mut physics_data_path=output_file.clone();
physics_data_path.set_extension("physicsdata");
tokio::fs::write(physics_data_path,data).await?;
}
if let Some(rbx_dom_weak::types::Variant::BinaryString(data))=instance.properties.get(&rbx_dom_weak::ustr("MeshData")){
let mut mesh_data_path=output_file.clone();
mesh_data_path.set_extension("meshdata");
tokio::fs::write(mesh_data_path,data).await?;
}
Ok(())
}

View File

@@ -1,8 +1,10 @@
use std::io::Read; use std::io::Read;
use rbxassetid::{RobloxAssetId,RobloxAssetIdParseErr}; use rbxassetid::{RobloxAssetId,RobloxAssetIdParseErr};
use strafesnet_common::model::Mesh;
use strafesnet_deferred_loader::{loader::Loader,texture::Texture}; use strafesnet_deferred_loader::{loader::Loader,texture::Texture};
use strafesnet_rbx_loader::mesh::{MeshIndex,MeshType,MeshWithSize}; use crate::data::RobloxMeshBytes;
use crate::rbx::RobloxPartDescription;
// disallow non-static lifetimes // disallow non-static lifetimes
fn static_ustr(s:&'static str)->rbx_dom_weak::Ustr{ fn static_ustr(s:&'static str)->rbx_dom_weak::Ustr{
@@ -16,7 +18,6 @@ fn read_entire_file(path:impl AsRef<std::path::Path>)->Result<Vec<u8>,std::io::E
Ok(data) Ok(data)
} }
#[expect(dead_code)]
#[derive(Debug)] #[derive(Debug)]
pub enum TextureError{ pub enum TextureError{
Io(std::io::Error), Io(std::io::Error),
@@ -57,13 +58,12 @@ impl Loader for TextureLoader{
} }
} }
#[expect(dead_code)]
#[derive(Debug)] #[derive(Debug)]
pub enum MeshError{ pub enum MeshError{
Io(std::io::Error), Io(std::io::Error),
RobloxAssetIdParse(RobloxAssetIdParseErr), RobloxAssetIdParse(RobloxAssetIdParseErr),
Mesh(strafesnet_rbx_loader::mesh::Error), Mesh(crate::mesh::Error),
Union(strafesnet_rbx_loader::union::Error), Union(crate::union::Error),
DecodeBinary(rbx_binary::DecodeError), DecodeBinary(rbx_binary::DecodeError),
OneChildPolicy, OneChildPolicy,
MissingInstance, MissingInstance,
@@ -84,13 +84,13 @@ impl From<RobloxAssetIdParseErr> for MeshError{
Self::RobloxAssetIdParse(value) Self::RobloxAssetIdParse(value)
} }
} }
impl From<strafesnet_rbx_loader::mesh::Error> for MeshError{ impl From<crate::mesh::Error> for MeshError{
fn from(value:strafesnet_rbx_loader::mesh::Error)->Self{ fn from(value:crate::mesh::Error)->Self{
Self::Mesh(value) Self::Mesh(value)
} }
} }
impl From<strafesnet_rbx_loader::union::Error> for MeshError{ impl From<crate::union::Error> for MeshError{
fn from(value:strafesnet_rbx_loader::union::Error)->Self{ fn from(value:crate::union::Error)->Self{
Self::Union(value) Self::Union(value)
} }
} }
@@ -100,6 +100,53 @@ impl From<rbx_binary::DecodeError> for MeshError{
} }
} }
#[derive(Hash,Eq,PartialEq)]
pub enum MeshType<'a>{
FileMesh,
Union{
mesh_data:&'a [u8],
physics_data:&'a [u8],
size_float_bits:[u32;3],
part_texture_description:RobloxPartDescription,
},
}
#[derive(Hash,Eq,PartialEq)]
pub struct MeshIndex<'a>{
mesh_type:MeshType<'a>,
content:&'a str,
}
impl MeshIndex<'_>{
pub fn file_mesh(content:&str)->MeshIndex<'_>{
MeshIndex{
mesh_type:MeshType::FileMesh,
content,
}
}
pub fn union<'a>(
content:&'a str,
mesh_data:&'a [u8],
physics_data:&'a [u8],
size:&rbx_dom_weak::types::Vector3,
part_texture_description:RobloxPartDescription,
)->MeshIndex<'a>{
MeshIndex{
mesh_type:MeshType::Union{
mesh_data,
physics_data,
size_float_bits:[size.x.to_bits(),size.y.to_bits(),size.z.to_bits()],
part_texture_description,
},
content,
}
}
}
#[derive(Clone)]
pub struct MeshWithSize{
pub(crate) mesh:Mesh,
pub(crate) size:strafesnet_common::integer::Planar64Vec3,
}
pub struct MeshLoader; pub struct MeshLoader;
impl MeshLoader{ impl MeshLoader{
pub fn new()->Self{ pub fn new()->Self{
@@ -116,11 +163,11 @@ impl Loader for MeshLoader{
let RobloxAssetId(asset_id)=index.content.parse()?; let RobloxAssetId(asset_id)=index.content.parse()?;
let file_name=format!("meshes/{}",asset_id); let file_name=format!("meshes/{}",asset_id);
let data=read_entire_file(file_name)?; let data=read_entire_file(file_name)?;
strafesnet_rbx_loader::mesh::convert(&data)? crate::mesh::convert(RobloxMeshBytes::new(data))?
}, },
MeshType::Union{mut physics_data,mut mesh_data,size_float_bits,part_texture_description}=>{ MeshType::Union{mut physics_data,mut mesh_data,size_float_bits,part_texture_description}=>{
// decode asset // decode asset
let size=size_float_bits.map(f32::from_bits).into(); let size=glam::Vec3::from_array(size_float_bits.map(f32::from_bits));
if !index.content.is_empty()&&(physics_data.is_empty()||mesh_data.is_empty()){ if !index.content.is_empty()&&(physics_data.is_empty()||mesh_data.is_empty()){
let RobloxAssetId(asset_id)=index.content.parse()?; let RobloxAssetId(asset_id)=index.content.parse()?;
let file_name=format!("unions/{}",asset_id); let file_name=format!("unions/{}",asset_id);
@@ -142,9 +189,9 @@ impl Loader for MeshLoader{
mesh_data=data.as_ref(); mesh_data=data.as_ref();
} }
} }
strafesnet_rbx_loader::union::convert(physics_data,mesh_data,size,part_texture_description)? crate::union::convert(physics_data,mesh_data,size,part_texture_description)?
}else{ }else{
strafesnet_rbx_loader::union::convert(physics_data,mesh_data,size,part_texture_description)? crate::union::convert(physics_data,mesh_data,size,part_texture_description)?
} }
}, },
}; };

View File

@@ -7,6 +7,7 @@ mod loader;
use std::io::{Cursor,Read,Seek}; use std::io::{Cursor,Read,Seek};
use std::collections::HashSet; use std::collections::HashSet;
use strafesnet_deferred_loader::deferred_loader::LoadFailureMode;
pub use rbxassetid::RobloxAssetId; pub use rbxassetid::RobloxAssetId;
use rbx_dom_weak::Instance; use rbx_dom_weak::Instance;
@@ -78,13 +79,14 @@ impl UniqueAssets{
match object.class.as_str(){ match object.class.as_str(){
"Beam"=>accumulate_content_id(&mut self.textures,object,"Texture"), "Beam"=>accumulate_content_id(&mut self.textures,object,"Texture"),
"Decal"=>accumulate_content(&mut self.textures,object,"TextureContent"), "Decal"=>accumulate_content(&mut self.textures,object,"TextureContent"),
"Texture"=>accumulate_content(&mut self.textures,object,"TextureContent"),
"FileMesh"=>accumulate_content_id(&mut self.textures,object,"TextureId"), "FileMesh"=>accumulate_content_id(&mut self.textures,object,"TextureId"),
"MeshPart"=>{ "MeshPart"=>{
accumulate_content(&mut self.textures,object,"TextureContent"); accumulate_content(&mut self.textures,object,"TextureContent");
accumulate_content(&mut self.meshes,object,"MeshContent"); accumulate_content(&mut self.meshes,object,"MeshContent");
}, },
"ParticleEmitter"=>accumulate_content_id(&mut self.textures,object,"Texture"),
"SpecialMesh"=>accumulate_content_id(&mut self.meshes,object,"MeshId"), "SpecialMesh"=>accumulate_content_id(&mut self.meshes,object,"MeshId"),
"ParticleEmitter"=>accumulate_content_id(&mut self.textures,object,"Texture"),
"Sky"=>{ "Sky"=>{
accumulate_content_id(&mut self.textures,object,"MoonTextureId"); accumulate_content_id(&mut self.textures,object,"MoonTextureId");
accumulate_content_id(&mut self.textures,object,"SkyboxBk"); accumulate_content_id(&mut self.textures,object,"SkyboxBk");
@@ -95,33 +97,71 @@ impl UniqueAssets{
accumulate_content_id(&mut self.textures,object,"SkyboxUp"); accumulate_content_id(&mut self.textures,object,"SkyboxUp");
accumulate_content_id(&mut self.textures,object,"SunTextureId"); accumulate_content_id(&mut self.textures,object,"SunTextureId");
}, },
"SurfaceAppearance"=>{
accumulate_content(&mut self.textures,object,"ColorMapContent");
accumulate_content(&mut self.textures,object,"MetalnessMapContent");
accumulate_content(&mut self.textures,object,"NormalMapContent");
accumulate_content(&mut self.textures,object,"RoughnessMapContent");
}
"Texture"=>accumulate_content(&mut self.textures,object,"TextureContent"),
"UnionOperation"=>accumulate_content_id(&mut self.unions,object,"AssetId"), "UnionOperation"=>accumulate_content_id(&mut self.unions,object,"AssetId"),
_=>(), _=>(),
} }
} }
} }
pub fn get_unique_assets(dom:&rbx_dom_weak::WeakDom)->UniqueAssets{ pub fn get_unique_assets(dom:rbx_dom_weak::WeakDom)->UniqueAssets{
let mut assets=UniqueAssets::default(); let mut assets=UniqueAssets::default();
for object in dom.descendants(){ for object in dom.into_raw().1.into_values(){
assets.collect(object); assets.collect(&object);
} }
assets assets
} }
pub fn get_unique_assets_from_file(data:&[u8])->Result<UniqueAssets,UniqueAssetError>{
let dom=load_dom(data).map_err(UniqueAssetError::LoadDom)?;
Ok(get_unique_assets(dom))
}
#[derive(Debug)]
pub enum UniqueAssetError{
LoadDom(LoadDomError),
}
#[derive(Debug)]
pub enum ConvertError{
IO(std::io::Error),
SNFMap(strafesnet_snf::map::Error),
RobloxLoad(strafesnet_rbx_loader::LoadError),
}
impl std::fmt::Display for ConvertError{
fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{
write!(f,"{self:?}")
}
}
impl std::error::Error for ConvertError{}
pub struct ConvertOutput{
pub snf:Vec<u8>,
pub script_errors:Vec<strafesnet_rbx_loader::RunnerError>,
pub convert_errors:strafesnet_rbx_loader::RecoverableErrors,
}
pub fn convert_to_snf(dom:rbx_dom_weak::WeakDom)->Result<ConvertOutput,ConvertError>{
let model=strafesnet_rbx_loader::Model::new(dom);
let mut place=strafesnet_rbx_loader::Place::from(model);
let script_errors=place.run_scripts().unwrap_or_else(|e|vec![e]);
let (map,convert_errors)=place.to_snf(LoadFailureMode::DefaultToNone).map_err(ConvertError::RobloxLoad)?;
let mut snf_buf=Vec::new();
strafesnet_snf::map::write_map(Cursor::new(&mut snf_buf),map).map_err(ConvertError::SNFMap)?;
Ok(ConvertOutput{
snf:snf_buf,
script_errors,
convert_errors,
})
}
#[derive(Debug,thiserror::Error)] #[derive(Debug,thiserror::Error)]
pub enum ConvertTextureError{ pub enum ConvertTextureError{
#[error("Image error {0:?}")] #[error("Image error {0:?}")]
Image(#[from]image::ImageError), Image(#[from]image::ImageError),
#[error("DDS encode error {0:?}")]
DDSEncode(#[from]image_dds::error::SurfaceError),
#[error("DDS create error {0:?}")] #[error("DDS create error {0:?}")]
DDS(#[from]image_dds::CreateDdsError), DDS(#[from]image_dds::CreateDdsError),
#[error("DDS write error {0:?}")] #[error("DDS write error {0:?}")]
@@ -136,14 +176,12 @@ pub fn convert_texture_to_dds(data:&[u8])->Result<Vec<u8>,ConvertTextureError>{
image_dds::ImageFormat::BC7RgbaUnormSrgb image_dds::ImageFormat::BC7RgbaUnormSrgb
}; };
let dds=image_dds::SurfaceRgba8{ let dds=image_dds::dds_from_image(
width:image.width(), &image,
height:image.height(), format,
depth:1, image_dds::Quality::Slow,
layers:1, image_dds::Mipmaps::GeneratedAutomatic,
mipmaps:1, )?;
data:image.as_raw(),
}.encode(format,image_dds::Quality::Slow,image_dds::Mipmaps::GeneratedAutomatic)?.to_dds()?;
let mut buf=Vec::new(); let mut buf=Vec::new();
dds.write(&mut Cursor::new(&mut buf))?; dds.write(&mut Cursor::new(&mut buf))?;

View File

@@ -1,14 +1,13 @@
use std::borrow::Cow;
use std::io::Cursor;
use std::path::PathBuf; use std::path::PathBuf;
use std::borrow::Cow;
use strafesnet_deferred_loader::loader::Loader; use strafesnet_deferred_loader::loader::Loader;
use strafesnet_deferred_loader::deferred_loader::{LoadFailureMode,MeshDeferredLoader,RenderConfigDeferredLoader}; use strafesnet_deferred_loader::deferred_loader::{MeshDeferredLoader,RenderConfigDeferredLoader};
use vbsp_entities_css::Entity; use vbsp_entities_css::Entity;
use anyhow::Result as AResult; use anyhow::Result as AResult;
use futures::StreamExt; use futures::StreamExt;
use super::{convert_texture_to_dds,load_texture}; use super::{convert_texture_to_dds,convert_to_snf,load_texture};
use super::{BspFinder,ConvertTextureError,LoadVMTError}; use super::{BspFinder,ConvertTextureError,LoadVMTError};
#[derive(clap::Subcommand)] #[derive(clap::Subcommand)]
@@ -64,7 +63,7 @@ enum ExtractTextureError{
#[error("Bsp error {0:?}")] #[error("Bsp error {0:?}")]
Bsp(#[from]vbsp::BspError), Bsp(#[from]vbsp::BspError),
#[error("MeshLoad error {0:?}")] #[error("MeshLoad error {0:?}")]
MeshLoad(#[from]super::loader::MeshError), MeshLoad(#[from]strafesnet_bsp_loader::loader::MeshError),
#[error("Load VMT error {0:?}")] #[error("Load VMT error {0:?}")]
LoadVMT(#[from]LoadVMTError), LoadVMT(#[from]LoadVMTError),
} }
@@ -137,7 +136,7 @@ async fn gimme_them_textures(path:&std::path::Path,vpk_list:&[strafesnet_bsp_loa
vpks:vpk_list vpks:vpk_list
}; };
let mut mesh_loader=super::loader::ModelLoader::new(finder); let mut mesh_loader=strafesnet_bsp_loader::loader::ModelLoader::new(finder);
// load models and collect requested textures // load models and collect requested textures
for model_path in mesh_deferred_loader.into_indices(){ for model_path in mesh_deferred_loader.into_indices(){
let model:vmdl::Model=match mesh_loader.load(model_path){ let model:vmdl::Model=match mesh_loader.load(model_path){
@@ -299,50 +298,3 @@ async fn source_to_snf(paths:Vec<PathBuf>,output_folder:PathBuf,vpk_paths:Vec<Pa
println!("elapsed={:?}", start.elapsed()); println!("elapsed={:?}", start.elapsed());
Ok(()) Ok(())
} }
#[expect(dead_code)]
#[derive(Debug)]
pub enum ConvertError{
IO(std::io::Error),
SNFMap(strafesnet_snf::map::Error),
BspRead(strafesnet_bsp_loader::ReadError),
BspLoadMesh(super::loader::MeshError),
BspLoadTexture(super::loader::TextureError),
}
impl std::fmt::Display for ConvertError{
fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{
write!(f,"{self:?}")
}
}
impl std::error::Error for ConvertError{}
pub fn convert_to_snf(bsp_data:&[u8],vpk_list:&[strafesnet_bsp_loader::Vpk])->Result<Vec<u8>,ConvertError>{
const FAILURE_MODE:LoadFailureMode=LoadFailureMode::DefaultToNone;
let bsp=strafesnet_bsp_loader::read(
Cursor::new(bsp_data)
).map_err(ConvertError::BspRead)?;
let mut texture_deferred_loader=RenderConfigDeferredLoader::new();
let mut mesh_deferred_loader=MeshDeferredLoader::new();
let map_step1=strafesnet_bsp_loader::bsp::convert(
&bsp,
&mut texture_deferred_loader,
&mut mesh_deferred_loader,
);
let mut mesh_loader=super::loader::MeshLoader::new(BspFinder{bsp:&bsp,vpks:vpk_list},&mut texture_deferred_loader);
let prop_meshes=mesh_deferred_loader.into_meshes(&mut mesh_loader,FAILURE_MODE).map_err(ConvertError::BspLoadMesh)?;
let map_step2=map_step1.add_prop_meshes(prop_meshes);
let mut texture_loader=super::loader::TextureLoader::new();
let render_configs=texture_deferred_loader.into_render_configs(&mut texture_loader,FAILURE_MODE).map_err(ConvertError::BspLoadTexture)?;
let map=map_step2.add_render_configs_and_textures(render_configs);
let mut snf_buf=Vec::new();
strafesnet_snf::map::write_map(Cursor::new(&mut snf_buf),map).map_err(ConvertError::SNFMap)?;
Ok(snf_buf)
}

View File

@@ -1,10 +1,10 @@
use std::{borrow::Cow, io::Read}; use std::{borrow::Cow, io::Read};
use strafesnet_common::model::Mesh;
use strafesnet_deferred_loader::{loader::Loader,texture::Texture}; use strafesnet_deferred_loader::{loader::Loader,texture::Texture};
use super::BspFinder; use crate::{Bsp,Vpk};
#[expect(dead_code)]
#[derive(Debug)] #[derive(Debug)]
pub enum TextureError{ pub enum TextureError{
Io(std::io::Error), Io(std::io::Error),
@@ -40,7 +40,6 @@ impl Loader for TextureLoader{
} }
} }
#[expect(dead_code)]
#[derive(Debug)] #[derive(Debug)]
pub enum MeshError{ pub enum MeshError{
Io(std::io::Error), Io(std::io::Error),
@@ -72,6 +71,33 @@ impl From<vbsp::BspError> for MeshError{
} }
} }
#[derive(Clone,Copy)]
pub struct BspFinder<'bsp,'vpk>{
pub bsp:&'bsp Bsp,
pub vpks:&'vpk [Vpk],
}
impl<'bsp,'vpk> BspFinder<'bsp,'vpk>{
pub fn find<'a>(&self,path:&str)->Result<Option<Cow<'a,[u8]>>,vbsp::BspError>
where
'bsp:'a,
'vpk:'a,
{
// search bsp
if let Some(data)=self.bsp.pack_get(path)?{
return Ok(Some(Cow::Owned(data)));
}
//search each vpk
for vpk in self.vpks{
if let Some(vpk_entry)=vpk.tree_get(path){
return Ok(Some(vpk_entry.get()?));
}
}
Ok(None)
}
}
pub struct ModelLoader<'bsp,'vpk>{ pub struct ModelLoader<'bsp,'vpk>{
finder:BspFinder<'bsp,'vpk>, finder:BspFinder<'bsp,'vpk>,
} }
@@ -128,10 +154,10 @@ impl MeshLoader<'_,'_,'_,'_>{
impl Loader for MeshLoader<'_,'_,'_,'_>{ impl Loader for MeshLoader<'_,'_,'_,'_>{
type Error=MeshError; type Error=MeshError;
type Index<'a>=&'a str where Self:'a; type Index<'a>=&'a str where Self:'a;
type Resource=strafesnet_bsp_loader::mesh::Mesh; type Resource=Mesh;
fn load<'a>(&'a mut self,index:Self::Index<'a>)->Result<Self::Resource,Self::Error>{ fn load<'a>(&'a mut self,index:Self::Index<'a>)->Result<Self::Resource,Self::Error>{
let model=ModelLoader::new(self.finder).load(index)?; let model=ModelLoader::new(self.finder).load(index)?;
let mesh=strafesnet_bsp_loader::mesh::convert_mesh(model,&mut self.deferred_loader); let mesh=crate::mesh::convert_mesh(model,&mut self.deferred_loader);
Ok(mesh) Ok(mesh)
} }
} }

View File

@@ -8,34 +8,8 @@ mod loader;
use std::path::PathBuf; use std::path::PathBuf;
use std::borrow::Cow; use std::borrow::Cow;
use std::io::Cursor; use std::io::Cursor;
use strafesnet_bsp_loader::loader::BspFinder;
use strafesnet_bsp_loader::{Bsp,Vpk}; use strafesnet_deferred_loader::deferred_loader::LoadFailureMode;
#[derive(Clone,Copy)]
pub struct BspFinder<'bsp,'vpk>{
pub bsp:&'bsp Bsp,
pub vpks:&'vpk [Vpk],
}
impl<'bsp,'vpk> BspFinder<'bsp,'vpk>{
pub fn find<'a>(&self,path:&str)->Result<Option<Cow<'a,[u8]>>,vbsp::BspError>
where
'bsp:'a,
'vpk:'a,
{
// search bsp
if let Some(data)=self.bsp.pack_get(path)?{
return Ok(Some(Cow::Owned(data)));
}
//search each vpk
for vpk in self.vpks{
if let Some(vpk_entry)=vpk.tree_get(path){
return Ok(Some(vpk_entry.get()?));
}
}
Ok(None)
}
}
enum VMTContent{ enum VMTContent{
VMT(String), VMT(String),
@@ -205,12 +179,39 @@ pub fn load_texture<'bsp,'vpk,'a>(finder:BspFinder<'bsp,'vpk>,texture_name:&str)
Ok(None) Ok(None)
} }
#[derive(Debug)]
pub enum ConvertError{
IO(std::io::Error),
SNFMap(strafesnet_snf::map::Error),
BspRead(strafesnet_bsp_loader::ReadError),
BspLoad(strafesnet_bsp_loader::LoadError),
}
impl std::fmt::Display for ConvertError{
fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{
write!(f,"{self:?}")
}
}
impl std::error::Error for ConvertError{}
pub fn convert_to_snf(bsp_data:&[u8],vpk_list:&[strafesnet_bsp_loader::Vpk])->Result<Vec<u8>,ConvertError>{
let bsp=strafesnet_bsp_loader::read(
Cursor::new(bsp_data)
).map_err(ConvertError::BspRead)?;
let mut mesh_loader=loader::MeshLoader::new(loader::BspFinder{bsp:self,vpks:vpk_list},&mut texture_deferred_loader);
let mut texture_loader=loader::TextureLoader::new();
let map=bsp.to_snf(LoadFailureMode::DefaultToNone,mesh_loader,texture_loader).map_err(ConvertError::BspLoad)?;
let mut snf_buf=Vec::new();
strafesnet_snf::map::write_map(Cursor::new(&mut snf_buf),map).map_err(ConvertError::SNFMap)?;
Ok(snf_buf)
}
#[derive(Debug,thiserror::Error)] #[derive(Debug,thiserror::Error)]
pub enum ConvertTextureError{ pub enum ConvertTextureError{
#[error("Vtf error {0:?}")] #[error("Vtf error {0:?}")]
Vtf(#[from]vtf::Error), Vtf(#[from]vtf::Error),
#[error("DDS encode error {0:?}")]
DDSEncode(#[from]image_dds::error::SurfaceError),
#[error("DDS create error {0:?}")] #[error("DDS create error {0:?}")]
DDS(#[from]image_dds::CreateDdsError), DDS(#[from]image_dds::CreateDdsError),
#[error("DDS write error {0:?}")] #[error("DDS write error {0:?}")]
@@ -226,22 +227,20 @@ pub fn convert_texture_to_dds(vtf_data:&[u8])->Result<Vec<u8>,ConvertTextureErro
}else{ }else{
image_dds::ImageFormat::BC7RgbaUnormSrgb image_dds::ImageFormat::BC7RgbaUnormSrgb
}; };
let dds=image_dds::SurfaceRgba8{ let dds=image_dds::dds_from_image(
width:image.width(), &image,
height:image.height(), format,
depth:1, image_dds::Quality::Slow,
layers:1, image_dds::Mipmaps::GeneratedAutomatic,
mipmaps:1, )?;
data:image.as_raw(),
}.encode(format,image_dds::Quality::Slow,image_dds::Mipmaps::GeneratedAutomatic)?.to_dds()?;
let mut buf=Vec::new(); let mut buf=Vec::new();
dds.write(&mut Cursor::new(&mut buf))?; dds.write(&mut Cursor::new(&mut buf))?;
Ok(buf) Ok(buf)
} }
pub fn read_vpks(vpk_paths:&[PathBuf])->Result<Vec<Vpk>,vpk::Error>{ pub fn read_vpks(vpk_paths:&[PathBuf])->Result<Vec<strafesnet_bsp_loader::Vpk>,vpk::Error>{
vpk_paths.iter().map(|vpk_path|{ vpk_paths.iter().map(|vpk_path|{
Ok(Vpk::new(vpk::VPK::read(vpk_path)?)) Ok(strafesnet_bsp_loader::Vpk::new(vpk::VPK::read(vpk_path)?))
}).collect() }).collect()
} }

View File

@@ -28,7 +28,7 @@ strafesnet_rbx_loader = { workspace = true, optional = true }
strafesnet_session.workspace = true strafesnet_session.workspace = true
strafesnet_settings.workspace = true strafesnet_settings.workspace = true
strafesnet_snf = { workspace = true, optional = true } strafesnet_snf = { workspace = true, optional = true }
wgpu.workspace = true wgpu = "28.0.0"
winit = "0.30.7" winit = "0.30.7"
[profile.dev] [profile.dev]

View File

@@ -20,7 +20,8 @@ WorkerDescription{
pub fn new( pub fn new(
mut graphics:graphics::GraphicsState, mut graphics:graphics::GraphicsState,
mut surface:strafesnet_graphics::surface::Surface<'_>, mut config:wgpu::SurfaceConfiguration,
surface:wgpu::Surface<'_>,
device:wgpu::Device, device:wgpu::Device,
queue:wgpu::Queue, queue:wgpu::Queue,
)->crate::compat_worker::INWorker<'_,Instruction>{ )->crate::compat_worker::INWorker<'_,Instruction>{
@@ -33,22 +34,30 @@ pub fn new(
Instruction::Resize(size,user_settings)=>{ Instruction::Resize(size,user_settings)=>{
println!("Resizing to {:?}",size); println!("Resizing to {:?}",size);
let t0=std::time::Instant::now(); let t0=std::time::Instant::now();
let size=glam::uvec2(size.width,size.height); config.width=size.width.max(1);
surface.configure(&device,size); config.height=size.height.max(1);
let size=surface.size(); surface.configure(&device,&config);
let fov=user_settings.calculate_fov(1.0,&size).as_vec2(); let fov=user_settings.calculate_fov(1.0,&glam::uvec2(config.width,config.height)).as_vec2();
graphics.resize(&device,size,fov); graphics.resize(&device,&config,fov);
println!("Resize took {:?}",t0.elapsed()); println!("Resize took {:?}",t0.elapsed());
} }
Instruction::Render(frame_state)=>{ Instruction::Render(frame_state)=>{
//this has to go deeper somehow //this has to go deeper somehow
let frame=surface.new_frame(&device).expect("Error creating new frame"); let frame=match surface.get_current_texture(){
Ok(frame)=>frame,
Err(_)=>{
surface.configure(&device,&config);
surface
.get_current_texture()
.expect("Failed to acquire next surface texture!")
}
};
let view=frame.texture.create_view(&wgpu::TextureViewDescriptor{
format:Some(config.view_formats[0]),
..wgpu::TextureViewDescriptor::default()
});
let mut encoder=device.create_command_encoder(&wgpu::CommandEncoderDescriptor{label:None}); graphics.render(&view,&device,&queue,graphics::view_inv(frame_state.pos(),frame_state.angles()));
graphics.encode_commands(&mut encoder,frame.view(),graphics::view_inv(frame_state.pos(),frame_state.angles()));
queue.submit([encoder.finish()]);
frame.present(); frame.present();
} }

View File

@@ -1,7 +1,5 @@
use strafesnet_graphics::setup; use strafesnet_graphics::setup;
const LIMITS:wgpu::Limits=wgpu::Limits::defaults();
fn create_window(title:&str,event_loop:&winit::event_loop::EventLoop<()>)->Result<winit::window::Window,winit::error::OsError>{ 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(); let mut attr=winit::window::WindowAttributes::default();
attr=attr.with_title(title); attr=attr.with_title(title);
@@ -15,20 +13,19 @@ pub async fn setup_and_start(title:&str){
println!("Initializing the surface..."); println!("Initializing the surface...");
let desc=wgpu::InstanceDescriptor::new_with_display_handle_from_env(Box::new(event_loop.owned_display_handle())); let instance=setup::step1::create_instance();
let instance=wgpu::Instance::new(desc);
let surface=setup::step2::create_surface(&instance,&window).unwrap(); 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 adapter=setup::step3::pick_adapter(&instance,&surface).await.expect("No suitable GPU adapters found on the system!");
let adapter_info=adapter.get_info(); let adapter_info=adapter.get_info();
println!("Using {} ({:?})",adapter_info.name,adapter_info.backend); println!("Using {} ({:?})", adapter_info.name, adapter_info.backend);
let (device,queue)=setup::step4::request_device(&adapter,LIMITS).await.unwrap(); let (device,queue)=setup::step4::request_device(&adapter).await.unwrap();
let size=window.inner_size(); let size=window.inner_size();
let surface=setup::step5::configure_surface(&adapter,&device,surface,(size.width,size.height)).unwrap(); let config=setup::step5::configure_surface(&adapter,&device,&surface,(size.width,size.height)).unwrap();
//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
@@ -38,7 +35,7 @@ pub async fn setup_and_start(title:&str){
device, device,
queue, queue,
surface, surface,
LIMITS, config,
); );
for arg in std::env::args().skip(1){ for arg in std::env::args().skip(1){

View File

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