Compare commits

..

9 Commits

133 changed files with 18483 additions and 10413 deletions

1
.gitignore vendored
View File

@@ -1,2 +1 @@
/target
.zed

2833
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -24,34 +24,3 @@ resolver = "2"
#lto = true
strip = true
codegen-units = 1
[workspace.lints.rust]
# unsafe_code = "forbid"
# missing_docs = "warn"
# missing_debug_implementations = "warn"
single_use_lifetimes = "warn"
trivial_casts = "warn"
unused_lifetimes = "warn"
unused_qualifications = "warn"
# variant_size_differences = "warn"
unexpected_cfgs = "warn"
[workspace.dependencies]
glam = "0.32.0"
wgpu = "29.0.0"
# engine
strafesnet_graphics = { path = "engine/graphics", registry = "strafesnet" }
strafesnet_physics = { version = "0.0.1", path = "engine/physics", registry = "strafesnet" }
strafesnet_session = { path = "engine/session", registry = "strafesnet" }
strafesnet_settings = { path = "engine/settings", registry = "strafesnet" }
# lib
fixed_wide = { version = "0.2.2", path = "lib/fixed_wide", registry = "strafesnet" }
linear_ops = { version = "0.1.1", path = "lib/linear_ops", registry = "strafesnet" }
ratio_ops = { version = "0.1.0", path = "lib/ratio_ops", registry = "strafesnet" }
strafesnet_bsp_loader = { version = "0.5.0", path = "lib/bsp_loader", registry = "strafesnet" }
strafesnet_common = { version = "0.8.7", path = "lib/common", 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_snf = { version = "0.3.2", path = "lib/snf", registry = "strafesnet" }

View File

@@ -1,15 +1,14 @@
[package]
name = "strafesnet_graphics"
version = "0.0.8"
version = "0.1.0"
edition = "2024"
[dependencies]
bytemuck = { version = "1.13.1", features = ["derive"] }
ddsfile = "0.5.1"
glam.workspace = true
glam = "0.30.0"
id = { version = "0.1.0", registry = "strafesnet" }
strafesnet_common.workspace = true
wgpu.workspace = true
[lints]
workspace = true
strafesnet_common = { path = "../../lib/common", registry = "strafesnet" }
strafesnet_session = { path = "../session", registry = "strafesnet" }
strafesnet_settings = { path = "../settings", registry = "strafesnet" }
wgpu = "24.0.0"

View File

@@ -1,9 +1,15 @@
use std::borrow::Cow;
use std::collections::{HashSet,HashMap};
use strafesnet_common::map;
use strafesnet_settings::settings;
use strafesnet_session::session;
use strafesnet_common::model::{self, ColorId, NormalId, PolygonIter, PositionId, RenderConfigId, TextureCoordinateId, VertexId};
use wgpu::{util::DeviceExt,AstcBlock,AstcChannel};
use crate::model::{self as model_graphics,IndexedGraphicsMeshOwnedRenderConfig,IndexedGraphicsMeshOwnedRenderConfigId,GraphicsMeshOwnedRenderConfig,GraphicsModelOwned,GraphicsVertex};
use crate::model::{self as model_graphics,IndexedGraphicsMeshOwnedRenderConfig,IndexedGraphicsMeshOwnedRenderConfigId,GraphicsMeshOwnedRenderConfig,GraphicsModelColor4,GraphicsModelOwned,GraphicsVertex};
pub fn required_limits()->wgpu::Limits{
wgpu::Limits::default()
}
struct Indices{
count:u32,
@@ -48,10 +54,6 @@ struct GraphicsPipelines{
model:wgpu::RenderPipeline,
}
pub fn view_inv(pos:glam::Vec3,angles:glam::Vec2)->glam::Mat4{
//f32 good enough for view matrix
glam::Mat4::from_mat3_translation(glam::Mat3::from_euler(glam::EulerRot::YXZ,angles.x,angles.y,0f32),pos)
}
struct GraphicsCamera{
screen_size:glam::UVec2,
fov:glam::Vec2,//slope
@@ -73,10 +75,15 @@ impl GraphicsCamera{
pub fn proj(&self)->glam::Mat4{
perspective_rh(self.fov.x,self.fov.y,0.4,4000.0)
}
pub fn world(&self,pos:glam::Vec3,angles:glam::Vec2)->glam::Mat4{
//f32 good enough for view matrix
glam::Mat4::from_translation(pos)*glam::Mat4::from_euler(glam::EulerRot::YXZ,angles.x,angles.y,0f32)
}
pub fn to_uniform_data(&self,view_inv:glam::Mat4)->[f32;16*4]{
pub fn to_uniform_data(&self,pos:glam::Vec3,angles:glam::Vec2)->[f32;16*4]{
let proj=self.proj();
let proj_inv=proj.inverse();
let view_inv=self.world(pos,angles);
let view=view_inv.inverse();
let mut raw=[0f32; 16 * 4];
@@ -87,7 +94,7 @@ impl GraphicsCamera{
raw
}
}
impl Default for GraphicsCamera{
impl std::default::Default for GraphicsCamera{
fn default()->Self{
Self{
screen_size:glam::UVec2::ONE,
@@ -96,28 +103,6 @@ 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_BYTES:usize=MODEL_BUFFER_SIZE*4;
fn get_instances_buffer_data(instances:&[GraphicsModelOwned])->Vec<f32>{
let mut raw=Vec::with_capacity(MODEL_BUFFER_SIZE*instances.len());
for mi in instances{
//model transform
raw.extend_from_slice(&AsRef::<[f32; 4*4]>::as_ref(&mi.transform)[..]);
//normal transform
raw.extend_from_slice(AsRef::<[f32; 3]>::as_ref(&mi.normal_transform.x_axis));
raw.extend_from_slice(&[0.0]);
raw.extend_from_slice(AsRef::<[f32; 3]>::as_ref(&mi.normal_transform.y_axis));
raw.extend_from_slice(&[0.0]);
raw.extend_from_slice(AsRef::<[f32; 3]>::as_ref(&mi.normal_transform.z_axis));
raw.extend_from_slice(&[0.0]);
//color
raw.extend_from_slice(AsRef::<[f32; 4]>::as_ref(&mi.color));
//texture color
raw.extend_from_slice(AsRef::<[f32; 4]>::as_ref(&mi.texture_color));
}
raw
}
pub struct GraphicsState{
pipelines:GraphicsPipelines,
bind_groups:GraphicsBindGroups,
@@ -129,19 +114,18 @@ pub struct GraphicsState{
models:Vec<GraphicsModel>,
depth_view:wgpu::TextureView,
staging_belt:wgpu::util::StagingBelt,
model_instances_uniform_len:usize,
}
impl GraphicsState{
const DEPTH_FORMAT:wgpu::TextureFormat=wgpu::TextureFormat::Depth24Plus;
fn create_depth_texture(
size:glam::UVec2,
config:&wgpu::SurfaceConfiguration,
device:&wgpu::Device,
)->wgpu::TextureView{
let depth_texture=device.create_texture(&wgpu::TextureDescriptor{
size:wgpu::Extent3d{
width:size.x,
height:size.y,
width:config.width,
height:config.height,
depth_or_array_layers:1,
},
mip_level_count:1,
@@ -158,9 +142,12 @@ impl GraphicsState{
pub fn clear(&mut self){
self.models.clear();
}
pub fn load_user_settings(&mut self,user_settings:&settings::UserSettings){
self.camera.fov=user_settings.calculate_fov(1.0,&self.camera.screen_size).as_vec2();
}
pub fn generate_models(&mut self,device:&wgpu::Device,queue:&wgpu::Queue,map:&map::CompleteMap){
//generate texture view per texture
let texture_views:HashMap<model::TextureId,wgpu::TextureView>=map.textures.iter().enumerate().filter_map(|(texture_id,texture_data)|{
let texture_views:HashMap<strafesnet_common::model::TextureId,wgpu::TextureView>=map.textures.iter().enumerate().filter_map(|(texture_id,texture_data)|{
let texture_id=model::TextureId::new(texture_id as u32);
let image=match ddsfile::Dds::read(std::io::Cursor::new(texture_data)){
Ok(image)=>image,
@@ -232,8 +219,7 @@ impl GraphicsState{
let instance=GraphicsModelOwned{
transform:model.transform.into(),
normal_transform:glam::Mat3::from_cols_array_2d(&model.transform.matrix3.to_array().map(|row|row.map(Into::into))).inverse().transpose(),
color:model.color,
texture_color:glam::Vec4::ONE,
color:GraphicsModelColor4::new(model.color),
};
//get or create owned mesh map
let owned_mesh_map=owned_mesh_id_from_mesh_id_render_config_id
@@ -263,15 +249,16 @@ impl GraphicsState{
owned_mesh_id
});
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;
polygon_list.extend(
graphics_group.groups.iter().flat_map(|polygon_group_id|{
mesh.polygon_groups[polygon_group_id.get() as usize].polys()
})
.map(|vertex_id_slice|
vertex_id_slice.to_vec()
)
);
match &mut owned_mesh.polys{
model::PolygonGroup::PolygonList(polygon_list)=>polygon_list.extend(
graphics_group.groups.iter().flat_map(|polygon_group_id|{
mesh.polygon_groups[polygon_group_id.get() as usize].polys()
})
.map(|vertex_id_slice|
vertex_id_slice.to_vec()
)
),
}
}
}
owned_mesh_map
@@ -279,20 +266,10 @@ impl GraphicsState{
for owned_mesh_id in owned_mesh_map.values(){
let owned_mesh=unique_render_config_models.get_mut(owned_mesh_id.get() as usize).unwrap();
let render_config=&map.render_configs[owned_mesh.render_config.get() as usize];
let no_texture=render_config.texture.is_none();
let instance_clone=if no_texture{
// this model has no chance of affecting pixels on the screen
if model.color.w==0.0{
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);
if model.color.w==0.0&&render_config.texture.is_none(){
continue;
}
owned_mesh.instances.push(instance.clone());
}
}
//check every model to see if it's using the same (texture,color) but has few instances,if it is combine it into one model
@@ -318,7 +295,7 @@ impl GraphicsState{
//separate instances by color
for (instance_id,instance) in model.instances.iter().enumerate(){
let model_instance_list=unique_color
.entry(instance.hashable_color())
.entry(instance.color)
.or_insert_with(||Vec::new());
//add model instance to list
model_instance_list.push((model_id,instance_id));
@@ -407,7 +384,6 @@ impl GraphicsState{
).collect()
));
}
let (color,texture_color)=color.color();
//push model into dedup
deduplicated_models.push(IndexedGraphicsMeshOwnedRenderConfig{
unique_pos,
@@ -420,8 +396,7 @@ impl GraphicsState{
instances:vec![GraphicsModelOwned{
transform:glam::Mat4::IDENTITY,
normal_transform:glam::Mat3::IDENTITY,
color,
texture_color,
color
}],
});
}
@@ -479,14 +454,16 @@ impl GraphicsState{
//.into_iter() the modeldata vec so entities can be /moved/ to models.entities
let mut model_count=0;
let mut instance_count=0;
let uniform_buffer_binding_size=required_limits().max_uniform_buffer_binding_size as usize;
let chunk_size=uniform_buffer_binding_size/MODEL_BUFFER_SIZE_BYTES;
self.models.reserve(models.len());
for model in models.into_iter(){
instance_count+=model.instances.len();
for instances_chunk in model.instances.rchunks(self.model_instances_uniform_len){
for instances_chunk in model.instances.rchunks(chunk_size){
model_count+=1;
let mut model_uniforms=get_instances_buffer_data(instances_chunk);
//TEMP: fill with zeroes to pass validation
model_uniforms.resize(MODEL_BUFFER_SIZE*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{
label:Some(format!("Model{} Buf",model_count).as_str()),
contents:bytemuck::cast_slice(&model_uniforms),
@@ -542,9 +519,7 @@ impl GraphicsState{
pub fn new(
device:&wgpu::Device,
queue:&wgpu::Queue,
size:glam::UVec2,
view_format:wgpu::TextureFormat,
limits:wgpu::Limits,
config:&wgpu::SurfaceConfiguration,
)->Self{
let camera_bind_group_layout=device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor{
label:None,
@@ -621,7 +596,7 @@ impl GraphicsState{
address_mode_w:wgpu::AddressMode::ClampToEdge,
mag_filter:wgpu::FilterMode::Linear,
min_filter:wgpu::FilterMode::Linear,
mipmap_filter:wgpu::MipmapFilterMode::Linear,
mipmap_filter:wgpu::FilterMode::Linear,
..Default::default()
});
let repeat_sampler=device.create_sampler(&wgpu::SamplerDescriptor{
@@ -631,23 +606,15 @@ impl GraphicsState{
address_mode_w:wgpu::AddressMode::Repeat,
mag_filter:wgpu::FilterMode::Linear,
min_filter:wgpu::FilterMode::Linear,
mipmap_filter:wgpu::MipmapFilterMode::Linear,
mipmap_filter:wgpu::FilterMode::Linear,
anisotropy_clamp:16,
..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
let shader=device.create_shader_module(wgpu::ShaderModuleDescriptor{
label:None,
source:wgpu::ShaderSource::Wgsl(Cow::Owned(shader)),
source:wgpu::ShaderSource::Wgsl(Cow::Borrowed(include_str!("../../../strafe-client/src/shader.wgsl"))),
});
//load textures
@@ -675,10 +642,10 @@ impl GraphicsState{
wgpu::TextureFormat::Astc{
block:AstcBlock::B4x4,
channel:AstcChannel::UnormSrgb,
}=>&include_bytes!("../images/astc.dds")[..],
wgpu::TextureFormat::Etc2Rgb8UnormSrgb=>&include_bytes!("../images/etc2.dds")[..],
wgpu::TextureFormat::Bc1RgbaUnormSrgb=>&include_bytes!("../images/bc1.dds")[..],
wgpu::TextureFormat::Bgra8UnormSrgb=>&include_bytes!("../images/bgra.dds")[..],
}=>&include_bytes!("../../../strafe-client/images/astc.dds")[..],
wgpu::TextureFormat::Etc2Rgb8UnormSrgb=>&include_bytes!("../../../strafe-client/images/etc2.dds")[..],
wgpu::TextureFormat::Bc1RgbaUnormSrgb=>&include_bytes!("../../../strafe-client/images/bc1.dds")[..],
wgpu::TextureFormat::Bgra8UnormSrgb=>&include_bytes!("../../../strafe-client/images/bgra.dds")[..],
_=>unreachable!(),
};
@@ -721,7 +688,7 @@ impl GraphicsState{
//squid
let squid_texture_view={
let bytes=include_bytes!("../images/squid.dds");
let bytes=include_bytes!("../../../strafe-client/images/squid.dds");
let image=ddsfile::Dds::read(&mut std::io::Cursor::new(bytes)).unwrap();
@@ -763,19 +730,19 @@ impl GraphicsState{
let model_pipeline_layout=device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor{
label:None,
bind_group_layouts:&[
Some(&camera_bind_group_layout),
Some(&skybox_texture_bind_group_layout),
Some(&model_bind_group_layout),
&camera_bind_group_layout,
&skybox_texture_bind_group_layout,
&model_bind_group_layout,
],
immediate_size:0,
push_constant_ranges:&[],
});
let sky_pipeline_layout=device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor{
label:None,
bind_group_layouts:&[
Some(&camera_bind_group_layout),
Some(&skybox_texture_bind_group_layout),
&camera_bind_group_layout,
&skybox_texture_bind_group_layout,
],
immediate_size:0,
push_constant_ranges:&[],
});
// Create the render pipelines
@@ -791,7 +758,7 @@ impl GraphicsState{
fragment:Some(wgpu::FragmentState{
module:&shader,
entry_point:Some("fs_sky"),
targets:&[Some(view_format.into())],
targets:&[Some(config.view_formats[0].into())],
compilation_options:wgpu::PipelineCompilationOptions::default(),
}),
primitive:wgpu::PrimitiveState{
@@ -800,13 +767,13 @@ impl GraphicsState{
},
depth_stencil:Some(wgpu::DepthStencilState{
format:Self::DEPTH_FORMAT,
depth_write_enabled:Some(false),
depth_compare:Some(wgpu::CompareFunction::LessEqual),
depth_write_enabled:false,
depth_compare:wgpu::CompareFunction::LessEqual,
stencil:wgpu::StencilState::default(),
bias:wgpu::DepthBiasState::default(),
}),
multisample:wgpu::MultisampleState::default(),
multiview_mask:None,
multiview:None,
cache:None,
});
let model_pipeline=device.create_render_pipeline(&wgpu::RenderPipelineDescriptor{
@@ -816,7 +783,7 @@ impl GraphicsState{
module:&shader,
entry_point:Some("vs_entity_texture"),
buffers:&[wgpu::VertexBufferLayout{
array_stride:size_of::<GraphicsVertex>() as wgpu::BufferAddress,
array_stride:std::mem::size_of::<GraphicsVertex>() as wgpu::BufferAddress,
step_mode:wgpu::VertexStepMode::Vertex,
attributes:&wgpu::vertex_attr_array![0=>Float32x3,1=>Float32x2,2=>Float32x3,3=>Float32x4],
}],
@@ -825,7 +792,7 @@ impl GraphicsState{
fragment:Some(wgpu::FragmentState{
module:&shader,
entry_point:Some("fs_entity_texture"),
targets:&[Some(view_format.into())],
targets:&[Some(config.view_formats[0].into())],
compilation_options:wgpu::PipelineCompilationOptions::default(),
}),
primitive:wgpu::PrimitiveState{
@@ -835,18 +802,18 @@ impl GraphicsState{
},
depth_stencil:Some(wgpu::DepthStencilState{
format:Self::DEPTH_FORMAT,
depth_write_enabled:Some(true),
depth_compare:Some(wgpu::CompareFunction::LessEqual),
depth_write_enabled:true,
depth_compare:wgpu::CompareFunction::LessEqual,
stencil:wgpu::StencilState::default(),
bias:wgpu::DepthBiasState::default(),
}),
multisample:wgpu::MultisampleState::default(),
multiview_mask:None,
multiview:None,
cache:None,
});
let camera=GraphicsCamera::default();
let camera_uniforms=camera.to_uniform_data(view_inv(glam::Vec3::ZERO,glam::Vec2::ZERO));
let camera_uniforms=camera.to_uniform_data(glam::Vec3::ZERO,glam::Vec2::ZERO);
let camera_buf=device.create_buffer_init(&wgpu::util::BufferInitDescriptor{
label:Some("Camera"),
contents:bytemuck::cast_slice(&camera_uniforms),
@@ -878,7 +845,7 @@ impl GraphicsState{
label:Some("Sky Texture"),
});
let depth_view=Self::create_depth_texture(size,device);
let depth_view=Self::create_depth_texture(config,device);
Self{
pipelines:GraphicsPipelines{
@@ -893,41 +860,45 @@ impl GraphicsState{
camera_buf,
models:Vec::new(),
depth_view,
staging_belt:wgpu::util::StagingBelt::new(device.clone(),0x100),
staging_belt:wgpu::util::StagingBelt::new(0x100),
bind_group_layouts:GraphicsBindGroupLayouts{model:model_bind_group_layout},
samplers:GraphicsSamplers{repeat:repeat_sampler},
temp_squid_texture_view:squid_texture_view,
model_instances_uniform_len,
}
}
pub fn resize(
&mut self,
device:&wgpu::Device,
size:glam::UVec2,
fov:glam::Vec2,
config:&wgpu::SurfaceConfiguration,
user_settings:&settings::UserSettings,
){
self.depth_view=Self::create_depth_texture(size,device);
self.camera.screen_size=size;
self.camera.fov=fov;
self.depth_view=Self::create_depth_texture(config,device);
self.camera.screen_size=glam::uvec2(config.width,config.height);
self.load_user_settings(user_settings);
}
pub fn encode_commands(
pub fn render(
&mut self,
encoder:&mut wgpu::CommandEncoder,
view:&wgpu::TextureView,
camera:glam::Mat4,
device:&wgpu::Device,
queue:&wgpu::Queue,
frame_state:session::FrameState,
){
//TODO:use scheduled frame times to create beautiful smoothing simulation physics extrapolation assuming no input
// TODO: find a way to call this directly after queue.submit()
self.staging_belt.recall();
let mut encoder=device.create_command_encoder(&wgpu::CommandEncoderDescriptor{label:None});
// update rotation
let camera_uniforms=self.camera.to_uniform_data(camera);
let camera_uniforms=self.camera.to_uniform_data(
frame_state.body.extrapolated_position(frame_state.time).map(Into::<f32>::into).to_array().into(),
frame_state.camera.simulate_move_angles(glam::IVec2::ZERO)
);
self.staging_belt
.write_buffer(
encoder,
&mut encoder,
&self.camera_buf,
0,
wgpu::BufferSize::new((camera_uniforms.len() * 4) as wgpu::BufferAddress).unwrap(),
device,
)
.copy_from_slice(bytemuck::cast_slice(&camera_uniforms));
//This code only needs to run when the uniforms change
@@ -962,7 +933,6 @@ impl GraphicsState{
}),
store:wgpu::StoreOp::Store,
},
depth_slice:None,
})],
depth_stencil_attachment:Some(wgpu::RenderPassDepthStencilAttachment{
view:&self.depth_view,
@@ -974,7 +944,6 @@ impl GraphicsState{
}),
timestamp_writes:Default::default(),
occlusion_query_set:Default::default(),
multiview_mask:None,
});
rpass.set_bind_group(0,&self.bind_groups.camera,&[]);
@@ -992,5 +961,28 @@ impl GraphicsState{
rpass.set_pipeline(&self.pipelines.skybox);
rpass.draw(0..3,0..1);
}
queue.submit(std::iter::once(encoder.finish()));
self.staging_belt.recall();
}
}
const MODEL_BUFFER_SIZE:usize=4*4 + 12 + 4;//let size=std::mem::size_of::<ModelInstance>();
const MODEL_BUFFER_SIZE_BYTES:usize=MODEL_BUFFER_SIZE*4;
fn get_instances_buffer_data(instances:&[GraphicsModelOwned])->Vec<f32>{
let mut raw=Vec::with_capacity(MODEL_BUFFER_SIZE*instances.len());
for mi in instances{
//model transform
raw.extend_from_slice(&AsRef::<[f32; 4*4]>::as_ref(&mi.transform)[..]);
//normal transform
raw.extend_from_slice(AsRef::<[f32; 3]>::as_ref(&mi.normal_transform.x_axis));
raw.extend_from_slice(&[0.0]);
raw.extend_from_slice(AsRef::<[f32; 3]>::as_ref(&mi.normal_transform.y_axis));
raw.extend_from_slice(&[0.0]);
raw.extend_from_slice(AsRef::<[f32; 3]>::as_ref(&mi.normal_transform.z_axis));
raw.extend_from_slice(&[0.0]);
//color
raw.extend_from_slice(AsRef::<[f32; 4]>::as_ref(&mi.color.get()));
}
raw
}

View File

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

View File

@@ -30,23 +30,19 @@ pub struct GraphicsMeshOwnedRenderConfig{
pub render_config:RenderConfigId,
pub instances:Vec<GraphicsModelOwned>,
}
#[derive(Clone,Copy,PartialEq,id::Id)]
pub struct GraphicsModelColor4(glam::Vec4);
impl std::hash::Hash for GraphicsModelColor4{
fn hash<H:std::hash::Hasher>(&self,state:&mut H) {
for &f in self.0.as_ref(){
bytemuck::cast::<f32,u32>(f).hash(state);
}
}
}
impl Eq for GraphicsModelColor4{}
#[derive(Clone)]
pub struct GraphicsModelOwned{
pub transform:glam::Mat4,
pub normal_transform:glam::Mat3,
pub color:glam::Vec4,
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))
}
pub color:GraphicsModelColor4,
}

View File

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

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

View File

@@ -2,18 +2,12 @@ use strafesnet_common::aabb;
use strafesnet_common::integer::{self,vec3,Time,Planar64,Planar64Vec3};
#[derive(Clone,Copy,Debug,Hash)]
pub struct Body<T>{
pub position:Planar64Vec3,
pub velocity:Planar64Vec3,
pub time:Time<T>,
pub position:Planar64Vec3,//I64 where 2^32 = 1 u
pub velocity:Planar64Vec3,//I64 where 2^32 = 1 u/s
pub acceleration:Planar64Vec3,//I64 where 2^32 = 1 u/s/s
pub time:Time<T>,//nanoseconds x xxxxD!
}
#[derive(Clone,Copy,Debug,Hash)]
pub struct Trajectory<T>{
pub position:Planar64Vec3,
pub velocity:Planar64Vec3,
pub acceleration:Planar64Vec3,
pub time:Time<T>,
}
impl<T> std::ops::Neg for Trajectory<T>{
impl<T> std::ops::Neg for Body<T>{
type Output=Self;
fn neg(self)->Self::Output{
Self{
@@ -24,48 +18,11 @@ impl<T> std::ops::Neg for Trajectory<T>{
}
}
}
impl<T:Copy> std::ops::Neg for &Trajectory<T>{
type Output=Trajectory<T>;
fn neg(self)->Self::Output{
Trajectory{
position:self.position,
velocity:-self.velocity,
acceleration:self.acceleration,
time:-self.time,
}
}
}
impl<T> Body<T>
where Time<T>:Copy,
{
pub const ZERO:Self=Self::new(vec3::zero(),vec3::zero(),Time::ZERO);
pub const fn new(position:Planar64Vec3,velocity:Planar64Vec3,time:Time<T>)->Self{
Self{
position,
velocity,
time,
}
}
pub const fn with_acceleration(self,acceleration:Planar64Vec3)->Trajectory<T>{
let Body{
position,
velocity,
time,
}=self;
Trajectory{
position,
velocity,
acceleration,
time,
}
}
}
impl<T> Trajectory<T>
where Time<T>:Copy,
{
pub const ZERO:Self=Self::new(vec3::zero(),vec3::zero(),vec3::zero(),Time::ZERO);
pub const ZERO:Self=Self::new(vec3::ZERO,vec3::ZERO,vec3::ZERO,Time::ZERO);
pub const fn new(position:Planar64Vec3,velocity:Planar64Vec3,acceleration:Planar64Vec3,time:Time<T>)->Self{
Self{
position,
@@ -74,31 +31,28 @@ impl<T> Trajectory<T>
time,
}
}
pub fn relative_to(&self,trj0:&Self,time:Time<T>)->Self{
pub const fn relative_to<'a>(&'a self,body0:&'a Body<T>)->VirtualBody<'a,T>{
//(p0,v0,a0,t0)
//(p1,v1,a1,t1)
Trajectory::new(
self.extrapolated_position(time)-trj0.extrapolated_position(time),
self.extrapolated_velocity(time)-trj0.extrapolated_velocity(time),
self.acceleration-trj0.acceleration,
time)
VirtualBody{
body0,
body1:self,
}
}
pub fn extrapolated_position(&self,time:Time<T>)->Planar64Vec3{
let dt=time-self.time;
self.position
+(self.velocity*dt).map(|elem|elem.divide().clamp_64())
+self.acceleration.map(|elem|(dt*dt*elem/2).divide().clamp_64())
+(self.velocity*dt).map(|elem|elem.divide().fix_1())
+self.acceleration.map(|elem|(dt*dt*elem/2).divide().fix_1())
}
pub fn extrapolated_velocity(&self,time:Time<T>)->Planar64Vec3{
let dt=time-self.time;
self.velocity+(self.acceleration*dt).map(|elem|elem.divide().clamp_64())
self.velocity+(self.acceleration*dt).map(|elem|elem.divide().fix_1())
}
pub fn extrapolated_body(&self,time:Time<T>)->Body<T>{
Body::new(
self.extrapolated_position(time),
self.extrapolated_velocity(time),
time,
)
pub fn advance_time(&mut self,time:Time<T>){
self.position=self.extrapolated_position(time);
self.velocity=self.extrapolated_velocity(time);
self.time=time;
}
pub fn extrapolated_position_ratio_dt<Num,Den,N1,D1,N2,N3,D2,N4,T1>(&self,dt:integer::Ratio<Num,Den>)->Planar64Vec3
where
@@ -117,12 +71,12 @@ impl<T> Trajectory<T>
D2:Copy,
Planar64:core::ops::Mul<D2,Output=N4>,
N4:integer::Divide<D2,Output=T1>,
T1:integer::Clamp<Planar64>,
T1:integer::Fix<Planar64>,
{
// a*dt^2/2 + v*dt + p
// (a*dt/2+v)*dt+p
(self.acceleration.map(|elem|dt*elem/2)+self.velocity).map(|elem|dt.mul_ratio(elem))
.map(|elem|elem.divide().clamp())+self.position
.map(|elem|elem.divide().fix())+self.position
}
pub fn extrapolated_velocity_ratio_dt<Num,Den,N1,T1>(&self,dt:integer::Ratio<Num,Den>)->Planar64Vec3
where
@@ -131,21 +85,19 @@ impl<T> Trajectory<T>
Num:core::ops::Mul<Planar64,Output=N1>,
Planar64:core::ops::Mul<Den,Output=N1>,
N1:integer::Divide<Den,Output=T1>,
T1:integer::Clamp<Planar64>,
T1:integer::Fix<Planar64>,
{
// a*dt + v
self.acceleration.map(|elem|(dt*elem).divide().clamp())+self.velocity
self.acceleration.map(|elem|(dt*elem).divide().fix())+self.velocity
}
pub fn extrapolated_body_ratio_dt(&self,dt:crate::model::GigaTime)->Body<T>{
Body::new(
self.extrapolated_position_ratio_dt(dt),
self.extrapolated_velocity_ratio_dt(dt),
self.time+dt.into(),
)
pub fn advance_time_ratio_dt(&mut self,dt:crate::model::GigaTime){
self.position=self.extrapolated_position_ratio_dt(dt);
self.velocity=self.extrapolated_velocity_ratio_dt(dt);
self.time+=dt.into();
}
pub fn infinity_dir(&self)->Option<Planar64Vec3>{
if self.velocity==vec3::zero(){
if self.acceleration==vec3::zero(){
if self.velocity==vec3::ZERO{
if self.acceleration==vec3::ZERO{
None
}else{
Some(self.acceleration)
@@ -181,12 +133,28 @@ impl<T> Trajectory<T>
}
impl<T> std::fmt::Display for Body<T>{
fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{
write!(f,"p({}) v({}) t({})",self.position,self.velocity,self.time)
}
}
impl<T> std::fmt::Display for Trajectory<T>{
fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{
write!(f,"p({}) v({}) a({}) t({})",self.position,self.velocity,self.acceleration,self.time)
}
}
pub struct VirtualBody<'a,T>{
body0:&'a Body<T>,
body1:&'a Body<T>,
}
impl<T> VirtualBody<'_,T>
where Time<T>:Copy,
{
pub fn extrapolated_position(&self,time:Time<T>)->Planar64Vec3{
self.body1.extrapolated_position(time)-self.body0.extrapolated_position(time)
}
pub fn extrapolated_velocity(&self,time:Time<T>)->Planar64Vec3{
self.body1.extrapolated_velocity(time)-self.body0.extrapolated_velocity(time)
}
pub fn acceleration(&self)->Planar64Vec3{
self.body1.acceleration-self.body0.acceleration
}
pub fn body(&self,time:Time<T>)->Body<T>{
Body::new(self.extrapolated_position(time),self.extrapolated_velocity(time),self.acceleration(),time)
}
}

View File

@@ -1,84 +1,43 @@
use crate::model::{into_giga_time,GigaTime};
use strafesnet_common::integer::fixed_types::{F64_32,F128_64,F256_128};
use strafesnet_common::integer::vec3::Vector3;
use strafesnet_common::integer::{Ratio,Planar64Vec3};
use crate::physics::{Time,Trajectory};
use crate::mesh_query::{FEV,DirectedEdge,MeshQuery,MeshTopology};
use crate::model::{GigaTime,FEV,MeshQuery,DirectedEdge};
use strafesnet_common::integer::{Fixed,Ratio,vec3::Vector3};
use crate::physics::{Time,Body};
use core::ops::Bound;
enum Transition<M:MeshTopology>{
enum Transition<M:MeshQuery>{
Miss,
Next(FEV<M>,GigaTime),
Hit(M::Face,GigaTime),
}
pub enum CrawlResult<M:MeshTopology>{
pub enum CrawlResult<M:MeshQuery>{
Miss(FEV<M>),
Hit(M::Face,GigaTime),
}
impl<M:MeshTopology> CrawlResult<M>{
impl<M:MeshQuery> CrawlResult<M>{
pub fn hit(self)->Option<(M::Face,GigaTime)>{
match self{
CrawlResult::Miss(_)=>None,
CrawlResult::Hit(face,time)=>Some((face,time)),
}
}
}
// TODO: move predict_collision_face_out algorithm in here or something
/// check_lower_bound
pub fn low<LhsNum,LhsDen,RhsNum,RhsDen,T>(lower_bound:&Bound<Ratio<LhsNum,LhsDen>>,dt:&Ratio<RhsNum,RhsDen>)->bool
where
RhsNum:Copy,
RhsDen:Copy,
LhsNum:Copy,
LhsDen:Copy,
LhsDen:strafesnet_common::integer::Parity,
RhsDen:strafesnet_common::integer::Parity,
LhsNum:core::ops::Mul<RhsDen,Output=T>,
LhsDen:core::ops::Mul<RhsNum,Output=T>,
T:Ord+Copy,
{
match lower_bound{
Bound::Included(time)=>time.le_ratio(*dt),
Bound::Excluded(time)=>time.lt_ratio(*dt),
Bound::Unbounded=>true,
}
}
/// check_upper_bound
pub fn upp<LhsNum,LhsDen,RhsNum,RhsDen,T>(dt:&Ratio<LhsNum,LhsDen>,upper_bound:&Bound<Ratio<RhsNum,RhsDen>>)->bool
where
RhsNum:Copy,
RhsDen:Copy,
LhsNum:Copy,
LhsDen:Copy,
LhsDen:strafesnet_common::integer::Parity,
RhsDen:strafesnet_common::integer::Parity,
LhsNum:core::ops::Mul<RhsDen,Output=T>,
LhsDen:core::ops::Mul<RhsNum,Output=T>,
T:Ord+Copy,
{
match upper_bound{
Bound::Included(time)=>dt.le_ratio(*time),
Bound::Excluded(time)=>dt.lt_ratio(*time),
Bound::Unbounded=>true,
pub fn miss(self)->Option<FEV<M>>{
match self{
CrawlResult::Miss(fev)=>Some(fev),
CrawlResult::Hit(_,_)=>None,
}
}
}
impl<F:Copy,M:MeshQuery<Normal=Vector3<F>,Offset=F256_128,Position=Planar64Vec3,Direction=Planar64Vec3>> FEV<M>
impl<F:Copy,M:MeshQuery<Normal=Vector3<F>,Offset=Fixed<4,128>>> FEV<M>
where
// This is hardcoded for MinkowskiMesh lol
M::Face:Copy,
M::Edge:Copy,
M::DirectedEdge:Copy,
M::Vert:Copy,
F:core::ops::Mul<F64_32,Output=F256_128>,
<F as core::ops::Mul<F64_32>>::Output:core::iter::Sum,
M::Offset:core::ops::Sub<<F as std::ops::Mul<F64_32>>::Output>,
F:core::ops::Mul<Fixed<1,32>,Output=Fixed<4,128>>,
<F as core::ops::Mul<Fixed<1,32>>>::Output:core::iter::Sum,
<M as MeshQuery>::Offset:core::ops::Sub<<F as std::ops::Mul<Fixed<1,32>>>::Output>,
{
fn next_transition(&self,mesh:&M,trajectory:&Trajectory,lower_bound:Bound<GigaTime>,mut upper_bound:Bound<GigaTime>)->Transition<M>{
fn next_transition(&self,body_time:GigaTime,mesh:&M,body:&Body,mut best_time:GigaTime)->Transition<M>{
//conflicting derivative means it crosses in the wrong direction.
//if the transition time is equal to an already tested transition, do not replace the current best.
let mut best_transition=Transition::Miss;
@@ -90,60 +49,56 @@ impl<F:Copy,M:MeshQuery<Normal=Vector3<F>,Offset=F256_128,Position=Planar64Vec3,
let (n,d)=mesh.face_nd(face_id);
//TODO: use higher precision d value?
//use the mesh transform translation instead of baking it into the d value.
for dt in F256_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(){
upper_bound=Bound::Included(dt);
for dt in Fixed::<4,128>::zeroes2((n.dot(body.position)-d)*2,n.dot(body.velocity)*2,n.dot(body.acceleration)){
if body_time.le_ratio(dt)&&dt.lt_ratio(best_time)&&n.dot(body.extrapolated_velocity_ratio_dt(dt)).is_negative(){
best_time=dt;
best_transition=Transition::Hit(face_id,dt);
break;
}
}
//test each edge collision time, ignoring roots with zero or conflicting derivative
mesh.for_each_face_edge(face_id,|directed_edge_id|{
for &directed_edge_id in mesh.face_edges(face_id).iter(){
let edge_n=mesh.directed_edge_n(directed_edge_id);
let n=n.cross(edge_n);
let &[v0,v1]=mesh.edge_verts(directed_edge_id.as_undirected()).as_ref();
let verts=mesh.edge_verts(directed_edge_id.as_undirected());
//WARNING: d is moved out of the *2 block because of adding two vertices!
//WARNING: precision is swept under the rug!
//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()){
if low(&lower_bound,&dt)&&upp(&dt,&upper_bound)&&n.dot(trajectory.extrapolated_velocity_ratio_dt(dt)).is_negative(){
upper_bound=Bound::Included(dt);
for dt in Fixed::<4,128>::zeroes2(n.dot(body.position*2-(mesh.vert(verts[0])+mesh.vert(verts[1]))).fix_4(),n.dot(body.velocity).fix_4()*2,n.dot(body.acceleration).fix_4()){
if body_time.le_ratio(dt)&&dt.lt_ratio(best_time)&&n.dot(body.extrapolated_velocity_ratio_dt(dt)).is_negative(){
best_time=dt;
best_transition=Transition::Next(FEV::Edge(directed_edge_id.as_undirected()),dt);
break;
}
}
});
}
//if none:
},
&FEV::Edge(edge_id)=>{
//test each face collision time, ignoring roots with zero or conflicting derivative
let edge_n=mesh.edge_n(edge_id);
let edge_verts=mesh.edge_verts(edge_id);
let &[ev0,ev1]=edge_verts.as_ref();
let (v0,v1)=(mesh.vert(ev0),mesh.vert(ev1));
let edge_n=v1-v0;
let delta_pos=trajectory.position*2-(v0+v1);
for (i,&edge_face_id) in mesh.edge_faces(edge_id).as_ref().iter().enumerate(){
let delta_pos=body.position*2-(mesh.vert(edge_verts[0])+mesh.vert(edge_verts[1]));
for (i,&edge_face_id) in mesh.edge_faces(edge_id).iter().enumerate(){
let face_n=mesh.face_nd(edge_face_id).0;
//edge_n gets parity from the order of edge_faces
let n=face_n.cross(edge_n)*((i as i64)*2-1);
//WARNING yada yada d *2
//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()){
if low(&lower_bound,&dt)&&upp(&dt,&upper_bound)&&n.dot(trajectory.extrapolated_velocity_ratio_dt(dt)).is_negative(){
upper_bound=Bound::Included(dt);
for dt in Fixed::<4,128>::zeroes2(n.dot(delta_pos).fix_4(),n.dot(body.velocity).fix_4()*2,n.dot(body.acceleration).fix_4()){
if body_time.le_ratio(dt)&&dt.lt_ratio(best_time)&&n.dot(body.extrapolated_velocity_ratio_dt(dt)).is_negative(){
best_time=dt;
best_transition=Transition::Next(FEV::Face(edge_face_id),dt);
break;
}
}
}
//test each vertex collision time, ignoring roots with zero or conflicting derivative
for (i,&vert_id) in edge_verts.as_ref().iter().enumerate(){
for (i,&vert_id) in edge_verts.iter().enumerate(){
//vertex normal gets parity from vert index
let n=edge_n*(1-2*(i as i64));
for dt in F128_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(){
let dt=Ratio::new(dt.num.widen_256(),dt.den.widen_256());
upper_bound=Bound::Included(dt);
for dt in Fixed::<2,64>::zeroes2((n.dot(body.position-mesh.vert(vert_id)))*2,n.dot(body.velocity)*2,n.dot(body.acceleration)){
if body_time.le_ratio(dt)&&dt.lt_ratio(best_time)&&n.dot(body.extrapolated_velocity_ratio_dt(dt)).is_negative(){
let dt=Ratio::new(dt.num.fix_4(),dt.den.fix_4());
best_time=dt;
best_transition=Transition::Next(FEV::Vert(vert_id),dt);
break;
}
@@ -153,30 +108,36 @@ impl<F:Copy,M:MeshQuery<Normal=Vector3<F>,Offset=F256_128,Position=Planar64Vec3,
},
&FEV::Vert(vert_id)=>{
//test each edge collision time, ignoring roots with zero or conflicting derivative
mesh.for_each_vert_edge(vert_id,|directed_edge_id|{
for &directed_edge_id in mesh.vert_edges(vert_id).iter(){
//edge is directed away from vertex, but we want the dot product to turn out negative
let n=-mesh.directed_edge_n(directed_edge_id);
for dt in F128_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(){
let dt=Ratio::new(dt.num.widen_256(),dt.den.widen_256());
upper_bound=Bound::Included(dt);
for dt in Fixed::<2,64>::zeroes2((n.dot(body.position-mesh.vert(vert_id)))*2,n.dot(body.velocity)*2,n.dot(body.acceleration)){
if body_time.le_ratio(dt)&&dt.lt_ratio(best_time)&&n.dot(body.extrapolated_velocity_ratio_dt(dt)).is_negative(){
let dt=Ratio::new(dt.num.fix_4(),dt.den.fix_4());
best_time=dt;
best_transition=Transition::Next(FEV::Edge(directed_edge_id.as_undirected()),dt);
break;
}
}
});
}
//if none:
},
}
best_transition
}
pub fn crawl(mut self,mesh:&M,trajectory:&Trajectory,lower_bound:Bound<&Time>,upper_bound:Bound<&Time>)->CrawlResult<M>{
let mut lower_bound=lower_bound.map(|&t|into_giga_time(t,trajectory.time));
let upper_bound=upper_bound.map(|&t|into_giga_time(t,trajectory.time));
pub fn crawl(mut self,mesh:&M,relative_body:&Body,start_time:Time,time_limit:Time)->CrawlResult<M>{
let mut body_time={
let r=(start_time-relative_body.time).to_ratio();
Ratio::new(r.num.fix_4(),r.den.fix_4())
};
let time_limit={
let r=(time_limit-relative_body.time).to_ratio();
Ratio::new(r.num.fix_4(),r.den.fix_4())
};
for _ in 0..20{
match self.next_transition(mesh,trajectory,lower_bound,upper_bound){
match self.next_transition(body_time,mesh,relative_body,time_limit){
Transition::Miss=>return CrawlResult::Miss(self),
Transition::Next(next_fev,next_time)=>(self,lower_bound)=(next_fev,Bound::Included(next_time)),
Transition::Next(next_fev,next_time)=>(self,body_time)=(next_fev,next_time),
Transition::Hit(face,time)=>return CrawlResult::Hit(face,time),
}
}

View File

@@ -1,11 +1,7 @@
mod body;
mod face_crawler;
mod mesh_query;
mod minkowski;
mod model;
mod push_solve;
mod minimum_difference;
mod minimum_difference_lua;
mod face_crawler;
mod model;
pub mod physics;

View File

@@ -1,57 +0,0 @@
#[derive(Debug)]
pub enum FEV<M:MeshTopology>{
Vert(M::Vert),
Edge(M::Edge),
Face(M::Face),
}
pub trait UndirectedEdge{
type DirectedEdge:DirectedEdge<UndirectedEdge=Self>;
fn as_directed(self,parity:bool)->Self::DirectedEdge;
}
pub trait DirectedEdge{
type UndirectedEdge:UndirectedEdge<DirectedEdge=Self>;
fn as_undirected(self)->Self::UndirectedEdge;
fn parity(&self)->bool;
fn reverse(self)->Self
where
Self:Sized
{
let parity=!self.parity();
self.as_undirected().as_directed(parity)
}
}
pub trait MeshTopology{
type Face;
type Edge:UndirectedEdge<DirectedEdge=Self::DirectedEdge>;
type DirectedEdge:DirectedEdge<UndirectedEdge=Self::Edge>;
type Vert;
fn for_each_vert_edge(&self,vert_id:Self::Vert,f:impl FnMut(Self::DirectedEdge));
fn for_each_vert_face(&self,vert_id:Self::Vert,f:impl FnMut(Self::Face));
fn edge_faces(&self,edge_id:Self::Edge)->impl AsRef<[Self::Face;2]>;
fn edge_verts(&self,edge_id:Self::Edge)->impl AsRef<[Self::Vert;2]>;
#[expect(unused)]
fn for_each_face_vert(&self,face_id:Self::Face,f:impl FnMut(Self::Vert));
fn for_each_face_edge(&self,face_id:Self::Face,f:impl FnMut(Self::DirectedEdge));
}
// Make face_nd d value relative
// euclidean point?
// Simplex physics
// Directed edge necessary?
// recursive for_each function calls
// define faces from vertices (Fixed<2> vs Fixed<3>)
pub trait MeshQuery:MeshTopology{
type Position;
type Direction;
type Normal;
type Offset;
fn vert(&self,vert_id:Self::Vert)->Self::Position;
fn farthest_vert(&self,dir:Self::Direction)->Self::Vert;
/// This must return a point inside the mesh.
fn hint_point(&self)->Self::Position;
fn face_nd(&self,face_id:Self::Face)->(Self::Normal,Self::Offset);
fn edge_n(&self,edge_id:Self::Edge)->Self::Direction;
fn directed_edge_n(&self,directed_edge_id:Self::DirectedEdge)->Self::Direction;
}

View File

@@ -1,962 +0,0 @@
use strafesnet_common::integer::fixed_types::{F128_64,F192_96,F256_128};
use strafesnet_common::integer::vec3;
use strafesnet_common::integer::vec3::Vector3;
use strafesnet_common::integer::{Fixed,Planar64,Planar64Vec3};
use crate::mesh_query::{FEV,DirectedEdge,MeshQuery,MeshTopology};
// TODO: remove mesh invert
use crate::minkowski::{MinkowskiMesh,MinkowskiVert};
// This algorithm is based on Lua code
// written by Trey Reynolds in 2021
type Simplex<const N:usize,Vert>=[Vert;N];
#[derive(Clone,Copy,Debug)]
enum Simplex1_3<Vert>{
Simplex1(Simplex<1,Vert>),
Simplex2(Simplex<2,Vert>),
Simplex3(Simplex<3,Vert>),
}
impl<Vert> Simplex1_3<Vert>{
fn push_front(self,v:Vert)->Simplex2_4<Vert>{
match self{
Simplex1_3::Simplex1([v0])=>Simplex2_4::Simplex2([v,v0]),
Simplex1_3::Simplex2([v0,v1])=>Simplex2_4::Simplex3([v,v0,v1]),
Simplex1_3::Simplex3([v0,v1,v2])=>Simplex2_4::Simplex4([v,v0,v1,v2]),
}
}
}
#[derive(Clone,Copy)]
enum Simplex2_4<Vert>{
Simplex2(Simplex<2,Vert>),
Simplex3(Simplex<3,Vert>),
Simplex4(Simplex<4,Vert>),
}
/*
local function absDet(r, u, v, w)
if w then
return math.abs((u - r):Cross(v - r):Dot(w - r))
elseif v then
return (u - r):Cross(v - r).magnitude
elseif u then
return (u - r).magnitude
else
return 1
end
end
*/
impl<Vert> Simplex2_4<Vert>{
fn det_is_zero<M:MeshQuery<Vert=Vert,Position=Planar64Vec3>>(self,mesh:&M)->bool{
match self{
Self::Simplex4([p0,p1,p2,p3])=>{
let p0=mesh.vert(p0);
let p1=mesh.vert(p1);
let p2=mesh.vert(p2);
let p3=mesh.vert(p3);
(p1-p0).cross(p2-p0).dot(p3-p0)==Fixed::ZERO
},
Self::Simplex3([p0,p1,p2])=>{
let p0=mesh.vert(p0);
let p1=mesh.vert(p1);
let p2=mesh.vert(p2);
(p1-p0).cross(p2-p0)==vec3::zero()
},
Self::Simplex2([p0,p1])=>{
let p0=mesh.vert(p0);
let p1=mesh.vert(p1);
p1-p0==vec3::zero()
}
}
}
}
/*
local function choosePerpendicularDirection(d)
local x, y, z = d.x, d.y, d.z
local best = math.min(x*x, y*y, z*z)
if x*x == best then
return Vector3.new(y*y + z*z, -x*y, -x*z)
elseif y*y == best then
return Vector3.new(-x*y, x*x + z*z, -y*z)
else
return Vector3.new(-x*z, -y*z, x*x + y*y)
end
end
*/
fn choose_perpendicular_direction(d:Planar64Vec3)->Planar64Vec3{
let x=d.x.abs();
let y=d.y.abs();
let z=d.z.abs();
if x<y&&x<z{
Vector3::new([Fixed::ZERO,-d.z,d.y])
}else if y<z{
Vector3::new([d.z,Fixed::ZERO,-d.x])
}else{
Vector3::new([-d.y,d.x,Fixed::ZERO])
}
}
const fn choose_any_direction()->Planar64Vec3{
vec3::X
}
fn narrow_dir2(dir:Vector3<F128_64>)->Planar64Vec3{
if dir==vec3::zero(){
return dir.narrow_64().unwrap();
}
let x=dir.x.as_bits().unsigned_abs().bit_width();
let y=dir.y.as_bits().unsigned_abs().bit_width();
let z=dir.z.as_bits().unsigned_abs().bit_width();
let big=x.max(y).max(z);
const MAX_BITS:u32=64+31;
if MAX_BITS<big{
dir>>(big-MAX_BITS)
}else{
dir
}.narrow_64().unwrap()
}
fn narrow_dir3(dir:Vector3<F192_96>)->Planar64Vec3{
if dir==vec3::zero(){
return dir.narrow_64().unwrap();
}
let x=dir.x.as_bits().unsigned_abs().bit_width();
let y=dir.y.as_bits().unsigned_abs().bit_width();
let z=dir.z.as_bits().unsigned_abs().bit_width();
let big=x.max(y).max(z);
const MAX_BITS:u32=96+31;
if MAX_BITS<big{
dir>>(big-MAX_BITS)
}else{
dir
}.narrow_64().unwrap()
}
fn reduce1<M:MeshQuery<Position=Planar64Vec3>>(
[v0]:Simplex<1,M::Vert>,
mesh:&M,
point:Planar64Vec3,
)->Reduced<M::Vert>
where M::Vert:Copy,
{
println!("reduce1");
// --debug.profilebegin("reduceSimplex0")
// local a = a1 - a0
let p0=mesh.vert(v0);
println!("p0={p0}");
// local p = -a
let p=-(p0+point);
println!("p={p}");
// local direction = p
let mut dir=p;
// if direction.magnitude == 0 then
// direction = chooseAnyDirection()
if dir==vec3::zero(){
dir=choose_any_direction();
}
// return direction, a0, a1
Reduced{
dir,
simplex:Simplex1_3::Simplex1([v0]),
}
}
// local function reduceSimplex1(a0, a1, b0, b1)
fn reduce2<M:MeshQuery<Position=Planar64Vec3>>(
[v0,v1]:Simplex<2,M::Vert>,
mesh:&M,
point:Planar64Vec3,
)->Reduced<M::Vert>
where
M::Vert:Copy
{
println!("reduce2");
// --debug.profilebegin("reduceSimplex1")
// local a = a1 - a0
// local b = b1 - b0
let p0=mesh.vert(v0);
let p1=mesh.vert(v1);
// local p = -a
// local u = b - a
let p=-(p0+point);
let u=p1-p0;
// -- modify to take into account the radiuses
// local p_u = p:Dot(u)
let p_u=p.dot(u);
// if p_u >= 0 then
if p_u.is_positive(){
// local direction = u:Cross(p):Cross(u)
let direction=u.cross(p).cross(u);
// if direction.magnitude == 0 then
if direction==vec3::zero(){
return Reduced{
dir:choose_perpendicular_direction(u),
simplex:Simplex1_3::Simplex2([v0,v1]),
};
}
// -- modify the direction to take into account a0R and b0R
// return direction, a0, a1, b0, b1
return Reduced{
dir:narrow_dir3(direction),
simplex:Simplex1_3::Simplex2([v0,v1]),
};
}
// local direction = p
let mut dir=p;
// if direction.magnitude == 0 then
if dir==vec3::zero(){
dir=choose_perpendicular_direction(u);
}
// return direction, a0, a1
Reduced{
dir,
simplex:Simplex1_3::Simplex1([v0]),
}
}
// local function reduceSimplex2(a0, a1, b0, b1, c0, c1)
fn reduce3<M:MeshQuery<Position=Planar64Vec3>>(
[v0,mut v1,v2]:Simplex<3,M::Vert>,
mesh:&M,
point:Planar64Vec3,
)->Reduced<M::Vert>
where
M::Vert:Copy
{
println!("reduce3");
// --debug.profilebegin("reduceSimplex2")
// local a = a1 - a0
// local b = b1 - b0
// local c = c1 - c0
let p0=mesh.vert(v0);
let p1=mesh.vert(v1);
let p2=mesh.vert(v2);
// local p = -a
// local u = b - a
// local v = c - a
let p=-(p0+point);
let mut u=p1-p0;
let v=p2-p0;
// local uv = u:Cross(v)
// local up = u:Cross(p)
// local pv = p:Cross(v)
// local uv_up = uv:Dot(up)
// local uv_pv = uv:Dot(pv)
let mut uv=u.cross(v);
let mut up=u.cross(p);
let pv=p.cross(v);
let uv_up=uv.dot(up);
let uv_pv=uv.dot(pv);
// if uv_up >= 0 and uv_pv >= 0 then
if uv_up.is_positive()&&uv_pv.is_positive(){
// local uvp = uv:Dot(p)
let uvp=uv.dot(p);
// local direction = uvp < 0 and -uv or uv
let direction=if uvp.is_negative(){
-uv
}else{
uv
};
// return direction, a0, a1, b0, b1, c0, c1
return Reduced{
dir:narrow_dir2(direction),
simplex:Simplex1_3::Simplex3([v0,v1,v2]),
};
}
// local u_u = u:Dot(u)
// local v_v = v:Dot(v)
// local uDist = uv_up/(u_u*v.magnitude)
// local vDist = uv_pv/(v_v*u.magnitude)
// local minDist2 = math.min(uDist, vDist)
let u_dist=uv_up*v.length();
let v_dist=uv_pv*u.length();
// if vDist == minDist2 then
if v_dist<u_dist{
u=v;
up=-pv;
uv=-uv;
// b0 = c0
// b1 = c1
v1=v2;
}
// local p_u = p:Dot(u)
let p_u=p.dot(u);
// if p_u >= 0 then
if p_u.is_positive(){
// local direction = up:Cross(u)
let direction=up.cross(u);
// if direction.magnitude == 0 then
if direction==vec3::zero(){
// direction = uv
return Reduced{
dir:narrow_dir2(uv),
simplex:Simplex1_3::Simplex2([v0,v1]),
};
}
// return direction, a0, a1, b0, b1
return Reduced{
dir:narrow_dir3(direction),
simplex:Simplex1_3::Simplex2([v0,v1]),
};
}
// local direction = p
let dir=p;
// if direction.magnitude == 0 then
if dir==vec3::zero(){
// direction = uv
return Reduced{
dir:narrow_dir2(uv),
simplex:Simplex1_3::Simplex1([v0]),
};
}
// return direction, a0, a0
Reduced{
dir,
simplex:Simplex1_3::Simplex1([v0]),
}
}
// local function reduceSimplex3(a0, a1, b0, b1, c0, c1, d0, d1)
fn reduce4<M:MeshQuery<Position=Planar64Vec3>>(
[v0,mut v1,mut v2,v3]:Simplex<4,M::Vert>,
mesh:&M,
point:Planar64Vec3,
)->Reduce<M::Vert>
where
M::Vert:Copy
{
println!("reduce4");
// --debug.profilebegin("reduceSimplex3")
// local a = a1 - a0
// local b = b1 - b0
// local c = c1 - c0
// local d = d1 - d0
let p0=mesh.vert(v0);
let p1=mesh.vert(v1);
let p2=mesh.vert(v2);
let p3=mesh.vert(v3);
// local p = -a
// local u = b - a
// local v = c - a
// local w = d - a
let p=-(p0+point);
let mut u=p1-p0;
let mut v=p2-p0;
let w=p3-p0;
// local uv = u:Cross(v)
// local vw = v:Cross(w)
// local wu = w:Cross(u)
// local uvw = uv:Dot(w)
// local pvw = vw:Dot(p)
// local upw = wu:Dot(p)
// local uvp = uv:Dot(p)
let mut uv=u.cross(v);
let vw=v.cross(w);
let wu=w.cross(u);
let uv_w=uv.dot(w);
let pv_w=vw.dot(p);
let up_w=wu.dot(p);
let uv_p=uv.dot(p);
// if pvw/uvw >= 0 and upw/uvw >= 0 and uvp/uvw >= 0 then
if pv_w.div_sign(uv_w).is_positive()
&&up_w.div_sign(uv_w).is_positive()
&&uv_p.div_sign(uv_w).is_positive(){
// origin is contained, this is a positive detection
// local direction = Vector3.new(0, 0, 0)
// return direction, a0, a1, b0, b1, c0, c1, d0, d1
return Reduce::Escape([v0,v1,v2,v3]);
}
// local uvwSign = uvw < 0 and -1 or uvw > 0 and 1 or 0
// local uvDist = uvp*uvwSign/uv.magnitude
// local vwDist = pvw*uvwSign/vw.magnitude
// local wuDist = upw*uvwSign/wu.magnitude
// local minDist3 = math.min(uvDist, vwDist, wuDist)
let uv_dist=uv_p.mul_sign(uv_w);
let vw_dist=pv_w.mul_sign(uv_w);
let wu_dist=up_w.mul_sign(uv_w);
let wu_len=wu.length();
let uv_len=uv.length();
let vw_len=vw.length();
if vw_dist*wu_len<wu_dist*vw_len{
// if vwDist == minDist3 then
if vw_dist*uv_len<uv_dist*vw_len{
(u,v)=(v,w);
uv=vw;
// uv_p=pv_w; // unused
// b0, c0 = c0, d0
// b1, c1 = c1, d1
(v1,v2)=(v2,v3);
}
}else{
// elseif wuDist == minDist3 then
if wu_dist*uv_len<uv_dist*wu_len{
(u,v)=(w,u);
uv=wu;
// uv_p=up_w; // unused
// b0, c0 = d0, b0
// b1, c1 = d1, b1
// before [a,b,c,d]
(v1,v2)=(v3,v1);
// after [a,d,b]
}
}
// local up = u:Cross(p)
// local pv = p:Cross(v)
// local uv_up = uv:Dot(up)
// local uv_pv = uv:Dot(pv)
let mut up=u.cross(p);
let pv=p.cross(v);
let uv_up=uv.dot(up);
let uv_pv=uv.dot(pv);
// if uv_up >= 0 and uv_pv >= 0 then
if uv_up.is_positive()&&uv_pv.is_positive(){
// local direction = uvw < 0 and uv or -uv
// return direction, a0, a1, b0, b1, c0, c1
let dir=if uv_w.is_negative(){
narrow_dir2(uv)
}else{
narrow_dir2(-uv)
};
return Reduce::Reduced(Reduced{
dir,
simplex:Simplex1_3::Simplex3([v0,v1,v2]),
});
}
// local u_u = u:Dot(u)
// local v_v = v:Dot(v)
// local uDist = uv_up/(u_u*v.magnitude)
// local vDist = uv_pv/(v_v*u.magnitude)
// local minDist2 = math.min(uDist, vDist)
let u_dist=uv_up*v.length();
let v_dist=uv_pv*u.length();
// if vDist == minDist2 then
if v_dist<u_dist{
u=v;
up=-pv;
uv=-uv;
// b0 = c0
// b1 = c1
v1=v2;
}
// local p_u = p:Dot(u)
let p_u=p.dot(u);
// if p_u >= 0 then
if p_u.is_positive(){
// local direction = up:Cross(u)
let direction=up.cross(u);
// if direction.magnitude == 0 then
if direction==vec3::zero(){
// direction = uvw < 0 and uv or -uv
// return direction, a0, a1, b0, b1
let dir=if uv_w.is_negative(){
narrow_dir2(uv)
}else{
narrow_dir2(-uv)
};
return Reduce::Reduced(Reduced{
dir,
simplex:Simplex1_3::Simplex2([v0,v1]),
});
}
// return direction, a0, a1, b0, b1
return Reduce::Reduced(Reduced{
dir:narrow_dir3(direction),
simplex:Simplex1_3::Simplex2([v0,v1]),
});
}
// local direction = p
let dir=p;
// if direction.magnitude == 0 then
if dir==vec3::zero(){
// direction = uvw < 0 and uv or -uv
let dir=if uv_w.is_negative(){
narrow_dir2(uv)
}else{
narrow_dir2(-uv)
};
return Reduce::Reduced(Reduced{
dir,
simplex:Simplex1_3::Simplex1([v0]),
});
}
// return direction, a0, a1
Reduce::Reduced(Reduced{
dir,
simplex:Simplex1_3::Simplex1([v0]),
})
}
struct Reduced<Vert>{
dir:Planar64Vec3,
simplex:Simplex1_3<Vert>,
}
enum Reduce<Vert>{
Escape(Simplex<4,Vert>),
Reduced(Reduced<Vert>),
}
impl<Vert> Simplex2_4<Vert>{
fn reduce<M:MeshQuery<Vert=Vert,Position=Planar64Vec3>>(self,mesh:&M,point:Planar64Vec3)->Reduce<Vert>
where
M::Vert:Copy
{
match self{
Self::Simplex2(simplex)=>Reduce::Reduced(reduce2(simplex,mesh,point)),
Self::Simplex3(simplex)=>Reduce::Reduced(reduce3(simplex,mesh,point)),
Self::Simplex4(simplex)=>reduce4(simplex,mesh,point),
}
}
}
//infinity fev algorithm state transition
#[derive(Debug)]
enum Transition<Vert>{
Done,//found closest vert, no edges are better
Vert(Vert),//transition to vert
}
enum EV<M:MeshTopology>{
Vert(M::Vert),
Edge(M::Edge),
}
impl<M:MeshTopology> From<EV<M>> for FEV<M>{
fn from(value:EV<M>)->Self{
match value{
EV::Vert(minkowski_vert)=>FEV::Vert(minkowski_vert),
EV::Edge(minkowski_edge)=>FEV::Edge(minkowski_edge),
}
}
}
trait Contains{
fn contains(&self,point:Planar64Vec3)->bool;
}
// convenience type to check if a point is within some threshold of a plane.
struct ThickPlane{
point:Planar64Vec3,
normal:Vector3<F128_64>,
epsilon:F192_96,
}
impl ThickPlane{
fn new<M:MeshQuery<Position=Planar64Vec3>>(mesh:&M,[v0,v1,v2]:Simplex<3,M::Vert>)->Self{
let p0=mesh.vert(v0);
let p1=mesh.vert(v1);
let p2=mesh.vert(v2);
let point=p0;
let normal=(p1-p0).cross(p2-p0);
// Allow ~ 2*sqrt(3) units of thickness on the plane
// This is to account for the variance of two voxels across the longest diagonal
let epsilon=(normal.length()*(Planar64::EPSILON*3)).wrap_192();
Self{point,normal,epsilon}
}
}
impl Contains for ThickPlane{
fn contains(&self,point:Planar64Vec3)->bool{
(point-self.point).dot(self.normal).abs()<=self.epsilon
}
}
struct ThickLine{
point:Planar64Vec3,
dir:Planar64Vec3,
epsilon:F256_128,
}
impl ThickLine{
fn new<M:MeshQuery<Position=Planar64Vec3>>(mesh:&M,[v0,v1]:Simplex<2,M::Vert>)->Self{
let p0=mesh.vert(v0);
let p1=mesh.vert(v1);
let point=p0;
let dir=p1-p0;
// Allow ~ 2*sqrt(3) units of thickness on the plane
// This is to account for the variance of two voxels across the longest diagonal
let epsilon=(dir.length_squared()*(Planar64::EPSILON*3)).widen_256();
Self{point,dir,epsilon}
}
}
impl Contains for ThickLine{
fn contains(&self,point:Planar64Vec3)->bool{
(point-self.point).cross(self.dir).length_squared()<=self.epsilon
}
}
struct EVFinder<'a,M,C>{
mesh:&'a M,
constraint:C,
best_distance_squared:F128_64,
}
impl<M:MeshQuery<Position=Planar64Vec3>,C:Contains> EVFinder<'_,M,C>
where
M::Vert:Copy,
M::DirectedEdge:Copy,
{
fn next_transition_vert(&mut self,vert_id:M::Vert,point:Planar64Vec3)->Transition<M::Vert>{
let mut best_transition=Transition::Done;
self.mesh.for_each_vert_edge(vert_id,|directed_edge_id|{
//test if this edge's opposite vertex closer
let edge_verts=self.mesh.edge_verts(directed_edge_id.as_undirected());
//select opposite vertex
let test_vert_id=edge_verts.as_ref()[directed_edge_id.parity() as usize];
let test_pos=self.mesh.vert(test_vert_id);
let diff=point-test_pos;
let distance_squared=diff.dot(diff);
// ensure test_vert_id is coplanar to simplex
if distance_squared<self.best_distance_squared&&self.constraint.contains(test_pos){
best_transition=Transition::Vert(test_vert_id);
self.best_distance_squared=distance_squared;
}
});
best_transition
}
fn final_ev(&mut self,vert_id:M::Vert,point:Planar64Vec3)->EV<M>{
let mut best_transition=EV::Vert(vert_id);
let vert_pos=self.mesh.vert(vert_id);
let diff=point-vert_pos;
self.mesh.for_each_vert_edge(vert_id,|directed_edge_id|{
//test if this edge is closer
let edge_verts=self.mesh.edge_verts(directed_edge_id.as_undirected());
let test_vert_id=edge_verts.as_ref()[directed_edge_id.parity() as usize];
let test_pos=self.mesh.vert(test_vert_id);
let edge_n=test_pos-vert_pos;
let d=edge_n.dot(diff);
//test the edge
let edge_nn=edge_n.dot(edge_n);
// ensure edge contains closest point and directed_edge_id is coplanar to simplex
if !d.is_negative()&&d<=edge_nn&&self.constraint.contains(test_pos){
let distance_squared={
let c=diff.cross(edge_n);
//wrap for speed
(c.dot(c)/edge_nn).divide().wrap_128()
};
if distance_squared<=self.best_distance_squared{
best_transition=EV::Edge(directed_edge_id.as_undirected());
self.best_distance_squared=distance_squared;
}
}
});
best_transition
}
fn crawl_boundaries(&mut self,mut vert_id:M::Vert,point:Planar64Vec3)->EV<M>
where
M::Vert:Copy
{
loop{
match self.next_transition_vert(vert_id,point){
Transition::Done=>return self.final_ev(vert_id,point),
Transition::Vert(new_vert_id)=>vert_id=new_vert_id,
}
}
}
}
/// This function hops along parallel vertices until it finds the EV which contains the closest point to `point`.
fn crawl_to_closest_ev<M:MeshQuery<Position=Planar64Vec3>>(mesh:&M,simplex:Simplex<2,M::Vert>,point:Planar64Vec3)->EV<M>
where
M::Vert:Copy,
M::DirectedEdge:Copy,
{
// naively start at the closest vertex
// the closest vertex is not necessarily the one with the fewest boundary hops
// but it doesn't matter, we will get there regardless.
let (vert_id,best_distance_squared)=simplex.into_iter().map(|vert_id|{
let diff=point-mesh.vert(vert_id);
(vert_id,diff.dot(diff))
}).min_by_key(|&(_,d)|d).unwrap();
let constraint=ThickLine::new(mesh,simplex);
let mut finder=EVFinder{constraint,mesh,best_distance_squared};
//start on any vertex
//cross uncrossable vertex-edge boundaries until you find the closest vertex or edge
//cross edge-face boundary if it's uncrossable
finder.crawl_boundaries(vert_id,point)
}
/// This function hops along connected vertices until it finds the FEV which contains the closest point to `point`.
fn crawl_to_closest_fev<'a>(mesh:&MinkowskiMesh<'a>,simplex:Simplex<3,MinkowskiVert>,point:Planar64Vec3)->FEV::<MinkowskiMesh<'a>>{
// naively start at the closest vertex
// the closest vertex is not necessarily the one with the fewest boundary hops
// but it doesn't matter, we will get there regardless.
let (vert_id,best_distance_squared)=simplex.into_iter().map(|vert_id|{
let diff=point-mesh.vert(vert_id);
(vert_id,diff.dot(diff))
}).min_by_key(|&(_,d)|d).unwrap();
let constraint=ThickPlane::new(mesh,simplex);
let mut finder=EVFinder{constraint,mesh,best_distance_squared};
//start on any vertex
//cross uncrossable vertex-edge boundaries until you find the closest vertex or edge
//cross edge-face boundary if it's uncrossable
match finder.crawl_boundaries(vert_id,point){
//if a vert is returned, it is the closest point to the infinity point
EV::Vert(vert_id)=>FEV::Vert(vert_id),
EV::Edge(edge_id)=>{
//cross to face if we are on the wrong side
let edge_n=mesh.edge_n(edge_id);
// point is multiplied by two because vert_sum sums two vertices.
let delta_pos=point*2-{
let &[v0,v1]=mesh.edge_verts(edge_id).as_ref();
mesh.vert(v0)+mesh.vert(v1)
};
for (i,&face_id) in mesh.edge_faces(edge_id).as_ref().iter().enumerate(){
//test if this face is closer
let (face_n,d)=mesh.face_nd(face_id);
//if test point is behind face, the face is invalid
// TODO: find out why I thought of this backwards
if !(face_n.dot(point)-d).is_positive(){
println!("behind");
continue;
}
//edge-face boundary nd, n facing out of the face towards the edge
let boundary_n=face_n.cross(edge_n)*(i as i64*2-1);
let boundary_d=boundary_n.dot(delta_pos);
//is test point behind edge, i.e. contained in the face
if !boundary_d.is_positive(){
//both faces cannot pass this condition, return early if one does.
return FEV::Face(face_id);
}else{
println!("boundary_d is positive");
}
}
FEV::Edge(edge_id)
},
}
}
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;
// TODO: remove mesh negation
minimum_difference::<ENABLE_FAST_FAIL,_,_>(&-mesh,point,
// on_exact
|is_intersecting,simplex|{
println!("on_exact simplex={simplex:?}");
if is_intersecting{
return None;
}
// Convert simplex to FEV
// Vertices must be inverted since the mesh is inverted
Some(match simplex{
Simplex1_3::Simplex1([v0])=>FEV::Vert(-v0),
Simplex1_3::Simplex2([v0,v1])=>{
// invert
let (v0,v1)=(-v0,-v1);
let ev=crawl_to_closest_ev(mesh,[v0,v1],point);
if !matches!(ev,EV::Edge(_)){
println!("I can't believe it's not an edge!");
}
ev.into()
},
Simplex1_3::Simplex3([v0,v1,v2])=>{
// invert
let (v0,v1,v2)=(-v0,-v1,-v2);
// 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
// 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);
if !matches!(fev,FEV::Face(_)){
println!("I can't believe it's not a face!");
}
fev
},
})
},
// on_escape
|_simplex|{
// intersection is guaranteed at this point
// local norm, dist, u0, u1, v0, v1, w0, w1 = expand(queryP, queryQ, a0, a1, b0, b1, c0, c1, d0, d1, 1e-5)
// let simplex=refine_to_exact(mesh,simplex);
None
},
// fast_fail value is irrelevant and will never be returned!
||unreachable!()
)
}
pub fn contains_point(mesh:&MinkowskiMesh<'_>,point:Planar64Vec3)->bool{
const ENABLE_FAST_FAIL:bool=true;
// TODO: remove mesh negation
minimum_difference::<ENABLE_FAST_FAIL,_,_>(&-mesh,point,
// on_exact
|is_intersecting,_simplex|{
is_intersecting
},
// on_escape
|_simplex|{
// intersection is guaranteed at this point
true
},
// fast_fail value
||false
)
}
// local function minimumDifference(
// queryP, radiusP,
// queryQ, radiusQ,
// exitRadius, testIntersection
// )
fn minimum_difference<const ENABLE_FAST_FAIL:bool,T,M:MeshQuery<Position=Planar64Vec3,Direction=Planar64Vec3>>(
mesh:&M,
point:Planar64Vec3,
on_exact:impl FnOnce(bool,Simplex1_3<M::Vert>)->T,
on_escape:impl FnOnce(Simplex<4,M::Vert>)->T,
on_fast_fail:impl FnOnce()->T,
)->T
where
M::Vert:Copy
{
// local initialAxis = queryQ() - queryP()
// local new_point_p = queryP(initialAxis)
// local new_point_q = queryQ(-initialAxis)
// local direction, a0, a1, b0, b1, c0, c1, d0, d1
let mut initial_axis=mesh.hint_point()+point;
// degenerate case
if initial_axis==vec3::zero(){
initial_axis=choose_any_direction();
}
println!("initial_axis={initial_axis}");
let last_point=mesh.farthest_vert(-initial_axis);
// this represents the 'a' value in the commented code
let mut last_pos=mesh.vert(last_point);
let Reduced{dir:mut direction,simplex:mut simplex_small}=reduce1([last_point],mesh,point);
// exitRadius = testIntersection and 0 or exitRadius or 1/0
// for _ = 1, 100 do
loop{
println!("direction={direction}");
// new_point_p = queryP(-direction)
// new_point_q = queryQ(direction)
// local next_point = new_point_q - new_point_p
let next_point=mesh.farthest_vert(direction);
let next_pos=mesh.vert(next_point);
// if -direction:Dot(next_point) > (exitRadius + radiusP + radiusQ)*direction.magnitude then
let d=direction.dot(next_pos+point);
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();
}
let simplex_big=simplex_small.push_front(next_point);
// if
// direction:Dot(next_point - a) <= 0 or
// absDet(next_point, a, b, c) < 1e-6
let d1=direction.dot(next_pos-last_pos);
let cond2=simplex_big.det_is_zero(mesh);
println!("d1={d1:?} cond2={cond2}");
if d1.is_negative()||cond2{
println!("on_exact");
// Found enough information to compute the exact closest point.
// local norm = direction.unit
// local dist = a:Dot(norm)
// local hits = -dist < radiusP + radiusQ
let is_intersecting=(last_pos+point).dot(direction).is_positive();
return on_exact(is_intersecting,simplex_small);
}
// direction, a0, a1, b0, b1, c0, c1, d0, d1 = reduceSimplex(new_point_p, new_point_q, a0, a1, b0, b1, c0, c1)
match simplex_big.reduce(mesh,point){
// if a and b and c and d then
Reduce::Escape(simplex)=>{
println!("on_escape");
// Enough information to conclude that the meshes are intersecting.
// Topology information is computed if needed.
return on_escape(simplex);
},
Reduce::Reduced(reduced)=>{
direction=reduced.dir;
simplex_small=reduced.simplex;
},
}
// next loop this will be a
last_pos=next_pos;
}
}
#[cfg(test)]
mod test{
use super::*;
use crate::model::{PhysicsMesh,PhysicsMeshView};
fn mesh_contains_point(mesh:PhysicsMeshView<'_>,point:Planar64Vec3)->bool{
const ENABLE_FAST_FAIL:bool=true;
// TODO: remove mesh negation
minimum_difference::<ENABLE_FAST_FAIL,_,_>(&mesh,point,
// on_exact
|is_intersecting,_simplex|{
is_intersecting
},
// on_escape
|_simplex|{
// intersection is guaranteed at this point
true
},
// fast_fail value
||false
)
}
#[test]
fn test_cube_points(){
let mesh=PhysicsMesh::unit_cube();
let mesh_view=mesh.complete_mesh_view();
for x in -2..=2{
for y in -2..=2{
for z in -2..=2{
let point=vec3::int(x,y,z)>>1;
assert!(mesh_contains_point(mesh_view,point),"Mesh did not contain point {point}");
}
}
}
}
}

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,413 +0,0 @@
use core::ops::{Bound,RangeBounds};
use strafesnet_common::integer::fixed_types::{F192_96,F256_128,F512_256};
use strafesnet_common::integer::vec3::Vector3;
use strafesnet_common::integer::{Planar64Vec3,Ratio,Fixed};
use crate::model::into_giga_time;
use crate::model::{SubmeshVertId,SubmeshEdgeId,SubmeshDirectedEdgeId,SubmeshFaceId,TransformedMesh,GigaTime};
use crate::mesh_query::{MeshQuery,MeshTopology,DirectedEdge,UndirectedEdge};
use crate::physics::{Time,Trajectory};
struct AsRefHelper<T>(T);
impl<T> AsRef<T> for AsRefHelper<T>{
fn as_ref(&self)->&T{
&self.0
}
}
//Note that a face on a minkowski mesh refers to a pair of fevs on the meshes it's summed from
//(face,vertex)
//(edge,edge)
//(vertex,face)
#[derive(Clone,Copy,Debug,Eq,PartialEq)]
pub enum MinkowskiVert{
VertVert(SubmeshVertId,SubmeshVertId),
}
// TODO: remove this
impl core::ops::Neg for MinkowskiVert{
type Output=Self;
fn neg(self)->Self::Output{
match self{
MinkowskiVert::VertVert(v0,v1)=>MinkowskiVert::VertVert(v1,v0),
}
}
}
#[derive(Clone,Copy,Debug)]
pub enum MinkowskiEdge{
VertEdge(SubmeshVertId,SubmeshEdgeId),
EdgeVert(SubmeshEdgeId,SubmeshVertId),
//EdgeEdge when edges are parallel
}
impl UndirectedEdge for MinkowskiEdge{
type DirectedEdge=MinkowskiDirectedEdge;
fn as_directed(self,parity:bool)->Self::DirectedEdge{
match self{
MinkowskiEdge::VertEdge(v0,e1)=>MinkowskiDirectedEdge::VertEdge(v0,e1.as_directed(parity)),
MinkowskiEdge::EdgeVert(e0,v1)=>MinkowskiDirectedEdge::EdgeVert(e0.as_directed(parity),v1),
}
}
}
#[derive(Clone,Copy,Debug)]
pub enum MinkowskiDirectedEdge{
VertEdge(SubmeshVertId,SubmeshDirectedEdgeId),
EdgeVert(SubmeshDirectedEdgeId,SubmeshVertId),
//EdgeEdge when edges are parallel
}
impl DirectedEdge for MinkowskiDirectedEdge{
type UndirectedEdge=MinkowskiEdge;
fn as_undirected(self)->Self::UndirectedEdge{
match self{
MinkowskiDirectedEdge::VertEdge(v0,e1)=>MinkowskiEdge::VertEdge(v0,e1.as_undirected()),
MinkowskiDirectedEdge::EdgeVert(e0,v1)=>MinkowskiEdge::EdgeVert(e0.as_undirected(),v1),
}
}
fn parity(&self)->bool{
match self{
MinkowskiDirectedEdge::VertEdge(_,e)
|MinkowskiDirectedEdge::EdgeVert(e,_)=>e.parity(),
}
}
}
#[derive(Clone,Copy,Debug,Hash)]
pub enum MinkowskiFace{
VertFace(SubmeshVertId,SubmeshFaceId),
EdgeEdge(SubmeshEdgeId,SubmeshEdgeId,bool),
FaceVert(SubmeshFaceId,SubmeshVertId),
//EdgeFace
//FaceEdge
//FaceFace
}
#[derive(Debug)]
pub struct MinkowskiMesh<'a>{
pub mesh0:TransformedMesh<'a>,
pub mesh1:TransformedMesh<'a>,
}
// TODO: remove this
impl<'a> core::ops::Neg for &MinkowskiMesh<'a>{
type Output=MinkowskiMesh<'a>;
fn neg(self)->Self::Output{
MinkowskiMesh::minkowski_sum(self.mesh1,self.mesh0)
}
}
impl MinkowskiMesh<'_>{
pub fn minkowski_sum<'a>(mesh0:TransformedMesh<'a>,mesh1:TransformedMesh<'a>)->MinkowskiMesh<'a>{
MinkowskiMesh{
mesh0,
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)>{
let start_position=match range.start_bound(){
Bound::Included(time)=>trajectory.extrapolated_position(*time),
Bound::Excluded(time)=>trajectory.extrapolated_position(*time),
Bound::Unbounded=>trajectory.position,
};
let fev=crate::minimum_difference::closest_fev_not_inside(self,start_position)?;
println!("fev={fev:?}");
//continue forwards along the body parabola
fev.crawl(self,trajectory,range.start_bound(),range.end_bound()).hit()
}
pub fn predict_collision_out(&self,trajectory:&Trajectory,range:impl RangeBounds<Time>)->Option<(MinkowskiFace,GigaTime)>{
let (lower_bound,upper_bound)=(range.start_bound(),range.end_bound());
// TODO: handle unbounded collision using infinity fev
let start_position=match upper_bound{
Bound::Included(time)=>trajectory.extrapolated_position(*time),
Bound::Excluded(time)=>trajectory.extrapolated_position(*time),
Bound::Unbounded=>trajectory.position,
};
let fev=crate::minimum_difference::closest_fev_not_inside(self,start_position)?;
// swap and negate bounds to do a time inversion
let (lower_bound,upper_bound)=(upper_bound.map(|&t|-t),lower_bound.map(|&t|-t));
let time_reversed_trajectory=-trajectory;
//continue backwards along the body parabola
fev.crawl(self,&time_reversed_trajectory,lower_bound.as_ref(),upper_bound.as_ref()).hit()
//no need to test -time<time_limit because of the first step
.map(|(face,time)|(face,-time))
}
pub fn predict_collision_face_out(&self,trajectory:&Trajectory,range:impl RangeBounds<Time>,contact_face_id:MinkowskiFace)->Option<(MinkowskiDirectedEdge,GigaTime)>{
// TODO: make better
use crate::face_crawler::{low,upp};
//no algorithm needed, there is only one state and two cases (Edge,None)
//determine when it passes an edge ("sliding off" case)
let start_time=range.start_bound().map(|&t|(t-trajectory.time).to_ratio());
let mut best_time=range.end_bound().map(|&t|into_giga_time(t,trajectory.time));
let mut best_edge=None;
let face_n=self.face_nd(contact_face_id).0;
self.for_each_face_edge(contact_face_id,|directed_edge_id|{
let edge_n=self.directed_edge_n(directed_edge_id);
//f x e points in
let n=face_n.cross(edge_n);
let &[v0,v1]=self.edge_verts(directed_edge_id.as_undirected()).as_ref();
let d=n.dot(self.vert(v0)+self.vert(v1));
//WARNING! d outside of *2
//WARNING: truncated precision
//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()){
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_edge=Some((directed_edge_id,dt));
break;
}
}
});
best_edge
}
pub fn contains_point(&self,point:Planar64Vec3)->bool{
crate::minimum_difference::contains_point(self,point)
}
}
impl MeshQuery for MinkowskiMesh<'_>{
type Direction=Planar64Vec3;
type Position=Planar64Vec3;
type Normal=Vector3<F192_96>;
type Offset=F256_128;
// TODO: relative d
fn face_nd(&self,face_id:MinkowskiFace)->(Self::Normal,Self::Offset){
match face_id{
MinkowskiFace::VertFace(v0,f1)=>{
let (n,d)=self.mesh1.face_nd(f1);
(-n,d-n.dot(self.mesh0.vert(v0)))
},
MinkowskiFace::EdgeEdge(e0,e1,parity)=>{
let edge0_n=self.mesh0.edge_n(e0);
let edge1_n=self.mesh1.edge_n(e1);
let &[e0v0,e0v1]=self.mesh0.edge_verts(e0).as_ref();
let &[e1v0,e1v1]=self.mesh1.edge_verts(e1).as_ref();
let n=edge0_n.cross(edge1_n);
let e0d=n.dot(self.mesh0.vert(e0v0)+self.mesh0.vert(e0v1));
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())
},
MinkowskiFace::FaceVert(f0,v1)=>{
let (n,d)=self.mesh0.face_nd(f0);
(n,d-n.dot(self.mesh1.vert(v1)))
},
}
}
fn vert(&self,vert_id:MinkowskiVert)->Planar64Vec3{
match vert_id{
MinkowskiVert::VertVert(v0,v1)=>{
self.mesh0.vert(v0)-self.mesh1.vert(v1)
},
}
}
fn hint_point(&self)->Planar64Vec3{
self.mesh0.hint_point()-self.mesh1.hint_point()
}
fn farthest_vert(&self,dir:Planar64Vec3)->MinkowskiVert{
MinkowskiVert::VertVert(self.mesh0.farthest_vert(dir),self.mesh1.farthest_vert(-dir))
}
fn edge_n(&self,edge_id:Self::Edge)->Self::Direction{
let &[v0,v1]=self.edge_verts(edge_id).as_ref();
self.vert(v1)-self.vert(v0)
}
fn directed_edge_n(&self,directed_edge_id:Self::DirectedEdge)->Self::Direction{
let &[v0,v1]=self.edge_verts(directed_edge_id.as_undirected()).as_ref();
(self.vert(v1)-self.vert(v0))*((directed_edge_id.parity() as i64)*2-1)
}
}
impl MeshTopology for MinkowskiMesh<'_>{
type Face=MinkowskiFace;
type Edge=MinkowskiEdge;
type DirectedEdge=MinkowskiDirectedEdge;
type Vert=MinkowskiVert;
fn for_each_vert_edge(&self,vert_id:Self::Vert,mut f:impl FnMut(Self::DirectedEdge)){
match vert_id{
MinkowskiVert::VertVert(v0,v1)=>{
//detect shared volume when the other mesh is mirrored along a test edge dir
let v0f={
let mut faces=Vec::new();
self.mesh0.for_each_vert_face(v0,|face|faces.push(face));
faces
};
let v1f={
let mut faces=Vec::new();
self.mesh1.for_each_vert_face(v1,|face|faces.push(face));
faces
};
let v0f_n:Vec<_>=v0f.iter().map(|&face_id|self.mesh0.face_nd(face_id).0).collect();
let v1f_n:Vec<_>=v1f.iter().map(|&face_id|self.mesh1.face_nd(face_id).0).collect();
// scratch vector
let mut face_normals=Vec::with_capacity(v0f.len()+v1f.len());
face_normals.clone_from(&v0f_n);
self.mesh0.for_each_vert_edge(v0,|directed_edge_id|{
let n=self.mesh0.directed_edge_n(directed_edge_id);
let nn=n.dot(n);
// TODO: there's gotta be a better way to do this
// drop faces beyond v0f_n
face_normals.truncate(v0f.len());
// make a set of faces from mesh0's perspective
for face_n in &v1f_n{
//add reflected mesh1 faces
//wrap for speed
face_normals.push(*face_n-(n*face_n.dot(n)*2/nn).divide().wrap_192());
}
if is_empty_volume(&face_normals){
f(MinkowskiDirectedEdge::EdgeVert(directed_edge_id,v1));
}
});
face_normals.clone_from(&v1f_n);
self.mesh1.for_each_vert_edge(v1,|directed_edge_id|{
let n=self.mesh1.directed_edge_n(directed_edge_id);
let nn=n.dot(n);
// drop faces beyond v1f_n
face_normals.truncate(v1f.len());
// make a set of faces from mesh1's perspective
for face_n in &v0f_n{
//wrap for speed
face_normals.push(*face_n-(n*face_n.dot(n)*2/nn).divide().wrap_192());
}
if is_empty_volume(&face_normals){
f(MinkowskiDirectedEdge::VertEdge(v0,directed_edge_id));
}
});
},
}
}
fn for_each_vert_face(&self,_vert_id:Self::Vert,_f:impl FnMut(Self::Face)){
unimplemented!()
}
fn edge_faces(&self,edge_id:Self::Edge)->impl AsRef<[Self::Face;2]>{
match edge_id{
MinkowskiEdge::VertEdge(v0,e1)=>{
//faces are listed backwards from the minkowski mesh
let v0e={
let mut edges=Vec::new();
self.mesh0.for_each_vert_edge(v0,|edge|edges.push(edge));
edges
};
let &[e1f0,e1f1]=self.mesh1.edge_faces(e1).as_ref();
AsRefHelper([(e1f1,false),(e1f0,true)].map(|(edge_face_id1,face_parity)|{
let mut best_edge=None;
let mut best_d:Ratio<F512_256,F512_256>=Ratio::new(Fixed::ZERO,Fixed::ONE);
let edge_face1_n=self.mesh1.face_nd(edge_face_id1).0;
let edge_face1_nn=edge_face1_n.dot(edge_face1_n);
for &directed_edge_id0 in &v0e{
let edge0_n=self.mesh0.directed_edge_n(directed_edge_id0);
//must be behind other face.
let d=edge_face1_n.dot(edge0_n);
if d.is_negative(){
let edge0_nn=edge0_n.dot(edge0_n);
// Assume not every number is huge
// TODO: revisit this
let dd=(d*d)/(edge_face1_nn*edge0_nn);
if best_d<dd{
best_d=dd;
best_edge=Some(directed_edge_id0);
}
}
}
best_edge.map_or(
MinkowskiFace::VertFace(v0,edge_face_id1),
|directed_edge_id0|MinkowskiFace::EdgeEdge(directed_edge_id0.as_undirected(),e1,directed_edge_id0.parity()^face_parity)
)
}))
},
MinkowskiEdge::EdgeVert(e0,v1)=>{
//tracking index with an external variable because .enumerate() is not available
let v1e={
let mut edges=Vec::new();
self.mesh1.for_each_vert_edge(v1,|edge|edges.push(edge));
edges
};
let &[e0f0,e0f1]=self.mesh0.edge_faces(e0).as_ref();
AsRefHelper([(e0f0,true),(e0f1,false)].map(|(edge_face_id0,face_parity)|{
let mut best_edge=None;
let mut best_d:Ratio<F512_256,F512_256>=Ratio::new(Fixed::ZERO,Fixed::ONE);
let edge_face0_n=self.mesh0.face_nd(edge_face_id0).0;
let edge_face0_nn=edge_face0_n.dot(edge_face0_n);
for &directed_edge_id1 in &v1e{
let edge1_n=self.mesh1.directed_edge_n(directed_edge_id1);
let d=edge_face0_n.dot(edge1_n);
if d.is_negative(){
let edge1_nn=edge1_n.dot(edge1_n);
let dd=(d*d)/(edge_face0_nn*edge1_nn);
if best_d<dd{
best_d=dd;
best_edge=Some(directed_edge_id1);
}
}
}
best_edge.map_or(
MinkowskiFace::FaceVert(edge_face_id0,v1),
|directed_edge_id1|MinkowskiFace::EdgeEdge(e0,directed_edge_id1.as_undirected(),directed_edge_id1.parity()^face_parity)
)
}))
},
}
}
fn edge_verts(&self,edge_id:Self::Edge)->impl AsRef<[Self::Vert;2]>{
AsRefHelper(match edge_id{
MinkowskiEdge::VertEdge(v0,e1)=>self.mesh1.edge_verts(e1).as_ref().map(|vert_id1|
MinkowskiVert::VertVert(v0,vert_id1)
),
MinkowskiEdge::EdgeVert(e0,v1)=>self.mesh0.edge_verts(e0).as_ref().map(|vert_id0|
MinkowskiVert::VertVert(vert_id0,v1)
),
})
}
fn for_each_face_vert(&self,_face_id:Self::Face,_f:impl FnMut(Self::Vert)){
unimplemented!()
}
fn for_each_face_edge(&self,face_id:Self::Face,mut f:impl FnMut(Self::DirectedEdge)){
match face_id{
MinkowskiFace::VertFace(v0,f1)=>{
self.mesh1.for_each_face_edge(f1,|edge_id1|
f(MinkowskiDirectedEdge::VertEdge(v0,edge_id1.reverse()))
)
},
MinkowskiFace::EdgeEdge(e0,e1,parity)=>{
let &[e0v0,e0v1]=self.mesh0.edge_verts(e0).as_ref();
let &[e1v0,e1v1]=self.mesh1.edge_verts(e1).as_ref();
//could sort this if ordered edges are needed
//probably just need to reverse this list according to parity
f(MinkowskiDirectedEdge::VertEdge(e0v0,e1.as_directed(parity)));
f(MinkowskiDirectedEdge::EdgeVert(e0.as_directed(!parity),e1v0));
f(MinkowskiDirectedEdge::VertEdge(e0v1,e1.as_directed(!parity)));
f(MinkowskiDirectedEdge::EdgeVert(e0.as_directed(parity),e1v1));
},
MinkowskiFace::FaceVert(f0,v1)=>{
self.mesh0.for_each_face_edge(f0,|edge_id0|
f(MinkowskiDirectedEdge::EdgeVert(edge_id0,v1))
)
},
}
}
}
fn is_empty_volume(normals:&[Vector3<F192_96>])->bool{
let len=normals.len();
for i in 0..len-1{
for j in i+1..len{
let n=normals[i].cross(normals[j]);
let mut d_comp=None;
for k in 0..len{
if k!=i&&k!=j{
let d=n.dot(normals[k]).is_negative();
if let &Some(comp)=&d_comp{
// This is testing if d_comp*d < 0
if comp^d{
return true;
}
}else{
d_comp=Some(d);
}
}
}
}
}
return false;
}
#[test]
fn test_is_empty_volume(){
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_192(),vec3::Y.widen_192(),vec3::Z.widen_192(),vec3::NEG_X.widen_192()]));
}

View File

@@ -1,15 +1,24 @@
use std::borrow::{Borrow,Cow};
use std::collections::{HashSet,HashMap};
use strafesnet_common::integer::fixed_types::{F128_64,F192_96,F256_128};
use core::ops::Range;
use strafesnet_common::integer::vec3::Vector3;
use strafesnet_common::model::{self,MeshId,PolygonIter};
use strafesnet_common::integer::{self,vec3,Fixed,Planar64,Planar64Vec3,Ratio};
use strafesnet_common::physics::Time;
use crate::mesh_query::{MeshQuery,MeshTopology,DirectedEdge,UndirectedEdge};
struct AsRefHelper<T>(T);
impl<T> AsRef<T> for AsRefHelper<T>{
fn as_ref(&self)->&T{
&self.0
type Body=crate::body::Body<strafesnet_common::physics::TimeInner>;
pub trait UndirectedEdge{
type DirectedEdge:Copy+DirectedEdge;
fn as_directed(&self,parity:bool)->Self::DirectedEdge;
}
pub trait DirectedEdge{
type UndirectedEdge:Copy+std::fmt::Debug+UndirectedEdge;
fn as_undirected(&self)->Self::UndirectedEdge;
fn parity(&self)->bool;
//this is stupid but may work fine
fn reverse(&self)-><<Self as DirectedEdge>::UndirectedEdge as UndirectedEdge>::DirectedEdge{
self.as_undirected().as_directed(!self.parity())
}
}
@@ -30,13 +39,13 @@ pub struct SubmeshFaceId(u32);
impl UndirectedEdge for SubmeshEdgeId{
type DirectedEdge=SubmeshDirectedEdgeId;
fn as_directed(self,parity:bool)->SubmeshDirectedEdgeId{
fn as_directed(&self,parity:bool)->SubmeshDirectedEdgeId{
SubmeshDirectedEdgeId(self.0|((parity as u32)<<(u32::BITS-1)))
}
}
impl DirectedEdge for SubmeshDirectedEdgeId{
type UndirectedEdge=SubmeshEdgeId;
fn as_undirected(self)->SubmeshEdgeId{
fn as_undirected(&self)->SubmeshEdgeId{
SubmeshEdgeId(self.0&!(1<<(u32::BITS-1)))
}
fn parity(&self)->bool{
@@ -44,34 +53,56 @@ impl DirectedEdge for SubmeshDirectedEdgeId{
}
}
//Vertex <-> Edge <-> Face -> Collide
#[derive(Debug)]
pub enum FEV<M:MeshQuery>{
Face(M::Face),
Edge(<M::Edge as DirectedEdge>::UndirectedEdge),
Vert(M::Vert),
}
//use Unit32 #[repr(C)] for map files
#[derive(Clone,Copy,Debug,Hash,Eq,PartialEq)]
#[derive(Clone,Hash,Eq,PartialEq)]
struct Face{
normal:Planar64Vec3,
dot:Planar64,
}
#[derive(Debug)]
struct Vert(Planar64Vec3);
#[derive(Debug)]
pub trait MeshQuery{
type Face:Clone;
type Edge:Clone+DirectedEdge;
type Vert:Clone;
// Vertex must be Planar64Vec3 because it represents an actual position
type Normal;
type Offset;
fn edge_n(&self,edge_id:<Self::Edge as DirectedEdge>::UndirectedEdge)->Planar64Vec3{
let verts=self.edge_verts(edge_id);
self.vert(verts[1].clone())-self.vert(verts[0].clone())
}
fn directed_edge_n(&self,directed_edge_id:Self::Edge)->Planar64Vec3{
let verts=self.edge_verts(directed_edge_id.as_undirected());
(self.vert(verts[1].clone())-self.vert(verts[0].clone()))*((directed_edge_id.parity() as i64)*2-1)
}
fn vert(&self,vert_id:Self::Vert)->Planar64Vec3;
fn face_nd(&self,face_id:Self::Face)->(Self::Normal,Self::Offset);
fn face_edges(&self,face_id:Self::Face)->Cow<[Self::Edge]>;
fn edge_faces(&self,edge_id:<Self::Edge as DirectedEdge>::UndirectedEdge)->Cow<[Self::Face;2]>;
fn edge_verts(&self,edge_id:<Self::Edge as DirectedEdge>::UndirectedEdge)->Cow<[Self::Vert;2]>;
fn vert_edges(&self,vert_id:Self::Vert)->Cow<[Self::Edge]>;
fn vert_faces(&self,vert_id:Self::Vert)->Cow<[Self::Face]>;
}
struct FaceRefs{
// I didn't write it down, but I assume the edges are directed
// clockwise when looking towards the face normal, i.e. right hand rule.
edges:Vec<SubmeshDirectedEdgeId>,
//verts are redundant, use edge[i].verts[0]
//verts:Vec<VertId>,
}
#[derive(Debug)]
struct EdgeRefs{
faces:[SubmeshFaceId;2],//left, right
verts:[SubmeshVertId;2],//start, end
verts:[SubmeshVertId;2],//bottom, top
}
#[derive(Debug)]
struct VertRefs{
faces:Vec<SubmeshFaceId>,
// edges are always directed away from the vert
edges:Vec<SubmeshDirectedEdgeId>,
}
#[derive(Debug)]
pub struct PhysicsMeshData{
//this contains all real and virtual faces used in both the complete mesh and convex submeshes
//faces are sorted such that all faces that belong to the complete mesh appear first, and then
@@ -81,7 +112,6 @@ pub struct PhysicsMeshData{
faces:Vec<Face>,//MeshFaceId indexes this list
verts:Vec<Vert>,//MeshVertId indexes this list
}
#[derive(Debug)]
pub struct PhysicsMeshTopology{
//mapping of local ids to PhysicsMeshData ids
faces:Vec<MeshFaceId>,//SubmeshFaceId indexes this list
@@ -107,12 +137,10 @@ impl From<MeshId> for PhysicsMeshId{
pub struct PhysicsSubmeshId(u32);
pub struct PhysicsMesh{
data:PhysicsMeshData,
// The complete mesh is unused at this time.
// complete_mesh:PhysicsMeshTopology,
// Submeshes are guaranteed to be convex and may contain
// "virtual" faces which are not part of the complete mesh.
// Physics calculations should never resolve to hitting
// a virtual face.
complete_mesh:PhysicsMeshTopology,
//Most objects in roblox maps are already convex, so the list length is 0
//as soon as the mesh is divided into 2 submeshes, the list length jumps to 2.
//length 1 is unnecessary since the complete mesh would be a duplicate of the only submesh, but would still function properly
submeshes:Vec<PhysicsMeshTopology>,
}
impl PhysicsMesh{
@@ -176,24 +204,19 @@ impl PhysicsMesh{
};
Self{
data,
// complete_mesh:mesh_topology.clone(),
submeshes:vec![mesh_topology],
complete_mesh:mesh_topology,
submeshes:Vec::new(),
}
}
pub fn unit_cylinder()->Self{
Self::unit_cube()
}
#[inline]
pub fn complete_mesh(&self)->&PhysicsMeshTopology{
// If there is exactly one submesh, then the complete mesh is identical to it.
if self.submeshes.len()==1{
self.submeshes.first().unwrap()
}else{
panic!("PhysicsMesh complete mesh is not known");
}
pub const fn complete_mesh(&self)->&PhysicsMeshTopology{
&self.complete_mesh
}
#[inline]
pub fn complete_mesh_view(&self)->PhysicsMeshView<'_>{
pub const fn complete_mesh_view(&self)->PhysicsMeshView{
PhysicsMeshView{
data:&self.data,
topology:self.complete_mesh(),
@@ -201,16 +224,21 @@ impl PhysicsMesh{
}
#[inline]
pub fn submeshes(&self)->&[PhysicsMeshTopology]{
&self.submeshes
//the complete mesh is already a convex mesh when len()==0, len()==1 is invalid but will still work
if self.submeshes.len()==0{
std::slice::from_ref(&self.complete_mesh)
}else{
&self.submeshes.as_slice()
}
}
#[inline]
pub fn submesh_view(&self,submesh_id:PhysicsSubmeshId)->PhysicsMeshView<'_>{
pub fn submesh_view(&self,submesh_id:PhysicsSubmeshId)->PhysicsMeshView{
PhysicsMeshView{
data:&self.data,
topology:&self.submeshes()[submesh_id.get() as usize],
}
}
pub fn submesh_views(&self)->impl Iterator<Item=PhysicsMeshView<'_>>{
pub fn submesh_views(&self)->impl Iterator<Item=PhysicsMeshView>{
self.submeshes().iter().map(|topology|PhysicsMeshView{
data:&self.data,
topology,
@@ -279,20 +307,14 @@ impl TryFrom<&model::Mesh> for PhysicsMesh{
if mesh.unique_pos.len()==0{
return Err(PhysicsMeshError::ZeroVertices);
}
// An empty physics mesh is a waste of resources
if mesh.physics_groups.len()==0{
return Err(PhysicsMeshError::NoPhysicsGroups);
}
let verts=mesh.unique_pos.iter().copied().map(Vert).collect();
//TODO: fix submeshes
//flat map mesh.physics_groups[$1].groups.polys()[$2] as face_id
//lower face_id points to upper face_id
//the same face is not allowed to be in multiple polygon groups
// because SubmeshFaceId -> CompleteMeshFaceId -> SubmeshFaceId is ambiguous
// when multiple SubmeshFaceId point to one MeshFaceId
let mut faces=Vec::new();
let mut face_id_from_face=HashMap::new();
let mesh_topologies:Vec<PhysicsMeshTopology>=mesh.physics_groups.iter().map(|physics_group|{
let mut mesh_topologies:Vec<PhysicsMeshTopology>=mesh.physics_groups.iter().map(|physics_group|{
//construct submesh
let mut submesh_faces=Vec::new();//these contain a map from submeshId->meshId
let mut submesh_verts=Vec::new();
@@ -350,14 +372,18 @@ impl TryFrom<&model::Mesh> for PhysicsMesh{
}
//assume face hash is stable, and there are no flush faces...
let face=Face{
normal:(normal/len as i64).divide().narrow_64().unwrap(),
dot:(dot/(len*len) as i64).narrow_64().unwrap(),
normal:(normal/len as i64).divide().fix_1(),
dot:(dot/(len*len) as i64).fix_1(),
};
let face_id=match face_id_from_face.get(&face){
Some(&face_id)=>face_id,
None=>{
let face_id=MeshFaceId::new(faces.len() as u32);
face_id_from_face.insert(face.clone(),face_id);
faces.push(face);
face_id
}
};
let face_id=*face_id_from_face.entry(face).or_insert_with(||{
let face_id=MeshFaceId::new(faces.len() as u32);
faces.push(face);
face_id
});
submesh_faces.push(face_id);
face_ref_guys.push(FaceRefEdges(face_edges));
}
@@ -365,16 +391,16 @@ impl TryFrom<&model::Mesh> for PhysicsMesh{
PhysicsMeshTopology{
faces:submesh_faces,
verts:submesh_verts,
face_topology:face_ref_guys.into_iter().map(|FaceRefEdges(edges)|{
FaceRefs{edges}
face_topology:face_ref_guys.into_iter().map(|face_ref_guy|{
FaceRefs{edges:face_ref_guy.0}
}).collect(),
edge_topology:edge_pool.edge_guys.into_iter().map(|(EdgeRefVerts(verts),EdgeRefFaces(faces))|
EdgeRefs{faces,verts}
edge_topology:edge_pool.edge_guys.into_iter().map(|(edge_ref_verts,edge_ref_faces)|
EdgeRefs{faces:edge_ref_faces.0,verts:edge_ref_verts.0}
).collect(),
vert_topology:vert_ref_guys.into_iter().map(|VertRefGuy{edges,faces}|
vert_topology:vert_ref_guys.into_iter().map(|vert_ref_guy|
VertRefs{
edges:edges.into_iter().collect(),
faces:faces.into_iter().collect(),
edges:vert_ref_guy.edges.into_iter().collect(),
faces:vert_ref_guy.faces.into_iter().collect(),
}
).collect(),
}
@@ -384,86 +410,52 @@ impl TryFrom<&model::Mesh> for PhysicsMesh{
faces,
verts,
},
// complete_mesh:None,
complete_mesh:mesh_topologies.pop().ok_or(PhysicsMeshError::NoPhysicsGroups)?,
submeshes:mesh_topologies,
})
}
}
#[derive(Debug,Clone,Copy)]
pub struct PhysicsMeshView<'a>{
data:&'a PhysicsMeshData,
topology:&'a PhysicsMeshTopology,
}
impl MeshQuery for PhysicsMeshView<'_>{
type Position=Planar64Vec3;
type Direction=Planar64Vec3;
type Face=SubmeshFaceId;
type Edge=SubmeshDirectedEdgeId;
type Vert=SubmeshVertId;
type Normal=Planar64Vec3;
type Offset=Planar64;
fn face_nd(&self,face_id:SubmeshFaceId)->(Planar64Vec3,Planar64){
let face_idx=self.topology.faces[face_id.get() as usize].get() as usize;
(self.data.faces[face_idx].normal,self.data.faces[face_idx].dot)
}
fn hint_point(&self)->Planar64Vec3{
// invariant: meshes always encompass the origin
vec3::zero()
}
fn farthest_vert(&self,dir:Planar64Vec3)->SubmeshVertId{
//this happens to be well-defined. there are no virtual virtices
SubmeshVertId::new(
self.topology.verts.iter()
.enumerate()
.max_by_key(|&(_,&vert_id)|
dir.dot(self.data.verts[vert_id.get() as usize].0)
)
//assume there is more than zero vertices.
.unwrap().0 as u32
)
}
//ideally I never calculate the vertex position, but I have to for the graphical meshes...
fn vert(&self,vert_id:SubmeshVertId)->Planar64Vec3{
let vert_idx=self.topology.verts[vert_id.get() as usize].get() as usize;
self.data.verts[vert_idx].0
}
fn edge_n(&self,edge_id:Self::Edge)->Self::Direction{
let &[v0,v1]=self.edge_verts(edge_id).as_ref();
self.vert(v1)-self.vert(v0)
fn face_edges(&self,face_id:SubmeshFaceId)->Cow<[SubmeshDirectedEdgeId]>{
Cow::Borrowed(&self.topology.face_topology[face_id.get() as usize].edges)
}
fn directed_edge_n(&self,directed_edge_id:Self::DirectedEdge)->Self::Direction{
let &[v0,v1]=self.edge_verts(directed_edge_id.as_undirected()).as_ref();
(self.vert(v1)-self.vert(v0))*((directed_edge_id.parity() as i64)*2-1)
fn edge_faces(&self,edge_id:SubmeshEdgeId)->Cow<[SubmeshFaceId;2]>{
Cow::Borrowed(&self.topology.edge_topology[edge_id.get() as usize].faces)
}
}
impl MeshTopology for PhysicsMeshView<'_>{
type Face=SubmeshFaceId;
type Edge=SubmeshEdgeId;
type DirectedEdge=SubmeshDirectedEdgeId;
type Vert=SubmeshVertId;
fn for_each_vert_edge(&self,vert_id:Self::Vert,f:impl FnMut(Self::DirectedEdge)){
self.topology.vert_topology[vert_id.get() as usize].edges.iter().copied().for_each(f);
fn edge_verts(&self,edge_id:SubmeshEdgeId)->Cow<[SubmeshVertId;2]>{
Cow::Borrowed(&self.topology.edge_topology[edge_id.get() as usize].verts)
}
fn for_each_vert_face(&self,vert_id:Self::Vert,f:impl FnMut(Self::Face)){
self.topology.vert_topology[vert_id.get() as usize].faces.iter().copied().for_each(f);
fn vert_edges(&self,vert_id:SubmeshVertId)->Cow<[SubmeshDirectedEdgeId]>{
Cow::Borrowed(&self.topology.vert_topology[vert_id.get() as usize].edges)
}
fn edge_faces(&self,edge_id:Self::Edge)->impl AsRef<[Self::Face;2]>{
AsRefHelper(self.topology.edge_topology[edge_id.get() as usize].faces)
}
fn edge_verts(&self,edge_id:Self::Edge)->impl AsRef<[Self::Vert;2]>{
AsRefHelper(self.topology.edge_topology[edge_id.get() as usize].verts)
}
fn for_each_face_vert(&self,_face_id:Self::Face,_f:impl FnMut(Self::Vert)){
unimplemented!()
}
fn for_each_face_edge(&self,face_id:Self::Face,f:impl FnMut(Self::DirectedEdge)){
self.topology.face_topology[face_id.get() as usize].edges.iter().copied().for_each(f);
fn vert_faces(&self,vert_id:SubmeshVertId)->Cow<[SubmeshFaceId]>{
Cow::Borrowed(&self.topology.vert_topology[vert_id.get() as usize].faces)
}
}
#[derive(Debug)]
pub struct PhysicsMeshTransform{
pub vertex:integer::Planar64Affine3,
pub normal:integer::mat3::Matrix3<F128_64>,
pub det:F192_96,
pub normal:integer::mat3::Matrix3<Fixed<2,64>>,
pub det:Fixed<3,96>,
}
impl PhysicsMeshTransform{
pub fn new(transform:integer::Planar64Affine3)->Self{
@@ -475,10 +467,9 @@ impl PhysicsMeshTransform{
}
}
#[derive(Debug,Clone,Copy)]
pub struct TransformedMesh<'a>{
view:PhysicsMeshView<'a>,
pub transform:&'a PhysicsMeshTransform,
transform:&'a PhysicsMeshTransform,
}
impl TransformedMesh<'_>{
pub const fn new<'a>(
@@ -490,27 +481,11 @@ impl TransformedMesh<'_>{
transform,
}
}
pub fn verts<'a>(&'a self)->impl Iterator<Item=Vector3<F128_64>>+'a{
pub fn verts<'a>(&'a self)->impl Iterator<Item=vec3::Vector3<Fixed<2,64>>>+'a{
self.view.data.verts.iter().map(|&Vert(pos)|self.transform.vertex.transform_point3(pos))
}
}
impl MeshQuery for TransformedMesh<'_>{
type Direction=Planar64Vec3;
type Position=Planar64Vec3;
type Normal=Vector3<F192_96>;
type Offset=F256_128;
fn face_nd(&self,face_id:SubmeshFaceId)->(Self::Normal,Self::Offset){
let (n,d)=self.view.face_nd(face_id);
let transformed_n=self.transform.normal*n;
let transformed_d=d*self.transform.det+transformed_n.dot(self.transform.vertex.translation);
(transformed_n,transformed_d)
}
fn vert(&self,vert_id:SubmeshVertId)->Planar64Vec3{
// wrap for speed
self.transform.vertex.transform_point3(self.view.vert(vert_id)).wrap_64()
}
fn hint_point(&self)->Planar64Vec3{
self.transform.vertex.translation
pub fn faces(&self)->impl Iterator<Item=SubmeshFaceId>{
(0..self.view.topology.faces.len() as u32).map(SubmeshFaceId::new)
}
fn farthest_vert(&self,dir:Planar64Vec3)->SubmeshVertId{
//this happens to be well-defined. there are no virtual virtices
@@ -524,47 +499,512 @@ impl MeshQuery for TransformedMesh<'_>{
.unwrap().0 as u32
)
}
fn edge_n(&self,edge_id:Self::Edge)->Self::Direction{
let &[v0,v1]=self.edge_verts(edge_id).as_ref();
self.vert(v1)-self.vert(v0)
}
fn directed_edge_n(&self,directed_edge_id:Self::DirectedEdge)->Self::Direction{
let &[v0,v1]=self.edge_verts(directed_edge_id.as_undirected()).as_ref();
(self.vert(v1)-self.vert(v0))*((directed_edge_id.parity() as i64)*2-1)
}
}
impl MeshTopology for TransformedMesh<'_>{
impl MeshQuery for TransformedMesh<'_>{
type Face=SubmeshFaceId;
type Edge=SubmeshEdgeId;
type DirectedEdge=SubmeshDirectedEdgeId;
type Edge=SubmeshDirectedEdgeId;
type Vert=SubmeshVertId;
#[inline]
fn for_each_vert_edge(&self,vert_id:Self::Vert,f:impl FnMut(Self::DirectedEdge)){
self.view.for_each_vert_edge(vert_id,f)
type Normal=Vector3<Fixed<3,96>>;
type Offset=Fixed<4,128>;
fn face_nd(&self,face_id:SubmeshFaceId)->(Self::Normal,Self::Offset){
let (n,d)=self.view.face_nd(face_id);
let transformed_n=self.transform.normal*n;
let transformed_d=d*self.transform.det+transformed_n.dot(self.transform.vertex.translation);
(transformed_n,transformed_d)
}
fn vert(&self,vert_id:SubmeshVertId)->Planar64Vec3{
self.transform.vertex.transform_point3(self.view.vert(vert_id)).fix_1()
}
#[inline]
fn for_each_vert_face(&self,vert_id:Self::Vert,f:impl FnMut(Self::Face)){
self.view.for_each_vert_face(vert_id,f)
fn face_edges(&self,face_id:SubmeshFaceId)->Cow<[SubmeshDirectedEdgeId]>{
self.view.face_edges(face_id)
}
#[inline]
fn edge_faces(&self,edge_id:Self::Edge)->impl AsRef<[Self::Face;2]>{
fn edge_faces(&self,edge_id:SubmeshEdgeId)->Cow<[SubmeshFaceId;2]>{
self.view.edge_faces(edge_id)
}
#[inline]
fn edge_verts(&self,edge_id:Self::Edge)->impl AsRef<[Self::Vert;2]>{
fn edge_verts(&self,edge_id:SubmeshEdgeId)->Cow<[SubmeshVertId;2]>{
self.view.edge_verts(edge_id)
}
fn for_each_face_vert(&self,_face_id:Self::Face,_f:impl FnMut(Self::Vert)){
unimplemented!()
#[inline]
fn vert_edges(&self,vert_id:SubmeshVertId)->Cow<[SubmeshDirectedEdgeId]>{
self.view.vert_edges(vert_id)
}
#[inline]
fn for_each_face_edge(&self,face_id:Self::Face,f:impl FnMut(Self::DirectedEdge)){
self.view.for_each_face_edge(face_id,f)
fn vert_faces(&self,vert_id:SubmeshVertId)->Cow<[SubmeshFaceId]>{
self.view.vert_faces(vert_id)
}
}
pub type GigaTime=Ratio<F256_128,F256_128>;
pub fn into_giga_time(time:Time,relative_to:Time)->GigaTime{
let r=(time-relative_to).to_ratio();
Ratio::new(r.num.widen_256(),r.den.widen_256())
//Note that a face on a minkowski mesh refers to a pair of fevs on the meshes it's summed from
//(face,vertex)
//(edge,edge)
//(vertex,face)
#[derive(Clone,Copy,Debug)]
pub enum MinkowskiVert{
VertVert(SubmeshVertId,SubmeshVertId),
}
#[derive(Clone,Copy,Debug)]
pub enum MinkowskiEdge{
VertEdge(SubmeshVertId,SubmeshEdgeId),
EdgeVert(SubmeshEdgeId,SubmeshVertId),
//EdgeEdge when edges are parallel
}
impl UndirectedEdge for MinkowskiEdge{
type DirectedEdge=MinkowskiDirectedEdge;
fn as_directed(&self,parity:bool)->Self::DirectedEdge{
match self{
MinkowskiEdge::VertEdge(v0,e1)=>MinkowskiDirectedEdge::VertEdge(*v0,e1.as_directed(parity)),
MinkowskiEdge::EdgeVert(e0,v1)=>MinkowskiDirectedEdge::EdgeVert(e0.as_directed(parity),*v1),
}
}
}
#[derive(Clone,Copy,Debug)]
pub enum MinkowskiDirectedEdge{
VertEdge(SubmeshVertId,SubmeshDirectedEdgeId),
EdgeVert(SubmeshDirectedEdgeId,SubmeshVertId),
//EdgeEdge when edges are parallel
}
impl DirectedEdge for MinkowskiDirectedEdge{
type UndirectedEdge=MinkowskiEdge;
fn as_undirected(&self)->Self::UndirectedEdge{
match self{
MinkowskiDirectedEdge::VertEdge(v0,e1)=>MinkowskiEdge::VertEdge(*v0,e1.as_undirected()),
MinkowskiDirectedEdge::EdgeVert(e0,v1)=>MinkowskiEdge::EdgeVert(e0.as_undirected(),*v1),
}
}
fn parity(&self)->bool{
match self{
MinkowskiDirectedEdge::VertEdge(_,e)
|MinkowskiDirectedEdge::EdgeVert(e,_)=>e.parity(),
}
}
}
#[derive(Clone,Copy,Debug,Hash,Eq,PartialEq)]
pub enum MinkowskiFace{
VertFace(SubmeshVertId,SubmeshFaceId),
EdgeEdge(SubmeshEdgeId,SubmeshEdgeId,bool),
FaceVert(SubmeshFaceId,SubmeshVertId),
//EdgeFace
//FaceEdge
//FaceFace
}
pub struct MinkowskiMesh<'a>{
mesh0:TransformedMesh<'a>,
mesh1:TransformedMesh<'a>,
}
//infinity fev algorithm state transition
#[derive(Debug)]
enum Transition{
Done,//found closest vert, no edges are better
Vert(MinkowskiVert),//transition to vert
}
enum EV{
Vert(MinkowskiVert),
Edge(MinkowskiEdge),
}
pub type GigaTime=Ratio<Fixed<4,128>,Fixed<4,128>>;
impl MinkowskiMesh<'_>{
pub fn minkowski_sum<'a>(mesh0:TransformedMesh<'a>,mesh1:TransformedMesh<'a>)->MinkowskiMesh<'a>{
MinkowskiMesh{
mesh0,
mesh1,
}
}
fn farthest_vert(&self,dir:Planar64Vec3)->MinkowskiVert{
MinkowskiVert::VertVert(self.mesh0.farthest_vert(dir),self.mesh1.farthest_vert(-dir))
}
fn next_transition_vert(&self,vert_id:MinkowskiVert,best_distance_squared:&mut Fixed<2,64>,infinity_dir:Planar64Vec3,point:Planar64Vec3)->Transition{
let mut best_transition=Transition::Done;
for &directed_edge_id in self.vert_edges(vert_id).iter(){
let edge_n=self.directed_edge_n(directed_edge_id);
//is boundary uncrossable by a crawl from infinity
let edge_verts=self.edge_verts(directed_edge_id.as_undirected());
//select opposite vertex
let test_vert_id=edge_verts[directed_edge_id.parity() as usize];
//test if it's closer
let diff=point-self.vert(test_vert_id);
if edge_n.dot(infinity_dir).is_zero(){
let distance_squared=diff.dot(diff);
if distance_squared<*best_distance_squared{
best_transition=Transition::Vert(test_vert_id);
*best_distance_squared=distance_squared;
}
}
}
best_transition
}
fn final_ev(&self,vert_id:MinkowskiVert,best_distance_squared:&mut Fixed<2,64>,infinity_dir:Planar64Vec3,point:Planar64Vec3)->EV{
let mut best_transition=EV::Vert(vert_id);
let diff=point-self.vert(vert_id);
for &directed_edge_id in self.vert_edges(vert_id).iter(){
let edge_n=self.directed_edge_n(directed_edge_id);
//is boundary uncrossable by a crawl from infinity
//check if time of collision is outside Time::MIN..Time::MAX
if edge_n.dot(infinity_dir).is_zero(){
let d=edge_n.dot(diff);
//test the edge
let edge_nn=edge_n.dot(edge_n);
if !d.is_negative()&&d<=edge_nn{
let distance_squared={
let c=diff.cross(edge_n);
(c.dot(c)/edge_nn).divide().fix_2()
};
if distance_squared<=*best_distance_squared{
best_transition=EV::Edge(directed_edge_id.as_undirected());
*best_distance_squared=distance_squared;
}
}
}
}
best_transition
}
fn crawl_boundaries(&self,mut vert_id:MinkowskiVert,infinity_dir:Planar64Vec3,point:Planar64Vec3)->EV{
let mut best_distance_squared={
let diff=point-self.vert(vert_id);
diff.dot(diff)
};
loop{
match self.next_transition_vert(vert_id,&mut best_distance_squared,infinity_dir,point){
Transition::Done=>return self.final_ev(vert_id,&mut best_distance_squared,infinity_dir,point),
Transition::Vert(new_vert_id)=>vert_id=new_vert_id,
}
}
}
/// This function drops a vertex down to an edge or a face if the path from infinity did not cross any vertex-edge boundaries but the point is supposed to have already crossed a boundary down from a vertex
fn infinity_fev(&self,infinity_dir:Planar64Vec3,point:Planar64Vec3)->FEV::<MinkowskiMesh>{
//start on any vertex
//cross uncrossable vertex-edge boundaries until you find the closest vertex or edge
//cross edge-face boundary if it's uncrossable
match self.crawl_boundaries(self.farthest_vert(infinity_dir),infinity_dir,point){
//if a vert is returned, it is the closest point to the infinity point
EV::Vert(vert_id)=>FEV::Vert(vert_id),
EV::Edge(edge_id)=>{
//cross to face if the boundary is not crossable and we are on the wrong side
let edge_n=self.edge_n(edge_id);
// point is multiplied by two because vert_sum sums two vertices.
let delta_pos=point*2-{
let &[v0,v1]=self.edge_verts(edge_id).borrow();
self.vert(v0)+self.vert(v1)
};
for (i,&face_id) in self.edge_faces(edge_id).iter().enumerate(){
let face_n=self.face_nd(face_id).0;
//edge-face boundary nd, n facing out of the face towards the edge
let boundary_n=face_n.cross(edge_n)*(i as i64*2-1);
let boundary_d=boundary_n.dot(delta_pos);
//check if time of collision is outside Time::MIN..Time::MAX
//infinity_dir can always be treated as a velocity
if !boundary_d.is_positive()&&boundary_n.dot(infinity_dir).is_zero(){
//both faces cannot pass this condition, return early if one does.
return FEV::Face(face_id);
}
}
FEV::Edge(edge_id)
},
}
}
// TODO: fundamentally improve this algorithm.
// All it needs to do is find the closest point on the mesh
// and return the FEV which the point resides on.
//
// What it actually does is use the above functions to trace a ray in from infinity,
// crawling the closest point along the mesh surface until the ray reaches
// the starting point to discover the final FEV.
//
// The actual collision prediction probably does a single test
// and then immediately returns with 0 FEV transitions on average,
// because of the strict time_limit constraint.
//
// Most of the calculation time is just calculating the starting point
// for the "actual" crawling algorithm below (predict_collision_{in|out}).
fn closest_fev_not_inside(&self,mut infinity_body:Body,start_time:Time)->Option<FEV<MinkowskiMesh>>{
infinity_body.infinity_dir().and_then(|dir|{
let infinity_fev=self.infinity_fev(-dir,infinity_body.position);
//a line is simpler to solve than a parabola
infinity_body.velocity=dir;
infinity_body.acceleration=vec3::ZERO;
//crawl in from negative infinity along a tangent line to get the closest fev
// TODO: change crawl_fev args to delta time? Optional values?
infinity_fev.crawl(self,&infinity_body,Time::MIN/4,start_time).miss()
})
}
pub fn predict_collision_in(&self,relative_body:&Body,Range{start:start_time,end:time_limit}:Range<Time>)->Option<(MinkowskiFace,GigaTime)>{
self.closest_fev_not_inside(relative_body.clone(),start_time).and_then(|fev|{
//continue forwards along the body parabola
fev.crawl(self,relative_body,start_time,time_limit).hit()
})
}
pub fn predict_collision_out(&self,relative_body:&Body,Range{start:start_time,end:time_limit}:Range<Time>)->Option<(MinkowskiFace,GigaTime)>{
//create an extrapolated body at time_limit
let infinity_body=-relative_body.clone();
self.closest_fev_not_inside(infinity_body,-time_limit).and_then(|fev|{
//continue backwards along the body parabola
fev.crawl(self,&infinity_body,-time_limit,-start_time).hit()
//no need to test -time<time_limit because of the first step
.map(|(face,time)|(face,-time))
})
}
pub fn predict_collision_face_out(&self,relative_body:&Body,Range{start:start_time,end:time_limit}:Range<Time>,contact_face_id:MinkowskiFace)->Option<(MinkowskiEdge,GigaTime)>{
//no algorithm needed, there is only one state and two cases (Edge,None)
//determine when it passes an edge ("sliding off" case)
let start_time={
let r=(start_time-relative_body.time).to_ratio();
Ratio::new(r.num,r.den)
};
let mut best_time={
let r=(time_limit-relative_body.time).to_ratio();
Ratio::new(r.num.fix_4(),r.den.fix_4())
};
let mut best_edge=None;
let face_n=self.face_nd(contact_face_id).0;
for &directed_edge_id in self.face_edges(contact_face_id).iter(){
let edge_n=self.directed_edge_n(directed_edge_id);
//f x e points in
let n=face_n.cross(edge_n);
let verts=self.edge_verts(directed_edge_id.as_undirected());
let d=n.dot(self.vert(verts[0])+self.vert(verts[1]));
//WARNING! d outside of *2
//WARNING: truncated precision
for dt in Fixed::<4,128>::zeroes2(((n.dot(relative_body.position))*2-d).fix_4(),n.dot(relative_body.velocity).fix_4()*2,n.dot(relative_body.acceleration).fix_4()){
if start_time.le_ratio(dt)&&dt.lt_ratio(best_time)&&n.dot(relative_body.extrapolated_velocity_ratio_dt(dt)).is_negative(){
best_time=dt;
best_edge=Some(directed_edge_id);
break;
}
}
}
best_edge.map(|e|(e.as_undirected(),best_time))
}
fn infinity_in(&self,infinity_body:Body)->Option<(MinkowskiFace,GigaTime)>{
let infinity_fev=self.infinity_fev(-infinity_body.velocity,infinity_body.position);
infinity_fev.crawl(self,&infinity_body,Time::MIN/4,infinity_body.time).hit()
}
pub fn is_point_in_mesh(&self,point:Planar64Vec3)->bool{
let infinity_body=Body::new(point,vec3::Y,vec3::ZERO,Time::ZERO);
//movement must escape the mesh forwards and backwards in time,
//otherwise the point is not inside the mesh
self.infinity_in(infinity_body)
.is_some_and(|_|
self.infinity_in(-infinity_body)
.is_some()
)
}
}
impl MeshQuery for MinkowskiMesh<'_>{
type Face=MinkowskiFace;
type Edge=MinkowskiDirectedEdge;
type Vert=MinkowskiVert;
type Normal=Vector3<Fixed<3,96>>;
type Offset=Fixed<4,128>;
// TODO: relative d
fn face_nd(&self,face_id:MinkowskiFace)->(Self::Normal,Self::Offset){
match face_id{
MinkowskiFace::VertFace(v0,f1)=>{
let (n,d)=self.mesh1.face_nd(f1);
(-n,d-n.dot(self.mesh0.vert(v0)))
},
MinkowskiFace::EdgeEdge(e0,e1,parity)=>{
let edge0_n=self.mesh0.edge_n(e0);
let edge1_n=self.mesh1.edge_n(e1);
let &[e0v0,e0v1]=self.mesh0.edge_verts(e0).borrow();
let &[e1v0,e1v1]=self.mesh1.edge_verts(e1).borrow();
let n=edge0_n.cross(edge1_n);
let e0d=n.dot(self.mesh0.vert(e0v0)+self.mesh0.vert(e0v1));
let e1d=n.dot(self.mesh1.vert(e1v0)+self.mesh1.vert(e1v1));
((n*(parity as i64*4-2)).fix_3(),((e0d-e1d)*(parity as i64*2-1)).fix_4())
},
MinkowskiFace::FaceVert(f0,v1)=>{
let (n,d)=self.mesh0.face_nd(f0);
(n,d-n.dot(self.mesh1.vert(v1)))
},
}
}
fn vert(&self,vert_id:MinkowskiVert)->Planar64Vec3{
match vert_id{
MinkowskiVert::VertVert(v0,v1)=>{
self.mesh0.vert(v0)-self.mesh1.vert(v1)
},
}
}
fn face_edges(&self,face_id:MinkowskiFace)->Cow<[MinkowskiDirectedEdge]>{
match face_id{
MinkowskiFace::VertFace(v0,f1)=>{
Cow::Owned(self.mesh1.face_edges(f1).iter().map(|&edge_id1|{
MinkowskiDirectedEdge::VertEdge(v0,edge_id1.reverse())
}).collect())
},
MinkowskiFace::EdgeEdge(e0,e1,parity)=>{
let e0v=self.mesh0.edge_verts(e0);
let e1v=self.mesh1.edge_verts(e1);
//could sort this if ordered edges are needed
//probably just need to reverse this list according to parity
Cow::Owned(vec![
MinkowskiDirectedEdge::VertEdge(e0v[0],e1.as_directed(parity)),
MinkowskiDirectedEdge::EdgeVert(e0.as_directed(!parity),e1v[0]),
MinkowskiDirectedEdge::VertEdge(e0v[1],e1.as_directed(!parity)),
MinkowskiDirectedEdge::EdgeVert(e0.as_directed(parity),e1v[1]),
])
},
MinkowskiFace::FaceVert(f0,v1)=>{
Cow::Owned(self.mesh0.face_edges(f0).iter().map(|&edge_id0|{
MinkowskiDirectedEdge::EdgeVert(edge_id0,v1)
}).collect())
},
}
}
fn edge_faces(&self,edge_id:MinkowskiEdge)->Cow<[MinkowskiFace;2]>{
match edge_id{
MinkowskiEdge::VertEdge(v0,e1)=>{
//faces are listed backwards from the minkowski mesh
let v0e=self.mesh0.vert_edges(v0);
let &[e1f0,e1f1]=self.mesh1.edge_faces(e1).borrow();
Cow::Owned([(e1f1,false),(e1f0,true)].map(|(edge_face_id1,face_parity)|{
let mut best_edge=None;
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_nn=edge_face1_n.dot(edge_face1_n);
for &directed_edge_id0 in v0e.iter(){
let edge0_n=self.mesh0.directed_edge_n(directed_edge_id0);
//must be behind other face.
let d=edge_face1_n.dot(edge0_n);
if d.is_negative(){
let edge0_nn=edge0_n.dot(edge0_n);
// Assume not every number is huge
// TODO: revisit this
let dd=(d*d)/(edge_face1_nn*edge0_nn);
if best_d<dd{
best_d=dd;
best_edge=Some(directed_edge_id0);
}
}
}
best_edge.map_or(
MinkowskiFace::VertFace(v0,edge_face_id1),
|directed_edge_id0|MinkowskiFace::EdgeEdge(directed_edge_id0.as_undirected(),e1,directed_edge_id0.parity()^face_parity)
)
}))
},
MinkowskiEdge::EdgeVert(e0,v1)=>{
//tracking index with an external variable because .enumerate() is not available
let v1e=self.mesh1.vert_edges(v1);
let &[e0f0,e0f1]=self.mesh0.edge_faces(e0).borrow();
Cow::Owned([(e0f0,true),(e0f1,false)].map(|(edge_face_id0,face_parity)|{
let mut best_edge=None;
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_nn=edge_face0_n.dot(edge_face0_n);
for &directed_edge_id1 in v1e.iter(){
let edge1_n=self.mesh1.directed_edge_n(directed_edge_id1);
let d=edge_face0_n.dot(edge1_n);
if d.is_negative(){
let edge1_nn=edge1_n.dot(edge1_n);
let dd=(d*d)/(edge_face0_nn*edge1_nn);
if best_d<dd{
best_d=dd;
best_edge=Some(directed_edge_id1);
}
}
}
best_edge.map_or(
MinkowskiFace::FaceVert(edge_face_id0,v1),
|directed_edge_id1|MinkowskiFace::EdgeEdge(e0,directed_edge_id1.as_undirected(),directed_edge_id1.parity()^face_parity)
)
}))
},
}
}
fn edge_verts(&self,edge_id:MinkowskiEdge)->Cow<[MinkowskiVert;2]>{
match edge_id{
MinkowskiEdge::VertEdge(v0,e1)=>{
Cow::Owned(self.mesh1.edge_verts(e1).map(|vert_id1|{
MinkowskiVert::VertVert(v0,vert_id1)
}))
},
MinkowskiEdge::EdgeVert(e0,v1)=>{
Cow::Owned(self.mesh0.edge_verts(e0).map(|vert_id0|{
MinkowskiVert::VertVert(vert_id0,v1)
}))
},
}
}
fn vert_edges(&self,vert_id:MinkowskiVert)->Cow<[MinkowskiDirectedEdge]>{
match vert_id{
MinkowskiVert::VertVert(v0,v1)=>{
let mut edges=Vec::new();
//detect shared volume when the other mesh is mirrored along a test edge dir
let v0f=self.mesh0.vert_faces(v0);
let v1f=self.mesh1.vert_faces(v1);
let v0f_n:Vec<_>=v0f.iter().map(|&face_id|self.mesh0.face_nd(face_id).0).collect();
let v1f_n:Vec<_>=v1f.iter().map(|&face_id|self.mesh1.face_nd(face_id).0).collect();
let the_len=v0f.len()+v1f.len();
for &directed_edge_id in self.mesh0.vert_edges(v0).iter(){
let n=self.mesh0.directed_edge_n(directed_edge_id);
let nn=n.dot(n);
// TODO: there's gotta be a better way to do this
//make a set of faces
let mut face_normals=Vec::with_capacity(the_len);
//add mesh0 faces as-is
face_normals.clone_from(&v0f_n);
for face_n in &v1f_n{
//add reflected mesh1 faces
face_normals.push(*face_n-(n*face_n.dot(n)*2/nn).divide().fix_3());
}
if is_empty_volume(face_normals){
edges.push(MinkowskiDirectedEdge::EdgeVert(directed_edge_id,v1));
}
}
for &directed_edge_id in self.mesh1.vert_edges(v1).iter(){
let n=self.mesh1.directed_edge_n(directed_edge_id);
let nn=n.dot(n);
let mut face_normals=Vec::with_capacity(the_len);
face_normals.clone_from(&v1f_n);
for face_n in &v0f_n{
face_normals.push(*face_n-(n*face_n.dot(n)*2/nn).divide().fix_3());
}
if is_empty_volume(face_normals){
edges.push(MinkowskiDirectedEdge::VertEdge(v0,directed_edge_id));
}
}
Cow::Owned(edges)
},
}
}
fn vert_faces(&self,_vert_id:MinkowskiVert)->Cow<[MinkowskiFace]>{
unimplemented!()
}
}
fn is_empty_volume(normals:Vec<Vector3<Fixed<3,96>>>)->bool{
let len=normals.len();
for i in 0..len-1{
for j in i+1..len{
let n=normals[i].cross(normals[j]);
let mut d_comp=None;
for k in 0..len{
if k!=i&&k!=j{
let d=n.dot(normals[k]).is_negative();
if let &Some(comp)=&d_comp{
// This is testing if d_comp*d < 0
if comp^d{
return true;
}
}else{
d_comp=Some(d);
}
}
}
}
}
return false;
}
#[test]
fn test_is_empty_volume(){
assert!(!is_empty_volume([vec3::X.fix_3(),vec3::Y.fix_3(),vec3::Z.fix_3()].to_vec()));
assert!(is_empty_volume([vec3::X.fix_3(),vec3::Y.fix_3(),vec3::Z.fix_3(),vec3::NEG_X.fix_3()].to_vec()));
}

File diff suppressed because it is too large Load Diff

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::{Fixed,Planar64Vec3,Ratio};
use strafesnet_common::ray::Ray;
@@ -13,10 +12,9 @@ use strafesnet_common::ray::Ray;
type Conts<'a>=arrayvec::ArrayVec<&'a Contact,4>;
// 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
#[derive(Debug,PartialEq)]
pub struct Contact{
pub position:Planar64Vec3,
pub velocity:Planar64Vec3,
@@ -30,39 +28,42 @@ impl Contact{
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)
}
/// 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)
}
}
//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>>>{
const EPSILON:Fixed<2,64>=Fixed::from_bits(Fixed::<2,64>::ONE.to_bits().shr(10));
let det=c0.normal.dot(c0.velocity);
if det.abs()==Fixed::ZERO{
if det.abs()<EPSILON{
return None;
}
let d0=c0.normal.dot(c0.position);
Some(c0.normal*d0/det)
}
fn solve2(c0:&Contact,c1:&Contact)->Option<Ratio<Vector3<F320_160>,F256_128>>{
fn solve2(c0:&Contact,c1:&Contact)->Option<Ratio<Vector3<Fixed<5,160>>,Fixed<4,128>>>{
const EPSILON:Fixed<4,128>=Fixed::from_bits(Fixed::<4,128>::ONE.to_bits().shr(10));
let u0_u1=c0.velocity.cross(c1.velocity);
let n0_n1=c0.normal.cross(c1.normal);
let det=u0_u1.dot(n0_n1);
if det.abs()==Fixed::ZERO{
if det.abs()<EPSILON{
return None;
}
let d0=c0.normal.dot(c0.position);
let d1=c1.normal.dot(c1.position);
Some((c1.normal.cross(u0_u1)*d0+u0_u1.cross(c0.normal)*d1)/det)
}
fn solve3(c0:&Contact,c1:&Contact,c2:&Contact)->Option<Ratio<Vector3<F256_128>,F192_96>>{
fn solve3(c0:&Contact,c1:&Contact,c2:&Contact)->Option<Ratio<Vector3<Fixed<4,128>>,Fixed<3,96>>>{
const EPSILON:Fixed<3,96>=Fixed::from_bits(Fixed::<3,96>::ONE.to_bits().shr(10));
let n0_n1=c0.normal.cross(c1.normal);
let det=c2.normal.dot(n0_n1);
if det.abs()==Fixed::ZERO{
if det.abs()<EPSILON{
return None;
}
let d0=c0.normal.dot(c0.position);
@@ -71,7 +72,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)
}
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);
if det==Fixed::ZERO{
return None;
@@ -79,7 +80,7 @@ fn decompose1(point:Planar64Vec3,u0:Planar64Vec3)->Option<[Ratio<F128_64,F128_64
let s0=u0.dot(point)/det;
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 det=u0_u1.dot(u0_u1);
if det==Fixed::ZERO{
@@ -89,7 +90,7 @@ fn decompose2(point:Planar64Vec3,u0:Planar64Vec3,u1:Planar64Vec3)->Option<[Ratio
let s1=u0_u1.dot(u0.cross(point))/det;
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);
if det==Fixed::ZERO{
return None;
@@ -148,22 +149,21 @@ fn is_space_enclosed_4(
}
const fn get_push_ray_0(point:Planar64Vec3)->Ray{
Ray{origin:point,direction:vec3::zero()}
Ray{origin:point,direction:vec3::ZERO}
}
fn get_push_ray_1(point:Planar64Vec3,c0:&Contact)->Option<Ray>{
//wrap for speed
let direction=solve1(c0)?.divide().wrap_64();
let direction=solve1(c0)?.divide().fix_1();
let [s0]=decompose1(direction,c0.velocity)?;
if s0.lt_ratio(RATIO_ZERO){
return None;
}
let origin=point+solve1(
&c0.relative_to(point),
)?.divide().wrap_64();
)?.divide().fix_1();
Some(Ray{origin,direction})
}
fn get_push_ray_2(point:Planar64Vec3,c0:&Contact,c1:&Contact)->Option<Ray>{
let direction=solve2(c0,c1)?.divide().wrap_64();
let direction=solve2(c0,c1)?.divide().fix_1();
let [s0,s1]=decompose2(direction,c0.velocity,c1.velocity)?;
if s0.lt_ratio(RATIO_ZERO)||s1.lt_ratio(RATIO_ZERO){
return None;
@@ -171,11 +171,11 @@ fn get_push_ray_2(point:Planar64Vec3,c0:&Contact,c1:&Contact)->Option<Ray>{
let origin=point+solve2(
&c0.relative_to(point),
&c1.relative_to(point),
)?.divide().wrap_64();
)?.divide().fix_1();
Some(Ray{origin,direction})
}
fn get_push_ray_3(point:Planar64Vec3,c0:&Contact,c1:&Contact,c2:&Contact)->Option<Ray>{
let direction=solve3(c0,c1,c2)?.divide().wrap_64();
let direction=solve3(c0,c1,c2)?.divide().fix_1();
let [s0,s1,s2]=decompose3(direction,c0.velocity,c1.velocity,c2.velocity)?;
if s0.lt_ratio(RATIO_ZERO)||s1.lt_ratio(RATIO_ZERO)||s2.lt_ratio(RATIO_ZERO){
return None;
@@ -184,14 +184,14 @@ fn get_push_ray_3(point:Planar64Vec3,c0:&Contact,c1:&Contact,c2:&Contact)->Optio
&c0.relative_to(point),
&c1.relative_to(point),
&c2.relative_to(point),
)?.divide().wrap_64();
)?.divide().fix_1();
Some(Ray{origin,direction})
}
const fn get_best_push_ray_and_conts_0<'a>(point:Planar64Vec3)->(Ray,Conts<'a>){
(get_push_ray_0(point),Conts::new_const())
}
fn get_best_push_ray_and_conts_1(point:Planar64Vec3,c0:&Contact)->Option<(Ray,Conts<'_>)>{
fn get_best_push_ray_and_conts_1(point:Planar64Vec3,c0:&Contact)->Option<(Ray,Conts)>{
get_push_ray_1(point,c0)
.map(|ray|(ray,Conts::from_iter([c0])))
}
@@ -273,26 +273,26 @@ 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()
.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()
)
.map(|contact|(contact.solve(ray),contact))
.min_by_key(|&(t,_)|t)
}
pub fn push_solve(contacts:&[Contact],point:Planar64Vec3)->(Planar64Vec3,Conts<'_>){
pub fn push_solve(contacts:&[Contact],point:Planar64Vec3)->Planar64Vec3{
let (mut ray,mut conts)=get_best_push_ray_and_conts_0(point);
loop{
let (next_t,next_cont)=match get_first_touch(contacts,&ray,&conts){
Some((t,cont))=>(t,cont),
None=>return (ray.origin,conts),
None=>return ray.origin,
};
if RATIO_ZERO.le_ratio(next_t){
return (ray.origin,conts);
return ray.origin;
}
//push_front
@@ -308,7 +308,7 @@ pub fn push_solve(contacts:&[Contact],point:Planar64Vec3)->(Planar64Vec3,Conts<'
let meet_point=ray.extrapolate(next_t);
match get_best_push_ray_and_conts(meet_point,conts.as_slice()){
Some((new_ray,new_conts))=>(ray,conts)=(new_ray,new_conts),
None=>return (meet_point,conts),
None=>return meet_point,
}
}
}
@@ -320,13 +320,14 @@ mod tests{
fn test_push_solve(){
let contacts=vec![
Contact{
position:vec3::zero(),
position:vec3::ZERO,
velocity:vec3::Y,
normal:vec3::Y,
}
];
let (point,conts)=push_solve(&contacts,vec3::NEG_Y);
assert_eq!(point,vec3::zero());
assert_eq!(conts.as_slice(),[&contacts[0]].as_slice());
assert_eq!(
vec3::ZERO,
push_solve(&contacts,vec3::NEG_Y)
);
}
}

View File

@@ -4,12 +4,9 @@ version = "0.1.0"
edition = "2024"
[dependencies]
glam.workspace = true
glam = "0.30.0"
replace_with = "0.1.7"
strafesnet_common.workspace = true
strafesnet_physics.workspace = true
strafesnet_settings.workspace = true
strafesnet_snf.workspace = true
[lints]
workspace = true
strafesnet_common = { path = "../../lib/common", registry = "strafesnet" }
strafesnet_physics = { path = "../physics", registry = "strafesnet" }
strafesnet_settings = { path = "../settings", registry = "strafesnet" }
strafesnet_snf = { path = "../../lib/snf", registry = "strafesnet" }

View File

@@ -31,7 +31,7 @@ pub enum SessionInputInstruction{
Mouse(glam::IVec2),
SetControl(strafesnet_common::physics::SetControlInstruction),
Mode(ImplicitModeInstruction),
Misc(MiscInstruction),
Misc(strafesnet_common::physics::MiscInstruction),
}
/// Implicit mode instruction are fed separately to session.
/// Session generates the explicit mode instructions interlaced with a SetSensitivity instruction
@@ -52,23 +52,16 @@ pub enum SessionControlInstruction{
pub enum SessionPlaybackInstruction{
SkipForward,
SkipBack,
TogglePaused,
DecreaseTimescale,
IncreaseTimescale,
}
pub struct FrameState{
pub trajectory:physics::Trajectory,
pub body:physics::Body,
pub camera:physics::PhysicsCamera,
pub time:PhysicsTime,
}
impl FrameState{
pub fn pos(&self)->glam::Vec3{
self.trajectory.extrapolated_position(self.time).map(Into::<f32>::into).to_array().into()
}
pub fn angles(&self)->glam::Vec2{
self.camera.simulate_move_angles(glam::IVec2::ZERO)
}
}
pub struct Simulation{
timer:Timer<Scaled<SessionTimeInner,PhysicsTimeInner>>,
@@ -84,9 +77,9 @@ impl Simulation{
physics,
}
}
pub fn get_frame_state(&self,time:SessionTime,data:&PhysicsData)->FrameState{
pub fn get_frame_state(&self,time:SessionTime)->FrameState{
FrameState{
trajectory:self.physics.camera_trajectory(data),
body:self.physics.camera_body(),
camera:self.physics.camera(),
time:self.timer.time(time),
}
@@ -159,10 +152,10 @@ enum ViewState{
pub struct Session{
directories:Directories,
user_settings:UserSettings,
mouse_interpolator:MouseInterpolator,
mouse_interpolator:crate::mouse_interpolator::MouseInterpolator,
view_state:ViewState,
//gui:GuiState
geometry_shared:PhysicsData,
geometry_shared:physics::PhysicsData,
simulation:Simulation,
// below fields not included in lite session
recording:Recording,
@@ -179,7 +172,7 @@ impl Session{
user_settings,
directories,
mouse_interpolator:MouseInterpolator::new(),
geometry_shared:PhysicsData::empty(),
geometry_shared:Default::default(),
simulation,
view_state:ViewState::Play,
recording:Default::default(),
@@ -191,13 +184,13 @@ impl Session{
}
fn change_map(&mut self,map:&strafesnet_common::map::CompleteMap){
self.simulation.physics.clear();
self.geometry_shared=PhysicsData::new(map);
self.geometry_shared.generate_models(map);
}
pub fn get_frame_state(&self,time:SessionTime)->Option<FrameState>{
match &self.view_state{
ViewState::Play=>Some(self.simulation.get_frame_state(time,&self.geometry_shared)),
ViewState::Play=>Some(self.simulation.get_frame_state(time)),
ViewState::Replay(bot_id)=>self.replays.get(bot_id).map(|replay|
replay.simulation.get_frame_state(time,&self.geometry_shared)
replay.simulation.get_frame_state(time)
),
}
}
@@ -246,16 +239,12 @@ impl InstructionConsumer<Instruction<'_>> for Session{
run_mouse_interpolator_instruction!(MouseInterpolatorInstruction::Mode(ModeInstruction::Reset));
run_mouse_interpolator_instruction!(MouseInterpolatorInstruction::Misc(MiscInstruction::SetSensitivity(self.user_settings().calculate_sensitivity())));
run_mouse_interpolator_instruction!(MouseInterpolatorInstruction::Mode(ModeInstruction::Restart(mode_id)));
// TODO: think about this harder. This works around a bug where you fall infinitely when you reset.
self.simulation.timer.set_time(ins.time,PhysicsTime::ZERO);
},
Instruction::Input(SessionInputInstruction::Mode(ImplicitModeInstruction::ResetAndSpawn(mode_id,spawn_id)))=>{
self.clear_recording();
run_mouse_interpolator_instruction!(MouseInterpolatorInstruction::Mode(ModeInstruction::Reset));
run_mouse_interpolator_instruction!(MouseInterpolatorInstruction::Misc(MiscInstruction::SetSensitivity(self.user_settings().calculate_sensitivity())));
run_mouse_interpolator_instruction!(MouseInterpolatorInstruction::Mode(ModeInstruction::Spawn(mode_id,spawn_id)));
// TODO: think about this harder. This works around a bug where you fall infinitely when you reset.
self.simulation.timer.set_time(ins.time,PhysicsTime::ZERO);
},
Instruction::Input(SessionInputInstruction::Misc(misc_instruction))=>{
run_mouse_interpolator_instruction!(MouseInterpolatorInstruction::Misc(misc_instruction));
@@ -264,14 +253,7 @@ impl InstructionConsumer<Instruction<'_>> for Session{
// don't flush the buffered instructions in the mouse interpolator
// until the mouse is confirmed to be not moving at a later time
// what if they pause for 5ms lmao
match &self.view_state{
ViewState::Play=>{
_=self.simulation.timer.set_paused(ins.time,paused);
},
ViewState::Replay(bot_id)=>if let Some(replay)=self.replays.get_mut(bot_id){
_=replay.simulation.timer.set_paused(ins.time,paused);
},
}
_=self.simulation.timer.set_paused(ins.time,paused);
},
Instruction::Control(SessionControlInstruction::CopyRecordingIntoReplayAndSpectate)=> if let ViewState::Play=self.view_state{
// Bind: B
@@ -392,6 +374,14 @@ impl InstructionConsumer<Instruction<'_>> for Session{
},
}
},
Instruction::Playback(SessionPlaybackInstruction::TogglePaused)=>{
match &self.view_state{
ViewState::Play=>(),
ViewState::Replay(bot_id)=>if let Some(replay)=self.replays.get_mut(bot_id){
_=replay.simulation.timer.set_paused(ins.time,!replay.simulation.timer.is_paused());
},
}
}
Instruction::ChangeMap(complete_map)=>{
self.clear_recording();
self.change_map(complete_map);

View File

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

View File

@@ -1 +0,0 @@
/test_files

View File

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

View File

@@ -1,28 +0,0 @@
#[expect(dead_code)]
#[derive(Debug)]
pub enum ReplayError{
IO(std::io::Error),
SNF(strafesnet_snf::Error),
SNFM(strafesnet_snf::map::Error),
SNFB(strafesnet_snf::bot::Error),
}
impl From<std::io::Error> for ReplayError{
fn from(value:std::io::Error)->Self{
Self::IO(value)
}
}
impl From<strafesnet_snf::Error> for ReplayError{
fn from(value:strafesnet_snf::Error)->Self{
Self::SNF(value)
}
}
impl From<strafesnet_snf::map::Error> for ReplayError{
fn from(value:strafesnet_snf::map::Error)->Self{
Self::SNFM(value)
}
}
impl From<strafesnet_snf::bot::Error> for ReplayError{
fn from(value:strafesnet_snf::bot::Error)->Self{
Self::SNFB(value)
}
}

View File

@@ -1,25 +1,47 @@
mod error;
mod util;
#[cfg(test)]
mod tests;
#[cfg(test)]
mod test_scenes;
use std::io::Cursor;
use std::path::Path;
use std::time::Instant;
use error::ReplayError;
use util::read_entire_file;
use strafesnet_physics::physics::{PhysicsData,PhysicsState,PhysicsContext};
fn main(){
let arg=std::env::args().skip(1).next();
match arg.as_deref(){
Some("determinism")|None=>test_determinism().unwrap(),
Some("replay")=>run_replay().unwrap(),
_=>println!("invalid argument"),
test_determinism().unwrap();
}
#[allow(unused)]
#[derive(Debug)]
enum ReplayError{
IO(std::io::Error),
SNF(strafesnet_snf::Error),
SNFM(strafesnet_snf::map::Error),
SNFB(strafesnet_snf::bot::Error),
}
impl From<std::io::Error> for ReplayError{
fn from(value:std::io::Error)->Self{
Self::IO(value)
}
}
impl From<strafesnet_snf::Error> for ReplayError{
fn from(value:strafesnet_snf::Error)->Self{
Self::SNF(value)
}
}
impl From<strafesnet_snf::map::Error> for ReplayError{
fn from(value:strafesnet_snf::map::Error)->Self{
Self::SNFM(value)
}
}
impl From<strafesnet_snf::bot::Error> for ReplayError{
fn from(value:strafesnet_snf::bot::Error)->Self{
Self::SNFB(value)
}
}
fn read_entire_file(path:impl AsRef<Path>)->Result<Cursor<Vec<u8>>,std::io::Error>{
let data=std::fs::read(path)?;
Ok(Cursor::new(data))
}
fn run_replay()->Result<(),ReplayError>{
println!("loading map file..");
@@ -31,8 +53,9 @@ fn run_replay()->Result<(),ReplayError>{
let bot=strafesnet_snf::read_bot(data)?.read_all()?;
// create recording
let mut physics_data=PhysicsData::default();
println!("generating models..");
let physics_data=PhysicsData::new(&map);
physics_data.generate_models(&map);
println!("simulating...");
let mut physics=PhysicsState::default();
for ins in bot.instructions{
@@ -71,16 +94,16 @@ fn segment_determinism(bot:strafesnet_snf::bot::Segment,physics_data:&PhysicsDat
for (i,ins) in bot.instructions.into_iter().enumerate(){
let state_deterministic=physics_deterministic.clone();
let state_filtered=physics_filtered.clone();
PhysicsContext::run_input_instruction(&mut physics_deterministic,physics_data,ins.clone());
PhysicsContext::run_input_instruction(&mut physics_deterministic,&physics_data,ins.clone());
match ins{
strafesnet_common::instruction::TimedInstruction{instruction:strafesnet_common::physics::Instruction::Idle,..}=>(),
other=>{
non_idle_count+=1;
// run
PhysicsContext::run_input_instruction(&mut physics_filtered,physics_data,other.clone());
PhysicsContext::run_input_instruction(&mut physics_filtered,&physics_data,other.clone());
// check if position matches
let b0=physics_deterministic.camera_trajectory(physics_data);
let b1=physics_filtered.camera_trajectory(physics_data);
let b0=physics_deterministic.camera_body();
let b1=physics_filtered.camera_body();
if b0.position!=b1.position{
let nanoseconds=start.elapsed().as_nanos() as u64;
println!("desync at instruction #{}",i);
@@ -140,8 +163,9 @@ fn test_determinism()->Result<(),ReplayError>{
let data=read_entire_file("../tools/bhop_maps/5692113331.snfm")?;
let map=strafesnet_snf::read_map(data)?.into_complete_map()?;
let mut physics_data=PhysicsData::default();
println!("generating models..");
let physics_data=PhysicsData::new(&map);
physics_data.generate_models(&map);
let (send,recv)=std::sync::mpsc::channel();

View File

@@ -1,113 +0,0 @@
use strafesnet_physics::physics::{InternalInstruction,PhysicsData,PhysicsState,PhysicsContext};
use strafesnet_common::gameplay_modes::NormalizedModes;
use strafesnet_common::gameplay_attributes::{CollisionAttributes,CollisionAttributesId};
use strafesnet_common::integer::{vec3,mat3,Planar64Affine3,Time};
use strafesnet_common::model::{Mesh,Model,MeshId,ModelId,RenderConfigId};
use strafesnet_common::map::CompleteMap;
use strafesnet_rbx_loader::primitives::{unit_cube,CubeFaceDescription};
struct TestSceneBuilder{
meshes:Vec<Mesh>,
models:Vec<Model>,
}
impl TestSceneBuilder{
fn new()->Self{
Self{
meshes:Vec::new(),
models:Vec::new(),
}
}
fn push_mesh(&mut self,mesh:Mesh)->MeshId{
let mesh_id=self.meshes.len();
self.meshes.push(mesh);
MeshId::new(mesh_id as u32)
}
fn push_mesh_instance(&mut self,mesh:MeshId,transform:Planar64Affine3)->ModelId{
let model=Model{
mesh,
attributes:CollisionAttributesId::new(0),
color:glam::Vec4::ONE,
transform,
};
let model_id=self.models.len();
self.models.push(model);
ModelId::new(model_id as u32)
}
fn build(self)->PhysicsData{
let modes=NormalizedModes::new(Vec::new());
let attributes=vec![CollisionAttributes::contact_default()];
let meshes=self.meshes;
let models=self.models;
let textures=Vec::new();
let render_configs=Vec::new();
PhysicsData::new(&CompleteMap{
modes,
attributes,
meshes,
models,
textures,
render_configs,
})
}
}
fn test_scene()->PhysicsData{
let mut builder=TestSceneBuilder::new();
let cube_face_description=CubeFaceDescription::new(Default::default(),RenderConfigId::new(0));
let mesh=builder.push_mesh(unit_cube(cube_face_description));
// place two 5x5x5 cubes.
builder.push_mesh_instance(mesh,Planar64Affine3::new(
mat3::from_diagonal(vec3::int(5,5,5)>>1),
vec3::int(0,0,0)
));
builder.push_mesh_instance(mesh,Planar64Affine3::new(
mat3::from_diagonal(vec3::int(5,5,5)>>1),
vec3::int(5,-5,0)
));
builder.build()
}
#[test]
fn simultaneous_collision(){
let physics_data=test_scene();
let body=strafesnet_physics::physics::Body::new(
(vec3::int(5+2,0,0)>>1)+vec3::int(1,1,0),
vec3::int(-1,-1,0),
Time::ZERO,
);
let mut physics=PhysicsState::new_with_body(body);
physics.style_mut().gravity=vec3::zero();
let mut phys_iter=PhysicsContext::iter_internal(&mut physics,&physics_data,Time::from_secs(2))
.filter(|ins|!matches!(ins.instruction,InternalInstruction::StrafeTick));
// the order that they hit does matter, but we aren't currently worrying about that.
// See multi-collision branch
assert_eq!(phys_iter.next().unwrap().time,Time::from_secs(1));
assert_eq!(phys_iter.next().unwrap().time,Time::from_secs(1));
assert!(phys_iter.next().is_none());
let body=physics.body();
assert_eq!(body.position,vec3::int(5,0,0));
assert_eq!(body.velocity,vec3::int(0,0,0));
assert_eq!(body.time,Time::from_secs(1));
}
#[test]
fn bug_3(){
let physics_data=test_scene();
let body=strafesnet_physics::physics::Body::new(
(vec3::int(5+2,0,0)>>1)+vec3::int(1,2,0),
vec3::int(-1,-1,0),
Time::ZERO,
);
let mut physics=PhysicsState::new_with_body(body);
physics.style_mut().gravity=vec3::zero();
let mut phys_iter=PhysicsContext::iter_internal(&mut physics,&physics_data,Time::from_secs(3))
.filter(|ins|!matches!(ins.instruction,InternalInstruction::StrafeTick));
// touch side of part at 0,0,0
assert_eq!(phys_iter.next().unwrap().time,Time::from_secs(1));
// touch top of part at 5,-5,0
assert_eq!(phys_iter.next().unwrap().time,Time::from_secs(2));
assert!(phys_iter.next().is_none());
let body=physics.body();
assert_eq!(body.position,vec3::int(5+2,0,0)>>1);
assert_eq!(body.velocity,vec3::int(0,0,0));
assert_eq!(body.time,Time::from_secs(2));
}

View File

@@ -1,96 +0,0 @@
use crate::error::ReplayError;
use crate::util::read_entire_file;
use strafesnet_physics::physics::{PhysicsData,PhysicsState,PhysicsContext};
#[test]
#[ignore]
fn physics_bug_2()->Result<(),ReplayError>{
println!("loading map file..");
let data=read_entire_file("test_files/bhop_monster_jam.snfm")?;
let map=strafesnet_snf::read_map(data)?.into_complete_map()?;
// create recording
println!("generating models..");
let physics_data=PhysicsData::new(&map);
println!("simulating...");
//teleport to bug
// body pos = Vector { array: [Fixed { bits: 554895163352 }, Fixed { bits: 1485633089990 }, Fixed { bits: 1279601007173 }] }
// after the fix it's still happening, possibly for a different reason, new position to evince:
// body pos = Vector { array: [Fixed { bits: 555690659654 }, Fixed { bits: 1490485868773 }, Fixed { bits: 1277783839382 }] }
use strafesnet_common::integer::{vec3,Time};
let body=strafesnet_physics::physics::Body::new(
vec3::raw_xyz(555690659654,1490485868773,1277783839382),
vec3::int(0,0,0),
Time::ZERO,
);
let mut physics=PhysicsState::new_with_body(body);
// wait one second to activate the bug
// hit=Some(ModelId(2262))
PhysicsContext::run_input_instruction(&mut physics,&physics_data,strafesnet_common::instruction::TimedInstruction{
time:Time::from_millis(500),
instruction:strafesnet_common::physics::Instruction::Idle,
});
Ok(())
}
#[test]
#[ignore]
fn physics_bug_3()->Result<(),ReplayError>{
println!("loading map file..");
let data=read_entire_file("../tools/bhop_maps/5692152916.snfm")?;
let map=strafesnet_snf::read_map(data)?.into_complete_map()?;
// create recording
println!("generating models..");
let physics_data=PhysicsData::new(&map);
println!("simulating...");
//teleport to bug
use strafesnet_common::integer::{vec3,Time};
let body=strafesnet_physics::physics::Body::new(
// bhop_toc corner position after wall hits
// vec3::raw_xyz(-1401734815424,3315081280280,-2466057177493),
// vec3::raw_xyz(0,-96915585363,1265),
// vec3::raw_xyz(0,-429496729600,0),
// alternate room center position
// vec3::raw_xyz(-1129043783837,3324870327882,-2014012350212),
// vec3::raw_xyz(0,-96915585363,1265),
// vec3::raw_xyz(0,-429496729600,0),
// corner setup before wall hits
// vec3::raw_xyz(-1392580080675,3325402529458,-2444727738679),
// vec3::raw_xyz(-30259028820,-22950929553,-71141663007),
// vec3::raw_xyz(0,-429496729600,0),
// Actual bug 3 repro
vec3::raw_xyz(-2505538624455,3357963283914,557275711118),
vec3::raw_xyz(204188283920,-282280474198,166172785440),
Time::ZERO,
);
let mut physics=PhysicsState::new_with_body(body);
// wait one second to activate the bug
PhysicsContext::run_input_instruction(&mut physics,&physics_data,strafesnet_common::instruction::TimedInstruction{
time:Time::from_millis(500),
instruction:strafesnet_common::physics::Instruction::Idle,
});
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,7 +0,0 @@
use std::io::Cursor;
use std::path::Path;
pub fn read_entire_file(path:impl AsRef<Path>)->Result<Cursor<Vec<u8>>,std::io::Error>{
let data=std::fs::read(path)?;
Ok(Cursor::new(data))
}

View File

@@ -1,6 +1,6 @@
[package]
name = "strafesnet_bsp_loader"
version = "0.5.0"
version = "0.3.0"
edition = "2024"
repository = "https://git.itzana.me/StrafesNET/strafe-project"
license = "MIT OR Apache-2.0"
@@ -10,13 +10,10 @@ authors = ["Rhys Lloyd <krakow20@gmail.com>"]
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
glam.workspace = true
strafesnet_common.workspace = true
strafesnet_deferred_loader.workspace = true
vbsp = "0.9.1"
glam = "0.30.0"
strafesnet_common = { version = "0.6.0", path = "../common", registry = "strafesnet" }
strafesnet_deferred_loader = { version = "0.5.0", path = "../deferred_loader", registry = "strafesnet" }
vbsp = "0.8.0"
vbsp-entities-css = "0.6.0"
vmdl = "0.2.0"
vpk = "0.3.0"
[lints]
workspace = true

View File

@@ -1,22 +1,21 @@
use strafesnet_common::integer::Planar64;
use strafesnet_common::{model,integer};
use strafesnet_common::integer::fixed_types::F192_96;
use strafesnet_common::integer::vec3::Vector3;
use strafesnet_common::integer::{Planar64,Planar64Vec3,Ratio};
use strafesnet_common::integer::{vec3::Vector3,Fixed,Ratio};
use crate::{valve_transform_normal,valve_transform_dist};
#[derive(Hash,Eq,PartialEq)]
struct Face{
normal:Planar64Vec3,
dot:Planar64,
normal:integer::Planar64Vec3,
dot:integer::Planar64,
}
#[derive(Debug)]
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 det=c2.normal.dot(n0_n1);
if det.abs().is_zero(){
@@ -35,7 +34,6 @@ pub enum PlanesToFacesError{
InitFace2,
InitIntersection,
FindNewIntersection,
// Narrow(strafesnet_common::integer::NarrowError),
EmptyFaces,
InfiniteLoop1,
InfiniteLoop2,
@@ -83,12 +81,12 @@ fn planes_to_faces(face_list:std::collections::HashSet<Face>)->Result<Faces,Plan
// test if any *other* faces occlude the intersection
for new_face in &face_list{
// 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.fix_2()/Planar64::ONE).lt_ratio(new_face.normal.dot(intersection.num)/intersection.den){
// replace one of the faces with the new face
// dont' try to replace face0 because we are exploring that face in particular
if let Some(new_intersection)=solve3(face0,new_face,face2){
// 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.fix_2()/Planar64::ONE).gt_ratio(face1.normal.dot(new_intersection.num)/new_intersection.den){
face1=new_face;
intersection=new_intersection;
continue 'find;
@@ -96,7 +94,7 @@ fn planes_to_faces(face_list:std::collections::HashSet<Face>)->Result<Faces,Plan
}
if let Some(new_intersection)=solve3(face0,face1,new_face){
// 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.fix_2()/Planar64::ONE).gt_ratio(face2.normal.dot(new_intersection.num)/new_intersection.den){
face2=new_face;
intersection=new_intersection;
continue 'find;
@@ -121,7 +119,7 @@ fn planes_to_faces(face_list:std::collections::HashSet<Face>)->Result<Faces,Plan
continue;
}
// 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.fix_2()/Planar64::ONE).lt_ratio(new_face.normal.dot(intersection.num)/intersection.den){
// abort! reject face0 entirely
continue 'face;
}
@@ -139,7 +137,7 @@ fn planes_to_faces(face_list:std::collections::HashSet<Face>)->Result<Faces,Plan
loop{
// push point onto vertices
// 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().fix_1());
// we looped back around to face1, we're done!
if core::ptr::eq(face1,face2){
@@ -188,7 +186,7 @@ fn planes_to_faces(face_list:std::collections::HashSet<Face>)->Result<Faces,Plan
}
}
#[expect(dead_code)]
#[allow(dead_code)]
#[derive(Debug)]
pub enum BrushToMeshError{
SliceBrushSides,
@@ -205,13 +203,13 @@ impl std::fmt::Display 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
let mut mb=model::MeshBuilder::new();
let color=mb.acquire_color_id(glam::Vec4::ONE);
let tex=mb.acquire_tex_id(glam::Vec2::ZERO);
// normals are ignored by physics
let normal=mb.acquire_normal_id(integer::vec3::zero());
let normal=mb.acquire_normal_id(integer::vec3::ZERO);
let polygon_list=faces.into_iter().map(|face|{
face.into_iter().map(|pos|{

View File

@@ -53,9 +53,9 @@ fn add_brush<'a>(
rendercolor.b as f32
])/255.0).extend(1.0);
match model.chars().next(){
match model.split_at(1){
// The first character of brush.model is '*'
Some('*')=>match model[1..].parse(){
("*",id_str)=>match id_str.parse(){
Ok(mesh_id)=>{
let mesh=model::MeshId::new(mesh_id);
world_models.push(
@@ -105,7 +105,7 @@ pub fn convert<'a>(
water:Some(attr::IntersectingWater{
viscosity:integer::Planar64::ONE,
density:integer::Planar64::ONE,
velocity:integer::vec3::zero(),
velocity:integer::vec3::ZERO,
}),
},
general:attr::GeneralAttributes::default(),
@@ -209,52 +209,204 @@ pub fn convert<'a>(
color:glam::Vec4::W,
});
// THE CUBE OF DESTINY
let destination_mesh_id=model::MeshId::new(world_meshes.len() as u32);
world_meshes.push(crate::brush::unit_cube());
const WHITE:vbsp::Color=vbsp::Color{r:255,g:255,b:255};
const ENTITY_ATTRIBUTE:attr::CollisionAttributesId=ATTRIBUTE_DECORATION;
const ENTITY_TRIGGER_ATTRIBUTE:attr::CollisionAttributesId=ATTRIBUTE_INTERSECT_DEFAULT;
for raw_ent in &bsp.entities{
macro_rules! ent_brush_default{
($entity:ident)=>{
add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,$entity.model,$entity.origin,$entity.rendercolor,ENTITY_ATTRIBUTE)
};
} macro_rules! ent_brush_prop{
($entity:ident)=>{
add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,$entity.model,$entity.origin,WHITE,ENTITY_ATTRIBUTE)
};
}
macro_rules! ent_brush_trigger{
($entity:ident)=>{
add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,$entity.model,$entity.origin,WHITE,ENTITY_TRIGGER_ATTRIBUTE)
};
}
match raw_ent.parse(){
Ok(Entity::Cycler(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,brush.rendercolor,ATTRIBUTE_DECORATION),
Ok(Entity::EnvSprite(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,brush.rendercolor,ATTRIBUTE_DECORATION),
Ok(Entity::FuncBreakable(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,brush.rendercolor,ATTRIBUTE_DECORATION),
Ok(Entity::FuncBrush(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,brush.rendercolor,ATTRIBUTE_DECORATION),
Ok(Entity::FuncButton(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,brush.rendercolor,ATTRIBUTE_DECORATION),
Ok(Entity::FuncDoor(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,brush.rendercolor,ATTRIBUTE_DECORATION),
Ok(Entity::FuncDoorRotating(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,brush.rendercolor,ATTRIBUTE_DECORATION),
Ok(Entity::AmbientGeneric(_ambient_generic))=>(),
Ok(Entity::Cycler(brush))=>ent_brush_default!(brush),
Ok(Entity::EnvBeam(_env_beam))=>(),
Ok(Entity::EnvBubbles(_env_bubbles))=>(),
Ok(Entity::EnvDetailController(_env_detail_controller))=>(),
Ok(Entity::EnvEmbers(_env_embers))=>(),
Ok(Entity::EnvEntityMaker(_env_entity_maker))=>(),
Ok(Entity::EnvExplosion(_env_explosion))=>(),
Ok(Entity::EnvFade(_env_fade))=>(),
Ok(Entity::EnvFire(_env_fire))=>(),
Ok(Entity::EnvFireTrail(_env_fire_trail))=>(),
Ok(Entity::EnvFiresource(_env_firesource))=>(),
Ok(Entity::EnvFogController(_env_fog_controller))=>(),
Ok(Entity::EnvHudhint(_env_hudhint))=>(),
Ok(Entity::EnvLaser(_env_laser))=>(),
Ok(Entity::EnvLightglow(_env_lightglow))=>(),
Ok(Entity::EnvPhysexplosion(_env_physexplosion))=>(),
Ok(Entity::EnvProjectedtexture(_env_projectedtexture))=>(),
Ok(Entity::EnvScreenoverlay(_env_screenoverlay))=>(),
Ok(Entity::EnvShake(_env_shake))=>(),
Ok(Entity::EnvShooter(_env_shooter))=>(),
Ok(Entity::EnvSmokestack(_env_smokestack))=>(),
Ok(Entity::EnvSoundscape(_env_soundscape))=>(),
Ok(Entity::EnvSoundscapeProxy(_env_soundscape_proxy))=>(),
Ok(Entity::EnvSoundscapeTriggerable(_env_soundscape_triggerable))=>(),
Ok(Entity::EnvSpark(_env_spark))=>(),
Ok(Entity::EnvSprite(brush))=>ent_brush_default!(brush),
Ok(Entity::EnvSpritetrail(_env_spritetrail))=>(),
Ok(Entity::EnvSteam(_env_steam))=>(),
Ok(Entity::EnvSun(_env_sun))=>(),
Ok(Entity::EnvTonemapController(_env_tonemap_controller))=>(),
Ok(Entity::EnvWind(_env_wind))=>(),
// trigger_teleport.filtername probably has to do with one of these
Ok(Entity::FilterActivatorClass(_filter_activator_class))=>(),
Ok(Entity::FilterActivatorName(_filter_activator_name))=>(),
Ok(Entity::FilterDamageType(_filter_damage_type))=>(),
Ok(Entity::FilterMulti(_filter_multi))=>(),
Ok(Entity::FuncAreaportal(_func_areaportal))=>(),
Ok(Entity::FuncAreaportalwindow(_func_areaportalwindow))=>(),
Ok(Entity::FuncBombTarget(_func_bomb_target))=>(),
Ok(Entity::FuncBreakable(brush))=>ent_brush_default!(brush),
Ok(Entity::FuncBreakableSurf(_func_breakable_surf))=>(),
Ok(Entity::FuncBrush(brush))=>ent_brush_default!(brush),
Ok(Entity::FuncButton(brush))=>ent_brush_default!(brush),
Ok(Entity::FuncBuyzone(_func_buyzone))=>(),
Ok(Entity::FuncClipVphysics(_func_clip_vphysics))=>(),
Ok(Entity::FuncConveyor(_func_conveyor))=>(),
// FuncDoor is Platform
Ok(Entity::FuncDoor(brush))=>ent_brush_default!(brush),
Ok(Entity::FuncDoorRotating(brush))=>ent_brush_default!(brush),
Ok(Entity::FuncDustcloud(_func_dustcloud))=>(),
Ok(Entity::FuncDustmotes(_func_dustmotes))=>(),
Ok(Entity::FuncFishPool(_func_fish_pool))=>(),
Ok(Entity::FuncFootstepControl(_func_footstep_control))=>(),
Ok(Entity::FuncHostageRescue(_func_hostage_rescue))=>(),
Ok(Entity::FuncIllusionary(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,brush.rendercolor,ATTRIBUTE_DECORATION),
Ok(Entity::FuncMonitor(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,brush.rendercolor,ATTRIBUTE_DECORATION),
Ok(Entity::FuncMovelinear(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,brush.rendercolor,ATTRIBUTE_DECORATION),
Ok(Entity::FuncPhysbox(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,brush.rendercolor,ATTRIBUTE_DECORATION),
Ok(Entity::FuncPhysboxMultiplayer(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,brush.rendercolor,ATTRIBUTE_DECORATION),
Ok(Entity::FuncRotButton(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,WHITE,ATTRIBUTE_DECORATION),
Ok(Entity::FuncRotating(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,brush.rendercolor,ATTRIBUTE_DECORATION),
Ok(Entity::FuncTracktrain(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,brush.rendercolor,ATTRIBUTE_DECORATION),
Ok(Entity::FuncTrain(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,brush.rendercolor,ATTRIBUTE_DECORATION),
Ok(Entity::FuncWall(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin.unwrap_or_default(),brush.rendercolor,ATTRIBUTE_DECORATION),
Ok(Entity::FuncWallToggle(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin.unwrap_or_default(),brush.rendercolor,ATTRIBUTE_DECORATION),
Ok(Entity::FuncWaterAnalog(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,brush.rendercolor.unwrap_or(WHITE),ATTRIBUTE_DECORATION),
Ok(Entity::PropDoorRotating(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,WHITE,ATTRIBUTE_DECORATION),
Ok(Entity::PropDynamic(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,WHITE,ATTRIBUTE_DECORATION),
Ok(Entity::PropDynamicOverride(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,WHITE,ATTRIBUTE_DECORATION),
Ok(Entity::PropPhysics(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,WHITE,ATTRIBUTE_DECORATION),
Ok(Entity::PropPhysicsMultiplayer(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,WHITE,ATTRIBUTE_DECORATION),
Ok(Entity::PropPhysicsOverride(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,WHITE,ATTRIBUTE_DECORATION),
Ok(Entity::PropRagdoll(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,WHITE,ATTRIBUTE_DECORATION),
Ok(Entity::TriggerGravity(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,WHITE,ATTRIBUTE_DECORATION),
Ok(Entity::TriggerHurt(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,WHITE,ATTRIBUTE_DECORATION),
Ok(Entity::TriggerLook(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,WHITE,ATTRIBUTE_DECORATION),
Ok(Entity::TriggerMultiple(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model.unwrap_or_default(),brush.origin,WHITE,ATTRIBUTE_DECORATION),
Ok(Entity::TriggerOnce(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,WHITE,ATTRIBUTE_DECORATION),
Ok(Entity::TriggerProximity(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,WHITE,ATTRIBUTE_DECORATION),
Ok(Entity::TriggerPush(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,WHITE,ATTRIBUTE_DECORATION),
Ok(Entity::TriggerSoundscape(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,WHITE,ATTRIBUTE_DECORATION),
Ok(Entity::TriggerTeleport(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model.unwrap_or_default(),brush.origin,WHITE,ATTRIBUTE_DECORATION),
Ok(Entity::TriggerVphysicsMotion(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,WHITE,ATTRIBUTE_DECORATION),
Ok(Entity::TriggerWind(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,WHITE,ATTRIBUTE_DECORATION),
Ok(Entity::InfoPlayerCounterterrorist(spawn))=>{
found_spawn=Some(valve_transform(spawn.origin.into()));
},
Ok(Entity::InfoPlayerTerrorist(spawn))=>{
found_spawn=Some(valve_transform(spawn.origin.into()));
},
Ok(Entity::FuncLod(_func_lod))=>(),
Ok(Entity::FuncMonitor(brush))=>ent_brush_default!(brush),
Ok(Entity::FuncMovelinear(brush))=>ent_brush_default!(brush),
Ok(Entity::FuncOccluder(_func_occluder))=>(),
Ok(Entity::FuncPhysbox(brush))=>ent_brush_default!(brush),
Ok(Entity::FuncPhysboxMultiplayer(brush))=>ent_brush_default!(brush),
Ok(Entity::FuncPrecipitation(_func_precipitation))=>(),
Ok(Entity::FuncRotButton(brush))=>ent_brush_prop!(brush),
Ok(Entity::FuncRotating(brush))=>ent_brush_default!(brush),
Ok(Entity::FuncSmokevolume(_func_smokevolume))=>(),
Ok(Entity::FuncTracktrain(brush))=>ent_brush_default!(brush),
Ok(Entity::FuncTrain(brush))=>ent_brush_default!(brush),
Ok(Entity::FuncWall(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin.unwrap_or_default(),brush.rendercolor,ENTITY_ATTRIBUTE),
Ok(Entity::FuncWallToggle(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin.unwrap_or_default(),brush.rendercolor,ENTITY_ATTRIBUTE),
Ok(Entity::FuncWaterAnalog(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,brush.rendercolor.unwrap_or(WHITE),ENTITY_ATTRIBUTE),
Ok(Entity::GamePlayerEquip(_game_player_equip))=>(),
Ok(Entity::GameText(_game_text))=>(),
Ok(Entity::GameUi(_game_ui))=>(),
Ok(Entity::GameWeaponManager(_game_weapon_manager))=>(),
Ok(Entity::HostageEntity(_hostage_entity))=>(),
Ok(Entity::InfoCameraLink(_info_camera_link))=>(),
Ok(Entity::InfoLadder(_info_ladder))=>(),
Ok(Entity::InfoLightingRelative(_info_lighting_relative))=>(),
Ok(Entity::InfoMapParameters(_info_map_parameters))=>(),
Ok(Entity::InfoNode(_info_node))=>(),
Ok(Entity::InfoNodeHint(_info_node_hint))=>(),
Ok(Entity::InfoParticleSystem(_info_particle_system))=>(),
Ok(Entity::InfoPlayerCounterterrorist(spawn))=>found_spawn=Some(spawn.origin),
Ok(Entity::InfoPlayerLogo(_info_player_logo))=>(),
Ok(Entity::InfoPlayerStart(_info_player_start))=>(),
Ok(Entity::InfoPlayerTerrorist(spawn))=>found_spawn=Some(spawn.origin),
Ok(Entity::InfoTarget(_info_target))=>(),
// InfoTeleportDestination is Spawn#
Ok(Entity::InfoTeleportDestination(_info_teleport_destination))=>(),
Ok(Entity::Infodecal(_infodecal))=>(),
Ok(Entity::KeyframeRope(_keyframe_rope))=>(),
Ok(Entity::Light(_light))=>(),
Ok(Entity::LightEnvironment(_light_environment))=>(),
Ok(Entity::LightSpot(_light_spot))=>(),
Ok(Entity::LogicAuto(_logic_auto))=>(),
Ok(Entity::LogicBranch(_logic_branch))=>(),
Ok(Entity::LogicCase(_logic_case))=>(),
Ok(Entity::LogicCompare(_logic_compare))=>(),
Ok(Entity::LogicMeasureMovement(_logic_measure_movement))=>(),
Ok(Entity::LogicRelay(_logic_relay))=>(),
Ok(Entity::LogicTimer(_logic_timer))=>(),
Ok(Entity::MathCounter(_math_counter))=>(),
Ok(Entity::MoveRope(_move_rope))=>(),
Ok(Entity::PathTrack(_path_track))=>(),
Ok(Entity::PhysBallsocket(_phys_ballsocket))=>(),
Ok(Entity::PhysConstraint(_phys_constraint))=>(),
Ok(Entity::PhysConstraintsystem(_phys_constraintsystem))=>(),
Ok(Entity::PhysHinge(_phys_hinge))=>(),
Ok(Entity::PhysKeepupright(_phys_keepupright))=>(),
Ok(Entity::PhysLengthconstraint(_phys_lengthconstraint))=>(),
Ok(Entity::PhysPulleyconstraint(_phys_pulleyconstraint))=>(),
Ok(Entity::PhysRagdollconstraint(_phys_ragdollconstraint))=>(),
Ok(Entity::PhysRagdollmagnet(_phys_ragdollmagnet))=>(),
Ok(Entity::PhysThruster(_phys_thruster))=>(),
Ok(Entity::PhysTorque(_phys_torque))=>(),
Ok(Entity::PlayerSpeedmod(_player_speedmod))=>(),
Ok(Entity::PlayerWeaponstrip(_player_weaponstrip))=>(),
Ok(Entity::PointCamera(_point_camera))=>(),
Ok(Entity::PointClientcommand(_point_clientcommand))=>(),
Ok(Entity::PointDevshotCamera(_point_devshot_camera))=>(),
Ok(Entity::PointServercommand(_point_servercommand))=>(),
Ok(Entity::PointSpotlight(_point_spotlight))=>(),
Ok(Entity::PointSurroundtest(_point_surroundtest))=>(),
Ok(Entity::PointTemplate(_point_template))=>(),
Ok(Entity::PointTesla(_point_tesla))=>(),
Ok(Entity::PointViewcontrol(_point_viewcontrol))=>(),
Ok(Entity::PropDoorRotating(brush))=>ent_brush_prop!(brush),
Ok(Entity::PropDynamic(brush))=>ent_brush_prop!(brush),
Ok(Entity::PropDynamicOverride(brush))=>ent_brush_prop!(brush),
Ok(Entity::PropPhysics(brush))=>ent_brush_prop!(brush),
Ok(Entity::PropPhysicsMultiplayer(brush))=>ent_brush_prop!(brush),
Ok(Entity::PropPhysicsOverride(brush))=>ent_brush_prop!(brush),
Ok(Entity::PropRagdoll(brush))=>ent_brush_prop!(brush),
Ok(Entity::ShadowControl(_shadow_control))=>(),
Ok(Entity::SkyCamera(_sky_camera))=>(),
Ok(Entity::TriggerGravity(brush))=>ent_brush_trigger!(brush),
Ok(Entity::TriggerHurt(brush))=>ent_brush_trigger!(brush),
Ok(Entity::TriggerLook(brush))=>ent_brush_trigger!(brush),
Ok(Entity::TriggerMultiple(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model.unwrap_or_default(),brush.origin,WHITE,ENTITY_TRIGGER_ATTRIBUTE),
Ok(Entity::TriggerOnce(brush))=>ent_brush_trigger!(brush),
Ok(Entity::TriggerProximity(brush))=>ent_brush_trigger!(brush),
// TriggerPush is booster
Ok(Entity::TriggerPush(brush))=>ent_brush_trigger!(brush),
Ok(Entity::TriggerSoundscape(brush))=>ent_brush_trigger!(brush),
// TriggerTeleport is Trigger#
Ok(Entity::TriggerTeleport(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model.unwrap_or_default(),brush.origin,WHITE,ENTITY_TRIGGER_ATTRIBUTE),
Ok(Entity::TriggerVphysicsMotion(brush))=>ent_brush_trigger!(brush),
Ok(Entity::TriggerWind(brush))=>ent_brush_trigger!(brush),
Ok(Entity::WaterLodControl(_water_lod_control))=>(),
Ok(Entity::WeaponAk47(_weapon_ak47))=>(),
Ok(Entity::WeaponAwp(_weapon_awp))=>(),
Ok(Entity::WeaponDeagle(_weapon_deagle))=>(),
Ok(Entity::WeaponElite(_weapon_elite))=>(),
Ok(Entity::WeaponFamas(_weapon_famas))=>(),
Ok(Entity::WeaponFiveseven(_weapon_fiveseven))=>(),
Ok(Entity::WeaponFlashbang(_weapon_flashbang))=>(),
Ok(Entity::WeaponG3sg1(_weapon_g3sg1))=>(),
Ok(Entity::WeaponGlock(_weapon_glock))=>(),
Ok(Entity::WeaponHegrenade(_weapon_hegrenade))=>(),
Ok(Entity::WeaponKnife(_weapon_knife))=>(),
Ok(Entity::WeaponM249(_weapon_m249))=>(),
Ok(Entity::WeaponM3(_weapon_m3))=>(),
Ok(Entity::WeaponM4a1(_weapon_m4a1))=>(),
Ok(Entity::WeaponMac10(_weapon_mac10))=>(),
Ok(Entity::WeaponP228(_weapon_p228))=>(),
Ok(Entity::WeaponP90(_weapon_p90))=>(),
Ok(Entity::WeaponScout(_weapon_scout))=>(),
Ok(Entity::WeaponSg550(_weapon_sg550))=>(),
Ok(Entity::WeaponSmokegrenade(_weapon_smokegrenade))=>(),
Ok(Entity::WeaponTmp(_weapon_tmp))=>(),
Ok(Entity::WeaponUmp45(_weapon_ump45))=>(),
Ok(Entity::WeaponUsp(_weapon_usp))=>(),
Ok(Entity::WeaponXm1014(_weapon_xm1014))=>(),
Ok(Entity::Worldspawn(_worldspawn))=>(),
Err(e)=>{
println!("Bsp Entity parse error: {e}");
},
@@ -295,7 +447,7 @@ pub fn convert<'a>(
attributes,
transform:integer::Planar64Affine3::new(
integer::mat3::identity(),
integer::vec3::zero(),
integer::vec3::ZERO,
),
color:glam::Vec4::ONE,
});
@@ -306,15 +458,12 @@ pub fn convert<'a>(
let mut modes_list=Vec::new();
if let Some(spawn_point)=found_spawn{
// create a new mesh
let mesh_id=model::MeshId::new(world_meshes.len() as u32);
world_meshes.push(crate::brush::unit_cube());
// create a new model
let model_id=model::ModelId::new(world_models.len() as u32);
world_models.push(model::Model{
mesh:mesh_id,
mesh:destination_mesh_id,
attributes:ATTRIBUTE_INTERSECT_DEFAULT,
transform:integer::Planar64Affine3::from_translation(spawn_point),
transform:integer::Planar64Affine3::from_translation(valve_transform(spawn_point.into())),
color:glam::Vec4::W,
});
@@ -347,9 +496,9 @@ pub struct PartialMap1{
modes:NormalizedModes,
}
impl PartialMap1{
pub fn add_prop_meshes(
pub fn add_prop_meshes<'a>(
self,
prop_meshes:Meshes<model::Mesh>,
prop_meshes:Meshes,
)->PartialMap2{
PartialMap2{
attributes:self.attributes,

View File

@@ -1,6 +1,9 @@
pub mod bsp;
pub mod mesh;
use strafesnet_deferred_loader::deferred_loader::{LoadFailureMode,MeshDeferredLoader,RenderConfigDeferredLoader};
mod bsp;
mod mesh;
mod brush;
pub mod loader;
const VALVE_SCALE:f32=1.0/16.0;
pub(crate) fn valve_transform_dist(d:f32)->strafesnet_common::integer::Planar64{
@@ -25,6 +28,28 @@ impl std::fmt::Display for ReadError{
}
impl std::error::Error for ReadError{}
#[derive(Debug)]
pub enum LoadError{
Texture(loader::TextureError),
Mesh(loader::MeshError),
}
impl std::fmt::Display for LoadError{
fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{
write!(f,"{self:?}")
}
}
impl std::error::Error for LoadError{}
impl From<loader::TextureError> for LoadError{
fn from(value:loader::TextureError)->Self{
Self::Texture(value)
}
}
impl From<loader::MeshError> for LoadError{
fn from(value:loader::MeshError)->Self{
Self::Mesh(value)
}
}
pub struct Bsp{
bsp:vbsp::Bsp,
case_folded_file_names:std::collections::HashMap<String,String>,
@@ -59,6 +84,28 @@ impl Bsp{
None=>Ok(None),
}
}
pub fn to_snf(&self,failure_mode:LoadFailureMode,vpk_list:&[Vpk])->Result<strafesnet_common::map::CompleteMap,LoadError>{
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 mut mesh_loader=loader::MeshLoader::new(loader::BspFinder{bsp:self,vpks:vpk_list},&mut texture_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 mut texture_loader=loader::TextureLoader::new();
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{
vpk:vpk::VPK,

View File

@@ -1,10 +1,11 @@
use std::{borrow::Cow, io::Read};
use strafesnet_common::model::Mesh;
use strafesnet_deferred_loader::{loader::Loader,texture::Texture};
use super::BspFinder;
use crate::{Bsp,Vpk};
#[expect(dead_code)]
#[allow(dead_code)]
#[derive(Debug)]
pub enum TextureError{
Io(std::io::Error),
@@ -21,17 +22,17 @@ impl From<std::io::Error> for TextureError{
}
}
pub struct TextureLoader;
impl TextureLoader{
pub struct TextureLoader<'a>(std::marker::PhantomData<&'a ()>);
impl TextureLoader<'_>{
pub fn new()->Self{
Self
Self(std::marker::PhantomData)
}
}
impl Loader for TextureLoader{
impl<'a> Loader for TextureLoader<'a>{
type Error=TextureError;
type Index<'a>=Cow<'a,str>;
type Index=Cow<'a,str>;
type Resource=Texture;
fn load(&mut self,index:Self::Index<'_>)->Result<Self::Resource,Self::Error>{
fn load(&mut self,index:Self::Index)->Result<Self::Resource,Self::Error>{
let file_name=format!("textures/{}.dds",index);
let mut file=std::fs::File::open(file_name)?;
let mut data=Vec::new();
@@ -40,7 +41,7 @@ impl Loader for TextureLoader{
}
}
#[expect(dead_code)]
#[allow(dead_code)]
#[derive(Debug)]
pub enum MeshError{
Io(std::io::Error),
@@ -72,24 +73,57 @@ impl From<vbsp::BspError> for MeshError{
}
}
pub struct ModelLoader<'bsp,'vpk>{
finder:BspFinder<'bsp,'vpk>,
#[derive(Clone,Copy)]
pub struct BspFinder<'bsp,'vpk>{
pub bsp:&'bsp Bsp,
pub vpks:&'vpk [Vpk],
}
impl ModelLoader<'_,'_>{
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,'a>{
finder:BspFinder<'bsp,'vpk>,
life:core::marker::PhantomData<&'a ()>,
}
impl ModelLoader<'_,'_,'_>{
#[inline]
pub const fn new<'bsp,'vpk>(
pub const fn new<'bsp,'vpk,'a>(
finder:BspFinder<'bsp,'vpk>,
)->ModelLoader<'bsp,'vpk>{
)->ModelLoader<'bsp,'vpk,'a>{
ModelLoader{
finder,
life:core::marker::PhantomData,
}
}
}
impl Loader for ModelLoader<'_,'_>{
impl<'bsp,'vpk,'a> Loader for ModelLoader<'bsp,'vpk,'a>
where
'bsp:'a,
'vpk:'a,
{
type Error=MeshError;
type Index<'a>=&'a str where Self:'a;
type Index=&'a str;
type Resource=vmdl::Model;
fn load<'a>(&'a mut self,index:Self::Index<'a>)->Result<Self::Resource,Self::Error>{
fn load(&mut self,index:Self::Index)->Result<Self::Resource,Self::Error>{
let mdl_path_lower=index.to_lowercase();
//.mdl, .vvd, .dx90.vtx
let path=std::path::PathBuf::from(mdl_path_lower.as_str());
@@ -109,29 +143,33 @@ impl Loader for ModelLoader<'_,'_>{
}
}
pub struct MeshLoader<'bsp,'vpk,'load,'str>{
pub struct MeshLoader<'bsp,'vpk,'load,'a>{
finder:BspFinder<'bsp,'vpk>,
deferred_loader:&'load mut strafesnet_deferred_loader::deferred_loader::RenderConfigDeferredLoader<Cow<'str,str>>,
deferred_loader:&'load mut strafesnet_deferred_loader::deferred_loader::RenderConfigDeferredLoader<Cow<'a,str>>,
}
impl MeshLoader<'_,'_,'_,'_>{
#[inline]
pub const fn new<'bsp,'vpk,'load,'str>(
pub const fn new<'bsp,'vpk,'load,'a>(
finder:BspFinder<'bsp,'vpk>,
deferred_loader:&'load mut strafesnet_deferred_loader::deferred_loader::RenderConfigDeferredLoader<Cow<'str,str>>,
)->MeshLoader<'bsp,'vpk,'load,'str>{
deferred_loader:&'load mut strafesnet_deferred_loader::deferred_loader::RenderConfigDeferredLoader<Cow<'a,str>>,
)->MeshLoader<'bsp,'vpk,'load,'a>{
MeshLoader{
finder,
deferred_loader
}
}
}
impl Loader for MeshLoader<'_,'_,'_,'_>{
impl<'bsp,'vpk,'load,'a> Loader for MeshLoader<'bsp,'vpk,'load,'a>
where
'bsp:'a,
'vpk:'a,
{
type Error=MeshError;
type Index<'a>=&'a str where Self:'a;
type Resource=strafesnet_bsp_loader::mesh::Mesh;
fn load<'a>(&'a mut self,index:Self::Index<'a>)->Result<Self::Resource,Self::Error>{
type Index=&'a str;
type Resource=Mesh;
fn load(&mut self,index:Self::Index)->Result<Self::Resource,Self::Error>{
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)
}
}

View File

@@ -5,8 +5,6 @@ use strafesnet_deferred_loader::deferred_loader::RenderConfigDeferredLoader;
use crate::valve_transform;
pub use model::Mesh;
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 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();
if texture_paths.len()!=1{
println!("WARNING: multiple texture paths");
@@ -63,7 +61,7 @@ pub fn convert_mesh(model:vmdl::Model,deferred_loader:&mut RenderConfigDeferredL
_=>None,
}
})
}).filter_map(|[v1,v2,v3]|{
}).flat_map(|[v1,v2,v3]|{
// this should probably be a fatal error :D
let v1=model_vertices.get(v1)?;
let v2=model_vertices.get(v2)?;

View File

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

View File

@@ -2,9 +2,7 @@ use crate::integer::{vec3,Planar64Vec3};
#[derive(Clone)]
pub struct Aabb{
// min is inclusive
min:Planar64Vec3,
// max is not inclusive
max:Planar64Vec3,
}
@@ -45,7 +43,7 @@ impl Aabb{
}
#[inline]
pub fn contains(&self,point:Planar64Vec3)->bool{
let bvec=self.min.le(point)&point.lt(self.max);
let bvec=self.min.lt(point)&point.lt(self.max);
bvec.all()
}
#[inline]
@@ -61,11 +59,11 @@ impl Aabb{
pub fn center(&self)->Planar64Vec3{
self.min.map_zip(self.max,|(min,max)|min.midpoint(max))
}
#[inline]
pub fn area_weight(&self)->fixed_wide::types::F128_64{
let d=self.max-self.min;
d.x*d.y+d.y*d.z+d.z*d.x
}
//probably use floats for area & volume because we don't care about precision
// pub fn area_weight(&self)->f32{
// let d=self.max-self.min;
// d.x*d.y+d.y*d.z+d.z*d.x
// }
// pub fn volume(&self)->f32{
// let d=self.max-self.min;
// d.x*d.y*d.z

View File

@@ -3,7 +3,7 @@ use std::collections::BTreeMap;
use crate::aabb::Aabb;
use crate::ray::Ray;
use crate::integer::{Ratio,Planar64,Planar64Vec3};
use crate::integer::{Ratio,Planar64};
use crate::instruction::{InstructionCollector,TimedInstruction};
//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,
start_point:Planar64Vec3,
collector:&mut InstructionCollector<&'a L,T>,
nodes:&mut BTreeMap<T,&'a BvhNode<L>>,
start_time:T,
intersect_leaf:&IntersectLeaf,
intersect_aabb:&IntersectAabb,
collector:&mut InstructionCollector<&'a L,Ratio<Planar64,Planar64>>,
nodes:&mut BTreeMap<Ratio<Planar64,Planar64>,&'a BvhNode<L>>,
ray:&Ray,
start_time:Ratio<Planar64,Planar64>,
f:&F,
)
where
T:Ord+Copy,
IntersectLeaf:Fn(&L)->Option<T>,
IntersectAabb:Fn(&Aabb)->Option<T>,
Ratio<Planar64,Planar64>:From<T>,
F:Fn(&L,&Ray)->Option<T>,
{
match &self.content{
RecursiveContent::Leaf(leaf)=>if let Some(time)=intersect_leaf(leaf){
let ins=TimedInstruction{time,instruction:leaf};
if start_time<ins.time&&ins.time<collector.time(){
RecursiveContent::Leaf(leaf)=>if let Some(time)=f(leaf,ray){
let ins=TimedInstruction{time:time.into(),instruction:leaf};
if start_time.lt_ratio(ins.time)&&ins.time.lt_ratio(collector.time()){
collector.collect(Some(ins));
}
},
RecursiveContent::Branch(children)=>for child in children{
if child.aabb.contains(start_point){
child.populate_nodes(start_point,collector,nodes,start_time,intersect_leaf,intersect_aabb);
if child.aabb.contains(ray.origin){
child.populate_nodes(collector,nodes,ray,start_time,f);
}else{
// Am I an upcoming superstar?
if let Some(t)=intersect_aabb(&child.aabb){
if start_time<t&&t<collector.time(){
if let Some(t)=intersect_aabb(ray,&child.aabb){
if start_time.lt_ratio(t)&&t.lt_ratio(collector.time()){
nodes.insert(t,child);
}
}
@@ -175,29 +174,27 @@ impl<L> BvhNode<L>{
},
}
}
/// Traverse the BVH using the given sampling functions.
/// 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>(
pub fn sample_ray<T,F>(
&self,
start_point:Planar64Vec3,
ray:&Ray,
start_time:T,
time_limit:T,
intersect_leaf:IntersectLeaf,
intersect_aabb:IntersectAabb,
f:F,
)->Option<(T,&L)>
where
T:Ord+Copy,
IntersectLeaf:Fn(&L)->Option<T>,
IntersectAabb:Fn(&Aabb)->Option<T>,
T:From<Ratio<Planar64,Planar64>>,
Ratio<Planar64,Planar64>:From<T>,
F:Fn(&L,&Ray)->Option<T>,
{
// source of nondeterminism when Aabb boundaries are coplanar
let mut nodes=BTreeMap::new();
let start_time=start_time.into();
let time_limit=time_limit.into();
let mut collector=InstructionCollector::new(time_limit);
// 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
while let Some((t,node))=nodes.pop_first(){
@@ -205,18 +202,18 @@ impl<L> BvhNode<L>{
break;
}
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};
// this lower bound can also be omitted
// 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));
}
},
// break open the node and predict collisions with the child nodes
RecursiveContent::Branch(children)=>for child in children{
// 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
// because child aabbs are guaranteed to be within the parent bounds.
if t<collector.time(){
@@ -248,19 +245,18 @@ pub fn generate_bvh<T>(boxen:Vec<(T,Aabb)>)->BvhNode<T>{
fn generate_bvh_node<T>(boxen:Vec<(T,Aabb)>,force:bool)->BvhNode<T>{
let n=boxen.len();
const MAX_TERMINAL_BRANCH_LEAF_NODES:usize=20;
if force||n<MAX_TERMINAL_BRANCH_LEAF_NODES{
let mut aabb_outer=Aabb::default();
let nodes=boxen.into_iter().map(|(data,aabb)|{
aabb_outer.join(&aabb);
if force||n<20{
let mut aabb=Aabb::default();
let nodes=boxen.into_iter().map(|b|{
aabb.join(&b.1);
BvhNode{
content:RecursiveContent::Leaf(data),
aabb,
content:RecursiveContent::Leaf(b.0),
aabb:b.1,
}
}).collect();
BvhNode{
content:RecursiveContent::Branch(nodes),
aabb:aabb_outer,
aabb,
}
}else{
let mut sort_x=Vec::with_capacity(n);
@@ -276,9 +272,9 @@ fn generate_bvh_node<T>(boxen:Vec<(T,Aabb)>,force:bool)->BvhNode<T>{
sort_y.sort_by_key(|&(_,c)|c);
sort_z.sort_by_key(|&(_,c)|c);
let h=n/2;
let (_,median_x)=sort_x[h];
let (_,median_y)=sort_y[h];
let (_,median_z)=sort_z[h];
let median_x=sort_x[h].1;
let median_y=sort_y[h].1;
let median_z=sort_z[h].1;
//locate a run of values equal to the median
//partition point gives the first index for which the predicate evaluates to false
let first_index_eq_median_x=sort_x.partition_point(|&(_,x)|x<median_x);
@@ -317,10 +313,10 @@ fn generate_bvh_node<T>(boxen:Vec<(T,Aabb)>,force:bool)->BvhNode<T>{
};
list_list[list_id].push((data,aabb));
}
let mut aabb=Aabb::default();
if list_list.len()==1{
generate_bvh_node(list_list.remove(0),true)
}else{
let mut aabb=Aabb::default();
BvhNode{
content:RecursiveContent::Branch(
list_list.into_iter().map(|b|{

View File

@@ -140,15 +140,6 @@ impl ModeId{
pub const MAIN:Self=Self(0);
pub const BONUS:Self=Self(1);
}
impl core::fmt::Display for ModeId{
fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->core::fmt::Result{
match self{
&Self::MAIN=>write!(f,"Main"),
&Self::BONUS=>write!(f,"Bonus"),
&Self(mode_id)=>write!(f,"Bonus{mode_id}"),
}
}
}
#[derive(Clone)]
pub struct Mode{
style:gameplay_style::StyleModifiers,
@@ -449,11 +440,11 @@ impl ModesBuilder{
}
NormalizedModes::new(modes.into_iter().map(|mode_builder|NormalizedMode(mode_builder.mode)).collect())
}
pub fn insert_mode(&mut self,mode_id:ModeId,mode:Mode)->Result<(),ExistingEntryError>{
error_if_exists(self.modes.insert(mode_id,mode))
pub fn insert_mode(&mut self,mode_id:ModeId,mode:Mode){
assert!(self.modes.insert(mode_id,mode).is_none(),"Cannot replace existing mode");
}
pub fn insert_stage(&mut self,mode_id:ModeId,stage_id:StageId,stage:Stage)->Result<(),ExistingEntryError>{
error_if_exists(self.stages.entry(mode_id).or_insert(HashMap::new()).insert(stage_id,stage))
pub fn insert_stage(&mut self,mode_id:ModeId,stage_id:StageId,stage:Stage){
assert!(self.stages.entry(mode_id).or_insert(HashMap::new()).insert(stage_id,stage).is_none(),"Cannot replace existing stage");
}
pub fn push_mode_update(&mut self,mode_id:ModeId,mode_update:ModeUpdate){
self.mode_updates.push((mode_id,mode_update));
@@ -462,12 +453,3 @@ impl ModesBuilder{
// self.stage_updates.push((mode_id,stage_id,stage_update));
// }
}
#[derive(Debug)]
pub struct ExistingEntryError;
fn error_if_exists<T>(value:Option<T>)->Result<(),ExistingEntryError>{
match value{
Some(_)=>Err(ExistingEntryError),
None=>Ok(())
}
}

View File

@@ -34,7 +34,7 @@ pub struct StyleModifiers{
//unused
pub mass:Planar64,
}
impl Default for StyleModifiers{
impl std::default::Default for StyleModifiers{
fn default()->Self{
Self::roblox_bhop()
}
@@ -66,18 +66,18 @@ impl JumpImpulse{
_mass:Planar64,
)->Planar64Vec3{
match self{
&JumpImpulse::Time(time)=>velocity-(*gravity*time).map(|t|t.divide().clamp_64()),
&JumpImpulse::Time(time)=>velocity-(*gravity*time).map(|t|t.divide().fix_1()),
&JumpImpulse::Height(height)=>{
//height==-v.y*v.y/(2*g.y);
//use energy to determine max height
let gg=gravity.length_squared();
let g=gg.sqrt();
let g=gg.sqrt().fix_1();
let v_g=gravity.dot(velocity);
//do it backwards
let radicand=v_g*v_g+(g*height*2).widen_256();
velocity-(*gravity*(radicand.sqrt().wrap_128()+v_g)/gg).divide().clamp_64()
let radicand=v_g*v_g+(g*height*2).fix_4();
velocity-(*gravity*(radicand.sqrt().fix_2()+v_g)/gg).divide().fix_1()
},
&JumpImpulse::Linear(jump_speed)=>velocity+(jump_dir*jump_speed/jump_dir.length()).divide().clamp_64(),
&JumpImpulse::Linear(jump_speed)=>velocity+(jump_dir*jump_speed/jump_dir.length()).divide().fix_1(),
&JumpImpulse::Energy(_energy)=>{
//calculate energy
//let e=gravity.dot(velocity);
@@ -91,10 +91,10 @@ impl JumpImpulse{
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
match self{
&JumpImpulse::Time(time)=>(gravity.length().wrap_64()*time/2).divide().clamp_64(),
&JumpImpulse::Height(height)=>(gravity.length()*height*2).sqrt().wrap_64(),
&JumpImpulse::Time(time)=>(gravity.length().fix_1()*time/2).divide().fix_1(),
&JumpImpulse::Height(height)=>(gravity.length()*height*2).sqrt().fix_1(),
&JumpImpulse::Linear(deltav)=>deltav,
&JumpImpulse::Energy(energy)=>(energy.sqrt()*2/mass.sqrt()).divide().clamp_64(),
&JumpImpulse::Energy(energy)=>(energy.sqrt()*2/mass.sqrt()).divide().fix_1(),
}
}
}
@@ -126,10 +126,10 @@ impl JumpSettings{
None=>rel_velocity,
};
let j=boost_vel.dot(jump_dir);
let js=jump_speed.widen_128();
let js=jump_speed.fix_2();
if j<js{
//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().fix_1()
}else{
//activate booster normally, jump does nothing
boost_vel
@@ -142,13 +142,13 @@ impl JumpSettings{
None=>rel_velocity,
};
let j=boost_vel.dot(jump_dir);
let js=jump_speed.widen_128();
let js=jump_speed.fix_2();
if j<js{
//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().fix_1()
}else{
//boost and jump add together
boost_vel+jump_dir.with_length(js).divide().wrap_64()
boost_vel+jump_dir.with_length(js).divide().fix_1()
}
}
(false,JumpCalculation::Max)=>{
@@ -159,10 +159,10 @@ impl JumpSettings{
None=>rel_velocity,
};
let boost_dot=boost_vel.dot(jump_dir);
let js=jump_speed.widen_128();
let js=jump_speed.fix_2();
if boost_dot<js{
//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().fix_1()
}else{
//activate booster normally, jump does nothing
boost_vel
@@ -174,7 +174,7 @@ impl JumpSettings{
Some(booster)=>booster.boost(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().fix_1()
},
}
}
@@ -267,9 +267,9 @@ pub struct StrafeSettings{
impl StrafeSettings{
pub fn tick_velocity(&self,velocity:Planar64Vec3,control_dir:Planar64Vec3)->Option<Planar64Vec3>{
let d=velocity.dot(control_dir);
let mv=self.mv.widen_128();
let mv=self.mv.fix_2();
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.fix_2().min(mv-d))).fix_1()),
false=>None,
}
}
@@ -290,7 +290,7 @@ pub struct PropulsionSettings{
}
impl PropulsionSettings{
pub fn acceleration(&self,control_dir:Planar64Vec3)->Planar64Vec3{
(control_dir*self.magnitude).clamp_64()
(control_dir*self.magnitude).fix_1()
}
}
@@ -310,16 +310,16 @@ pub struct WalkSettings{
impl WalkSettings{
pub fn accel(&self,target_diff:Planar64Vec3,gravity:Planar64Vec3)->Planar64{
//TODO: fallible walk accel
let diff_len=target_diff.length().wrap_64();
let diff_len=target_diff.length().fix_1();
let friction=if diff_len<self.accelerate.topspeed{
self.static_friction
}else{
self.kinetic_friction
};
self.accelerate.accel.min((-gravity.y*friction).clamp_64())
self.accelerate.accel.min((-gravity.y*friction).fix_1())
}
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{
return control_dir;
}
let nn=normal.length_squared();
@@ -329,19 +329,19 @@ impl WalkSettings{
let dd=d*d;
if dd<nnmm{
let cr=normal.cross(control_dir);
if cr==crate::integer::vec3::zero(){
crate::integer::vec3::zero()
if cr==crate::integer::vec3::ZERO_2{
crate::integer::vec3::ZERO
}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().fix_1()
}
}else{
crate::integer::vec3::zero()
crate::integer::vec3::ZERO
}
}
pub fn is_slope_walkable(&self,normal:Planar64Vec3,up:Planar64Vec3)->bool{
//normal is not guaranteed to be unit length
let ny=normal.dot(up);
let h=normal.length().wrap_64();
let h=normal.length().fix_1();
//remember this is a normal vector
ny.is_positive()&&h*self.surf_dot<ny
}
@@ -360,7 +360,7 @@ impl LadderSettings{
self.accelerate.accel
}
pub fn get_ladder_target_velocity(&self,mut control_dir:Planar64Vec3,normal:Planar64Vec3)->Planar64Vec3{
if control_dir==crate::integer::vec3::zero(){
if control_dir==crate::integer::vec3::ZERO{
return control_dir;
}
let nn=normal.length_squared();
@@ -368,13 +368,13 @@ impl LadderSettings{
let nnmm=nn*mm;
let d=normal.dot(control_dir);
let mut dd=d*d;
if (self.dot*self.dot*nnmm).clamp_256()<dd{
if (self.dot*self.dot*nnmm).fix_4()<dd{
if d.is_negative(){
control_dir=Planar64Vec3::new([Planar64::ZERO,mm.clamp_64(),Planar64::ZERO]);
control_dir=Planar64Vec3::new([Planar64::ZERO,mm.fix_1(),Planar64::ZERO]);
}else{
control_dir=Planar64Vec3::new([Planar64::ZERO,-mm.clamp_64(),Planar64::ZERO]);
control_dir=Planar64Vec3::new([Planar64::ZERO,-mm.fix_1(),Planar64::ZERO]);
}
dd=(normal.y*normal.y).widen_256();
dd=(normal.y*normal.y).fix_4();
}
//n=d if you are standing on top of a ladder and press E.
//two fixes:
@@ -382,13 +382,13 @@ impl LadderSettings{
//- fix the underlying issue
if dd<nnmm{
let cr=normal.cross(control_dir);
if cr==crate::integer::vec3::zero(){
crate::integer::vec3::zero()
if cr==crate::integer::vec3::ZERO_2{
crate::integer::vec3::ZERO
}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().fix_1()
}
}else{
crate::integer::vec3::zero()
crate::integer::vec3::ZERO
}
}
}
@@ -417,7 +417,7 @@ impl Hitbox{
}
pub fn source()->Self{
Self{
halfsize:((int3(33,73,33)>>1)*VALVE_SCALE).narrow_64().unwrap(),
halfsize:((int3(33,73,33)>>1)*VALVE_SCALE).fix_1(),
mesh:HitboxMesh::Box,
}
}
@@ -538,11 +538,11 @@ impl StyleModifiers{
tick_rate:Ratio64::new(100,AbsoluteTime::ONE_SECOND.get() as u64).unwrap(),
}),
jump:Some(JumpSettings{
impulse:JumpImpulse::Height((int(52)*VALVE_SCALE).narrow_64().unwrap()),
impulse:JumpImpulse::Height((int(52)*VALVE_SCALE).fix_1()),
calculation:JumpCalculation::JumpThenBoost,
limit_minimum:true,
}),
gravity:(int3(0,-800,0)*VALVE_SCALE).narrow_64().unwrap(),
gravity:(int3(0,-800,0)*VALVE_SCALE).fix_1(),
mass:int(1),
rocket:None,
walk:Some(WalkSettings{
@@ -565,7 +565,7 @@ impl StyleModifiers{
magnitude:int(12),//?
}),
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).fix_1(),
}
}
pub fn source_surf()->Self{
@@ -574,16 +574,16 @@ impl StyleModifiers{
controls_mask_state:Controls::all(),
strafe:Some(StrafeSettings{
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).fix_1()),
mv:Planar64::raw(30<<28),
tick_rate:Ratio64::new(66,AbsoluteTime::ONE_SECOND.get() as u64).unwrap(),
}),
jump:Some(JumpSettings{
impulse:JumpImpulse::Height((int(52)*VALVE_SCALE).narrow_64().unwrap()),
impulse:JumpImpulse::Height((int(52)*VALVE_SCALE).fix_1()),
calculation:JumpCalculation::JumpThenBoost,
limit_minimum:true,
}),
gravity:(int3(0,-800,0)*VALVE_SCALE).narrow_64().unwrap(),
gravity:(int3(0,-800,0)*VALVE_SCALE).fix_1(),
mass:int(1),
rocket:None,
walk:Some(WalkSettings{
@@ -606,7 +606,7 @@ impl StyleModifiers{
magnitude:int(12),//?
}),
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).fix_1(),
}
}
}

View File

@@ -34,41 +34,12 @@ pub trait InstructionFeedback<I,T>:InstructionEmitter<I,Time=T>+InstructionConsu
self.process_instruction(instruction);
}
}
#[inline]
fn into_iter(self,time_limit:T)->InstructionIter<I,T,Self>
where
Self:Sized
{
InstructionIter{
time_limit,
feedback:self,
_phantom:core::marker::PhantomData,
}
}
}
impl<I,T,F> InstructionFeedback<I,T> for F
impl<I,T,X> InstructionFeedback<I,T> for X
where
T:Copy,
F:InstructionEmitter<I,Time=T>+InstructionConsumer<I,Time=T>,
X:InstructionEmitter<I,Time=T>+InstructionConsumer<I,Time=T>,
{}
pub struct InstructionIter<I,T:Copy,F:InstructionFeedback<I,T>>{
time_limit:T,
feedback:F,
_phantom:core::marker::PhantomData<I>,
}
impl<I,T,F> Iterator for InstructionIter<I,T,F>
where
I:Clone,
T:Clone+Copy,
F:InstructionFeedback<I,T>,
{
type Item=TimedInstruction<I,T>;
fn next(&mut self)->Option<Self::Item>{
let instruction=self.feedback.next_instruction(self.time_limit)?;
self.feedback.process_instruction(instruction.clone());
Some(instruction)
}
}
//PROPER PRIVATE FIELDS!!!
pub struct InstructionCollector<I,T>{

View File

@@ -1,11 +1,5 @@
pub use fixed_wide::fixed::*;
pub use ratio_ops::ratio::{Ratio,Divide,Parity};
pub mod fixed_types{
pub use fixed_wide::types::*;
}
use fixed_wide::types::F128_64;
pub use fixed_wide::fixed::{Fixed,Fix};
pub use ratio_ops::ratio::{Ratio,Divide};
//integer units
@@ -62,19 +56,11 @@ impl<T> Time<T>{
pub const fn coerce<U>(self)->Time<U>{
Time::raw(self.0)
}
#[inline]
pub const fn div_euclid(self,other:Self)->Self{
Time::raw(self.0.div_euclid(other.0))
}
#[inline]
pub const fn rem_euclid(self,other:Self)->Self{
Time::raw(self.0.rem_euclid(other.0))
}
}
impl<T> From<Planar64> for Time<T>{
#[inline]
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)).fix_1().to_raw())
}
}
impl<T> From<Time<T>> for Ratio<Planar64,Planar64>{
@@ -87,11 +73,11 @@ impl<T,Num,Den,N1,T1> From<Ratio<Num,Den>> for Time<T>
where
Num:core::ops::Mul<Planar64,Output=N1>,
N1:Divide<Den,Output=T1>,
T1:Clamp<Planar64>,
T1:Fix<Planar64>,
{
#[inline]
fn from(value:Ratio<Num,Den>)->Self{
Self::raw((value*Planar64::raw(1_000_000_000)).divide().clamp().to_raw())
Self::raw((value*Planar64::raw(1_000_000_000)).divide().fix().to_raw())
}
}
impl<T> std::fmt::Display for Time<T>{
@@ -100,7 +86,7 @@ impl<T> std::fmt::Display for Time<T>{
write!(f,"{}s+{:09}ns",self.0/Self::ONE_SECOND.0,self.0%Self::ONE_SECOND.0)
}
}
impl<T> Default for Time<T>{
impl<T> std::default::Default for Time<T>{
fn default()->Self{
Self::raw(0)
}
@@ -140,51 +126,36 @@ 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::RemAssign,rem_assign);
impl<T> std::ops::Mul for Time<T>{
type Output=Ratio<F128_64,F128_64>;
type Output=Ratio<fixed_wide::fixed::Fixed<2,64>,fixed_wide::fixed::Fixed<2,64>>;
#[inline]
fn mul(self,rhs:Self)->Self::Output{
Ratio::new(Fixed::raw(self.0)*Fixed::raw(rhs.0),Fixed::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 {
($op:ident,$method:ident)=>{
impl<T> core::ops::$op<i64> for Time<T>{
type Output=Self;
#[inline]
fn $method(self,rhs:i64)->Self::Output{
Self::raw(self.0.$method(rhs))
}
}
}
}
impl_time_i64_rhs_operator!(Div,div);
impl_time_i64_rhs_operator!(Mul,mul);
impl_time_i64_rhs_operator!(Shr,shr);
impl_time_i64_rhs_operator!(Shl,shl);
impl<T> core::ops::Mul<Time<T>> for Planar64{
type Output=Ratio<F128_64,Planar64>;
impl<T> std::ops::Div<i64> for Time<T>{
type Output=Self;
#[inline]
fn div(self,rhs:i64)->Self::Output{
Self::raw(self.0/rhs)
}
}
impl<T> std::ops::Mul<i64> for Time<T>{
type Output=Self;
#[inline]
fn mul(self,rhs:i64)->Self::Output{
Self::raw(self.0*rhs)
}
}
impl<T> core::ops::Mul<Time<T>> for Planar64{
type Output=Ratio<Fixed<2,64>,Planar64>;
fn mul(self,rhs:Time<T>)->Self::Output{
Ratio::new(self*Fixed::raw(rhs.0),Planar64::raw(1_000_000_000))
}
}
impl<T> From<Time<T>> for f32{
#[inline]
fn from(value:Time<T>)->Self{
value.get() as f32/Time::<T>::ONE_SECOND.get() as f32
}
}
impl<T> From<Time<T>> for f64{
#[inline]
fn from(value:Time<T>)->Self{
value.get() as f64/Time::<T>::ONE_SECOND.get() as f64
}
}
#[cfg(test)]
mod test_time{
use super::*;
use fixed_wide::types::F64_32;
type Time=AbsoluteTime;
type Time=super::AbsoluteTime;
#[test]
fn time_from_planar64(){
let a:Time=Planar64::from(1).into();
@@ -198,13 +169,13 @@ mod test_time{
#[test]
fn time_squared(){
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]
fn time_times_planar64(){
let a=Time::from_secs(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)));
}
}
@@ -242,11 +213,11 @@ impl Ratio64{
}
#[inline]
pub const fn mul_int(&self,rhs:i64)->i64{
(rhs as i128*self.num as i128/self.den as i128) as i64
rhs*self.num/(self.den as i64)
}
#[inline]
pub const fn rhs_div_int(&self,rhs:i64)->i64{
(rhs as i128*self.den as i128/self.num as i128) as i64
rhs*(self.den as i64)/self.num
}
#[inline]
pub const fn mul_ref(&self,rhs:&Ratio64)->Ratio64{
@@ -291,6 +262,7 @@ fn integer_decode_f64(f: f64) -> (u64, i16, i8) {
pub enum Ratio64TryFromFloatError{
Nan,
Infinite,
Subnormal,
HighlyNegativeExponent(i16),
HighlyPositiveExponent(i16),
}
@@ -325,10 +297,8 @@ fn ratio64_from_mes((m,e,s):(u64,i16,i8))->Result<Ratio64,Ratio64TryFromFloatErr
Ok(Ratio64::new(num as i64,den as u64).unwrap())
}else if e<0{
// simple exact representation
Ok(Ratio64::new((m as i64)*(s as i64),1<<-e).unwrap())
}else if (64-m.leading_zeros() as i16)+e<64{
// integer
Ok(Ratio64::new((m as i64)*(s as i64)*(1<<e),1).unwrap())
}else{
Err(Ratio64TryFromFloatError::HighlyPositiveExponent(e))
@@ -360,29 +330,6 @@ impl TryFrom<f64> for Ratio64{
}
}
}
#[cfg(test)]
fn req(r0:Ratio64,r1:Ratio64){
println!("r0={r0:?} r1={r1:?}");
assert_eq!(r0.num(),r1.num(),"Nums not eq");
assert_eq!(r0.den(),r1.den(),"Dens not eq");
}
#[test]
fn test_ratio64_from_float(){
req(2.0.try_into().unwrap(),Ratio64::new(2,1).unwrap());
req(1.0.try_into().unwrap(),Ratio64::new(1,1).unwrap());
req(0.5.try_into().unwrap(),Ratio64::new(1,2).unwrap());
req(1.1.try_into().unwrap(),Ratio64::new(2476979795053773,2251799813685248).unwrap());
req(0.8.try_into().unwrap(),Ratio64::new(3602879701896397,4503599627370496).unwrap());
req(0.61.try_into().unwrap(),Ratio64::new(5494391545392005,9007199254740992).unwrap());
req(0.01.try_into().unwrap(),Ratio64::new(5764607523034235,576460752303423488).unwrap());
req(0.001.try_into().unwrap(),Ratio64::new(1152921504606847,1152921504606846976).unwrap());
req(0.00001.try_into().unwrap(),Ratio64::new(89605456633725,8960545663372499267).unwrap());
req(0.00000000001.try_into().unwrap(),Ratio64::new(35204848,3520484800000000213).unwrap());
req(0.000000000000000001.try_into().unwrap(),Ratio64::new(11,10999999999999999213).unwrap());
req(2222222222222.0.try_into().unwrap(),Ratio64::new(2222222222222,1).unwrap());
req(core::f64::consts::PI.try_into().unwrap(),Ratio64::new(884279719003555,281474976710656).unwrap());
}
impl std::ops::Mul<Ratio64> for Ratio64{
type Output=Ratio64;
#[inline]
@@ -454,10 +401,6 @@ impl Angle32{
pub const NEG_FRAC_PI_2:Self=Self(-1<<30);
pub const PI:Self=Self(-1<<31);
#[inline]
pub const fn raw(num:i32)->Self{
Self(num)
}
#[inline]
pub const fn wrap_from_i64(theta:i64)->Self{
//take lower bits
//note: this was checked on compiler explorer and compiles to 1 instruction!
@@ -572,8 +515,8 @@ fn angle_sin_cos(){
println!("cordic s={} c={}",(s/h).divide(),(c/h).divide());
let (fs,fc)=f.sin_cos();
println!("float s={} c={}",fs,fc);
assert!(close_enough((c/h).divide().wrap_64(),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((c/h).divide().fix_1(),Planar64::raw((fc*((1u64<<32) as f64)) as i64)));
assert!(close_enough((s/h).divide().fix_1(),Planar64::raw((fs*((1u64<<32) as f64)) as i64)));
}
test_angle(1.0);
test_angle(std::f64::consts::PI/4.0);
@@ -604,8 +547,8 @@ impl TryFrom<[f32;3]> for Unit32Vec3{
}
*/
pub type Planar64TryFromFloatError=FixedFromFloatError;
pub type Planar64=fixed_wide::types::F64_32;
pub type Planar64TryFromFloatError=fixed_wide::fixed::FixedFromFloatError;
pub type Planar64=fixed_wide::types::I32F32;
pub type Planar64Vec3=linear_ops::types::Vector3<Planar64>;
pub type Planar64Mat3=linear_ops::types::Matrix3<Planar64>;
pub mod vec3{
@@ -613,6 +556,8 @@ pub mod vec3{
pub use linear_ops::types::Vector3;
pub const MIN:Planar64Vec3=Planar64Vec3::new([Planar64::MIN;3]);
pub const MAX:Planar64Vec3=Planar64Vec3::new([Planar64::MAX;3]);
pub const ZERO:Planar64Vec3=Planar64Vec3::new([Planar64::ZERO;3]);
pub const ZERO_2:linear_ops::types::Vector3<Fixed::<2,64>>=linear_ops::types::Vector3::new([Fixed::<2,64>::ZERO;3]);
pub const X:Planar64Vec3=Planar64Vec3::new([Planar64::ONE,Planar64::ZERO,Planar64::ZERO]);
pub const Y:Planar64Vec3=Planar64Vec3::new([Planar64::ZERO,Planar64::ONE,Planar64::ZERO]);
pub const Z:Planar64Vec3=Planar64Vec3::new([Planar64::ZERO,Planar64::ZERO,Planar64::ONE]);
@@ -621,10 +566,6 @@ pub mod vec3{
pub const NEG_Y:Planar64Vec3=Planar64Vec3::new([Planar64::ZERO,Planar64::NEG_ONE,Planar64::ZERO]);
pub const NEG_Z:Planar64Vec3=Planar64Vec3::new([Planar64::ZERO,Planar64::ZERO,Planar64::NEG_ONE]);
pub const NEG_ONE:Planar64Vec3=Planar64Vec3::new([Planar64::NEG_ONE,Planar64::NEG_ONE,Planar64::NEG_ONE]);
// TODO: use #![feature(generic_const_items)] when stabilized https://github.com/rust-lang/rust/issues/113521
pub const fn zero<const N:usize,const F:usize>()->Vector3<Fixed<N,F>>{
Vector3::new([Fixed::ZERO;3])
}
#[inline]
pub const fn int(x:i32,y:i32,z:i32)->Planar64Vec3{
Planar64Vec3::new([Planar64::raw((x as i64)<<32),Planar64::raw((y as i64)<<32),Planar64::raw((z as i64)<<32)])
@@ -684,8 +625,8 @@ pub mod mat3{
let (yc,ys)=y.cos_sin();
Planar64Mat3::from_cols([
Planar64Vec3::new([xc,Planar64::ZERO,-xs]),
Planar64Vec3::new([(xs*ys).wrap_64(),yc,(xc*ys).wrap_64()]),
Planar64Vec3::new([(xs*yc).wrap_64(),-ys,(xc*yc).wrap_64()]),
Planar64Vec3::new([(xs*ys).fix_1(),yc,(xc*ys).fix_1()]),
Planar64Vec3::new([(xs*yc).fix_1(),-ys,(xc*yc).fix_1()]),
])
}
#[inline]
@@ -707,13 +648,13 @@ pub mod mat3{
}
}
#[derive(Clone,Copy,Debug,Default,Hash,Eq,PartialEq)]
#[derive(Clone,Copy,Default,Hash,Eq,PartialEq)]
pub struct Planar64Affine3{
pub matrix3:Planar64Mat3,//includes scale above 1
pub translation:Planar64Vec3,
}
impl Planar64Affine3{
pub const IDENTITY:Self=Self::new(mat3::identity(),vec3::zero());
pub const IDENTITY:Self=Self::new(mat3::identity(),vec3::ZERO);
#[inline]
pub const fn new(matrix3:Planar64Mat3,translation:Planar64Vec3)->Self{
Self{matrix3,translation}
@@ -726,8 +667,8 @@ impl Planar64Affine3{
}
}
#[inline]
pub fn transform_point3(&self,point:Planar64Vec3)->vec3::Vector3<F128_64>{
self.translation.widen_128()+self.matrix3*point
pub fn transform_point3(&self,point:Planar64Vec3)->vec3::Vector3<Fixed<2,64>>{
self.translation.fix_2()+self.matrix3*point
}
}
impl Into<glam::Mat4> for Planar64Affine3{

View File

@@ -1,6 +1,6 @@
use std::collections::HashMap;
use crate::integer::{Planar64,Planar64Vec3,Planar64Affine3};
use crate::integer::{Planar64Vec3,Planar64Affine3};
use crate::gameplay_attributes;
pub type TextureCoordinate=glam::Vec2;
@@ -168,11 +168,6 @@ impl MeshBuilder{
}
}
pub fn acquire_pos_id(&mut self,pos:Planar64Vec3)->PositionId{
// Truncate the 16 most precise bits of the vertex positions.
// This allows the normal vectors to exactly represent the face.
// Remove this in Mesh V2
const MASK:Planar64=Planar64::raw(!((1<<16)-1));
let pos=pos.map(|c|c&MASK);
*self.pos_id_from.entry(pos).or_insert_with(||{
let pos_id=PositionId::new(self.unique_pos.len() as u32);
self.unique_pos.push(pos);

View File

@@ -36,7 +36,6 @@ pub enum SetControlInstruction{
SetMoveForward(bool),
SetJump(bool),
SetZoom(bool),
SetSprint(bool),
}
#[derive(Clone,Debug)]
pub enum ModeInstruction{

View File

@@ -13,8 +13,8 @@ impl Ray{
Num:core::ops::Mul<Planar64,Output=N1>,
Planar64:core::ops::Mul<Den,Output=N1>,
N1:integer::Divide<Den,Output=T1>,
T1:integer::Clamp<Planar64>,
T1:integer::Fix<Planar64>,
{
self.origin+self.direction.map(|elem|(t*elem).divide().clamp())
self.origin+self.direction.map(|elem|(t*elem).divide().fix())
}
}

View File

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

View File

@@ -69,8 +69,10 @@ impl<In,Out> Scaled<In,Out>
const fn get_scale(&self)->Ratio64{
self.scale
}
const fn set_scale(&mut self,new_scale:Ratio64){
fn set_scale(&mut self,time:Time<In>,new_scale:Ratio64){
let new_time=self.get_time(time);
self.scale=new_scale;
self.set_time(time,new_time);
}
}
@@ -130,7 +132,7 @@ pub struct TimerFixed<T:TimerState,P:PauseState>{
_paused:P,
}
//some scaled timer methods are generic across PauseState
//scaled timer methods are generic across PauseState
impl<P:PauseState,In,Out> TimerFixed<Scaled<In,Out>,P>
where Time<In>:Copy,
{
@@ -145,22 +147,8 @@ impl<P:PauseState,In,Out> TimerFixed<Scaled<In,Out>,P>
pub const fn get_scale(&self)->Ratio64{
self.state.get_scale()
}
}
// setting the scale of an unpaused timer is different than a paused timer
impl<In,Out> TimerFixed<Scaled<In,Out>,Unpaused>
where Time<In>:Copy,
{
pub fn set_scale(&mut self,time:Time<In>,new_scale:Ratio64){
let new_time=self.state.get_time(time);
self.state.set_scale(new_scale);
self.state.set_time(time,new_time);
}
}
impl<In,Out> TimerFixed<Scaled<In,Out>,Paused>
where Time<In>:Copy,
{
pub fn set_scale(&mut self,new_scale:Ratio64){
self.state.set_scale(new_scale);
self.state.set_scale(time,new_scale)
}
}
@@ -317,7 +305,7 @@ impl<In,Out> Timer<Scaled<In,Out>>
}
pub fn set_scale(&mut self,time:Time<In>,new_scale:Ratio64){
match self{
Self::Paused(timer)=>timer.set_scale(new_scale),
Self::Paused(timer)=>timer.set_scale(time,new_scale),
Self::Unpaused(timer)=>timer.set_scale(time,new_scale),
}
}

View File

@@ -1,6 +1,6 @@
[package]
name = "strafesnet_deferred_loader"
version = "0.6.0"
version = "0.5.0"
edition = "2024"
repository = "https://git.itzana.me/StrafesNET/strafe-project"
license = "MIT OR Apache-2.0"
@@ -10,7 +10,4 @@ authors = ["Rhys Lloyd <krakow20@gmail.com>"]
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
strafesnet_common.workspace = true
[lints]
workspace = true
strafesnet_common = { version = "0.6.0", path = "../common", registry = "strafesnet" }

View File

@@ -2,7 +2,7 @@ use std::collections::HashMap;
use crate::loader::Loader;
use crate::mesh::Meshes;
use crate::texture::{RenderConfigs,Texture};
use strafesnet_common::model::{MeshId,RenderConfig,RenderConfigId,TextureId};
use strafesnet_common::model::{Mesh,MeshId,RenderConfig,RenderConfigId,TextureId};
#[derive(Clone,Copy,Debug)]
pub enum LoadFailureMode{
@@ -45,7 +45,7 @@ impl<H:core::hash::Hash+Eq> RenderConfigDeferredLoader<H>{
pub fn into_indices(self)->impl Iterator<Item=H>{
self.render_config_id_from_asset_id.into_keys().flatten()
}
pub fn into_render_configs<'a,L:Loader<Resource=Texture,Index<'a>=H>+'a>(mut self,loader:&mut L,failure_mode:LoadFailureMode)->Result<RenderConfigs,L::Error>{
pub fn into_render_configs<L:Loader<Resource=Texture,Index=H>>(mut self,loader:&mut L,failure_mode:LoadFailureMode)->Result<RenderConfigs,L::Error>{
let mut sorted_textures=vec![None;self.texture_count as usize];
for (index_option,render_config_id) in self.render_config_id_from_asset_id{
let render_config=&mut self.render_configs[render_config_id.get() as usize];
@@ -93,7 +93,7 @@ impl<H:core::hash::Hash+Eq> MeshDeferredLoader<H>{
pub fn into_indices(self)->impl Iterator<Item=H>{
self.mesh_id_from_asset_id.into_keys()
}
pub fn into_meshes<'a,M:Clone,L:Loader<Resource=M,Index<'a>=H>+'a>(self,loader:&mut L,failure_mode:LoadFailureMode)->Result<Meshes<M>,L::Error>{
pub fn into_meshes<L:Loader<Resource=Mesh,Index=H>>(self,loader:&mut L,failure_mode:LoadFailureMode)->Result<Meshes,L::Error>{
let mut mesh_list=vec![None;self.mesh_id_from_asset_id.len()];
for (index,mesh_id) in self.mesh_id_from_asset_id{
let resource_result=loader.load(index);

View File

@@ -2,7 +2,7 @@ use std::error::Error;
pub trait Loader{
type Error:Error;
type Index<'a> where Self:'a;
type Index;
type Resource;
fn load(&mut self,index:Self::Index<'_>)->Result<Self::Resource,Self::Error>;
fn load(&mut self,index:Self::Index)->Result<Self::Resource,Self::Error>;
}

View File

@@ -1,15 +1,15 @@
use strafesnet_common::model::MeshId;
use strafesnet_common::model::{Mesh,MeshId};
pub struct Meshes<M>{
meshes:Vec<Option<M>>,
pub struct Meshes{
meshes:Vec<Option<Mesh>>,
}
impl<M> Meshes<M>{
pub(crate) const fn new(meshes:Vec<Option<M>>)->Self{
impl Meshes{
pub(crate) const fn new(meshes:Vec<Option<Mesh>>)->Self{
Self{
meshes,
}
}
pub fn consume(self)->impl Iterator<Item=(MeshId,M)>{
pub fn consume(self)->impl Iterator<Item=(MeshId,Mesh)>{
self.meshes.into_iter().enumerate().filter_map(|(mesh_id,maybe_mesh)|
maybe_mesh.map(|mesh|(MeshId::new(mesh_id as u32),mesh))
)

View File

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

File diff suppressed because it is too large Load Diff

View File

@@ -1,273 +1,195 @@
use crate::fixed::Fixed;
use crate::types::{F64_32,F128_64,F192_96,F512_256};
use crate::types::I32F32;
use crate::types::I256F256;
#[test]
fn you_can_add_numbers(){
let a=F512_256::from((3i128*2).pow(4));
assert_eq!(a+a,F512_256::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);
let a=I256F256::from((3i128*2).pow(4));
assert_eq!(a+a,I256F256::from((3i128*2).pow(4)*2));
}
#[test]
fn to_f32(){
let a=F64_32::ZERO;
let f:f32=a.into();
assert_eq!(f,0.0f32);
let a=F64_32::from(1)>>2;
let a=I256F256::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=F64_32::MIN;
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 a=I256F256::from(0);
let f:f32=(-a).into();
assert_eq!(f,0f32);
let a=F512_256::from(237946589723468975i64)<<16;
let a=I256F256::from(237946589723468975i64)<<16;
let f:f32=a.into();
assert_eq!(f,237946589723468975f32*2.0f32.powi(16));
}
#[test]
fn to_f64(){
let a=F64_32::ZERO;
let f:f64=a.into();
assert_eq!(f,0.0f64);
let a=F64_32::from(1)>>2;
let a=I256F256::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=F64_32::MIN;
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 a=I256F256::from(0);
let f:f64=(-a).into();
assert_eq!(f,0f64);
let a=F512_256::from(237946589723468975i64)<<16;
let a=I256F256::from(237946589723468975i64)<<16;
let f:f64=a.into();
assert_eq!(f,237946589723468975f64*2.0f64.powi(16));
}
#[test]
fn from_f32(){
let a=F64_32::ZERO;
let b:Result<F64_32,_>=0.0f32.try_into();
let a=I256F256::from(1)>>2;
let b:Result<I256F256,_>=0.25f32.try_into();
assert_eq!(b,Ok(a));
let a=F512_256::from(1)>>2;
let b:Result<F512_256,_>=0.25f32.try_into();
let a=I256F256::from(-1)>>2;
let b:Result<I256F256,_>=(-0.25f32).try_into();
assert_eq!(b,Ok(a));
let a=F512_256::from(-1)>>2;
let b:Result<F512_256,_>=(-0.25f32).try_into();
let a=I256F256::from(0);
let b:Result<I256F256,_>=0.try_into();
assert_eq!(b,Ok(a));
let a=F512_256::from(0);
let b:Result<F512_256,_>=0.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();
let a=I256F256::from(0b101011110101001010101010000000000000000000000000000i64)<<16;
let b:Result<I256F256,_>=(0b101011110101001010101010000000000000000000000000000u64 as f32*2.0f32.powi(16)).try_into();
assert_eq!(b,Ok(a));
//I32F32::MAX into f32 is truncated into this value
let a=F64_32::raw(0b111111111111111111111111000000000000000000000000000000000000000i64);
let b:Result<F64_32,_>=Into::<f32>::into(F64_32::MAX).try_into();
let a=I32F32::raw(0b111111111111111111111111000000000000000000000000000000000000000i64);
let b:Result<I32F32,_>=Into::<f32>::into(I32F32::MAX).try_into();
assert_eq!(b,Ok(a));
//I32F32::MIN hits a special case since it's not representable as a positive signed integer
//TODO: don't return an overflow because this is technically possible
let a=F64_32::MIN;
let f:f32=a.into();
let b:Result<F64_32,_>=f.try_into();
let _a=I32F32::MIN;
let b:Result<I32F32,_>=Into::<f32>::into(I32F32::MIN).try_into();
assert_eq!(b,Err(crate::fixed::FixedFromFloatError::Overflow));
//16 is within the 24 bits of float precision
let a=-F64_32::MIN.widen_128();
let f:f32=a.into();
let b:Result<F64_32,_>=f.try_into();
let b:Result<I32F32,_>=Into::<f32>::into(-I32F32::MIN.fix_2()).try_into();
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));
//test many cases
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 b:Result<F128_64,_>=f.try_into();
let b:Result<crate::fixed::Fixed<2,64>,_>=f.try_into();
assert_eq!(b,Ok(a));
}
}
#[test]
fn from_f64(){
let a=F64_32::ZERO;
let b:Result<F64_32,_>=0.0f64.try_into();
let a=I256F256::from(1)>>2;
let b:Result<I256F256,_>=0.25f64.try_into();
assert_eq!(b,Ok(a));
let a=F512_256::from(1)>>2;
let b:Result<F512_256,_>=0.25f64.try_into();
let a=I256F256::from(-1)>>2;
let b:Result<I256F256,_>=(-0.25f64).try_into();
assert_eq!(b,Ok(a));
let a=F512_256::from(-1)>>2;
let b:Result<F512_256,_>=(-0.25f64).try_into();
let a=I256F256::from(0);
let b:Result<I256F256,_>=0.try_into();
assert_eq!(b,Ok(a));
let a=F512_256::from(0);
let b:Result<F512_256,_>=0.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();
let a=I256F256::from(0b101011110101001010101010000000000000000000000000000i64)<<16;
let b:Result<I256F256,_>=(0b101011110101001010101010000000000000000000000000000u64 as f64*2.0f64.powi(16)).try_into();
assert_eq!(b,Ok(a));
}
#[test]
fn you_can_shr_numbers(){
let a=F64_32::from(4);
assert_eq!(a>>1,F64_32::from(2));
let a=I32F32::from(4);
assert_eq!(a>>1,I32F32::from(2));
}
#[test]
fn test_wide_mul(){
let a=F64_32::ONE;
let aa=a.wide_mul_64_64(a);
assert_eq!(aa,F128_64::ONE);
let a=I32F32::ONE;
let aa=a.wide_mul_1_1(a);
assert_eq!(aa,crate::types::I64F64::ONE);
}
#[test]
fn test_wide_div(){
let a=F64_32::ONE*4;
let b=F64_32::ONE*2;
let wide_a=a.wide_mul_64_64(F64_32::ONE);
let wide_b=b.wide_mul_64_64(F64_32::ONE);
let ab=a.wide_div_64_64(b);
assert_eq!(ab,F128_64::ONE*2);
let wab=wide_a.wide_div_128_64(b);
assert_eq!(wab,F192_96::ONE*2);
let awb=a.wide_div_64_128(wide_b);
assert_eq!(awb,F192_96::ONE*2);
let a=I32F32::ONE*4;
let b=I32F32::ONE*2;
let wide_a=a.wide_mul_1_1(I32F32::ONE);
let wide_b=b.wide_mul_1_1(I32F32::ONE);
let ab=a.wide_div_1_1(b);
assert_eq!(ab,crate::types::I64F64::ONE*2);
let wab=wide_a.wide_div_2_1(b);
assert_eq!(wab,crate::fixed::Fixed::<3,96>::ONE*2);
let awb=a.wide_div_1_2(wide_b);
assert_eq!(awb,crate::fixed::Fixed::<3,96>::ONE*2);
}
#[test]
fn test_wide_mul_repeated() {
let a=F64_32::from(2);
let b=F64_32::from(3);
let a=I32F32::from(2);
let b=I32F32::from(3);
let w1=a.wide_mul_64_64(b);
let w2=w1.wide_mul_128_128(w1);
let w3=w2.wide_mul_256_256(w2);
let w1=a.wide_mul_1_1(b);
let w2=w1.wide_mul_2_2(w1);
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]
fn test_bint(){
let a=F64_32::ONE;
assert_eq!(a*2,F64_32::from(2));
let a=I32F32::ONE;
assert_eq!(a*2,I32F32::from(2));
}
#[test]
fn test_wrap(){
assert_eq!(F64_32::ONE,F512_256::ONE.wrap_64());
assert_eq!(F64_32::NEG_ONE,F512_256::NEG_ONE.wrap_64());
}
#[test]
fn test_narrow(){
assert_eq!(Ok(F64_32::ONE),F512_256::ONE.narrow_64());
assert_eq!(Ok(F64_32::NEG_ONE),F512_256::NEG_ONE.narrow_64());
}
#[test]
fn test_widen(){
assert_eq!(F64_32::ONE.widen_512(),F512_256::ONE);
assert_eq!(F64_32::NEG_ONE.widen_512(),F512_256::NEG_ONE);
}
#[test]
fn test_clamp(){
assert_eq!(F64_32::ONE,F512_256::ONE.clamp_64());
assert_eq!(F64_32::NEG_ONE,F512_256::NEG_ONE.clamp_64());
fn test_fix(){
assert_eq!(I32F32::ONE.fix_8(),I256F256::ONE);
assert_eq!(I32F32::ONE,I256F256::ONE.fix_1());
assert_eq!(I32F32::NEG_ONE.fix_8(),I256F256::NEG_ONE);
assert_eq!(I32F32::NEG_ONE,I256F256::NEG_ONE.fix_1());
}
#[test]
fn test_sqrt(){
let a=F64_32::ONE*4;
assert_eq!(a.sqrt(),F64_32::from(2));
let a=I32F32::ONE*4;
assert_eq!(a.sqrt(),I32F32::from(2));
}
#[test]
fn test_sqrt_zero(){
let a=F64_32::ZERO;
assert_eq!(a.sqrt(),F64_32::ZERO);
let a=I32F32::ZERO;
assert_eq!(a.sqrt(),I32F32::ZERO);
}
#[test]
fn test_sqrt_low(){
let a=F64_32::HALF;
let a=I32F32::HALF;
let b=a.fixed_mul(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
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_ans=f.sqrt();
let i=(f_ans*((1u64<<32) as f64)) as u64;
let r=F64_32::from_u64(i);
let i=(f_ans*((1u64<<32) as f64)) as i64;
let r=I32F32::from_bits(bnum::BInt::<1>::from(i));
//mimic the behaviour of the algorithm,
//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){
return r+F64_32::EPSILON;
if (r+I32F32::EPSILON).wide_mul_1_1(r+I32F32::EPSILON)==n.wide_mul_1_1(I32F32::ONE){
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){
return r-F64_32::EPSILON;
if (r-I32F32::EPSILON).wide_mul_1_1(r-I32F32::EPSILON)==n.wide_mul_1_1(I32F32::ONE){
return r-I32F32::EPSILON;
}
return r;
}
fn test_exact(n:F64_32){
fn test_exact(n:I32F32){
assert_eq!(n.sqrt(),find_equiv_sqrt_via_f64(n));
}
#[test]
fn test_sqrt_exact(){
//43
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]
fn test_sqrt_max(){
let a=F64_32::MAX;
let a=I32F32::MAX;
test_exact(a);
}
#[test]
@@ -275,9 +197,9 @@ fn test_sqrt_max(){
fn test_zeroes_normal(){
// (x-1)*(x+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]));
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]));
}
#[test]
@@ -285,25 +207,12 @@ fn test_zeroes_normal(){
fn test_zeroes_deferred_division(){
// (x-1)*(x+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([
ratio_ops::ratio::Ratio::new(F64_32::ONE*2,F64_32::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::NEG_ONE*2),
ratio_ops::ratio::Ratio::new(I32F32::ONE*2,I32F32::ONE*2),
])
);
}
#[test]
fn test_debug(){
assert_eq!(format!("{:?}",F64_32::EPSILON),"0.00000001");
assert_eq!(format!("{:?}",F64_32::ONE),"1.00000000");
assert_eq!(format!("{:?}",F64_32::TWO),"2.00000000");
assert_eq!(format!("{:?}",F64_32::MAX),"7fffffff.ffffffff");
assert_eq!(format!("{:?}",F64_32::try_from(core::f64::consts::PI).unwrap()),"3.243f6a88");
assert_eq!(format!("{:?}",F64_32::NEG_EPSILON),"-0.00000001");
assert_eq!(format!("{:?}",F64_32::NEG_ONE),"-1.00000000");
assert_eq!(format!("{:?}",F64_32::NEG_TWO),"-2.00000000");
assert_eq!(format!("{:?}",F64_32::MIN),"-80000000.00000000");
}

View File

@@ -1,7 +1,4 @@
use crate::fixed::BNUM_DIGIT_WIDTH;
pub type F64_32=crate::fixed::Fixed<{64/BNUM_DIGIT_WIDTH},32>;
pub type F128_64=crate::fixed::Fixed<{128/BNUM_DIGIT_WIDTH},64>;
pub type F192_96=crate::fixed::Fixed<{192/BNUM_DIGIT_WIDTH},96>;
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>;
pub type I32F32=crate::fixed::Fixed<1,32>;
pub type I64F64=crate::fixed::Fixed<2,64>;
pub type I128F128=crate::fixed::Fixed<4,128>;
pub type I256F256=crate::fixed::Fixed<8,256>;

View File

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

View File

@@ -1,6 +1,6 @@
[package]
name = "linear_ops"
version = "0.1.1"
version = "0.1.0"
edition = "2024"
repository = "https://git.itzana.me/StrafesNET/strafe-project"
license = "MIT OR Apache-2.0"
@@ -14,12 +14,9 @@ fixed-wide=["dep:fixed_wide","dep:paste"]
deferred-division=["dep:ratio_ops"]
[dependencies]
ratio_ops = { workspace = true, optional = true }
fixed_wide = { workspace = true, optional = true }
ratio_ops = { version = "0.1.0", path = "../ratio_ops", registry = "strafesnet", optional = true }
fixed_wide = { version = "0.1.2", path = "../fixed_wide", registry = "strafesnet", optional = true }
paste = { version = "1.0.15", optional = true }
[dev-dependencies]
fixed_wide = { workspace = true, features = ["wide-mul"] }
[lints]
workspace = true
fixed_wide = { path = "../fixed_wide", registry = "strafesnet", features = ["wide-mul"] }

View File

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

View File

@@ -204,21 +204,13 @@ macro_rules! impl_matrix_named_fields_shape {
type Target=$struct_outer<Vector<$size_inner,T>>;
#[inline]
fn deref(&self)->&Self::Target{
// This cast is valid because Matrix has #[repr(transparent)]
let ptr:*const [[T;$size_inner];$size_outer]=&self.array;
let ptr=ptr as *const Self::Target;
// SAFETY: this pointer is non-null because it comes from a reference
unsafe{&*ptr}
unsafe{core::mem::transmute(&self.array)}
}
}
impl<T> core::ops::DerefMut for Matrix<$size_outer,$size_inner,T>{
#[inline]
fn deref_mut(&mut self)->&mut Self::Target{
// This cast is valid because Matrix has #[repr(transparent)]
let ptr:*mut [[T;$size_inner];$size_outer]=&mut self.array;
let ptr=ptr as *mut Self::Target;
// SAFETY: this pointer is non-null because it comes from a reference
unsafe{&mut*ptr}
unsafe{core::mem::transmute(&mut self.array)}
}
}
}

View File

@@ -58,15 +58,6 @@ macro_rules! impl_vector {
}
}
impl<const N:usize,T,E:std::fmt::Debug> Vector<N,Result<T,E>>{
#[inline]
pub fn unwrap(self)->Vector<N,T>{
Vector{
array:self.array.map(Result::unwrap)
}
}
}
impl<const N:usize,T:Ord> Vector<N,T>{
#[inline]
pub fn min(self,rhs:Self)->Self{
@@ -330,21 +321,13 @@ macro_rules! impl_vector_named_fields {
type Target=$struct<T>;
#[inline]
fn deref(&self)->&Self::Target{
// This cast is valid because Vector has #[repr(transparent)]
let ptr:*const [T;$size]=&self.array;
let ptr=ptr as *const Self::Target;
// SAFETY: this pointer is non-null because it comes from a reference
unsafe{&*ptr}
unsafe{core::mem::transmute(&self.array)}
}
}
impl<T> core::ops::DerefMut for Vector<$size,T>{
#[inline]
fn deref_mut(&mut self)->&mut Self::Target{
// This cast is valid because Vector has #[repr(transparent)]
let ptr:*mut [T;$size]=&mut self.array;
let ptr=ptr as *mut Self::Target;
// SAFETY: this pointer is non-null because it comes from a reference
unsafe{&mut*ptr}
unsafe{core::mem::transmute(&mut self.array)}
}
}
}

View File

@@ -1,9 +1,9 @@
use crate::types::{Matrix3,Matrix3x2,Matrix3x4,Matrix4x2,Vector3};
type Planar64=fixed_wide::types::F64_32;
type Planar64Wide1=fixed_wide::types::F128_64;
type Planar64=fixed_wide::types::I32F32;
type Planar64Wide1=fixed_wide::types::I64F64;
//type Planar64Wide2=fixed_wide::types::I128F128;
type Planar64Wide3=fixed_wide::types::F512_256;
type Planar64Wide3=fixed_wide::types::I256F256;
#[test]
fn wide_vec3(){
@@ -72,7 +72,7 @@ fn wide_matrix_det(){
]);
// In[2]:= Det[{{1, 2, 3}, {4, 5, 7}, {6, 8, 9}}]
// 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]

View File

@@ -8,6 +8,3 @@ description = "Ratio operations using trait bounds for avoiding division like th
authors = ["Rhys Lloyd <krakow20@gmail.com>"]
[dependencies]
[lints]
workspace = true

View File

@@ -1,6 +1,6 @@
[package]
name = "strafesnet_rbx_loader"
version = "0.10.2"
version = "0.6.0"
edition = "2024"
repository = "https://git.itzana.me/StrafesNET/strafe-project"
license = "MIT OR Apache-2.0"
@@ -11,18 +11,14 @@ authors = ["Rhys Lloyd <krakow20@gmail.com>"]
[dependencies]
bytemuck = "1.14.3"
glam.workspace = true
regex = { version = "1.11.3", default-features = false, features = ["unicode-perl"] }
rbx_mesh = "0.6.0"
glam = "0.30.0"
lazy-regex = "3.1.0"
rbx_binary = { version = "0.7.4", registry = "strafesnet" }
rbx_dom_weak = { version = "2.7.0", registry = "strafesnet" }
rbx_mesh = "0.3.1"
rbx_reflection_database = { version = "0.2.10", registry = "strafesnet" }
rbx_xml = { version = "0.13.3", 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" }
strafesnet_common.workspace = true
strafesnet_deferred_loader.workspace = true
rbx_binary = "2.0.1"
rbx_dom_weak = "4.1.0"
rbx_reflection = "6.1.0"
rbx_reflection_database = "2.0.2"
rbx_xml = "2.0.1"
[lints]
workspace = true
roblox_emulator = { version = "0.4.7", path = "../roblox_emulator", registry = "strafesnet" }
strafesnet_common = { version = "0.6.0", path = "../common", registry = "strafesnet" }
strafesnet_deferred_loader = { version = "0.5.0", path = "../deferred_loader", registry = "strafesnet" }

View File

@@ -1,249 +0,0 @@
use std::collections::HashSet;
use std::num::ParseIntError;
use strafesnet_common::gameplay_modes::{StageId,ModeId};
use strafesnet_common::integer::{FixedFromFloatError,Planar64TryFromFloatError};
/// A collection of errors which can be ignored at your peril
#[derive(Debug,Default)]
pub struct RecoverableErrors{
/// A basepart has an invalid / missing property.
pub basepart_property:Vec<InstancePath>,
/// A part has an unconvertable CFrame.
pub basepart_cframe:Vec<CFrameError>,
/// A part has an unconvertable Velocity.
pub basepart_velocity:Vec<Planar64ConvertError>,
/// A part has an invalid / missing property.
pub part_property:Vec<InstancePath>,
/// A part has an invalid shape.
pub part_shape:Vec<ShapeError>,
/// A meshpart has an invalid / missing property.
pub meshpart_property:Vec<InstancePath>,
/// A meshpart has no mesh.
pub meshpart_content:Vec<InstancePath>,
/// A basepart has an unsupported subclass.
pub unsupported_class:HashSet<String>,
/// A decal has an invalid / missing property.
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.
pub normal_id:Vec<NormalIdError>,
/// A texture has an invalid / missing property.
pub texture_property:Vec<InstancePath>,
/// A mode_id failed to parse.
pub mode_id_parse_int:Vec<ParseIntContext>,
/// There is a duplicate mode.
pub duplicate_mode:HashSet<ModeId>,
/// A mode_id failed to parse.
pub stage_id_parse_int:Vec<ParseIntContext>,
/// A Stage was duplicated leading to undefined behaviour.
pub duplicate_stage:HashSet<DuplicateStageError>,
/// A WormholeOut id failed to parse.
pub wormhole_out_id_parse_int:Vec<ParseIntContext>,
/// A WormholeOut was duplicated leading to undefined behaviour.
pub duplicate_wormhole_out:HashSet<u32>,
/// A WormholeIn id failed to parse.
pub wormhole_in_id_parse_int:Vec<ParseIntContext>,
/// A jump limit failed to parse.
pub jump_limit_parse_int:Vec<ParseIntContext>,
}
impl RecoverableErrors{
pub fn count(&self)->usize{
self.basepart_property.len()+
self.basepart_cframe.len()+
self.basepart_velocity.len()+
self.part_property.len()+
self.part_shape.len()+
self.meshpart_property.len()+
self.meshpart_content.len()+
self.unsupported_class.len()+
self.decal_property.len()+
self.surface_appearance_property.len()+
self.normal_id.len()+
self.texture_property.len()+
self.mode_id_parse_int.len()+
self.duplicate_mode.len()+
self.stage_id_parse_int.len()+
self.duplicate_stage.len()+
self.wormhole_out_id_parse_int.len()+
self.duplicate_wormhole_out.len()+
self.wormhole_in_id_parse_int.len()+
self.jump_limit_parse_int.len()
}
}
fn write_comma_separated<T>(
f:&mut std::fmt::Formatter<'_>,
mut it:impl Iterator<Item=T>,
custom_write:impl Fn(&mut std::fmt::Formatter<'_>,T)->std::fmt::Result
)->std::fmt::Result{
if let Some(t)=it.next(){
custom_write(f,t)?;
for t in it{
write!(f,", ")?;
custom_write(f,t)?;
}
}
Ok(())
}
macro_rules! write_instance_path_error{
($f:ident,$self:ident,$field:ident,$class:literal,$class_plural:literal,$problem:literal)=>{
let len=$self.$field.len();
if len!=0{
let plural=if len==1{$class}else{$class_plural};
write!($f,"The following {plural} {}: ",$problem)?;
write_comma_separated($f,$self.$field.iter(),|f,InstancePath(path)|
write!(f,"{path}")
)?;
writeln!($f)?;
}
};
}
macro_rules! write_duplicate_error{
($f:ident,$self:ident,$field:ident,$class:literal,$class_plural:literal)=>{
let len=$self.$field.len();
if len!=0{
let plural=if len==1{$class}else{$class_plural};
write!($f,"The following {plural} duplicates: ")?;
write_comma_separated($f,$self.$field.iter(),|f,id|
write!(f,"{id}")
)?;
writeln!($f)?;
}
};
}
macro_rules! write_bespoke_error{
($f:ident,$self:ident,$field:ident,$class:literal,$class_plural:literal,$problem:literal,$path_field:ident,$error_field:ident)=>{
let len=$self.$field.len();
if len!=0{
let plural=if len==1{$class}else{$class_plural};
write!($f,"The following {plural} {}: ",$problem)?;
write_comma_separated($f,$self.$field.iter(),|f,context|
write!(f,"{} ({})",context.$path_field,context.$error_field)
)?;
writeln!($f)?;
}
};
}
impl core::fmt::Display for RecoverableErrors{
fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{
write_instance_path_error!(f,self,basepart_property,"BasePart is","BaseParts are","missing a property");
write_bespoke_error!(f,self,basepart_cframe,"BasePart","BaseParts","CFrame float convert failed",path,error);
write_bespoke_error!(f,self,basepart_velocity,"BasePart","BaseParts","Velocity float convert failed",path,error);
write_instance_path_error!(f,self,part_property,"Part is","Parts are","missing a property");
write_bespoke_error!(f,self,part_shape,"Part","Parts","Shape is invalid",path,shape);
write_instance_path_error!(f,self,meshpart_property,"MeshPart is","MeshParts are","missing a property");
write_instance_path_error!(f,self,meshpart_content,"MeshPart has","MeshParts have","no mesh");
{
let len=self.unsupported_class.len();
if len!=0{
let plural=if len==1{"Class is"}else{"Classes are"};
write!(f,"The following {plural} not supported: ")?;
write_comma_separated(f,self.unsupported_class.iter(),|f,classname|write!(f,"{classname}"))?;
writeln!(f)?;
}
}
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_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_duplicate_error!(f,self,duplicate_mode,"ModeId has","ModeIds have");
write_bespoke_error!(f,self,stage_id_parse_int,"StageId","StageIds","failed to parse",context,error);
write_duplicate_error!(f,self,duplicate_stage,"StageId has","StageIds have");
write_bespoke_error!(f,self,wormhole_out_id_parse_int,"WormholeOutId","WormholeOutIds","failed to parse",context,error);
write_duplicate_error!(f,self,duplicate_wormhole_out,"WormholeOutId has","WormholeOutIds have");
write_bespoke_error!(f,self,wormhole_in_id_parse_int,"WormholeInId","WormholeInIds","failed to parse",context,error);
write_bespoke_error!(f,self,jump_limit_parse_int,"jump limit","jump limits","failed to parse",context,error);
Ok(())
}
}
/// A Decal was missing required properties
#[derive(Debug)]
pub struct InstancePath(pub String);
impl core::fmt::Display for InstancePath{
fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{
self.0.fmt(f)
}
}
impl InstancePath{
pub fn new(dom:&rbx_dom_weak::WeakDom,instance:&rbx_dom_weak::Instance)->InstancePath{
let mut names:Vec<_>=core::iter::successors(
Some(instance),
|i|dom.get_by_ref(i.parent())
).map(
|i|i.name.as_str()
).collect();
// discard the name of the root object
names.pop();
names.reverse();
InstancePath(names.join("."))
}
}
#[derive(Debug)]
pub struct ParseIntContext{
pub context:String,
pub error:ParseIntError,
}
impl ParseIntContext{
pub fn parse<T:core::str::FromStr<Err=ParseIntError>>(input:&str)->Result<T,Self>{
input.parse().map_err(|error|ParseIntContext{
context:input.to_owned(),
error,
})
}
}
#[derive(Debug)]
pub struct NormalIdError{
pub path:InstancePath,
pub normal_id:u32,
}
#[derive(Debug)]
pub struct ShapeError{
pub path:InstancePath,
pub shape:u32,
}
#[derive(Debug)]
pub enum CFrameErrorType{
ZeroDeterminant,
Convert(FixedFromFloatError),
}
impl core::fmt::Display for CFrameErrorType{
fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{
write!(f,"{self:?}")
}
}
#[derive(Debug)]
pub struct CFrameError{
pub path:InstancePath,
pub error:CFrameErrorType,
}
#[derive(Debug)]
pub struct Planar64ConvertError{
pub path:InstancePath,
pub error:Planar64TryFromFloatError,
}
#[derive(Debug,Hash,Eq,PartialEq)]
pub struct DuplicateStageError{
pub mode_id:ModeId,
pub stage_id:StageId,
}
impl core::fmt::Display for DuplicateStageError{
fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{
write!(f,"{}-Spawn{}",self.mode_id,self.stage_id.get())
}
}

View File

@@ -1,22 +1,41 @@
use std::io::Read;
use rbx_dom_weak::WeakDom;
use roblox_emulator::context::Context;
use strafesnet_deferred_loader::deferred_loader::{LoadFailureMode,MeshDeferredLoader,RenderConfigDeferredLoader};
pub use error::RecoverableErrors;
pub use roblox_emulator::runner::Error as RunnerError;
mod rbx;
mod mesh;
mod union;
pub mod loader;
mod primitives;
pub mod rbx;
pub mod mesh;
mod error;
pub mod union;
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{
dom:WeakDom,
}
impl Model{
pub fn new(dom:WeakDom)->Self{
fn new(dom:WeakDom)->Self{
Self{dom}
}
pub fn into_place(self)->Place{
let Self{mut dom}=self;
let context=roblox_emulator::context::Context::from_mut(&mut dom);
let services=context.convert_into_place();
Place{dom,services}
}
pub fn to_snf(&self,failure_mode:LoadFailureMode)->Result<strafesnet_common::map::CompleteMap,LoadError>{
to_snf(self,failure_mode)
}
}
impl AsRef<WeakDom> for Model{
fn as_ref(&self)->&WeakDom{
@@ -25,39 +44,106 @@ impl AsRef<WeakDom> for Model{
}
pub struct Place{
context:Context,
dom:WeakDom,
services:roblox_emulator::context::Services,
}
impl Place{
pub fn new(dom:WeakDom)->Result<Self,roblox_emulator::context::ServicesError>{
let context=Context::from_place(dom)?;
Ok(Self{
context,
pub fn new(dom:WeakDom)->Option<Self>{
let context=roblox_emulator::context::Context::from_ref(&dom);
Some(Self{
services:context.find_services()?,
dom,
})
}
pub fn run_scripts(&mut self)->Result<Vec<RunnerError>,RunnerError>{
let Place{context}=self;
let runner=roblox_emulator::runner::Runner::new()?;
pub fn run_scripts(&mut self){
let Place{dom,services}=self;
let runner=roblox_emulator::runner::Runner::new().unwrap();
let context=roblox_emulator::context::Context::from_mut(dom);
let scripts=context.scripts();
let runnable=runner.runnable_context(context)?;
let mut errors=Vec::new();
let runnable=runner.runnable_context_with_services(context,services).unwrap();
for script in scripts{
if let Err(e)=runnable.run_script(script){
errors.push(e);
println!("runner error: {e}");
}
}
Ok(errors)
}
pub fn to_snf(&self,failure_mode:LoadFailureMode)->Result<strafesnet_common::map::CompleteMap,LoadError>{
to_snf(self,failure_mode)
}
}
impl AsRef<WeakDom> for Place{
fn as_ref(&self)->&WeakDom{
self.context.as_ref()
&self.dom
}
}
impl From<Model> for Place{
fn from(model:Model)->Self{
let context=Context::from_model(model.dom);
Self{
context,
}
#[derive(Debug)]
pub enum ReadError{
RbxBinary(rbx_binary::DecodeError),
RbxXml(rbx_xml::DecodeError),
Io(std::io::Error),
UnknownFileFormat,
}
impl std::fmt::Display for ReadError{
fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{
write!(f,"{self:?}")
}
}
impl std::error::Error for ReadError{}
pub fn read<R:Read>(input:R)->Result<Model,ReadError>{
let mut buf=std::io::BufReader::new(input);
let peek=std::io::BufRead::fill_buf(&mut buf).map_err(ReadError::Io)?;
match &peek[0..8]{
b"<roblox!"=>rbx_binary::from_reader(buf).map(Model::new).map_err(ReadError::RbxBinary),
b"<roblox "=>rbx_xml::from_reader_default(buf).map(Model::new).map_err(ReadError::RbxXml),
_=>Err(ReadError::UnknownFileFormat),
}
}
#[derive(Debug)]
pub enum LoadError{
Texture(loader::TextureError),
Mesh(loader::MeshError),
}
impl std::fmt::Display for LoadError{
fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{
write!(f,"{self:?}")
}
}
impl std::error::Error for LoadError{}
impl From<loader::TextureError> for LoadError{
fn from(value:loader::TextureError)->Self{
Self::Texture(value)
}
}
impl From<loader::MeshError> for LoadError{
fn from(value:loader::MeshError)->Self{
Self::Mesh(value)
}
}
fn to_snf(dom:impl AsRef<WeakDom>,failure_mode:LoadFailureMode)->Result<strafesnet_common::map::CompleteMap,LoadError>{
let dom=dom.as_ref();
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 mut mesh_loader=loader::MeshLoader::new();
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 mut texture_loader=loader::TextureLoader::new();
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)
}

View File

@@ -1,13 +1,10 @@
use std::io::Read;
use rbxassetid::{RobloxAssetId,RobloxAssetIdParseErr};
use strafesnet_common::model::Mesh;
use strafesnet_deferred_loader::{loader::Loader,texture::Texture};
use strafesnet_rbx_loader::mesh::{MeshIndex,MeshType,MeshWithSize};
// disallow non-static lifetimes
fn static_ustr(s:&'static str)->rbx_dom_weak::Ustr{
rbx_dom_weak::ustr(s)
}
use crate::data::RobloxMeshBytes;
use crate::rbx::RobloxFaceTextureDescription;
fn read_entire_file(path:impl AsRef<std::path::Path>)->Result<Vec<u8>,std::io::Error>{
let mut file=std::fs::File::open(path)?;
@@ -16,7 +13,7 @@ fn read_entire_file(path:impl AsRef<std::path::Path>)->Result<Vec<u8>,std::io::E
Ok(data)
}
#[expect(dead_code)]
#[allow(dead_code)]
#[derive(Debug)]
pub enum TextureError{
Io(std::io::Error),
@@ -39,17 +36,17 @@ impl From<RobloxAssetIdParseErr> for TextureError{
}
}
pub struct TextureLoader;
impl TextureLoader{
pub struct TextureLoader<'a>(std::marker::PhantomData<&'a ()>);
impl TextureLoader<'_>{
pub fn new()->Self{
Self
Self(std::marker::PhantomData)
}
}
impl Loader for TextureLoader{
impl<'a> Loader for TextureLoader<'a>{
type Error=TextureError;
type Index<'a>=&'a str;
type Index=&'a str;
type Resource=Texture;
fn load(&mut self,index:Self::Index<'_>)->Result<Self::Resource,Self::Error>{
fn load(&mut self,index:Self::Index)->Result<Self::Resource,Self::Error>{
let RobloxAssetId(asset_id)=index.parse()?;
let file_name=format!("textures/{}.dds",asset_id);
let data=read_entire_file(file_name)?;
@@ -57,13 +54,13 @@ impl Loader for TextureLoader{
}
}
#[expect(dead_code)]
#[allow(dead_code)]
#[derive(Debug)]
pub enum MeshError{
Io(std::io::Error),
RobloxAssetIdParse(RobloxAssetIdParseErr),
Mesh(strafesnet_rbx_loader::mesh::Error),
Union(strafesnet_rbx_loader::union::Error),
Mesh(crate::mesh::Error),
Union(crate::union::Error),
DecodeBinary(rbx_binary::DecodeError),
OneChildPolicy,
MissingInstance,
@@ -84,13 +81,13 @@ impl From<RobloxAssetIdParseErr> for MeshError{
Self::RobloxAssetIdParse(value)
}
}
impl From<strafesnet_rbx_loader::mesh::Error> for MeshError{
fn from(value:strafesnet_rbx_loader::mesh::Error)->Self{
impl From<crate::mesh::Error> for MeshError{
fn from(value:crate::mesh::Error)->Self{
Self::Mesh(value)
}
}
impl From<strafesnet_rbx_loader::union::Error> for MeshError{
fn from(value:strafesnet_rbx_loader::union::Error)->Self{
impl From<crate::union::Error> for MeshError{
fn from(value:crate::union::Error)->Self{
Self::Union(value)
}
}
@@ -100,27 +97,68 @@ impl From<rbx_binary::DecodeError> for MeshError{
}
}
pub struct MeshLoader;
impl MeshLoader{
pub fn new()->Self{
Self
#[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:[Option<RobloxFaceTextureDescription>;6],
},
}
#[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:crate::rbx::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,
}
}
}
impl Loader for MeshLoader{
pub struct MeshLoader<'a>(std::marker::PhantomData<&'a ()>);
impl MeshLoader<'_>{
pub fn new()->Self{
Self(std::marker::PhantomData)
}
}
impl<'a> Loader for MeshLoader<'a>{
type Error=MeshError;
type Index<'a>=MeshIndex<'a>;
type Resource=MeshWithSize;
fn load(&mut self,index:Self::Index<'_>)->Result<Self::Resource,Self::Error>{
type Index=MeshIndex<'a>;
type Resource=Mesh;
fn load(&mut self,index:Self::Index)->Result<Self::Resource,Self::Error>{
let mesh=match index.mesh_type{
MeshType::FileMesh=>{
let RobloxAssetId(asset_id)=index.content.parse()?;
let file_name=format!("meshes/{}",asset_id);
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}=>{
// 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()){
let RobloxAssetId(asset_id)=index.content.parse()?;
let file_name=format!("unions/{}",asset_id);
@@ -133,18 +171,18 @@ impl Loader for MeshLoader{
return Err(MeshError::MissingInstance);
};
if physics_data.is_empty(){
if let Some(rbx_dom_weak::types::Variant::BinaryString(data))=instance.properties.get(&static_ustr("PhysicsData")){
if let Some(rbx_dom_weak::types::Variant::BinaryString(data))=instance.properties.get("PhysicsData"){
physics_data=data.as_ref();
}
}
if mesh_data.is_empty(){
if let Some(rbx_dom_weak::types::Variant::BinaryString(data))=instance.properties.get(&static_ustr("MeshData")){
if let Some(rbx_dom_weak::types::Variant::BinaryString(data))=instance.properties.get("MeshData"){
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{
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

@@ -1,77 +1,13 @@
use std::collections::HashMap;
use rbx_mesh::mesh::{Vertex2,Vertex2Truncated};
use strafesnet_common::aabb::Aabb;
use strafesnet_common::integer::vec3;
use strafesnet_common::model::{self,ColorId,IndexedVertex,PolygonGroup,PolygonList,RenderConfigId,VertexId};
use crate::rbx::RobloxPartDescription;
#[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
}
}
use strafesnet_common::{integer::vec3,model::{self,ColorId,IndexedVertex,NormalId,PolygonGroup,PolygonList,PositionId,RenderConfigId,TextureCoordinateId,VertexId}};
#[allow(dead_code)]
#[derive(Debug)]
pub enum Error{
NoPolygons,
RbxMesh(rbx_mesh::mesh::Error),
Planar64Vec3(strafesnet_common::integer::Planar64TryFromFloatError),
RbxMesh(rbx_mesh::mesh::Error)
}
impl std::fmt::Display for Error{
fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{
@@ -80,50 +16,69 @@ impl std::fmt::Display for Error{
}
impl std::error::Error for Error{}
fn ingest_vertices2(
fn ingest_vertices2<
AcquirePosId,
AcquireTexId,
AcquireNormalId,
AcquireColorId,
AcquireVertexId,
>(
vertices:Vec<Vertex2>,
mb:&mut model::MeshBuilder,
)->HashMap<rbx_mesh::mesh::VertexId2,VertexId>{
acquire_pos_id:&mut AcquirePosId,
acquire_tex_id:&mut AcquireTexId,
acquire_normal_id:&mut AcquireNormalId,
acquire_color_id:&mut AcquireColorId,
acquire_vertex_id:&mut AcquireVertexId,
)->Result<HashMap<rbx_mesh::mesh::VertexId2,VertexId>,Error>
where
AcquirePosId:FnMut([f32;3])->Result<PositionId,Error>,
AcquireTexId:FnMut([f32;2])->TextureCoordinateId,
AcquireNormalId:FnMut([f32;3])->Result<NormalId,Error>,
AcquireColorId:FnMut([f32;4])->ColorId,
AcquireVertexId:FnMut(IndexedVertex)->VertexId,
{
//this monster is collecting a map of old_vertices_index -> unique_vertices_index
//while also doing the inserting unique entries into lists simultaneously
// vertex positions that fail to convert are DROPPED
vertices.into_iter().enumerate().filter_map(|(vertex_id,vertex)|Some((
Ok(vertices.into_iter().enumerate().map(|(vertex_id,vertex)|Ok((
rbx_mesh::mesh::VertexId2(vertex_id as u32),
{
let vertex=IndexedVertex{
pos:mb.acquire_pos_id(vec3::try_from_f32_array(vertex.pos).ok()?),
tex:mb.acquire_tex_id(glam::Vec2::from_array(vertex.tex)),
normal:mb.acquire_normal_id(vec3::try_from_f32_array(vertex.norm).ok()?),
color:mb.acquire_color_id(glam::Vec4::from_array(vertex.color.map(|f|f as f32/255.0f32))),
};
mb.acquire_vertex_id(vertex)
}
))).collect()
acquire_vertex_id(IndexedVertex{
pos:acquire_pos_id(vertex.pos)?,
tex:acquire_tex_id(vertex.tex),
normal:acquire_normal_id(vertex.norm)?,
color:acquire_color_id(vertex.color.map(|f|f as f32/255.0f32))
}),
))).collect::<Result<_,_>>()?)
}
fn ingest_vertices_truncated2(
fn ingest_vertices_truncated2<
AcquirePosId,
AcquireTexId,
AcquireNormalId,
AcquireVertexId,
>(
vertices:Vec<Vertex2Truncated>,
mb:&mut model::MeshBuilder,
acquire_pos_id:&mut AcquirePosId,
acquire_tex_id:&mut AcquireTexId,
acquire_normal_id:&mut AcquireNormalId,
static_color_id:ColorId,//pick one color and fill everything with it
)->HashMap<rbx_mesh::mesh::VertexId2,VertexId>{
acquire_vertex_id:&mut AcquireVertexId,
)->Result<HashMap<rbx_mesh::mesh::VertexId2,VertexId>,Error>
where
AcquirePosId:FnMut([f32;3])->Result<PositionId,Error>,
AcquireTexId:FnMut([f32;2])->TextureCoordinateId,
AcquireNormalId:FnMut([f32;3])->Result<NormalId,Error>,
AcquireVertexId:FnMut(IndexedVertex)->VertexId,
{
//this monster is collecting a map of old_vertices_index -> unique_vertices_index
//while also doing the inserting unique entries into lists simultaneously
// vertex positions that fail to convert are DROPPED
vertices.into_iter().enumerate().filter_map(|(vertex_id,vertex)|Some((
Ok(vertices.into_iter().enumerate().map(|(vertex_id,vertex)|Ok((
rbx_mesh::mesh::VertexId2(vertex_id as u32),
{
let vertex=IndexedVertex{
pos:mb.acquire_pos_id(vec3::try_from_f32_array(vertex.pos).ok()?),
tex:mb.acquire_tex_id(glam::Vec2::from_array(vertex.tex)),
normal:mb.acquire_normal_id(vec3::try_from_f32_array(vertex.norm).ok()?),
color:static_color_id,
};
mb.acquire_vertex_id(vertex)
}
))).collect()
}
fn new_polygon_list_checked(list:Vec<model::IndexedVertexList>)->Option<PolygonList>{
(!list.is_empty()).then_some(PolygonList::new(list))
acquire_vertex_id(IndexedVertex{
pos:acquire_pos_id(vertex.pos)?,
tex:acquire_tex_id(vertex.tex),
normal:acquire_normal_id(vertex.norm)?,
color:static_color_id
}),
))).collect::<Result<_,_>>()?)
}
fn ingest_faces2_lods3(
@@ -133,90 +88,130 @@ fn ingest_faces2_lods3(
lods:&[rbx_mesh::mesh::Lod3],
){
//faces have to be split into polygon groups based on lod
polygon_groups.extend(lods.windows(2).filter_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)|
Some(vec![*vertex_id_map.get(&v0)?,*vertex_id_map.get(&v1)?,*vertex_id_map.get(&v2)?])
).collect())?))
polygon_groups.extend(lods.windows(2).map(|lod_pair|
PolygonGroup::PolygonList(PolygonList::new(faces[lod_pair[0].0 as usize..lod_pair[1].0 as usize].iter().map(|rbx_mesh::mesh::Face2(v0,v1,v2)|
vec![vertex_id_map[&v0],vertex_id_map[&v1],vertex_id_map[&v2]]
).collect()))
))
}
pub fn convert(roblox_mesh_bytes:&[u8])->Result<MeshWithSize,Error>{
pub fn convert(roblox_mesh_bytes:crate::data::RobloxMeshBytes)->Result<model::Mesh,Error>{
//generate that mesh boi
let mut unique_pos=Vec::new();
let mut pos_id_from=HashMap::new();
let mut unique_tex=Vec::new();
let mut tex_id_from=HashMap::new();
let mut unique_normal=Vec::new();
let mut normal_id_from=HashMap::new();
let mut unique_color=Vec::new();
let mut color_id_from=HashMap::new();
let mut unique_vertices=Vec::new();
let mut vertex_id_from=HashMap::new();
let mut polygon_groups=Vec::new();
let mut mb=model::MeshBuilder::new();
match rbx_mesh::read_versioned(std::io::Cursor::new(roblox_mesh_bytes)).map_err(Error::RbxMesh)?{
rbx_mesh::mesh::Mesh::V1(mesh)=>{
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|{
let mut ingest_vertex1=|vertex:&rbx_mesh::mesh::Vertex1|{
let vertex=IndexedVertex{
pos:mb.acquire_pos_id(vec3::try_from_f32_array(vertex.pos).ok()?),
tex:mb.acquire_tex_id(glam::vec2(vertex.tex[0],vertex.tex[1])),
normal:mb.acquire_normal_id(vec3::try_from_f32_array(vertex.norm).ok()?),
color:color_id,
};
Some(mb.acquire_vertex_id(vertex))
};
Some(vec![ingest_vertex1(&trip[0])?,ingest_vertex1(&trip[1])?,ingest_vertex1(&trip[2])?])
}).collect());
if let Some(polygon_list)=polygon_list{
polygon_groups.push(PolygonGroup::PolygonList(polygon_list));
}
let mut acquire_pos_id=|pos|{
let p=vec3::try_from_f32_array(pos).map_err(Error::Planar64Vec3)?;
Ok(PositionId::new(*pos_id_from.entry(p).or_insert_with(||{
let pos_id=unique_pos.len();
unique_pos.push(p);
pos_id
}) as u32))
};
let mut acquire_tex_id=|tex|{
let h=bytemuck::cast::<[f32;2],[u32;2]>(tex);
TextureCoordinateId::new(*tex_id_from.entry(h).or_insert_with(||{
let tex_id=unique_tex.len();
unique_tex.push(glam::Vec2::from_array(tex));
tex_id
}) as u32)
};
let mut acquire_normal_id=|normal|{
let n=vec3::try_from_f32_array(normal).map_err(Error::Planar64Vec3)?;
Ok(NormalId::new(*normal_id_from.entry(n).or_insert_with(||{
let normal_id=unique_normal.len();
unique_normal.push(n);
normal_id
}) as u32))
};
let mut acquire_color_id=|color|{
let h=bytemuck::cast::<[f32;4],[u32;4]>(color);
ColorId::new(*color_id_from.entry(h).or_insert_with(||{
let color_id=unique_color.len();
unique_color.push(glam::Vec4::from_array(color));
color_id
}) as u32)
};
let mut acquire_vertex_id=|vertex:IndexedVertex|{
VertexId::new(*vertex_id_from.entry(vertex.clone()).or_insert_with(||{
let vertex_id=unique_vertices.len();
unique_vertices.push(vertex);
vertex_id
}) as u32)
};
match rbx_mesh::read_versioned(roblox_mesh_bytes.cursor()).map_err(Error::RbxMesh)?{
rbx_mesh::mesh::VersionedMesh::Version1(mesh)=>{
let color_id=acquire_color_id([1.0f32;4]);
polygon_groups.push(PolygonGroup::PolygonList(PolygonList::new(mesh.vertices.chunks_exact(3).map(|trip|{
let mut ingest_vertex1=|vertex:&rbx_mesh::mesh::Vertex1|Ok(acquire_vertex_id(IndexedVertex{
pos:acquire_pos_id(vertex.pos)?,
tex:acquire_tex_id([vertex.tex[0],vertex.tex[1]]),
normal:acquire_normal_id(vertex.norm)?,
color:color_id,
}));
Ok(vec![ingest_vertex1(&trip[0])?,ingest_vertex1(&trip[1])?,ingest_vertex1(&trip[2])?])
}).collect::<Result<_,_>>()?)));
},
rbx_mesh::mesh::Mesh::V2(mesh)=>{
rbx_mesh::mesh::VersionedMesh::Version2(mesh)=>{
let vertex_id_map=match mesh.header.sizeof_vertex{
rbx_mesh::mesh::SizeOfVertex2::Truncated=>{
//pick white and make all the vertices white
let color_id=mb.acquire_color_id(glam::Vec4::ONE);
ingest_vertices_truncated2(mesh.vertices_truncated,&mut mb,color_id)
let color_id=acquire_color_id([1.0f32;4]);
ingest_vertices_truncated2(mesh.vertices_truncated,&mut acquire_pos_id,&mut acquire_tex_id,&mut acquire_normal_id,color_id,&mut acquire_vertex_id)
},
rbx_mesh::mesh::SizeOfVertex2::Full=>ingest_vertices2(mesh.vertices,&mut mb),
};
rbx_mesh::mesh::SizeOfVertex2::Full=>ingest_vertices2(mesh.vertices,&mut acquire_pos_id,&mut acquire_tex_id,&mut acquire_normal_id,&mut acquire_color_id,&mut acquire_vertex_id),
}?;
//one big happy group for all the faces
let polygon_list=new_polygon_list_checked(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)?])
).collect());
if let Some(polygon_list)=polygon_list{
polygon_groups.push(PolygonGroup::PolygonList(polygon_list));
}
polygon_groups.push(PolygonGroup::PolygonList(PolygonList::new(mesh.faces.into_iter().map(|face|
vec![vertex_id_map[&face.0],vertex_id_map[&face.1],vertex_id_map[&face.2]]
).collect())));
},
rbx_mesh::mesh::Mesh::V3(mesh)=>{
rbx_mesh::mesh::VersionedMesh::Version3(mesh)=>{
let vertex_id_map=match mesh.header.sizeof_vertex{
rbx_mesh::mesh::SizeOfVertex2::Truncated=>{
let color_id=mb.acquire_color_id(glam::Vec4::ONE);
ingest_vertices_truncated2(mesh.vertices_truncated,&mut mb,color_id)
let color_id=acquire_color_id([1.0f32;4]);
ingest_vertices_truncated2(mesh.vertices_truncated,&mut acquire_pos_id,&mut acquire_tex_id,&mut acquire_normal_id,color_id,&mut acquire_vertex_id)
},
rbx_mesh::mesh::SizeOfVertex2::Full=>ingest_vertices2(mesh.vertices,&mut mb),
};
rbx_mesh::mesh::SizeOfVertex2::Full=>ingest_vertices2(mesh.vertices,&mut acquire_pos_id,&mut acquire_tex_id,&mut acquire_normal_id,&mut acquire_color_id,&mut acquire_vertex_id),
}?;
ingest_faces2_lods3(&mut polygon_groups,&vertex_id_map,&mesh.faces,&mesh.lods);
},
rbx_mesh::mesh::Mesh::V4(mesh)=>{
let vertex_id_map=ingest_vertices2(mesh.vertices,&mut mb);
rbx_mesh::mesh::VersionedMesh::Version4(mesh)=>{
let vertex_id_map=ingest_vertices2(
mesh.vertices,&mut acquire_pos_id,&mut acquire_tex_id,&mut acquire_normal_id,&mut acquire_color_id,&mut acquire_vertex_id
)?;
ingest_faces2_lods3(&mut polygon_groups,&vertex_id_map,&mesh.faces,&mesh.lods);
},
rbx_mesh::mesh::Mesh::V5(mesh)=>{
let vertex_id_map=ingest_vertices2(mesh.vertices,&mut mb);
rbx_mesh::mesh::VersionedMesh::Version5(mesh)=>{
let vertex_id_map=ingest_vertices2(
mesh.vertices,&mut acquire_pos_id,&mut acquire_tex_id,&mut acquire_normal_id,&mut acquire_color_id,&mut acquire_vertex_id
)?;
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(
Ok(model::Mesh{
unique_pos,
unique_normal,
unique_tex,
unique_color,
unique_vertices,
polygon_groups,
//these should probably be moved to the model...
//but what if models want to use the same texture
vec![model::IndexedGraphicsGroup{
graphics_groups:vec![model::IndexedGraphicsGroup{
render:RenderConfigId::new(0),
//the lowest lod is highest quality
groups:vec![model::PolygonGroupId::new(0)]
}],
//disable physics
Vec::new(),
);
let mut aabb=Aabb::default();
for &point in &mesh.unique_pos{
aabb.grow(point);
}
Ok(MeshWithSize{mesh,size:aabb.size()})
physics_groups:Vec::new(),
})
}

View File

@@ -1,6 +1,5 @@
use crate::rbx::{RobloxPartDescription,RobloxWedgeDescription,RobloxCornerWedgeDescription};
use strafesnet_common::model::{Color4,TextureCoordinate,Mesh,MeshBuilder,IndexedGraphicsGroup,IndexedPhysicsGroup,IndexedVertex,PolygonGroupId,PolygonGroup,PolygonList,PositionId,TextureCoordinateId,NormalId,ColorId,VertexId,RenderConfigId};
use strafesnet_common::integer::{vec3,Planar64,Planar64Vec3};
use strafesnet_common::model::{Color4,TextureCoordinate,Mesh,IndexedGraphicsGroup,IndexedPhysicsGroup,IndexedVertex,PolygonGroupId,PolygonGroup,PolygonList,PositionId,TextureCoordinateId,NormalId,ColorId,VertexId,RenderConfigId};
use strafesnet_common::integer::{vec3,Planar64Vec3};
#[derive(Debug)]
pub enum Primitives{
@@ -10,22 +9,7 @@ pub enum Primitives{
Wedge,
CornerWedge,
}
#[derive(Debug)]
pub struct PrimitivesError;
impl TryFrom<u32> for Primitives{
type Error=PrimitivesError;
fn try_from(value:u32)->Result<Self,Self::Error>{
match value{
0=>Ok(Primitives::Sphere),
1=>Ok(Primitives::Cube),
2=>Ok(Primitives::Cylinder),
3=>Ok(Primitives::Wedge),
4=>Ok(Primitives::CornerWedge),
_=>Err(PrimitivesError),
}
}
}
#[derive(Clone,Copy,Hash,PartialEq,Eq)]
#[derive(Hash,PartialEq,Eq)]
pub enum CubeFace{
Right,
Top,
@@ -34,29 +18,13 @@ pub enum CubeFace{
Bottom,
Front,
}
#[derive(Debug)]
pub struct CubeFaceError;
impl TryFrom<u32> for CubeFace{
type Error=CubeFaceError;
fn try_from(value:u32)->Result<Self,Self::Error>{
match value{
0=>Ok(CubeFace::Right),
1=>Ok(CubeFace::Top),
2=>Ok(CubeFace::Back),
3=>Ok(CubeFace::Left),
4=>Ok(CubeFace::Bottom),
5=>Ok(CubeFace::Front),
_=>Err(CubeFaceError),
}
}
}
const CUBE_DEFAULT_TEXTURE_COORDS:[TextureCoordinate;4]=[
TextureCoordinate::new(0.0,0.0),
TextureCoordinate::new(1.0,0.0),
TextureCoordinate::new(1.0,1.0),
TextureCoordinate::new(0.0,1.0),
];
pub const CUBE_DEFAULT_VERTICES:[Planar64Vec3;8]=[
const CUBE_DEFAULT_VERTICES:[Planar64Vec3;8]=[
vec3::int(-1,-1, 1),//0 left bottom back
vec3::int( 1,-1, 1),//1 right bottom back
vec3::int( 1, 1, 1),//2 right top back
@@ -66,7 +34,7 @@ pub const CUBE_DEFAULT_VERTICES:[Planar64Vec3;8]=[
vec3::int( 1,-1,-1),//6 right bottom front
vec3::int(-1,-1,-1),//7 left bottom front
];
pub const CUBE_DEFAULT_NORMALS:[Planar64Vec3;6]=[
const CUBE_DEFAULT_NORMALS:[Planar64Vec3;6]=[
vec3::int( 1, 0, 0),//CubeFace::Right
vec3::int( 0, 1, 0),//CubeFace::Top
vec3::int( 0, 0, 1),//CubeFace::Back
@@ -75,36 +43,103 @@ pub const CUBE_DEFAULT_NORMALS:[Planar64Vec3;6]=[
vec3::int( 0, 0,-1),//CubeFace::Front
];
pub struct CubeFaceDescription([FaceDescription;Self::FACES]);
#[derive(Hash,PartialEq,Eq)]
pub enum WedgeFace{
Right,
TopFront,
Back,
Left,
Bottom,
}
const WEDGE_DEFAULT_NORMALS:[Planar64Vec3;5]=[
vec3::int( 1, 0, 0),//Wedge::Right
vec3::int( 0, 1,-1),//Wedge::TopFront
vec3::int( 0, 0, 1),//Wedge::Back
vec3::int(-1, 0, 0),//Wedge::Left
vec3::int( 0,-1, 0),//Wedge::Bottom
];
/*
local cornerWedgeVerticies = {
Vector3.new(-1/2,-1/2,-1/2),7
Vector3.new(-1/2,-1/2, 1/2),0
Vector3.new( 1/2,-1/2,-1/2),6
Vector3.new( 1/2,-1/2, 1/2),1
Vector3.new( 1/2, 1/2,-1/2),5
}
*/
#[derive(Hash,PartialEq,Eq)]
pub enum CornerWedgeFace{
Right,
TopBack,
TopLeft,
Bottom,
Front,
}
const CORNERWEDGE_DEFAULT_NORMALS:[Planar64Vec3;5]=[
vec3::int( 1, 0, 0),//CornerWedge::Right
vec3::int( 0, 1, 1),//CornerWedge::BackTop
vec3::int(-1, 1, 0),//CornerWedge::LeftTop
vec3::int( 0,-1, 0),//CornerWedge::Bottom
vec3::int( 0, 0,-1),//CornerWedge::Front
];
#[derive(Default)]
pub struct CubeFaceDescription([Option<FaceDescription>;6]);
impl CubeFaceDescription{
pub const FACES:usize=6;
pub fn new(RobloxPartDescription(part_description):RobloxPartDescription,textureless_render_id:RenderConfigId)->Self{
Self(part_description.map(|face_description|match face_description{
Some(roblox_texture_transform)=>roblox_texture_transform.to_face_description(),
None=>FaceDescription::new_with_render_id(textureless_render_id),
}))
pub fn insert(&mut self,index:CubeFace,value:FaceDescription){
self.0[index as usize]=Some(value);
}
pub fn pairs(self)->impl Iterator<Item=(usize,FaceDescription)>{
self.0.into_iter().enumerate().filter_map(|(i,v)|v.map(|u|(i,u)))
}
}
pub struct WedgeFaceDescription([FaceDescription;Self::FACES]);
pub fn unit_cube(render:RenderConfigId)->Mesh{
let mut t=CubeFaceDescription::default();
t.insert(CubeFace::Right,FaceDescription::new_with_render_id(render));
t.insert(CubeFace::Top,FaceDescription::new_with_render_id(render));
t.insert(CubeFace::Back,FaceDescription::new_with_render_id(render));
t.insert(CubeFace::Left,FaceDescription::new_with_render_id(render));
t.insert(CubeFace::Bottom,FaceDescription::new_with_render_id(render));
t.insert(CubeFace::Front,FaceDescription::new_with_render_id(render));
generate_partial_unit_cube(t)
}
#[derive(Default)]
pub struct WedgeFaceDescription([Option<FaceDescription>;5]);
impl WedgeFaceDescription{
pub const FACES:usize=5;
pub fn new(RobloxWedgeDescription(part_description):RobloxWedgeDescription,textureless_render_id:RenderConfigId)->Self{
Self(part_description.map(|face_description|match face_description{
Some(roblox_texture_transform)=>roblox_texture_transform.to_face_description(),
None=>FaceDescription::new_with_render_id(textureless_render_id),
}))
pub fn insert(&mut self,index:WedgeFace,value:FaceDescription){
self.0[index as usize]=Some(value);
}
pub fn pairs(self)->std::iter::FilterMap<std::iter::Enumerate<std::array::IntoIter<Option<FaceDescription>,5>>,impl FnMut((usize,Option<FaceDescription>))->Option<(usize,FaceDescription)>>{
self.0.into_iter().enumerate().filter_map(|v|v.1.map(|u|(v.0,u)))
}
}
pub struct CornerWedgeFaceDescription([FaceDescription;Self::FACES]);
// pub fn unit_wedge(render:RenderConfigId)->Mesh{
// let mut t=WedgeFaceDescription::default();
// t.insert(WedgeFace::Right,FaceDescription::new_with_render_id(render));
// t.insert(WedgeFace::TopFront,FaceDescription::new_with_render_id(render));
// t.insert(WedgeFace::Back,FaceDescription::new_with_render_id(render));
// t.insert(WedgeFace::Left,FaceDescription::new_with_render_id(render));
// t.insert(WedgeFace::Bottom,FaceDescription::new_with_render_id(render));
// generate_partial_unit_wedge(t)
// }
#[derive(Default)]
pub struct CornerWedgeFaceDescription([Option<FaceDescription>;5]);
impl CornerWedgeFaceDescription{
pub const FACES:usize=5;
pub fn new(RobloxCornerWedgeDescription(part_description):RobloxCornerWedgeDescription,textureless_render_id:RenderConfigId)->Self{
Self(part_description.map(|face_description|match face_description{
Some(roblox_texture_transform)=>roblox_texture_transform.to_face_description(),
None=>FaceDescription::new_with_render_id(textureless_render_id),
}))
pub fn insert(&mut self,index:CornerWedgeFace,value:FaceDescription){
self.0[index as usize]=Some(value);
}
pub fn pairs(self)->std::iter::FilterMap<std::iter::Enumerate<std::array::IntoIter<Option<FaceDescription>,5>>,impl FnMut((usize,Option<FaceDescription>))->Option<(usize,FaceDescription)>>{
self.0.into_iter().enumerate().filter_map(|v|v.1.map(|u|(v.0,u)))
}
}
// pub fn unit_cornerwedge(render:RenderConfigId)->Mesh{
// let mut t=CornerWedgeFaceDescription::default();
// t.insert(CornerWedgeFace::Right,FaceDescription::new_with_render_id(render));
// t.insert(CornerWedgeFace::TopBack,FaceDescription::new_with_render_id(render));
// t.insert(CornerWedgeFace::TopLeft,FaceDescription::new_with_render_id(render));
// t.insert(CornerWedgeFace::Bottom,FaceDescription::new_with_render_id(render));
// t.insert(CornerWedgeFace::Front,FaceDescription::new_with_render_id(render));
// generate_partial_unit_cornerwedge(t)
// }
#[derive(Clone)]
pub struct FaceDescription{
@@ -113,7 +148,7 @@ pub struct FaceDescription{
pub color:Color4,
}
impl FaceDescription{
pub fn new_with_render_id(render:RenderConfigId)->Self{
pub fn new_with_render_id(render:RenderConfigId)->Self {
Self{
render,
transform:glam::Affine2::IDENTITY,
@@ -121,51 +156,51 @@ impl FaceDescription{
}
}
}
pub const CUBE_DEFAULT_POLYS:[[[u32;2];4];6]=[
pub fn generate_partial_unit_cube(face_descriptions:CubeFaceDescription)->Mesh{
const CUBE_DEFAULT_POLYS:[[[u32;3];4];6]=[
// right (1, 0, 0)
[
[6,2],//[vertex,tex]
[5,1],
[2,0],
[1,3],
[6,2,0],//[vertex,tex,norm]
[5,1,0],
[2,0,0],
[1,3,0],
],
// top (0, 1, 0)
[
[5,3],
[4,2],
[3,1],
[2,0],
[5,3,1],
[4,2,1],
[3,1,1],
[2,0,1],
],
// back (0, 0, 1)
[
[0,3],
[1,2],
[2,1],
[3,0],
[0,3,2],
[1,2,2],
[2,1,2],
[3,0,2],
],
// left (-1, 0, 0)
[
[0,2],
[3,1],
[4,0],
[7,3],
[0,2,3],
[3,1,3],
[4,0,3],
[7,3,3],
],
// bottom (0,-1, 0)
[
[1,1],
[0,0],
[7,3],
[6,2],
[1,1,4],
[0,0,4],
[7,3,4],
[6,2,4],
],
// front (0, 0,-1)
[
[4,1],
[5,0],
[6,3],
[7,2],
[4,1,5],
[5,0,5],
[6,3,5],
[7,2,5],
],
];
pub fn unit_cube(CubeFaceDescription(face_descriptions):CubeFaceDescription)->Mesh{
let mut generated_pos=Vec::new();
let mut generated_tex=Vec::new();
let mut generated_normal=Vec::new();
@@ -176,7 +211,7 @@ pub fn unit_cube(CubeFaceDescription(face_descriptions):CubeFaceDescription)->Me
let mut physics_group=IndexedPhysicsGroup::default();
let mut transforms=Vec::new();
//note that on a cube every vertex is guaranteed to be unique, so there's no need to hash them against existing vertices.
for (face_id,face_description) in face_descriptions.into_iter().enumerate(){
for (face_id,face_description) in face_descriptions.pairs(){
//assume that scanning short lists is faster than hashing.
let transform_index=if let Some(transform_index)=transforms.iter().position(|&transform|transform==face_description.transform){
transform_index
@@ -203,8 +238,8 @@ pub fn unit_cube(CubeFaceDescription(face_descriptions):CubeFaceDescription)->Me
//push vertices as they are needed
let group_id=PolygonGroupId::new(polygon_groups.len() as u32);
polygon_groups.push(PolygonGroup::PolygonList(PolygonList::new(vec![
CUBE_DEFAULT_POLYS[face_id].map(|[pos_id,tex_id]|{
let pos=CUBE_DEFAULT_VERTICES[pos_id as usize];
CUBE_DEFAULT_POLYS[face_id].map(|tup|{
let pos=CUBE_DEFAULT_VERTICES[tup[0] as usize];
let pos_index=if let Some(pos_index)=generated_pos.iter().position(|&p|p==pos){
pos_index
}else{
@@ -216,7 +251,7 @@ pub fn unit_cube(CubeFaceDescription(face_descriptions):CubeFaceDescription)->Me
//always push vertex
let vertex=IndexedVertex{
pos:PositionId::new(pos_index),
tex:TextureCoordinateId::new(tex_id+4*transform_index),
tex:TextureCoordinateId::new(tup[1]+4*transform_index),
normal:NormalId::new(normal_index),
color:ColorId::new(color_index),
};
@@ -243,49 +278,42 @@ pub fn unit_cube(CubeFaceDescription(face_descriptions):CubeFaceDescription)->Me
}
}
//don't think too hard about the copy paste because this is all going into the map tool eventually...
pub fn unit_wedge(WedgeFaceDescription(face_descriptions):WedgeFaceDescription)->Mesh{
const WEDGE_DEFAULT_POLYS:[&[[u32;2]];5]=[
pub fn generate_partial_unit_wedge(face_descriptions:WedgeFaceDescription)->Mesh{
const WEDGE_DEFAULT_POLYS:[&[[u32;3]];5]=[
// right (1, 0, 0)
&[
[6,2],//[vertex,tex]
[2,0],
[1,3],
[6,2,0],//[vertex,tex,norm]
[2,0,0],
[1,3,0],
],
// FrontTop (0, 1, -1)
&[
[3,1],
[2,0],
[6,3],
[7,2],
[3,1,1],
[2,0,1],
[6,3,1],
[7,2,1],
],
// back (0, 0, 1)
&[
[0,3],
[1,2],
[2,1],
[3,0],
[0,3,2],
[1,2,2],
[2,1,2],
[3,0,2],
],
// left (-1, 0, 0)
&[
[0,2],
[3,1],
[7,3],
[0,2,3],
[3,1,3],
[7,3,3],
],
// bottom (0,-1, 0)
&[
[1,1],
[0,0],
[7,3],
[6,2],
[1,1,4],
[0,0,4],
[7,3,4],
[6,2,4],
],
];
const WEDGE_DEFAULT_NORMALS:[Planar64Vec3;5]=[
vec3::int( 1, 0, 0),//Wedge::Right
vec3::int( 0, 1,-1),//Wedge::TopFront
vec3::int( 0, 0, 1),//Wedge::Back
vec3::int(-1, 0, 0),//Wedge::Left
vec3::int( 0,-1, 0),//Wedge::Bottom
];
let mut generated_pos=Vec::new();
let mut generated_tex=Vec::new();
let mut generated_normal=Vec::new();
@@ -296,7 +324,7 @@ pub fn unit_wedge(WedgeFaceDescription(face_descriptions):WedgeFaceDescription)-
let mut physics_group=IndexedPhysicsGroup::default();
let mut transforms=Vec::new();
//note that on a cube every vertex is guaranteed to be unique, so there's no need to hash them against existing vertices.
for (face_id,face_description) in face_descriptions.into_iter().enumerate(){
for (face_id,face_description) in face_descriptions.pairs(){
//assume that scanning short lists is faster than hashing.
let transform_index=if let Some(transform_index)=transforms.iter().position(|&transform|transform==face_description.transform){
transform_index
@@ -323,8 +351,8 @@ pub fn unit_wedge(WedgeFaceDescription(face_descriptions):WedgeFaceDescription)-
//push vertices as they are needed
let group_id=PolygonGroupId::new(polygon_groups.len() as u32);
polygon_groups.push(PolygonGroup::PolygonList(PolygonList::new(vec![
WEDGE_DEFAULT_POLYS[face_id].iter().map(|&[pos_id,tex_id]|{
let pos=CUBE_DEFAULT_VERTICES[pos_id as usize];
WEDGE_DEFAULT_POLYS[face_id].iter().map(|tup|{
let pos=CUBE_DEFAULT_VERTICES[tup[0] as usize];
let pos_index=if let Some(pos_index)=generated_pos.iter().position(|&p|p==pos){
pos_index
}else{
@@ -336,7 +364,7 @@ pub fn unit_wedge(WedgeFaceDescription(face_descriptions):WedgeFaceDescription)-
//always push vertex
let vertex=IndexedVertex{
pos:PositionId::new(pos_index),
tex:TextureCoordinateId::new(tex_id+4*transform_index),
tex:TextureCoordinateId::new(tup[1]+4*transform_index),
normal:NormalId::new(normal_index),
color:ColorId::new(color_index),
};
@@ -363,47 +391,40 @@ pub fn unit_wedge(WedgeFaceDescription(face_descriptions):WedgeFaceDescription)-
}
}
pub fn unit_cornerwedge(CornerWedgeFaceDescription(face_descriptions):CornerWedgeFaceDescription)->Mesh{
const CORNERWEDGE_DEFAULT_POLYS:[&[[u32;2]];5]=[
pub fn generate_partial_unit_cornerwedge(face_descriptions:CornerWedgeFaceDescription)->Mesh{
const CORNERWEDGE_DEFAULT_POLYS:[&[[u32;3]];5]=[
// right (1, 0, 0)
&[
[6,2],//[vertex,tex]
[5,1],
[1,3],
[6,2,0],//[vertex,tex,norm]
[5,1,0],
[1,3,0],
],
// BackTop (0, 1, 1)
&[
[5,3],
[0,1],
[1,0],
[5,3,1],
[0,1,1],
[1,0,1],
],
// LeftTop (-1, 1, 0)
&[
[5,3],
[7,2],
[0,1],
[5,3,2],
[7,2,2],
[0,1,2],
],
// bottom (0,-1, 0)
&[
[1,1],
[0,0],
[7,3],
[6,2],
[1,1,3],
[0,0,3],
[7,3,3],
[6,2,3],
],
// front (0, 0,-1)
&[
[5,0],
[6,3],
[7,2],
[5,0,4],
[6,3,4],
[7,2,4],
],
];
const CORNERWEDGE_DEFAULT_NORMALS:[Planar64Vec3;5]=[
vec3::int( 1, 0, 0),//CornerWedge::Right
vec3::int( 0, 1, 1),//CornerWedge::BackTop
vec3::int(-1, 1, 0),//CornerWedge::LeftTop
vec3::int( 0,-1, 0),//CornerWedge::Bottom
vec3::int( 0, 0,-1),//CornerWedge::Front
];
let mut generated_pos=Vec::new();
let mut generated_tex=Vec::new();
let mut generated_normal=Vec::new();
@@ -414,7 +435,7 @@ pub fn unit_cornerwedge(CornerWedgeFaceDescription(face_descriptions):CornerWedg
let mut physics_group=IndexedPhysicsGroup::default();
let mut transforms=Vec::new();
//note that on a cube every vertex is guaranteed to be unique, so there's no need to hash them against existing vertices.
for (face_id,face_description) in face_descriptions.into_iter().enumerate(){
for (face_id,face_description) in face_descriptions.pairs(){
//assume that scanning short lists is faster than hashing.
let transform_index=if let Some(transform_index)=transforms.iter().position(|&transform|transform==face_description.transform){
transform_index
@@ -441,8 +462,8 @@ pub fn unit_cornerwedge(CornerWedgeFaceDescription(face_descriptions):CornerWedg
//push vertices as they are needed
let group_id=PolygonGroupId::new(polygon_groups.len() as u32);
polygon_groups.push(PolygonGroup::PolygonList(PolygonList::new(vec![
CORNERWEDGE_DEFAULT_POLYS[face_id].iter().map(|&[pos_id,tex_id]|{
let pos=CUBE_DEFAULT_VERTICES[pos_id as usize];
CORNERWEDGE_DEFAULT_POLYS[face_id].iter().map(|tup|{
let pos=CUBE_DEFAULT_VERTICES[tup[0] as usize];
let pos_index=if let Some(pos_index)=generated_pos.iter().position(|&p|p==pos){
pos_index
}else{
@@ -454,7 +475,7 @@ pub fn unit_cornerwedge(CornerWedgeFaceDescription(face_descriptions):CornerWedg
//always push vertex
let vertex=IndexedVertex{
pos:PositionId::new(pos_index),
tex:TextureCoordinateId::new(tex_id+4*transform_index),
tex:TextureCoordinateId::new(tup[1]+4*transform_index),
normal:NormalId::new(normal_index),
color:ColorId::new(color_index),
};
@@ -480,133 +501,3 @@ pub fn unit_cornerwedge(CornerWedgeFaceDescription(face_descriptions):CornerWedg
physics_groups:vec![physics_group],
}
}
// TODO: fix face texture orientation
pub fn unit_cylinder(face_descriptions:CubeFaceDescription)->Mesh{
// cylinder is oriented about the x axis
// roblox cylinders use projected grid coordinates
/// how many grid coordinates to use (positive and negative)
const GON:i32=3;
/// grid perimeter
const POINTS:[[i32;2];4*2*GON as usize]=const{
let mut points=[[0;2];{4*2*GON as usize}];
let mut i=-GON;
while i<GON{
points[(i+GON) as usize]=[i,GON];
points[(i+GON+1*2*GON) as usize]=[GON,-i];
points[(i+GON+2*2*GON) as usize]=[-i,-GON];
points[(i+GON+3*2*GON) as usize]=[-GON,i];
i+=1;
}
points
};
let mut mb=MeshBuilder::new();
let mut polygon_groups=Vec::with_capacity(CubeFaceDescription::FACES);
let mut graphics_groups=Vec::with_capacity(CubeFaceDescription::FACES);
let mut physics_group=IndexedPhysicsGroup{groups:Vec::with_capacity(CubeFaceDescription::FACES)};
let CubeFaceDescription([right,top,back,left,bottom,front])=face_descriptions;
macro_rules! end_face{
($face_description:expr,$end:expr,$iter:expr)=>{
let normal=mb.acquire_normal_id($end);
let color=mb.acquire_color_id($face_description.color);
// single polygon for physics
let polygon:Vec<_>=$iter.map(|[x,y]|{
let tex=mb.acquire_tex_id(
$face_description.transform.transform_point2(
(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());
mb.acquire_vertex_id(IndexedVertex{pos,tex,normal,color})
}).collect();
// fanned polygons for graphics
let pos=mb.acquire_pos_id($end);
let tex=mb.acquire_tex_id($face_description.transform.transform_point2(glam::Vec2::ONE/2.0));
let center=mb.acquire_vertex_id(IndexedVertex{pos,tex,normal,color});
let polygon_list=(0..POINTS.len()).map(|i|
vec![center,polygon[i],polygon[(i+1)%POINTS.len()]]
).collect();
// end face graphics
let group_id=PolygonGroupId::new(polygon_groups.len() as u32);
polygon_groups.push(PolygonGroup::PolygonList(PolygonList::new(polygon_list)));
graphics_groups.push(IndexedGraphicsGroup{
render:$face_description.render,
groups:vec![group_id],
});
// end face physics
let polygon_list=vec![polygon];
let group_id=PolygonGroupId::new(polygon_groups.len() as u32);
polygon_groups.push(PolygonGroup::PolygonList(PolygonList::new(polygon_list)));
physics_group.groups.push(group_id);
}
}
macro_rules! tex{
($face_description:expr,$tex:expr)=>{{
let [x,y]=$tex;
$face_description.transform.transform_point2(
glam::vec2((x+GON) as f32,(y+GON) as f32)/(2*GON) as f32
)
}};
}
macro_rules! barrel_face{
($face_description:expr,$loop:ident,$lo_dir:expr,$hi_dir:expr,$tex_0:expr,$tex_1:expr,$tex_2:expr,$tex_3:expr)=>{
let mut polygon_list=Vec::with_capacity(CubeFaceDescription::FACES);
for $loop in -GON..GON{
// lo Z
let lz_dir=$lo_dir.with_length(Planar64::ONE).divide().wrap_64();
// hi Z
let hz_dir=$hi_dir.with_length(Planar64::ONE).divide().wrap_64();
// pos
let lx_lz_pos=mb.acquire_pos_id(vec3::NEG_X+lz_dir);
let lx_hz_pos=mb.acquire_pos_id(vec3::NEG_X+hz_dir);
let hx_hz_pos=mb.acquire_pos_id(vec3::X+hz_dir);
let hx_lz_pos=mb.acquire_pos_id(vec3::X+lz_dir);
// tex
let lx_lz_tex=mb.acquire_tex_id(tex!($face_description,$tex_0));
let lx_hz_tex=mb.acquire_tex_id(tex!($face_description,$tex_1));
let hx_hz_tex=mb.acquire_tex_id(tex!($face_description,$tex_2));
let hx_lz_tex=mb.acquire_tex_id(tex!($face_description,$tex_3));
// norm
let lz_norm=mb.acquire_normal_id(lz_dir);
let hz_norm=mb.acquire_normal_id(hz_dir);
// color
let color=mb.acquire_color_id($face_description.color);
polygon_list.push(vec![
mb.acquire_vertex_id(IndexedVertex{pos:lx_lz_pos,tex:lx_lz_tex,normal:lz_norm,color}),
mb.acquire_vertex_id(IndexedVertex{pos:lx_hz_pos,tex:lx_hz_tex,normal:hz_norm,color}),
mb.acquire_vertex_id(IndexedVertex{pos:hx_hz_pos,tex:hx_hz_tex,normal:hz_norm,color}),
mb.acquire_vertex_id(IndexedVertex{pos:hx_lz_pos,tex:hx_lz_tex,normal:lz_norm,color}),
]);
}
// push face
let group_id=PolygonGroupId::new(polygon_groups.len() as u32);
polygon_groups.push(PolygonGroup::PolygonList(PolygonList::new(polygon_list)));
graphics_groups.push(IndexedGraphicsGroup{
render:$face_description.render,
groups:vec![group_id],
});
physics_group.groups.push(group_id);
};
}
end_face!(right, vec3::X,POINTS.into_iter());
barrel_face!(top, z,vec3::int(0,GON,z),vec3::int(0,GON,z+1), [GON,z],[GON,z+1],[-GON,z+1],[-GON,z]);
barrel_face!(back, y,vec3::int(0,y+1,GON),vec3::int(0,y,GON), [GON,y+1],[GON,y],[-GON,y],[-GON,y+1]);
end_face!(left, vec3::NEG_X,POINTS.into_iter().rev());
barrel_face!(bottom, z,vec3::int(0,-GON,z+1),vec3::int(0,-GON,z), [-GON,z+1],[-GON,z],[GON,z],[GON,z+1]);
barrel_face!(front, y,vec3::int(0,y,-GON),vec3::int(0,y+1,-GON), [-GON,y],[-GON,y+1],[GON,y+1],[GON,y]);
let physics_groups=vec![physics_group];
mb.build(polygon_groups,graphics_groups,physics_groups)
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,12 +1,8 @@
use crate::mesh::MeshWithSize;
use crate::rbx::RobloxPartDescription;
use crate::primitives::{CUBE_DEFAULT_VERTICES,CUBE_DEFAULT_POLYS,FaceDescription};
use rbx_mesh::mesh_data::{VertexId as MeshDataVertexId,NormalId as MeshDataNormalId,NormalId2 as MeshDataNormalId2,NormalId5 as MeshDataNormalId5};
use rbx_mesh::physics_data::VertexId as PhysicsDataVertexId;
use strafesnet_common::model::{self,IndexedVertex,MeshBuilder,PolygonGroup,PolygonGroupId,PolygonList,RenderConfigId,VertexId};
use rbx_mesh::mesh_data::NormalId2 as MeshDataNormalId2;
use strafesnet_common::model::{self,IndexedVertex,PolygonGroup,PolygonGroupId,PolygonList,RenderConfigId};
use strafesnet_common::integer::vec3;
#[allow(dead_code)]
#[derive(Debug)]
pub enum Error{
Block,
@@ -24,7 +20,7 @@ impl std::fmt::Display for Error{
// wacky state machine to make sure all vertices in a face agree upon what NormalId to use.
// Roblox duplicates this information per vertex when it should only exist per-face.
enum MeshDataNormalStatus{
Agree(MeshDataNormalId),
Agree(MeshDataNormalId2),
Conflicting,
}
struct MeshDataNormalChecker{
@@ -34,7 +30,7 @@ impl MeshDataNormalChecker{
fn new()->Self{
Self{status:None}
}
fn check(&mut self,normal:MeshDataNormalId){
fn check(&mut self,normal:MeshDataNormalId2){
self.status=match self.status.take(){
None=>Some(MeshDataNormalStatus::Agree(normal)),
Some(MeshDataNormalStatus::Agree(old_normal))=>{
@@ -47,7 +43,7 @@ impl MeshDataNormalChecker{
Some(MeshDataNormalStatus::Conflicting)=>Some(MeshDataNormalStatus::Conflicting),
};
}
fn into_agreed_normal(self)->Option<MeshDataNormalId>{
fn into_agreed_normal(self)->Option<MeshDataNormalId2>{
self.status.and_then(|status|match status{
MeshDataNormalStatus::Agree(normal)=>Some(normal),
MeshDataNormalStatus::Conflicting=>None,
@@ -55,133 +51,20 @@ impl MeshDataNormalChecker{
}
}
fn build_mesh2(
mb:&mut MeshBuilder,
polygon_groups_normal_id:&mut [Vec<Vec<VertexId>>;NORMAL_FACES],
cube_face_description:&[Option<FaceDescription>;NORMAL_FACES],
mesh:rbx_mesh::mesh_data::Mesh2,
)->Result<(),Error>{
//autoscale to size, idk what roblox is doing with the graphics mesh size
let mut pos_min=glam::Vec3::MAX;
let mut pos_max=glam::Vec3::MIN;
for vertex in &mesh.vertices{
let p=vertex.pos.into();
pos_min=pos_min.min(p);
pos_max=pos_max.max(p);
}
let graphics_size=pos_max-pos_min;
for [MeshDataVertexId(vertex_id0),MeshDataVertexId(vertex_id1),MeshDataVertexId(vertex_id2)] in mesh.faces{
let face=[
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_id2 as usize).ok_or(Error::MissingVertexId(vertex_id2))?,
];
let mut normal_agreement_checker=MeshDataNormalChecker::new();
let face=face.into_iter().map(|vertex|{
let MeshDataNormalId2(normal_id)=vertex.normal_id;
normal_agreement_checker.check(normal_id);
let pos=glam::Vec3::from_array(vertex.pos)/graphics_size;
let pos=mb.acquire_pos_id(vec3::try_from_f32_array(pos.to_array())?);
let normal=mb.acquire_normal_id(vec3::try_from_f32_array(vertex.norm)?);
let tex_coord=glam::Vec2::from_array(vertex.tex);
let maybe_face_description=&cube_face_description[normal_id as usize-1];
let (tex,color)=match maybe_face_description{
Some(face_description)=>{
// transform texture coordinates and set decal color
let tex=mb.acquire_tex_id(face_description.transform.transform_point2(tex_coord));
let color=mb.acquire_color_id(face_description.color);
(tex,color)
},
None=>{
// texture coordinates don't matter and pass through mesh vertex color
let tex=mb.acquire_tex_id(tex_coord);
let color=mb.acquire_color_id(glam::Vec4::from_array(vertex.color.map(|f|f as f32/255.0f32)));
(tex,color)
},
};
Ok(mb.acquire_vertex_id(IndexedVertex{pos,tex,normal,color}))
}).collect::<Result<Vec<_>,_>>().map_err(Error::Planar64Vec3)?;
if let Some(normal_id)=normal_agreement_checker.into_agreed_normal(){
polygon_groups_normal_id[normal_id as usize-1].push(face);
}else{
print!("[union] Empty face!");
}
}
Ok(())
}
fn build_mesh5(
mb:&mut MeshBuilder,
polygon_groups_normal_id:&mut [Vec<Vec<VertexId>>;NORMAL_FACES],
cube_face_description:&[Option<FaceDescription>;NORMAL_FACES],
mesh:rbx_mesh::mesh_data::CSGMDL5,
)->Result<(),Error>{
//autoscale to size, idk what roblox is doing with the graphics mesh size
let mut pos_min=glam::Vec3::MAX;
let mut pos_max=glam::Vec3::MIN;
for &pos in &mesh.positions{
let p=pos.into();
pos_min=pos_min.min(p);
pos_max=pos_max.max(p);
}
let graphics_size=pos_max-pos_min;
for face in mesh.faces.indices.chunks_exact(3){
let mut normal_agreement_checker=MeshDataNormalChecker::new();
let face=face.into_iter().map(|&vertex_id|{
let vertex_index=vertex_id as usize;
let &pos=mesh.positions.get(vertex_index).ok_or(Error::MissingVertexId(vertex_id))?;
let &MeshDataNormalId5(normal_id)=mesh.normal_ids.get(vertex_index).ok_or(Error::MissingVertexId(vertex_id))?;
let &norm=mesh.normals.get(vertex_index).ok_or(Error::MissingVertexId(vertex_id))?;
let &tex=mesh.tex.get(vertex_index).ok_or(Error::MissingVertexId(vertex_id))?;
let &color=mesh.colors.get(vertex_index).ok_or(Error::MissingVertexId(vertex_id))?;
normal_agreement_checker.check(normal_id);
let pos=glam::Vec3::from_array(pos)/graphics_size;
let pos=mb.acquire_pos_id(vec3::try_from_f32_array(pos.to_array()).map_err(Error::Planar64Vec3)?);
let normal=mb.acquire_normal_id(vec3::try_from_f32_array(norm).map_err(Error::Planar64Vec3)?);
let tex_coord=glam::Vec2::from_array(tex);
let maybe_face_description=&cube_face_description[normal_id as usize-1];
let (tex,color)=match maybe_face_description{
Some(face_description)=>{
// transform texture coordinates and set decal color
let tex=mb.acquire_tex_id(face_description.transform.transform_point2(tex_coord));
let color=mb.acquire_color_id(face_description.color);
(tex,color)
},
None=>{
// texture coordinates don't matter and pass through mesh vertex color
let tex=mb.acquire_tex_id(tex_coord);
let color=mb.acquire_color_id(glam::Vec4::from_array(color.map(|f|f as f32/255.0f32)));
(tex,color)
},
};
Ok(mb.acquire_vertex_id(IndexedVertex{pos,tex,normal,color}))
}).collect::<Result<Vec<_>,_>>()?;
if let Some(normal_id)=normal_agreement_checker.into_agreed_normal(){
polygon_groups_normal_id[normal_id as usize-1].push(face);
}else{
print!("[union] Empty face!");
}
}
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;
impl std::error::Error for Error{}
pub fn convert(
roblox_physics_data:&[u8],
roblox_mesh_data:&[u8],
size:glam::Vec3,
RobloxPartDescription(part_texture_description):RobloxPartDescription,
)->Result<MeshWithSize,Error>{
part_texture_description:crate::rbx::RobloxPartDescription,
)->Result<model::Mesh,Error>{
const NORMAL_FACES:usize=6;
let mut polygon_groups_normal_id=vec![Vec::new();NORMAL_FACES];
// build graphics and physics meshes
let mut mb=MeshBuilder::new();
let mut mb=strafesnet_common::model::MeshBuilder::new();
// graphics
let (polygon_groups_normal_id,graphics_groups)=if !roblox_mesh_data.is_empty(){
let mut polygon_groups_normal_id:[_;NORMAL_FACES]=[vec![],vec![],vec![],vec![],vec![],vec![]];
let graphics_groups=if !roblox_mesh_data.is_empty(){
// create per-face texture coordinate affine transforms
let cube_face_description=part_texture_description.map(|opt|opt.map(|mut t|{
t.transform.set_size(1.0,1.0);
@@ -191,34 +74,58 @@ pub fn convert(
let mesh_data=rbx_mesh::read_mesh_data_versioned(
std::io::Cursor::new(roblox_mesh_data)
).map_err(Error::RobloxMeshData)?;
match mesh_data{
let graphics_mesh=match mesh_data{
rbx_mesh::mesh_data::MeshData::CSGK(_)=>return Err(Error::Block),
rbx_mesh::mesh_data::MeshData::CSGMDL(rbx_mesh::mesh_data::CSGMDL::V2(mesh_data2))=>build_mesh2(&mut mb,&mut polygon_groups_normal_id,&cube_face_description,mesh_data2.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::CSGMDL2(mesh_data2))=>mesh_data2.mesh,
rbx_mesh::mesh_data::MeshData::CSGMDL(rbx_mesh::mesh_data::CSGMDL::CSGMDL4(mesh_data4))=>mesh_data4.mesh,
};
let graphics_groups=polygon_groups_normal_id
.iter()
.enumerate()
.filter(|&(_,group)|!group.is_empty())
.enumerate()
.map(|(polygon_group_id,(normal_id,_))|{
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)
for [vertex_id0,vertex_id1,vertex_id2] in graphics_mesh.faces{
let face=[
graphics_mesh.vertices.get(vertex_id0.0 as usize).ok_or(Error::MissingVertexId(vertex_id0.0))?,
graphics_mesh.vertices.get(vertex_id1.0 as usize).ok_or(Error::MissingVertexId(vertex_id1.0))?,
graphics_mesh.vertices.get(vertex_id2.0 as usize).ok_or(Error::MissingVertexId(vertex_id2.0))?,
];
let mut normal_agreement_checker=MeshDataNormalChecker::new();
let face=face.into_iter().map(|vertex|{
normal_agreement_checker.check(vertex.normal_id);
let pos=mb.acquire_pos_id(vec3::try_from_f32_array(vertex.pos)?);
let normal=mb.acquire_normal_id(vec3::try_from_f32_array(vertex.norm)?);
let tex_coord=glam::Vec2::from_array(vertex.tex);
let maybe_face_description=&cube_face_description[vertex.normal_id as usize-1];
let (tex,color)=match maybe_face_description{
Some(face_description)=>{
// transform texture coordinates and set decal color
let tex=mb.acquire_tex_id(face_description.transform.transform_point2(tex_coord));
let color=mb.acquire_color_id(face_description.color);
(tex,color)
},
None=>{
// texture coordinates don't matter and pass through mesh vertex color
let tex=mb.acquire_tex_id(tex_coord);
let color=mb.acquire_color_id(glam::Vec4::from_array(vertex.color.map(|f|f as f32/255.0f32)));
(tex,color)
},
};
Ok(mb.acquire_vertex_id(IndexedVertex{pos,tex,normal,color}))
}).collect::<Result<Vec<_>,_>>().map_err(Error::Planar64Vec3)?;
if let Some(normal_id)=normal_agreement_checker.into_agreed_normal(){
polygon_groups_normal_id[normal_id as usize-1].push(face);
}else{
panic!("Empty face!");
}
}
(0..NORMAL_FACES).map(|polygon_group_id|{
model::IndexedGraphicsGroup{
render:cube_face_description[polygon_group_id].as_ref().map_or(RenderConfigId::new(0),|face_description|face_description.render),
groups:vec![PolygonGroupId::new(polygon_group_id as u32)]
}
}).collect()
}else{
([vec![],vec![],vec![],vec![],vec![],vec![]],Vec::new())
Vec::new()
};
//physics
let polygon_groups_normal_it=polygon_groups_normal_id.into_iter().filter_map(|faces|
// graphics polygon groups (to be rendered)
Some(PolygonGroup::PolygonList(new_polygon_list_checked(faces)?))
);
let polygon_groups:Vec<PolygonGroup>=if !roblox_physics_data.is_empty(){
let physics_convex_meshes=if !roblox_physics_data.is_empty(){
let physics_data=rbx_mesh::read_physics_data_versioned(
std::io::Cursor::new(roblox_physics_data)
).map_err(Error::RobloxPhysicsData)?;
@@ -227,58 +134,44 @@ pub fn convert(
// have not seen this format in practice
|rbx_mesh::physics_data::PhysicsData::CSGPHS(rbx_mesh::physics_data::CSGPHS::Block)
=>return Err(Error::Block),
rbx_mesh::physics_data::PhysicsData::CSGPHS(rbx_mesh::physics_data::CSGPHS::V3(meshes))
|rbx_mesh::physics_data::PhysicsData::CSGPHS(rbx_mesh::physics_data::CSGPHS::V5(meshes))
=>meshes.meshes,
rbx_mesh::physics_data::PhysicsData::CSGPHS(rbx_mesh::physics_data::CSGPHS::V6(meshes))
=>vec![meshes.mesh],
rbx_mesh::physics_data::PhysicsData::CSGPHS(rbx_mesh::physics_data::CSGPHS::V7(meshes))
rbx_mesh::physics_data::PhysicsData::CSGPHS(rbx_mesh::physics_data::CSGPHS::Meshes(meshes))
=>meshes.meshes,
rbx_mesh::physics_data::PhysicsData::CSGPHS(rbx_mesh::physics_data::CSGPHS::PhysicsInfoMesh(pim))
=>vec![pim.mesh],
};
let physics_convex_meshes_it=physics_convex_meshes.into_iter().filter_map(|mesh|{
// this can be factored out of the loop but I am lazy
let color=mb.acquire_color_id(glam::Vec4::ONE);
let tex=mb.acquire_tex_id(glam::Vec2::ZERO);
// physics polygon groups (to do physics)
let polygons=mesh.faces.into_iter().map(|[PhysicsDataVertexId(vertex_id0),PhysicsDataVertexId(vertex_id1),PhysicsDataVertexId(vertex_id2)]|{
let face=[
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_id2 as usize).ok_or(Error::MissingVertexId(vertex_id2))?,
].map(|v|glam::Vec3::from_slice(v)/size);
let vertex_norm=(face[1]-face[0])
.cross(face[2]-face[0]);
let normal=mb.acquire_normal_id(vec3::try_from_f32_array(vertex_norm.to_array()).map_err(Error::Planar64Vec3)?);
face.into_iter().map(|vertex_pos|{
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}))
}).collect()
}).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<_,_>>()?
physics_convex_meshes
}else{
// generate a unit cube as default physics
let pos_list=CUBE_DEFAULT_VERTICES.map(|pos|mb.acquire_pos_id(pos>>1));
let tex=mb.acquire_tex_id(glam::Vec2::ZERO);
let normal=mb.acquire_normal_id(vec3::zero());
let color=mb.acquire_color_id(glam::Vec4::ONE);
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})
).to_vec()).to_vec()));
polygon_groups_normal_it.chain([polygon_group]).collect()
Vec::new()
};
let physics_groups=(graphics_groups.len()..polygon_groups.len()).map(|id|model::IndexedPhysicsGroup{
let polygon_groups:Vec<PolygonGroup>=polygon_groups_normal_id.into_iter().map(|faces|
// graphics polygon groups (to be rendered)
Ok(PolygonGroup::PolygonList(PolygonList::new(faces)))
).chain(physics_convex_meshes.into_iter().map(|mesh|{
// this can be factored out of the loop but I am lazy
let color=mb.acquire_color_id(glam::Vec4::ONE);
let tex=mb.acquire_tex_id(glam::Vec2::ZERO);
// physics polygon groups (to do physics)
Ok(PolygonGroup::PolygonList(PolygonList::new(mesh.faces.into_iter().map(|[vertex_id0,vertex_id1,vertex_id2]|{
let face=[
mesh.vertices.get(vertex_id0.0 as usize).ok_or(Error::MissingVertexId(vertex_id0.0))?,
mesh.vertices.get(vertex_id1.0 as usize).ok_or(Error::MissingVertexId(vertex_id1.0))?,
mesh.vertices.get(vertex_id2.0 as usize).ok_or(Error::MissingVertexId(vertex_id2.0))?,
].map(|v|glam::Vec3::from_slice(v)/size);
let vertex_norm=(face[1]-face[0])
.cross(face[2]-face[0]);
let normal=mb.acquire_normal_id(vec3::try_from_f32_array(vertex_norm.to_array()).map_err(Error::Planar64Vec3)?);
face.into_iter().map(|vertex_pos|{
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}))
}).collect()
}).collect::<Result<_,_>>()?)))
})).collect::<Result<_,_>>()?;
let physics_groups=(NORMAL_FACES..polygon_groups.len()).map(|id|model::IndexedPhysicsGroup{
groups:vec![PolygonGroupId::new(id as u32)]
}).collect();
let mesh=mb.build(
Ok(mb.build(
polygon_groups,
graphics_groups,
physics_groups,
);
Ok(MeshWithSize::new(mesh,vec3::ONE))
))
}

View File

@@ -9,6 +9,3 @@ authors = ["Rhys Lloyd <krakow20@gmail.com>"]
[dependencies]
url = "2.5.4"
[lints]
workspace = true

View File

@@ -1,6 +1,6 @@
[package]
name = "roblox_emulator"
version = "0.5.3"
version = "0.4.7"
edition = "2024"
repository = "https://git.itzana.me/StrafesNET/strafe-project"
license = "MIT OR Apache-2.0"
@@ -12,13 +12,10 @@ default=["run-service"]
run-service=[]
[dependencies]
glam.workspace = true
mlua = { version = "0.11.3", features = ["luau"] }
phf = { version = "0.13.1", features = ["macros"] }
rbx_dom_weak = "4.1.0"
rbx_reflection = "6.1.0"
rbx_reflection_database = "2.0.2"
rbx_types = "3.1.0"
[lints]
workspace = true
glam = "0.30.0"
mlua = { version = "0.10.1", features = ["luau"] }
phf = { version = "0.11.2", features = ["macros"] }
rbx_dom_weak = { version = "2.7.0", registry = "strafesnet" }
rbx_reflection = { version = "4.7.0", registry = "strafesnet" }
rbx_reflection_database = { version = "0.2.10", registry = "strafesnet" }
rbx_types = { version = "1.10.0", registry = "strafesnet" }

View File

@@ -1,121 +1,93 @@
use crate::util::static_ustr;
use rbx_dom_weak::{types::Ref,InstanceBuilder,WeakDom};
#[derive(Debug)]
pub enum ServicesError{
WorkspaceNotFound,
}
impl std::fmt::Display for ServicesError{
fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{
write!(f,"{self:?}")
}
}
impl std::error::Error for ServicesError{}
pub struct Services{
pub(crate) game:Ref,
pub(crate) workspace:Ref,
pub fn class_is_a(class:&str,superclass:&str)->bool{
class==superclass
||rbx_reflection_database::get().classes.get(class)
.is_some_and(|descriptor|
descriptor.superclass.as_ref().is_some_and(|class_super|
class_is_a(class_super,superclass)
)
)
}
impl Services{
fn find_services(dom:&WeakDom)->Result<Services,ServicesError>{
Ok(Services{
workspace:*dom.root().children().iter().find(|&&r|
dom.get_by_ref(r).is_some_and(|instance|instance.class=="Workspace")
).ok_or(ServicesError::WorkspaceNotFound)?,
game:dom.root_ref(),
})
}
}
pub type LuaAppData=&'static mut WeakDom;
#[repr(transparent)]
pub struct Context{
pub(crate)dom:WeakDom,
pub(crate)services:Services,
}
impl Context{
pub fn from_place(dom:WeakDom)->Result<Context,ServicesError>{
let services=Services::find_services(&dom)?;
Ok(Self{dom,services})
pub const fn new(dom:WeakDom)->Self{
Self{dom}
}
pub fn script_singleton(source:String)->(Context,crate::runner::instance::Instance){
pub fn script_singleton(source:String)->(Context,crate::runner::instance::Instance,Services){
let script=InstanceBuilder::new("Script")
.with_property("Source",rbx_types::Variant::String(source));
let script_ref=script.referent();
let dom=WeakDom::new(
let mut context=Self::new(WeakDom::new(
InstanceBuilder::new("DataModel")
.with_child(script)
);
let context=Self::from_model(dom);
(context,crate::runner::instance::Instance::new_unchecked(script_ref))
));
let services=context.convert_into_place();
(context,crate::runner::instance::Instance::new(script_ref),services)
}
pub fn from_ref(dom:&WeakDom)->&Context{
unsafe{&*(dom as *const WeakDom as *const Context)}
}
pub fn from_mut(dom:&mut WeakDom)->&mut Context{
unsafe{&mut *(dom as *mut WeakDom as *mut Context)}
}
/// Creates an iterator over all items of a particular class.
pub fn superclass_iter<'a>(&'a self,superclass:&'a str)->impl Iterator<Item=Ref>+'a{
let db=rbx_reflection_database::get().unwrap();
let Some(superclass)=db.classes.get(superclass)else{
panic!("Invalid class");
};
self.dom.descendants().filter_map(|instance|{
let class=db.classes.get(instance.class.as_str())?;
db.has_superclass(class,superclass).then(||instance.referent())
})
self.dom.descendants().filter(|&instance|
class_is_a(instance.class.as_ref(),superclass)
).map(|instance|instance.referent())
}
pub fn scripts(&self)->Vec<crate::runner::instance::Instance>{
self.superclass_iter("Script")
.filter_map(|script_ref|{
let script=self.dom.get_by_ref(script_ref)?;
if let None|Some(rbx_dom_weak::types::Variant::Bool(false))=script.properties.get(&static_ustr("Disabled")){
return Some(crate::runner::instance::Instance::new_unchecked(script_ref));
}
None
})
.collect()
self.superclass_iter("LuaSourceContainer").map(crate::runner::instance::Instance::new).collect()
}
pub fn from_model(mut dom:WeakDom)->Context{
pub fn find_services(&self)->Option<Services>{
Some(Services{
workspace:*self.dom.root().children().iter().find(|&&r|
self.dom.get_by_ref(r).is_some_and(|instance|instance.class=="Workspace")
)?,
game:self.dom.root_ref(),
})
}
pub fn convert_into_place(&mut self)->Services{
//snapshot root instances
let children=dom.root().children().to_owned();
let children=self.dom.root().children().to_owned();
//insert services
let game=dom.root_ref();
let terrain_bldr=InstanceBuilder::new("Terrain")
.with_properties([
("CFrame",rbx_dom_weak::types::Variant::CFrame(rbx_dom_weak::types::CFrame::new(rbx_dom_weak::types::Vector3::new(0.0,0.0,0.0),rbx_dom_weak::types::Matrix3::identity()))),
("Size",rbx_dom_weak::types::Variant::Vector3(rbx_dom_weak::types::Vector3::new(1.0,1.0,1.0))),
("Velocity",rbx_dom_weak::types::Variant::Vector3(rbx_dom_weak::types::Vector3::new(0.0,0.0,0.0))),
("Transparency",rbx_dom_weak::types::Variant::Float32(0.0)),
("Color",rbx_dom_weak::types::Variant::Color3uint8(rbx_dom_weak::types::Color3uint8::new(255,255,255))),
("CanCollide",rbx_dom_weak::types::Variant::Bool(true)),
]);
let workspace=dom.insert(game,
let game=self.dom.root_ref();
let terrain_bldr=InstanceBuilder::new("Terrain");
let workspace=self.dom.insert(game,
InstanceBuilder::new("Workspace")
//Set Workspace.Terrain property equal to Terrain
.with_property("Terrain",terrain_bldr.referent())
.with_child(terrain_bldr)
);
{
//Lowercase and upper case workspace property!
let game=self.dom.root_mut();
game.properties.insert("workspace".to_owned(),rbx_types::Variant::Ref(workspace));
game.properties.insert("Workspace".to_owned(),rbx_types::Variant::Ref(workspace));
}
self.dom.insert(game,InstanceBuilder::new("Lighting"));
//transfer original root instances into workspace
for instance in children{
dom.transfer_within(instance,workspace);
self.dom.transfer_within(instance,workspace);
}
{
//Lowercase and upper case workspace property!
let game=dom.root_mut();
// TODO: DELETE THIS!
game.properties.insert(static_ustr("workspace"),rbx_types::Variant::Ref(workspace));
game.properties.insert(static_ustr("Workspace"),rbx_types::Variant::Ref(workspace));
Services{
game,
workspace,
}
dom.insert(game,InstanceBuilder::new("Lighting"));
let services=Services{game,workspace};
Self{dom,services}
}
}
impl AsRef<WeakDom> for Context{
fn as_ref(&self)->&WeakDom{
&self.dom
}
pub struct Services{
pub game:Ref,
pub workspace:Ref,
}

View File

@@ -1,4 +1,3 @@
mod util;
pub mod runner;
pub mod context;
#[cfg(feature="run-service")]
@@ -6,7 +5,3 @@ pub(crate) mod scheduler;
#[cfg(test)]
mod tests;
pub mod mlua{
pub use mlua::{Result,Error};
}

View File

@@ -1,59 +0,0 @@
use super::color3::Color3;
#[derive(Clone,Copy)]
pub struct BrickColor(rbx_types::BrickColor);
impl BrickColor{
pub fn from_name(name:&str)->Option<Self>{
Some(BrickColor(rbx_types::BrickColor::from_name(name)?))
}
pub fn from_number(number:u16)->Option<Self>{
Some(BrickColor(rbx_types::BrickColor::from_number(number)?))
}
}
pub fn set_globals(lua:&mlua::Lua,globals:&mlua::Table)->Result<(),mlua::Error>{
let table=lua.create_table()?;
table.raw_set("new",
lua.create_function(|_,r:mlua::Value|
match r{
mlua::Value::String(name)=>Ok(BrickColor::from_name(&*name.to_str()?)),
mlua::Value::Integer(number)=>Ok(BrickColor::from_number(number as u16)),
_=>Err(mlua::Error::runtime("Unsupported arguments"))
}
)?
)?;
macro_rules! brickcolor_constructor{
($fname:expr,$internal:ident)=>{
table.raw_set($fname,
lua.create_function(|_,_:()|
Ok(BrickColor(rbx_types::BrickColor::$internal))
)?
)?;
};
}
brickcolor_constructor!("White",White);
brickcolor_constructor!("Gray",MediumStoneGrey);
brickcolor_constructor!("DarkGray",DarkStoneGrey);
brickcolor_constructor!("Black",Black);
brickcolor_constructor!("Red",BrightRed);
brickcolor_constructor!("Yellow",BrightYellow);
brickcolor_constructor!("Green",DarkGreen);
brickcolor_constructor!("Blue",BrightBlue);
globals.set("BrickColor",table)?;
Ok(())
}
impl mlua::UserData for BrickColor{
fn add_fields<F:mlua::UserDataFields<Self>>(fields:&mut F){
fields.add_field_method_get("Color",|_,BrickColor(this)|{
let rbx_types::Color3uint8{r,g,b}=this.to_color3uint8();
Ok(Color3::from_rgb(r,g,b))
});
}
}
type_from_lua_userdata!(BrickColor);

View File

@@ -1,10 +1,7 @@
use mlua::FromLua;
use super::number::Number;
use super::vector3::Vector3;
#[derive(Clone,Copy)]
pub struct CFrame(glam::Affine3A);
pub struct CFrame(pub(crate)glam::Affine3A);
impl CFrame{
pub fn new(
@@ -37,14 +34,14 @@ fn vec3_from_glam(v:rbx_types::Vector3)->glam::Vec3A{
glam::vec3a(v.x,v.y,v.z)
}
impl From<CFrame> for rbx_types::CFrame{
fn from(CFrame(cf):CFrame)->rbx_types::CFrame{
impl Into<rbx_types::CFrame> for CFrame{
fn into(self)->rbx_types::CFrame{
rbx_types::CFrame::new(
vec3_to_glam(cf.translation),
vec3_to_glam(self.0.translation),
rbx_types::Matrix3::new(
vec3_to_glam(cf.matrix3.x_axis),
vec3_to_glam(cf.matrix3.y_axis),
vec3_to_glam(cf.matrix3.z_axis),
vec3_to_glam(self.0.matrix3.x_axis),
vec3_to_glam(self.0.matrix3.y_axis),
vec3_to_glam(self.0.matrix3.z_axis),
)
)
}
@@ -63,25 +60,16 @@ impl From<rbx_types::CFrame> for CFrame{
}
pub fn set_globals(lua:&mlua::Lua,globals:&mlua::Table)->Result<(),mlua::Error>{
let table=lua.create_table()?;
let cframe_table=lua.create_table()?;
//CFrame.new
table.raw_set("new",
lua.create_function(|lua,tuple:(
mlua::Value,mlua::Value,Option<Number>,
Option<Number>,Option<Number>,Option<Number>,
Option<Number>,Option<Number>,Option<Number>,
Option<Number>,Option<Number>,Option<Number>,
cframe_table.raw_set("new",
lua.create_function(|_,tuple:(
mlua::Value,mlua::Value,Option<f32>,
Option<f32>,Option<f32>,Option<f32>,
Option<f32>,Option<f32>,Option<f32>,
Option<f32>,Option<f32>,Option<f32>,
)|match tuple{
//CFrame.new()
(
mlua::Value::Nil,mlua::Value::Nil,None,
None,None,None,
None,None,None,
None,None,None,
)=>{
Ok(CFrame(glam::Affine3A::IDENTITY))
},
//CFrame.new(pos)
(
mlua::Value::UserData(pos),mlua::Value::Nil,None,
@@ -89,8 +77,8 @@ pub fn set_globals(lua:&mlua::Lua,globals:&mlua::Table)->Result<(),mlua::Error>{
None,None,None,
None,None,None,
)=>{
let Vector3(pos):&Vector3=&*pos.borrow()?;
Ok(CFrame::point(pos.x,pos.y,pos.z))
let pos:Vector3=pos.take()?;
Ok(CFrame::point(pos.0.x,pos.0.y,pos.0.z))
},
//TODO: CFrame.new(pos,look)
(
@@ -99,99 +87,85 @@ pub fn set_globals(lua:&mlua::Lua,globals:&mlua::Table)->Result<(),mlua::Error>{
None,None,None,
None,None,None,
)=>{
let _pos:&Vector3=&*pos.borrow()?;
let _look:&Vector3=&*look.borrow()?;
let _pos:Vector3=pos.take()?;
let _look:Vector3=look.take()?;
Err(mlua::Error::runtime("Not yet implemented"))
},
//CFrame.new(x,y,z)
(
x,y,Some(z),
mlua::Value::Number(x),mlua::Value::Number(y),Some(z),
None,None,None,
None,None,None,
None,None,None,
)=>Ok(CFrame::point(Number::from_lua(x,lua)?.into(),Number::from_lua(y,lua)?.into(),z.into())),
)=>Ok(CFrame::point(x as f32,y as f32,z)),
//CFrame.new(x,y,z,xx,yx,zx,xy,yy,zy,xz,yz,zz)
(
x,y,Some(z),
mlua::Value::Number(x),mlua::Value::Number(y),Some(z),
Some(xx),Some(yx),Some(zx),
Some(xy),Some(yy),Some(zy),
Some(xz),Some(yz),Some(zz),
)=>Ok(CFrame::new(Number::from_lua(x,lua)?.into(),Number::from_lua(y,lua)?.into(),z.into(),
xx.into(),yx.into(),zx.into(),
xy.into(),yy.into(),zy.into(),
xz.into(),yz.into(),zz.into(),
)=>Ok(CFrame::new(x as f32,y as f32,z,
xx,yx,zx,
xy,yy,zy,
xz,yz,zz,
)),
_=>Err(mlua::Error::runtime("Invalid arguments"))
})?
)?;
//CFrame.Angles
let from_euler_angles=lua.create_function(|_,(x,y,z):(Number,Number,Number)|
Ok(CFrame::angles(x.into(),y.into(),z.into()))
cframe_table.raw_set("Angles",
lua.create_function(|_,(x,y,z):(f32,f32,f32)|
Ok(CFrame::angles(x,y,z))
)?
)?;
table.raw_set("Angles",from_euler_angles.clone())?;
table.raw_set("fromEulerAnglesXYZ",from_euler_angles.clone())?;
table.raw_set("FromEulerAnglesXYZ",from_euler_angles)?;
globals.set("CFrame",table)?;
globals.set("CFrame",cframe_table)?;
Ok(())
}
impl mlua::UserData for CFrame{
fn add_fields<F:mlua::UserDataFields<Self>>(fields:&mut F){
fields.add_field_method_get("p",|_,CFrame(this)|Ok(Vector3(this.translation)));
fields.add_field_method_get("x",|_,CFrame(this)|Ok(this.translation.x));
fields.add_field_method_get("X",|_,CFrame(this)|Ok(this.translation.x));
fields.add_field_method_get("y",|_,CFrame(this)|Ok(this.translation.y));
fields.add_field_method_get("Y",|_,CFrame(this)|Ok(this.translation.y));
fields.add_field_method_get("z",|_,CFrame(this)|Ok(this.translation.z));
fields.add_field_method_get("Z",|_,CFrame(this)|Ok(this.translation.z));
fields.add_field_method_get("rightVector",|_,CFrame(this)|Ok(Vector3(this.matrix3.x_axis)));
fields.add_field_method_get("RightVector",|_,CFrame(this)|Ok(Vector3(this.matrix3.x_axis)));
fields.add_field_method_get("upVector",|_,CFrame(this)|Ok(Vector3(this.matrix3.y_axis)));
fields.add_field_method_get("UpVector",|_,CFrame(this)|Ok(Vector3(this.matrix3.y_axis)));
fields.add_field_method_get("lookVector",|_,CFrame(this)|Ok(Vector3(-this.matrix3.z_axis)));
fields.add_field_method_get("LookVector",|_,CFrame(this)|Ok(Vector3(-this.matrix3.z_axis)));
fields.add_field_method_get("XVector",|_,CFrame(this)|Ok(Vector3(this.matrix3.row(0))));
fields.add_field_method_get("YVector",|_,CFrame(this)|Ok(Vector3(this.matrix3.row(1))));
fields.add_field_method_get("ZVector",|_,CFrame(this)|Ok(Vector3(this.matrix3.row(2))));
//CFrame.p
fields.add_field_method_get("p",|_,this|Ok(Vector3(this.0.translation)));
}
fn add_methods<M:mlua::UserDataMethods<Self>>(methods:&mut M){
methods.add_method("components",|_,CFrame(this),()|Ok((
this.translation.x,
this.translation.y,
this.translation.z,
this.matrix3.x_axis.x,
this.matrix3.y_axis.x,
this.matrix3.z_axis.x,
this.matrix3.x_axis.y,
this.matrix3.y_axis.y,
this.matrix3.z_axis.y,
this.matrix3.x_axis.z,
this.matrix3.y_axis.z,
this.matrix3.z_axis.z,
methods.add_method("components",|_,this,()|Ok((
this.0.translation.x,
this.0.translation.y,
this.0.translation.z,
this.0.matrix3.x_axis.x,
this.0.matrix3.y_axis.x,
this.0.matrix3.z_axis.x,
this.0.matrix3.x_axis.y,
this.0.matrix3.y_axis.y,
this.0.matrix3.z_axis.y,
this.0.matrix3.x_axis.z,
this.0.matrix3.y_axis.z,
this.0.matrix3.z_axis.z,
)));
methods.add_method("VectorToWorldSpace",|_,CFrame(this),Vector3(v):Vector3|
Ok(Vector3(this.transform_vector3a(v)))
methods.add_method("VectorToWorldSpace",|_,this,v:Vector3|
Ok(Vector3(this.0.transform_vector3a(v.0)))
);
methods.add_meta_function(mlua::MetaMethod::Mul,|_,(CFrame(this),CFrame(val)):(Self,Self)|Ok(Self(this*val)));
methods.add_meta_function(mlua::MetaMethod::ToString,|_,CFrame(this):Self|
//methods.add_meta_method(mlua::MetaMethod::Mul,|_,this,val:&Vector3|Ok(Vector3(this.0.matrix3*val.0+this.0.translation)));
methods.add_meta_function(mlua::MetaMethod::Mul,|_,(this,val):(Self,Self)|Ok(Self(this.0*val.0)));
methods.add_meta_function(mlua::MetaMethod::ToString,|_,this:Self|
Ok(format!("CFrame.new({},{},{},{},{},{},{},{},{},{},{},{})",
this.translation.x,
this.translation.y,
this.translation.z,
this.matrix3.x_axis.x,
this.matrix3.y_axis.x,
this.matrix3.z_axis.x,
this.matrix3.x_axis.y,
this.matrix3.y_axis.y,
this.matrix3.z_axis.y,
this.matrix3.x_axis.z,
this.matrix3.y_axis.z,
this.matrix3.z_axis.z,
this.0.translation.x,
this.0.translation.y,
this.0.translation.z,
this.0.matrix3.x_axis.x,
this.0.matrix3.y_axis.x,
this.0.matrix3.z_axis.x,
this.0.matrix3.x_axis.y,
this.0.matrix3.y_axis.y,
this.0.matrix3.z_axis.y,
this.0.matrix3.x_axis.z,
this.0.matrix3.y_axis.z,
this.0.matrix3.z_axis.z,
))
);
}

View File

@@ -1,5 +1,3 @@
use super::number::Number;
#[derive(Clone,Copy)]
pub struct Color3{
r:f32,
@@ -10,37 +8,28 @@ impl Color3{
pub const fn new(r:f32,g:f32,b:f32)->Self{
Self{r,g,b}
}
pub const fn from_rgb(r:u8,g:u8,b:u8)->Self{
Color3::new(r as f32/255.0,g as f32/255.0,b as f32/255.0)
}
}
impl From<rbx_types::Color3> for Color3{
fn from(value:rbx_types::Color3)->Color3{
Color3::new(value.r,value.g,value.b)
}
}
impl From<Color3> for rbx_types::Color3{
fn from(value:Color3)->rbx_types::Color3{
rbx_types::Color3::new(value.r,value.g,value.b)
impl Into<rbx_types::Color3> for Color3{
fn into(self)->rbx_types::Color3{
rbx_types::Color3::new(self.r,self.g,self.b)
}
}
pub fn set_globals(lua:&mlua::Lua,globals:&mlua::Table)->Result<(),mlua::Error>{
let table=lua.create_table()?;
let color3_table=lua.create_table()?;
table.raw_set("new",
lua.create_function(|_,(r,g,b):(Number,Number,Number)|
Ok(Color3::new(r.into(),g.into(),b.into()))
color3_table.raw_set("new",
lua.create_function(|_,(r,g,b):(f32,f32,f32)|
Ok(Color3::new(r,g,b))
)?
)?;
color3_table.raw_set("fromRGB",
lua.create_function(|_,(r,g,b):(u8,u8,u8)|
Ok(Color3::new(r as f32/255.0,g as f32/255.0,b as f32/255.0))
)?
)?;
let from_rgb=lua.create_function(|_,(r,g,b):(u8,u8,u8)|
Ok(Color3::from_rgb(r,g,b))
)?;
table.raw_set("fromRGB",from_rgb.clone())?;
table.raw_set("FromRGB",from_rgb)?;
globals.set("Color3",table)?;
globals.set("Color3",color3_table)?;
Ok(())
}

View File

@@ -1,29 +1,31 @@
#[derive(Clone)]
pub struct ColorSequence(rbx_types::ColorSequence);
#[derive(Clone,Copy)]
pub struct ColorSequence{}
impl ColorSequence{
pub const fn new(keypoints:Vec<rbx_types::ColorSequenceKeypoint>)->Self{
Self(rbx_types::ColorSequence{keypoints})
pub const fn new()->Self{
Self{}
}
}
impl From<ColorSequence> for rbx_types::ColorSequence{
fn from(ColorSequence(value):ColorSequence)->rbx_types::ColorSequence{
value
impl Into<rbx_types::ColorSequence> for ColorSequence{
fn into(self)->rbx_types::ColorSequence{
rbx_types::ColorSequence{
keypoints:Vec::new()
}
}
}
pub fn set_globals(lua:&mlua::Lua,globals:&mlua::Table)->Result<(),mlua::Error>{
let table=lua.create_table()?;
let number_sequence_table=lua.create_table()?;
table.raw_set("new",
number_sequence_table.raw_set("new",
lua.create_function(|_,_:mlua::MultiValue|
Ok(ColorSequence::new(Vec::new()))
Ok(ColorSequence::new())
)?
)?;
globals.set("ColorSequence",table)?;
globals.set("ColorSequence",number_sequence_table)?;
Ok(())
}
impl mlua::UserData for ColorSequence{}
type_from_lua_userdata_clone!(ColorSequence);
type_from_lua_userdata!(ColorSequence);

View File

@@ -1,136 +1,63 @@
#[derive(Clone,Copy)]
pub struct EnumItem<'a>{
name:Option<&'a str>,
value:u32,
}
impl<'a> EnumItem<'a>{
fn known_name((name,&value):(&'a std::borrow::Cow<'a,str>,&u32))->Self{
Self{name:Some(name.as_ref()),value}
}
}
impl From<rbx_types::Enum> for EnumItem<'_>{
fn from(e:rbx_types::Enum)->Self{
EnumItem{
name:None,
value:e.to_u32(),
}
}
}
impl From<EnumItem<'_>> for rbx_types::Enum{
fn from(e:EnumItem)->rbx_types::Enum{
rbx_types::Enum::from_u32(e.value)
}
}
impl PartialEq for EnumItem<'_>{
fn eq(&self,other:&EnumItem<'_>)->bool{
self.value==other.value&&{
// if both names are known, they must match, otherwise whatever
match (self.name,other.name){
(Some(lhs),Some(rhs))=>lhs==rhs,
_=>true,
}
}
}
}
use mlua::IntoLua;
#[derive(Clone,Copy)]
pub struct Enums;
impl Enums{
pub fn get(&self,index:&str)->Option<EnumItems<'static>>{
let db=rbx_reflection_database::get().unwrap();
db.enums.get(index).map(|ed|EnumItems{ed})
}
}
pub struct Enum(u32);
#[derive(Clone,Copy)]
pub struct EnumItems<'a>{
pub struct EnumItems;
#[derive(Clone,Copy)]
pub struct EnumItem<'a>{
ed:&'a rbx_reflection::EnumDescriptor<'a>,
}
impl<'a> EnumItems<'a>{
pub fn from_value(&self,value:u32)->Option<EnumItem<'a>>{
self.ed.items.iter().find(|&(_,&v)|v==value).map(EnumItem::known_name)
}
pub fn from_name(&self,name:&str)->Option<EnumItem<'a>>{
self.ed.items.get_key_value(name).map(EnumItem::known_name)
}
pub fn from_enum(&self,enum_item:EnumItem)->Option<EnumItem<'a>>{
match enum_item.name{
Some(s)=>{
let got=self.from_name(s)?;
(got.value==enum_item.value).then_some(got)
},
None=>self.from_value(enum_item.value)
}
impl Into<rbx_types::Enum> for Enum{
fn into(self)->rbx_types::Enum{
rbx_types::Enum::from_u32(self.0)
}
}
pub enum CoerceEnum<'a>{
Integer(i64),
String(mlua::String),
Enum(EnumItem<'a>),
}
impl CoerceEnum<'_>{
pub fn coerce_to<'a>(self,enum_items:EnumItems<'a>)->mlua::Result<EnumItem<'a>>{
match self{
CoerceEnum::Integer(int)=>enum_items.from_value(int as u32),
CoerceEnum::String(s)=>enum_items.from_name(&*s.to_str()?),
CoerceEnum::Enum(enum_item)=>enum_items.from_enum(enum_item),
}.ok_or_else(||mlua::Error::runtime(format!("Bad {} EnumItem",enum_items.ed.name)))
}
}
impl mlua::FromLua for CoerceEnum<'_>{
fn from_lua(value:mlua::Value,_lua:&mlua::Lua)->Result<Self,mlua::Error>{
match value{
mlua::Value::Integer(int)=>Ok(CoerceEnum::Integer(int)),
mlua::Value::String(s)=>Ok(CoerceEnum::String(s)),
mlua::Value::UserData(ud)=>Ok(CoerceEnum::Enum(*ud.borrow()?)),
other=>Err(mlua::Error::runtime(format!("Expected {} got {:?}",stringify!(Enum),other))),
}
impl<'a> EnumItem<'a>{
const fn new(ed:&'a rbx_reflection::EnumDescriptor)->Self{
Self{ed}
}
}
pub fn set_globals(_lua:&mlua::Lua,globals:&mlua::Table)->Result<(),mlua::Error>{
globals.set("Enum",Enums)
globals.set("Enum",EnumItems)
}
impl mlua::UserData for EnumItems<'static>{
fn add_fields<F:mlua::UserDataFields<Self>>(_fields:&mut F){
}
fn add_methods<M:mlua::UserDataMethods<Self>>(methods:&mut M){
methods.add_method("FromName",|_,this:&EnumItems,name:mlua::String|Ok(this.from_name(&*name.to_str()?)));
methods.add_method("FromValue",|_,this:&EnumItems,value:u32|Ok(this.from_value(value)));
methods.add_method("GetEnumItems",|_,this:&EnumItems,()|->mlua::Result<Vec<EnumItem>>{
Ok(this.ed.items.iter().map(EnumItem::known_name).collect())
});
methods.add_meta_function(mlua::MetaMethod::Index,|_,(this,val):(EnumItems,mlua::String)|{
let index=&*val.to_str()?;
Ok(this.ed.items.get_key_value(index).map(EnumItem::known_name))
});
}
}
type_from_lua_userdata_lua_lifetime!(EnumItems);
impl mlua::UserData for Enums{
fn add_fields<F:mlua::UserDataFields<Self>>(_fields:&mut F){
}
fn add_methods<M:mlua::UserDataMethods<Self>>(methods:&mut M){
methods.add_meta_function(mlua::MetaMethod::Index,|_,(enums,val):(Self,mlua::String)|{
Ok(enums.get(&*val.to_str()?))
});
}
}
type_from_lua_userdata!(Enums);
impl mlua::UserData for EnumItem<'_>{
fn add_fields<F:mlua::UserDataFields<Self>>(fields:&mut F){
fields.add_field_method_get("Name",|_,this|Ok(this.name));
fields.add_field_method_get("Value",|_,this|Ok(this.value));
fn add_fields<F:mlua::UserDataFields<Self>>(_fields:&mut F){
}
fn add_methods<M:mlua::UserDataMethods<Self>>(methods:&mut M){
methods.add_meta_function(mlua::MetaMethod::Eq,|_,(lhs,rhs):(EnumItem<'_>,EnumItem<'_>)|{
Ok(lhs==rhs)
methods.add_meta_function(mlua::MetaMethod::Index,|lua,(this,val):(EnumItem<'_>,mlua::String)|{
match this.ed.items.get(&*val.to_str()?){
Some(&id)=>Enum(id).into_lua(lua),
None=>mlua::Value::Nil.into_lua(lua),
}
});
}
}
type_from_lua_userdata_lua_lifetime!(EnumItem);
impl mlua::UserData for EnumItems{
fn add_fields<F:mlua::UserDataFields<Self>>(_fields:&mut F){
}
fn add_methods<M:mlua::UserDataMethods<Self>>(methods:&mut M){
methods.add_meta_function(mlua::MetaMethod::Index,|lua,(_,val):(Self,mlua::String)|{
let db=rbx_reflection_database::get();
match db.enums.get(&*val.to_str()?){
Some(ed)=>EnumItem::new(ed).into_lua(lua),
None=>mlua::Value::Nil.into_lua(lua),
}
});
}
}
type_from_lua_userdata!(EnumItems);
impl mlua::UserData for Enum{
fn add_fields<F:mlua::UserDataFields<Self>>(_fields:&mut F){
}
fn add_methods<M:mlua::UserDataMethods<Self>>(_methods:&mut M){
}
}
type_from_lua_userdata!(Enum);

View File

@@ -2,48 +2,49 @@ use std::collections::{hash_map::Entry,HashMap};
use mlua::{FromLua,FromLuaMulti,IntoLua,IntoLuaMulti};
use rbx_types::Ref;
use rbx_dom_weak::{Ustr,InstanceBuilder,WeakDom};
use rbx_dom_weak::{InstanceBuilder,WeakDom};
use crate::util::static_ustr;
use crate::runner::number::Number;
use crate::runner::vector3::Vector3;
pub fn set_globals(lua:&mlua::Lua,globals:&mlua::Table)->Result<(),mlua::Error>{
//class functions store
lua.set_app_data(ClassMethodsStore::default());
lua.set_app_data(InstanceValueStore::default());
let table=lua.create_table()?;
let instance_table=lua.create_table()?;
//Instance.new
table.raw_set("new",
instance_table.raw_set("new",
lua.create_function(|lua,(class_name,parent):(mlua::String,Option<Instance>)|{
let class_name_str=&*class_name.to_str()?;
let parent_ref=parent.map_or(Ref::none(),|instance|instance.referent);
let parent=parent.ok_or_else(||mlua::Error::runtime("Nil Parent not yet supported"))?;
dom_mut(lua,|dom|{
Ok(Instance::new_unchecked(dom.insert(parent_ref,InstanceBuilder::new(class_name_str))))
//TODO: Nil instances
Ok(Instance::new(dom.insert(parent.referent,InstanceBuilder::new(class_name_str))))
})
})?
)?;
globals.set("Instance",table)?;
globals.set("Instance",instance_table)?;
Ok(())
}
// LMAO look at this function!
pub fn dom_mut<T>(lua:&mlua::Lua,mut f:impl FnMut(&mut WeakDom)->mlua::Result<T>)->mlua::Result<T>{
let mut dom=lua.app_data_mut::<crate::context::LuaAppData>().ok_or_else(||mlua::Error::runtime("DataModel missing"))?;
let mut dom=lua.app_data_mut::<&'static mut WeakDom>().ok_or_else(||mlua::Error::runtime("DataModel missing"))?;
f(*dom)
}
pub fn class_is_a(class:&str,superclass:&str)->bool{
let db=rbx_reflection_database::get().unwrap();
let (Some(class),Some(superclass))=(db.classes.get(class),db.classes.get(superclass))else{
return false;
};
db.has_superclass(class,superclass)
fn coerce_float32(value:&mlua::Value)->Option<f32>{
match value{
&mlua::Value::Integer(i)=>Some(i as f32),
&mlua::Value::Number(f)=>Some(f as f32),
_=>None,
}
}
fn get_full_name(dom:&WeakDom,instance:&rbx_dom_weak::Instance)->String{
fn get_full_name(dom:&rbx_dom_weak::WeakDom,instance:&rbx_dom_weak::Instance)->String{
let mut full_name=instance.name.clone();
let mut pref=instance.parent();
while let Some(parent)=dom.get_by_ref(pref){
@@ -57,7 +58,7 @@ fn get_full_name(dom:&WeakDom,instance:&rbx_dom_weak::Instance)->String{
pub fn get_name_source(lua:&mlua::Lua,script:Instance)->Result<(String,String),mlua::Error>{
dom_mut(lua,|dom|{
let instance=script.get(dom)?;
let source=match instance.properties.get(&static_ustr("Source")){
let source=match instance.properties.get("Source"){
Some(rbx_dom_weak::types::Variant::String(s))=>s.clone(),
_=>Err(mlua::Error::external("Missing script.Source"))?,
};
@@ -65,46 +66,28 @@ pub fn get_name_source(lua:&mlua::Lua,script:Instance)->Result<(String,String),m
})
}
pub fn find_first_child<'a>(dom:&'a WeakDom,instance:&rbx_dom_weak::Instance,name:&str)->Option<&'a rbx_dom_weak::Instance>{
pub fn find_first_child<'a>(dom:&'a rbx_dom_weak::WeakDom,instance:&rbx_dom_weak::Instance,name:&str)->Option<&'a rbx_dom_weak::Instance>{
instance.children().iter().filter_map(|&r|dom.get_by_ref(r)).find(|inst|inst.name==name)
}
pub fn find_first_descendant<'a>(dom:&'a WeakDom,instance:&rbx_dom_weak::Instance,name:&str)->Option<&'a rbx_dom_weak::Instance>{
pub fn find_first_descendant<'a>(dom:&'a rbx_dom_weak::WeakDom,instance:&rbx_dom_weak::Instance,name:&str)->Option<&'a rbx_dom_weak::Instance>{
dom.descendants_of(instance.referent()).find(|&inst|inst.name==name)
}
pub fn find_first_child_of_class<'a>(dom:&'a WeakDom,instance:&rbx_dom_weak::Instance,class:&str)->Option<&'a rbx_dom_weak::Instance>{
pub fn find_first_child_of_class<'a>(dom:&'a rbx_dom_weak::WeakDom,instance:&rbx_dom_weak::Instance,class:&str)->Option<&'a rbx_dom_weak::Instance>{
instance.children().iter().filter_map(|&r|dom.get_by_ref(r)).find(|inst|inst.class==class)
}
pub fn find_first_descendant_of_class<'a>(dom:&'a WeakDom,instance:&rbx_dom_weak::Instance,class:&str)->Option<&'a rbx_dom_weak::Instance>{
pub fn find_first_descendant_of_class<'a>(dom:&'a rbx_dom_weak::WeakDom,instance:&rbx_dom_weak::Instance,class:&str)->Option<&'a rbx_dom_weak::Instance>{
dom.descendants_of(instance.referent()).find(|&inst|inst.class==class)
}
pub fn find_first_child_which_is_a<'a>(dom:&'a WeakDom,instance:&rbx_dom_weak::Instance,superclass:&str)->Option<&'a rbx_dom_weak::Instance>{
let db=rbx_reflection_database::get().unwrap();
let superclass_descriptor=db.classes.get(superclass)?;
instance.children().iter().filter_map(|&r|dom.get_by_ref(r)).find(|inst|{
db.classes.get(inst.class.as_str()).is_some_and(|descriptor|db.has_superclass(descriptor,superclass_descriptor))
})
}
pub fn find_first_descendant_which_is_a<'a>(dom:&'a WeakDom,instance:&rbx_dom_weak::Instance,superclass:&str)->Option<&'a rbx_dom_weak::Instance>{
let db=rbx_reflection_database::get().unwrap();
let superclass_descriptor=db.classes.get(superclass)?;
dom.descendants_of(instance.referent()).find(|inst|{
db.classes.get(inst.class.as_str()).is_some_and(|descriptor|db.has_superclass(descriptor,superclass_descriptor))
})
}
#[derive(Clone,Copy)]
pub struct Instance{
referent:Ref,
}
impl Instance{
pub const fn new_unchecked(referent:Ref)->Self{
pub const fn new(referent:Ref)->Self{
Self{referent}
}
pub fn new(referent:Ref)->Option<Self>{
referent.is_some().then_some(Self{referent})
}
pub fn get<'a>(&self,dom:&'a WeakDom)->mlua::Result<&'a rbx_dom_weak::Instance>{
dom.get_by_ref(self.referent).ok_or_else(||mlua::Error::runtime("Instance missing"))
}
@@ -114,25 +97,40 @@ impl Instance{
}
type_from_lua_userdata!(Instance);
//TODO: update rbx_reflection and use dom.superclasses_iter
pub struct SuperClassIter<'a> {
database: &'a rbx_reflection::ReflectionDatabase<'a>,
descriptor: Option<&'a rbx_reflection::ClassDescriptor<'a>>,
}
impl<'a> SuperClassIter<'a> {
fn next_descriptor(&self) -> Option<&'a rbx_reflection::ClassDescriptor<'a>> {
let superclass = self.descriptor?.superclass.as_ref()?;
self.database.classes.get(superclass)
}
}
impl<'a> Iterator for SuperClassIter<'a> {
type Item = &'a rbx_reflection::ClassDescriptor<'a>;
fn next(&mut self) -> Option<Self::Item> {
let next_descriptor = self.next_descriptor();
std::mem::replace(&mut self.descriptor, next_descriptor)
}
}
impl mlua::UserData for Instance{
fn add_fields<F:mlua::UserDataFields<Self>>(fields:&mut F){
fn get_parent(lua:&mlua::Lua,this:&Instance)->mlua::Result<Option<Instance>>{
fields.add_field_method_get("Parent",|lua,this|{
dom_mut(lua,|dom|{
let instance=this.get(dom)?;
Ok(Instance::new(instance.parent()))
})
}
fields.add_field_method_get("parent",get_parent);
fields.add_field_method_get("Parent",get_parent);
fn set_parent(lua:&mlua::Lua,this:&mut Instance,new_parent:Option<Instance>)->mlua::Result<()>{
let parent_ref=new_parent.map_or(Ref::none(),|instance|instance.referent);
});
fields.add_field_method_set("Parent",|lua,this,val:Option<Instance>|{
let parent=val.ok_or_else(||mlua::Error::runtime("Nil Parent not yet supported"))?;
dom_mut(lua,|dom|{
dom.transfer_within(this.referent,parent_ref);
dom.transfer_within(this.referent,parent.referent);
Ok(())
})
}
fields.add_field_method_set("parent",set_parent);
fields.add_field_method_set("Parent",set_parent);
});
fields.add_field_method_get("Name",|lua,this|{
dom_mut(lua,|dom|{
let instance=this.get(dom)?;
@@ -150,38 +148,22 @@ impl mlua::UserData for Instance{
fields.add_field_method_get("ClassName",|lua,this|{
dom_mut(lua,|dom|{
let instance=this.get(dom)?;
Ok(instance.class.to_owned())
Ok(instance.class.clone())
})
});
}
fn add_methods<M:mlua::UserDataMethods<Self>>(methods:&mut M){
fn clone(lua:&mlua::Lua,this:&Instance,_:())->mlua::Result<Instance>{
dom_mut(lua,|dom|{
let instance_ref=dom.clone_within(this.referent);
Ok(Instance::new_unchecked(instance_ref))
})
}
methods.add_method("clone",clone);
methods.add_method("Clone",clone);
fn get_children(lua:&mlua::Lua,this:&Instance,_:())->mlua::Result<Vec<Instance>>{
methods.add_method("GetChildren",|lua,this,_:()|
dom_mut(lua,|dom|{
let instance=this.get(dom)?;
let children:Vec<_>=instance
.children()
.iter()
.copied()
.map(Instance::new_unchecked)
.map(Instance::new)
.collect();
Ok(children)
})
}
methods.add_method("children",get_children);
methods.add_method("GetChildren",get_children);
methods.add_method("GetFullName",|lua,this,()|
dom_mut(lua,|dom|{
let instance=this.get(dom)?;
Ok(get_full_name(dom,instance))
})
);
fn ffc(lua:&mlua::Lua,this:&Instance,(name,search_descendants):(mlua::String,Option<bool>))->mlua::Result<Option<Instance>>{
let name_str=&*name.to_str()?;
@@ -193,7 +175,7 @@ impl mlua::UserData for Instance{
false=>find_first_child(dom,instance,name_str),
}
.map(|instance|
Instance::new_unchecked(instance.referent())
Instance::new(instance.referent())
)
)
})
@@ -203,29 +185,13 @@ impl mlua::UserData for Instance{
methods.add_method("FindFirstChildOfClass",|lua,this,(class,search_descendants):(mlua::String,Option<bool>)|{
let class_str=&*class.to_str()?;
dom_mut(lua,|dom|{
let inst=this.get(dom)?;
Ok(
match search_descendants.unwrap_or(false){
true=>find_first_descendant_of_class(dom,inst,class_str),
false=>find_first_child_of_class(dom,inst,class_str),
true=>find_first_descendant_of_class(dom,this.get(dom)?,class_str),
false=>find_first_child_of_class(dom,this.get(dom)?,class_str),
}
.map(|instance|
Instance::new_unchecked(instance.referent())
)
)
})
});
methods.add_method("FindFirstChildWhichIsA",|lua,this,(class,search_descendants):(mlua::String,Option<bool>)|{
let class_str=&*class.to_str()?;
dom_mut(lua,|dom|{
let inst=this.get(dom)?;
Ok(
match search_descendants.unwrap_or(false){
true=>find_first_descendant_which_is_a(dom,inst,class_str),
false=>find_first_child_which_is_a(dom,inst,class_str),
}
.map(|instance|
Instance::new_unchecked(instance.referent())
Instance::new(instance.referent())
)
)
})
@@ -235,42 +201,24 @@ impl mlua::UserData for Instance{
let children:Vec<_>=dom
.descendants_of(this.referent)
.map(|instance|
Instance::new_unchecked(instance.referent())
Instance::new(instance.referent())
)
.collect();
Ok(children)
})
);
methods.add_method("IsAncestorOf",|lua,this,descendant:Instance|
dom_mut(lua,|dom|{
let instance=descendant.get(dom)?;
Ok(std::iter::successors(Some(instance),|inst|dom.get_by_ref(inst.parent())).any(|inst|inst.referent()==this.referent))
})
);
methods.add_method("IsDescendantOf",|lua,this,ancestor:Instance|
methods.add_method("IsA",|lua,this,classname:mlua::String|
dom_mut(lua,|dom|{
let instance=this.get(dom)?;
Ok(std::iter::successors(Some(instance),|inst|dom.get_by_ref(inst.parent())).any(|inst|inst.referent()==ancestor.referent))
Ok(crate::context::class_is_a(instance.class.as_str(),&*classname.to_str()?))
})
);
fn is_a(lua:&mlua::Lua,this:&Instance,classname:mlua::String)->mlua::Result<bool>{
methods.add_method("Destroy",|lua,this,()|
dom_mut(lua,|dom|{
let instance=this.get(dom)?;
Ok(class_is_a(instance.class.as_str(),&*classname.to_str()?))
})
}
methods.add_method("isA",is_a);
methods.add_method("IsA",is_a);
fn destroy(lua:&mlua::Lua,this:&Instance,_:())->mlua::Result<()>{
dom_mut(lua,|dom|{
dom.transfer_within(this.referent,Ref::none());
dom.destroy(this.referent);
Ok(())
})
}
methods.add_method("remove",destroy);
methods.add_method("Remove",destroy);
methods.add_method("destroy",destroy);
methods.add_method("Destroy",destroy);
);
methods.add_meta_function(mlua::MetaMethod::ToString,|lua,this:Instance|{
dom_mut(lua,|dom|{
let instance=this.get(dom)?;
@@ -282,40 +230,41 @@ impl mlua::UserData for Instance{
dom_mut(lua,|dom|{
let instance=this.get(dom)?;
//println!("__index t={} i={index:?}",instance.name);
let db=rbx_reflection_database::get().unwrap();
let db=rbx_reflection_database::get();
let class=db.classes.get(instance.class.as_str()).ok_or_else(||mlua::Error::runtime("Class missing"))?;
// Find existing property
// Interestingly, ustr can know ahead of time if
// a property does not exist in any runtime instance
match Ustr::from_existing(index_str)
.and_then(|index_ustr|
instance.properties.get(&index_ustr).cloned()
)
//Find existing property
match instance.properties.get(index_str)
.cloned()
//Find default value
.or_else(||db.find_default_property(class,index_str).cloned())
//Find virtual property
.or_else(||db.superclasses_iter(class).find_map(|class|
find_virtual_property(&instance.properties,class,index_str)
))
.or_else(||{
SuperClassIter{
database:db,
descriptor:Some(class),
}
.find_map(|class|
find_virtual_property(&instance.properties,class,index_str)
)
})
{
Some(rbx_types::Variant::Bool(val))=>return val.into_lua(lua),
Some(rbx_types::Variant::Int32(val))=>return val.into_lua(lua),
Some(rbx_types::Variant::Int64(val))=>return val.into_lua(lua),
Some(rbx_types::Variant::Float32(val))=>return val.into_lua(lua),
Some(rbx_types::Variant::Float64(val))=>return val.into_lua(lua),
Some(rbx_types::Variant::String(val))=>return val.into_lua(lua),
Some(rbx_types::Variant::Ref(val))=>return Instance::new_unchecked(val).into_lua(lua),
Some(rbx_types::Variant::Enum(e))=>return crate::runner::r#enum::EnumItem::from(e).into_lua(lua),
Some(rbx_types::Variant::Color3(c))=>return crate::runner::color3::Color3::from(c).into_lua(lua),
Some(rbx_types::Variant::CFrame(cf))=>return crate::runner::cframe::CFrame::from(cf).into_lua(lua),
Some(rbx_types::Variant::Vector2(v))=>return crate::runner::vector2::Vector2::from(v).into_lua(lua),
Some(rbx_types::Variant::Vector3(v))=>return crate::runner::vector3::Vector3::from(v).into_lua(lua),
Some(rbx_types::Variant::Ref(val))=>return Instance::new(val).into_lua(lua),
Some(rbx_types::Variant::CFrame(cf))=>return Into::<crate::runner::cframe::CFrame>::into(cf).into_lua(lua),
Some(rbx_types::Variant::Vector3(v))=>return Into::<crate::runner::vector3::Vector3>::into(v).into_lua(lua),
None=>(),
other=>return Err(mlua::Error::runtime(format!("Instance.__index Unsupported property type instance={} index={index_str} value={other:?}",instance.name))),
}
//find a function with a matching name
if let Some(function)=class_methods_store_mut(lua,|cf|{
db.superclasses_iter(class).find_map(|class|{
let mut iter=SuperClassIter{
database:db,
descriptor:Some(class),
};
iter.find_map(|class|{
let mut class_methods=cf.get_or_create_class_methods(&class.name)?;
class_methods.get_or_create_function(lua,index_str)
.transpose()
@@ -336,106 +285,69 @@ impl mlua::UserData for Instance{
}
//find a child with a matching name
find_first_child(dom,instance,index_str)
.map(|instance|Instance::new_unchecked(instance.referent()))
.map(|instance|Instance::new(instance.referent()))
.into_lua(lua)
})
});
methods.add_meta_function(mlua::MetaMethod::NewIndex,|lua,(this,index,value):(Instance,mlua::String,mlua::Value)|{
let index_str=&*index.to_str()?;
dom_mut(lua,|dom|{
let instance=this.get_mut(dom)?;
let db=rbx_reflection_database::get().unwrap();
//println!("__newindex t={} i={index:?} v={value:?}",instance.name);
let index_str=&*index.to_str()?;
let db=rbx_reflection_database::get();
let class=db.classes.get(instance.class.as_str()).ok_or_else(||mlua::Error::runtime("Class missing"))?;
let property=db.superclasses_iter(class).find_map(|cls|
cls.properties.get(index_str)
).ok_or_else(||
mlua::Error::runtime(format!("Property '{index_str}' missing on class '{}'",class.name))
)?;
let value=match &property.data_type{
let mut iter=SuperClassIter{
database:db,
descriptor:Some(class),
};
let property=iter.find_map(|cls|cls.properties.get(index_str)).ok_or_else(||mlua::Error::runtime(format!("Property '{index_str}' missing on class '{}'",class.name)))?;
match &property.data_type{
rbx_reflection::DataType::Value(rbx_types::VariantType::Vector3)=>{
let typed_value:Vector3=*value.as_userdata().ok_or_else(||mlua::Error::runtime("Expected Userdata"))?.borrow()?;
instance.properties.insert(index_str.to_owned(),rbx_types::Variant::Vector3(typed_value.into()));
},
rbx_reflection::DataType::Value(rbx_types::VariantType::Float32)=>{
let typed_value=Number::from_lua(value.clone(),lua)?.to_f32();
rbx_types::Variant::Float32(typed_value)
let typed_value:f32=coerce_float32(&value).ok_or_else(||mlua::Error::runtime("Expected f32"))?;
instance.properties.insert(index_str.to_owned(),rbx_types::Variant::Float32(typed_value));
},
rbx_reflection::DataType::Enum(enum_name)=>{
let typed_value=match &value{
&mlua::Value::Integer(int)=>Ok(rbx_types::Enum::from_u32(int as u32)),
&mlua::Value::Number(num)=>Ok(rbx_types::Enum::from_u32(num as u32)),
mlua::Value::String(s)=>{
let e=db.enums.get(enum_name).ok_or_else(||mlua::Error::runtime("Database DataType Enum name does not exist"))?;
let e=db.enums.get(enum_name).ok_or_else(||mlua::Error::runtime("Database DataType Enum name does not exist"))?;
Ok(rbx_types::Enum::from_u32(*e.items.get(&*s.to_str()?).ok_or_else(||mlua::Error::runtime("Invalid enum item"))?))
},
mlua::Value::UserData(any_user_data)=>{
let e:crate::runner::r#enum::EnumItem=*any_user_data.borrow()?;
let e:crate::runner::r#enum::Enum=*any_user_data.borrow()?;
Ok(e.into())
},
_=>Err(mlua::Error::runtime("Expected Enum")),
}?;
rbx_types::Variant::Enum(typed_value)
instance.properties.insert(index_str.to_owned(),rbx_types::Variant::Enum(typed_value));
},
rbx_reflection::DataType::Value(rbx_types::VariantType::Color3)=>{
let typed_value:crate::runner::color3::Color3=*value.as_userdata().ok_or_else(||mlua::Error::runtime("Expected Color3"))?.borrow()?;
rbx_types::Variant::Color3(typed_value.into())
instance.properties.insert(index_str.to_owned(),rbx_types::Variant::Color3(typed_value.into()));
},
rbx_reflection::DataType::Value(rbx_types::VariantType::Bool)=>{
let typed_value=value.as_boolean().ok_or_else(||mlua::Error::runtime("Expected boolean"))?;
rbx_types::Variant::Bool(typed_value)
},
rbx_reflection::DataType::Value(rbx_types::VariantType::Int32)=>{
let typed_value=value.as_i32().ok_or_else(||mlua::Error::runtime("Expected Int32"))?;
rbx_types::Variant::Int32(typed_value)
instance.properties.insert(index_str.to_owned(),rbx_types::Variant::Bool(typed_value));
},
rbx_reflection::DataType::Value(rbx_types::VariantType::String)=>{
let typed_value=match &value{
mlua::Value::Integer(i)=>i.to_string(),
mlua::Value::Number(n)=>n.to_string(),
mlua::Value::String(s)=>s.to_str()?.to_owned(),
_=>return Err(mlua::Error::runtime("Expected string")),
};
rbx_types::Variant::String(typed_value)
},
rbx_reflection::DataType::Value(rbx_types::VariantType::UDim2)=>{
let typed_value:&crate::runner::udim2::UDim2=&*value.as_userdata().ok_or_else(||mlua::Error::runtime("Expected UDim2"))?.borrow()?;
rbx_types::Variant::UDim2(typed_value.clone().into())
},
rbx_reflection::DataType::Value(rbx_types::VariantType::NumberRange)=>{
let typed_value:&crate::runner::number_range::NumberRange=&*value.as_userdata().ok_or_else(||mlua::Error::runtime("Expected NumberRange"))?.borrow()?;
rbx_types::Variant::NumberRange(typed_value.clone().into())
let typed_value=value.as_str().ok_or_else(||mlua::Error::runtime("Expected boolean"))?;
instance.properties.insert(index_str.to_owned(),rbx_types::Variant::String(typed_value.to_owned()));
},
rbx_reflection::DataType::Value(rbx_types::VariantType::NumberSequence)=>{
let typed_value:&crate::runner::number_sequence::NumberSequence=&*value.as_userdata().ok_or_else(||mlua::Error::runtime("Expected NumberSequence"))?.borrow()?;
rbx_types::Variant::NumberSequence(typed_value.clone().into())
let typed_value:crate::runner::number_sequence::NumberSequence=*value.as_userdata().ok_or_else(||mlua::Error::runtime("Expected NumberSequence"))?.borrow()?;
instance.properties.insert(index_str.to_owned(),rbx_types::Variant::NumberSequence(typed_value.into()));
},
rbx_reflection::DataType::Value(rbx_types::VariantType::ColorSequence)=>{
let typed_value:&crate::runner::color_sequence::ColorSequence=&*value.as_userdata().ok_or_else(||mlua::Error::runtime("Expected ColorSequence"))?.borrow()?;
rbx_types::Variant::ColorSequence(typed_value.clone().into())
},
rbx_reflection::DataType::Value(rbx_types::VariantType::Vector2)=>{
let typed_value:crate::runner::vector2::Vector2=*value.as_userdata().ok_or_else(||mlua::Error::runtime("Expected Vector2"))?.borrow()?;
rbx_types::Variant::Vector2(typed_value.clone().into())
},
rbx_reflection::DataType::Value(rbx_types::VariantType::Vector3)=>{
let typed_value:crate::runner::vector3::Vector3=*value.as_userdata().ok_or_else(||mlua::Error::runtime("Expected Vector3"))?.borrow()?;
rbx_types::Variant::Vector3(typed_value.clone().into())
},
rbx_reflection::DataType::Value(rbx_types::VariantType::CFrame)=>{
let typed_value:crate::runner::cframe::CFrame=*value.as_userdata().ok_or_else(||mlua::Error::runtime("Expected CFrame"))?.borrow()?;
rbx_types::Variant::CFrame(typed_value.clone().into())
},
rbx_reflection::DataType::Value(rbx_types::VariantType::ContentId)=>{
let typed_value=value.as_string().ok_or_else(||mlua::Error::runtime("Expected string"))?.to_str()?.to_owned();
rbx_types::Variant::ContentId(typed_value.into())
},
rbx_reflection::DataType::Value(rbx_types::VariantType::Ref)=>{
// why clone?
let typed_value=Option::<Instance>::from_lua(value.clone(),lua)?;
rbx_types::Variant::Ref(typed_value.map_or(Ref::none(),|instance|instance.referent))
let typed_value:crate::runner::color_sequence::ColorSequence=*value.as_userdata().ok_or_else(||mlua::Error::runtime("Expected ColorSequence"))?.borrow()?;
instance.properties.insert(index_str.to_owned(),rbx_types::Variant::ColorSequence(typed_value.into()));
},
other=>return Err(mlua::Error::runtime(format!("Unimplemented property type: {other:?}"))),
};
// the index is known to be a real property at this point
// allow creating a permanent ustr (memory leak)
let index_ustr=rbx_dom_weak::ustr(index_str);
instance.properties.insert(index_ustr,value);
}
Ok(())
})
});
@@ -460,53 +372,28 @@ type CFD=phf::Map<&'static str,// Class name
ClassFunctionPointer
>
>;
const GET_SERVICE:ClassFunctionPointer=cf!(|lua,_this,service:mlua::String|{
dom_mut(lua,|dom|{
//dom.root_ref()==this.referent ?
let service=&*service.to_str()?;
match service{
"Lighting"|"RunService"|"Players"|"Workspace"|"MaterialService"|"TweenService"=>{
let referent=find_first_child_of_class(dom,dom.root(),service)
.map(|instance|instance.referent())
.unwrap_or_else(||
dom.insert(dom.root_ref(),InstanceBuilder::new(service))
);
Ok(Instance::new_unchecked(referent))
},
other=>Err(mlua::Error::runtime(format!("Service '{other}' not supported"))),
}
})
});
const GET_PLAYERS:ClassFunctionPointer=cf!(|_lua,_this,()|->mlua::Result<_>{
Ok(Vec::<Instance>::new())
});
const NO_OP:ClassFunctionPointer=cf!(|_lua,_this,_:mlua::MultiValue|->mlua::Result<_>{Ok(())});
static CLASS_FUNCTION_DATABASE:CFD=phf::phf_map!{
"DataModel"=>phf::phf_map!{
"service"=>GET_SERVICE,
"GetService"=>GET_SERVICE,
"GetService"=>cf!(|lua,_this,service:mlua::String|{
dom_mut(lua,|dom|{
//dom.root_ref()==this.referent ?
let service=&*service.to_str()?;
match service{
"Lighting"|"RunService"=>{
let referent=find_first_child_of_class(dom,dom.root(),service)
.map(|instance|instance.referent())
.unwrap_or_else(||
dom.insert(dom.root_ref(),InstanceBuilder::new(service))
);
Ok(Instance::new(referent))
},
other=>Err::<Instance,_>(mlua::Error::runtime(format!("Service '{other}' not supported"))),
}
})
}),
},
"Terrain"=>phf::phf_map!{
"FillBall"=>cf!(|_lua,_,_:(crate::runner::vector3::Vector3,Number,crate::runner::r#enum::CoerceEnum)|mlua::Result::Ok(())),
"FillBlock"=>cf!(|_lua,_,_:(crate::runner::cframe::CFrame,crate::runner::vector3::Vector3,crate::runner::r#enum::CoerceEnum)|mlua::Result::Ok(())),
"FillCylinder"=>cf!(|_lua,_,_:(crate::runner::cframe::CFrame,Number,Number,crate::runner::r#enum::CoerceEnum)|mlua::Result::Ok(())),
"SetMaterialColor"=>cf!(|_lua,_,_:(crate::runner::r#enum::CoerceEnum,crate::runner::color3::Color3)|mlua::Result::Ok(())),
},
"Players"=>phf::phf_map!{
"players"=>GET_PLAYERS,
"GetPlayers"=>GET_PLAYERS,
},
"Sound"=>phf::phf_map!{
"Play"=>NO_OP,
},
"TweenService"=>phf::phf_map!{
"Create"=>cf!(|_lua,_,(instance,tween_info,goal):(Instance,crate::runner::tween_info::TweenInfo,mlua::Table)|->mlua::Result<_>{
Ok(crate::runner::tween::Tween::create(
instance,
tween_info,
goal,
))
}),
"FillBlock"=>cf!(|_lua,_,_:(crate::runner::cframe::CFrame,Vector3,crate::runner::r#enum::Enum)|mlua::Result::Ok(()))
},
};
@@ -522,7 +409,7 @@ struct ClassMethodsStore{
}
impl ClassMethodsStore{
/// return self.classes[class] or create the ClassMethods and then return it
fn get_or_create_class_methods(&mut self,class:&str)->Option<ClassMethods<'_>>{
fn get_or_create_class_methods(&mut self,class:&str)->Option<ClassMethods>{
// Use get_entry to get the &'static str keys of the database
// and use it as a key for the classes hashmap
CLASS_FUNCTION_DATABASE.get_entry(class)
@@ -593,16 +480,16 @@ static VIRTUAL_PROPERTY_DATABASE:VPD=phf::phf_map!{
};
fn find_virtual_property(
properties:&rbx_dom_weak::UstrMap<rbx_types::Variant>,
properties:&HashMap<String,rbx_types::Variant>,
class:&rbx_reflection::ClassDescriptor,
index:&str,
index:&str
)->Option<rbx_types::Variant>{
//Find virtual property
let class_virtual_properties=VIRTUAL_PROPERTY_DATABASE.get(&class.name)?;
let virtual_property=class_virtual_properties.get(index)?;
//Get source property
let variant=properties.get(&static_ustr(virtual_property.property))?;
let variant=properties.get(virtual_property.property)?;
//Transform Source property with provided function
(virtual_property.pointer)(variant)
@@ -617,30 +504,11 @@ type LUD=phf::Map<&'static str,// Class name
CreateUserData
>
>;
fn create_script_signal(lua:&mlua::Lua)->mlua::Result<mlua::AnyUserData>{
lua.create_any_userdata(crate::runner::script_signal::ScriptSignal::new())
}
static LAZY_USER_DATA:LUD=phf::phf_map!{
"RunService"=>phf::phf_map!{
"Stepped"=>create_script_signal,
"Heartbeat"=>create_script_signal,
"RenderStepped"=>create_script_signal,
},
"Players"=>phf::phf_map!{
"PlayerAdded"=>create_script_signal,
},
"BasePart"=>phf::phf_map!{
"Touched"=>create_script_signal,
"TouchEnded"=>create_script_signal,
},
"Instance"=>phf::phf_map!{
"ChildAdded"=>create_script_signal,
"ChildRemoved"=>create_script_signal,
"DescendantAdded"=>create_script_signal,
"DescendantRemoved"=>create_script_signal,
},
"ClickDetector"=>phf::phf_map!{
"MouseClick"=>create_script_signal,
"RenderStepped"=>|lua|{
lua.create_any_userdata(crate::runner::script_signal::ScriptSignal::new())
},
},
};
#[derive(Default)]
@@ -656,7 +524,7 @@ pub struct InstanceValues<'a>{
values:&'a mut HashMap<&'static str,mlua::AnyUserData>,
}
impl InstanceValueStore{
pub fn get_or_create_instance_values(&mut self,instance:&rbx_dom_weak::Instance)->Option<InstanceValues<'_>>{
pub fn get_or_create_instance_values(&mut self,instance:&rbx_dom_weak::Instance)->Option<InstanceValues>{
LAZY_USER_DATA.get(instance.class.as_str())
.map(|named_values|
InstanceValues{

View File

@@ -10,18 +10,6 @@ macro_rules! type_from_lua_userdata{
}
};
}
macro_rules! type_from_lua_userdata_clone{
($ty:ident)=>{
impl mlua::FromLua for $ty{
fn from_lua(value:mlua::Value,_lua:&mlua::Lua)->Result<Self,mlua::Error>{
match value{
mlua::Value::UserData(ud)=>Ok(ud.borrow::<Self>()?.clone()),
other=>Err(mlua::Error::runtime(format!("Expected {} got {:?}",stringify!($ty),other))),
}
}
}
};
}
macro_rules! type_from_lua_userdata_lua_lifetime{
($ty:ident)=>{
impl mlua::FromLua for $ty<'static>{

View File

@@ -3,19 +3,10 @@ mod macros;
mod runner;
mod r#enum;
mod task;
mod udim;
mod tween;
mod udim2;
mod color3;
mod cframe;
mod number;
mod vector2;
mod vector3;
mod brickcolor;
mod tween_info;
pub mod instance;
mod number_range;
mod script_signal;
mod color_sequence;
mod number_sequence;

View File

@@ -1,43 +0,0 @@
// the goal of this module is to provide an intermediate type
// that is guaranteed to be some kind of number, and provide
// methods to coerce it into various more specific types.
#[derive(Clone,Copy)]
pub enum Number{
Integer(i64),
Number(f64),
}
macro_rules! impl_ty{
($ident:ident,$ty:ty)=>{
impl Number{
#[inline]
pub fn $ident(self)->$ty{
match self{
Self::Integer(int)=>int as $ty,
Self::Number(num)=>num as $ty,
}
}
}
impl From<Number> for $ty{
fn from(value:Number)->$ty{
value.$ident()
}
}
};
}
impl_ty!(to_u32,u32);
impl_ty!(to_i32,i32);
impl_ty!(to_f32,f32);
impl_ty!(to_u64,u64);
impl_ty!(to_i64,i64);
impl_ty!(to_f64,f64);
impl mlua::FromLua for Number{
fn from_lua(value:mlua::Value,_lua:&mlua::Lua)->Result<Self,mlua::Error>{
match value{
mlua::Value::Integer(int)=>Ok(Number::Integer(int)),
mlua::Value::Number(num)=>Ok(Number::Number(num)),
other=>Err(mlua::Error::runtime(format!("Expected {} got {:?}",stringify!(Number),other))),
}
}
}

View File

@@ -1,34 +0,0 @@
use super::number::Number;
#[derive(Clone)]
pub struct NumberRange(rbx_types::NumberRange);
impl NumberRange{
pub const fn new(min:f32,max:f32)->Self{
Self(rbx_types::NumberRange{min,max})
}
}
impl From<NumberRange> for rbx_types::NumberRange{
fn from(NumberRange(value):NumberRange)->rbx_types::NumberRange{
value
}
}
pub fn set_globals(lua:&mlua::Lua,globals:&mlua::Table)->Result<(),mlua::Error>{
let table=lua.create_table()?;
table.raw_set("new",
lua.create_function(|_,(min,max):(Number,Option<Number>)|{
Ok(match max{
Some(max)=>NumberRange::new(min.into(),max.into()),
None=>NumberRange::new(min.into(),min.into()),
})
})?
)?;
globals.set("NumberRange",table)?;
Ok(())
}
impl mlua::UserData for NumberRange{}
type_from_lua_userdata_clone!(NumberRange);

View File

@@ -1,36 +1,31 @@
#[derive(Clone)]
pub struct NumberSequence(rbx_types::NumberSequence);
#[derive(Clone,Copy)]
pub struct NumberSequence{}
impl NumberSequence{
pub const fn new(keypoints:Vec<rbx_types::NumberSequenceKeypoint>)->Self{
Self(rbx_types::NumberSequence{keypoints})
pub const fn new()->Self{
Self{}
}
}
impl From<NumberSequence> for rbx_types::NumberSequence{
fn from(NumberSequence(value):NumberSequence)->rbx_types::NumberSequence{
value
impl Into<rbx_types::NumberSequence> for NumberSequence{
fn into(self)->rbx_types::NumberSequence{
rbx_types::NumberSequence{
keypoints:Vec::new()
}
}
}
pub fn set_globals(lua:&mlua::Lua,globals:&mlua::Table)->Result<(),mlua::Error>{
let table=lua.create_table()?;
let number_sequence_table=lua.create_table()?;
table.raw_set("new",
number_sequence_table.raw_set("new",
lua.create_function(|_,_:mlua::MultiValue|
Ok(NumberSequence::new(Vec::new()))
Ok(NumberSequence::new())
)?
)?;
globals.set("NumberSequence",table)?;
globals.set("NumberSequence",number_sequence_table)?;
Ok(())
}
impl mlua::UserData for NumberSequence{}
impl mlua::FromLua for NumberSequence{
fn from_lua(value:mlua::Value,_lua:&mlua::Lua)->Result<Self,mlua::Error>{
match value{
mlua::Value::UserData(ud)=>Ok(ud.borrow::<Self>()?.clone()),
other=>Err(mlua::Error::runtime(format!("Expected {} got {:?}",stringify!(NumberSequence),other))),
}
}
}
type_from_lua_userdata!(NumberSequence);

View File

@@ -12,12 +12,14 @@ pub enum Error{
error:mlua::Error
},
RustLua(mlua::Error),
NoServices,
}
impl std::fmt::Display for Error{
fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{
match self{
Self::Lua{source,error}=>write!(f,"lua error: source:\n{source}\n{error}"),
Self::RustLua(error)=>write!(f,"rust-side lua error: {error}"),
other=>write!(f,"{other:?}"),
}
}
}
@@ -29,30 +31,20 @@ fn init(lua:&mlua::Lua)->mlua::Result<()>{
//global environment
let globals=lua.globals();
super::task::set_globals(lua,&globals)?;
#[cfg(feature="run-service")]
crate::scheduler::set_globals(lua,&globals)?;
super::script_signal::set_globals(lua,&globals)?;
super::r#enum::set_globals(lua,&globals)?;
super::udim::set_globals(lua,&globals)?;
super::udim2::set_globals(lua,&globals)?;
super::color3::set_globals(lua,&globals)?;
super::brickcolor::set_globals(lua,&globals)?;
super::vector2::set_globals(lua,&globals)?;
super::vector3::set_globals(lua,&globals)?;
super::cframe::set_globals(lua,&globals)?;
super::instance::instance::set_globals(lua,&globals)?;
super::tween_info::set_globals(lua,&globals)?;
super::number_range::set_globals(lua,&globals)?;
super::number_sequence::set_globals(lua,&globals)?;
super::color_sequence::set_globals(lua,&globals)?;
Ok(())
}
unsafe fn extend_lifetime_mut<'a,T>(src:&mut T)->&'a mut T{
let ptr:*mut T=src;
unsafe{&mut*ptr}
}
impl Runner{
pub fn new()->Result<Self,Error>{
let runner=Self{
@@ -62,20 +54,22 @@ impl Runner{
Ok(runner)
}
pub fn runnable_context<'a>(self,context:&'a mut Context)->Result<Runnable<'a>,Error>{
let services=context.find_services().ok_or(Error::NoServices)?;
self.runnable_context_with_services(context,&services)
}
pub fn runnable_context_with_services<'a>(self,context:&'a mut Context,services:&crate::context::Services)->Result<Runnable<'a>,Error>{
{
let globals=self.lua.globals();
globals.set("game",super::instance::Instance::new_unchecked(context.services.game)).map_err(Error::RustLua)?;
globals.set("workspace",super::instance::Instance::new_unchecked(context.services.workspace)).map_err(Error::RustLua)?;
globals.set("game",super::instance::Instance::new(services.game)).map_err(Error::RustLua)?;
globals.set("workspace",super::instance::Instance::new(services.workspace)).map_err(Error::RustLua)?;
}
// SAFETY: This is not a &'static mut WeakDom,
// but as long as Runnable<'a> holds the lifetime of &'a mut Context
// it is a valid unique reference.
self.lua.set_app_data::<crate::context::LuaAppData>(unsafe{extend_lifetime_mut(&mut context.dom)});
//this makes set_app_data shut up about the lifetime
self.lua.set_app_data::<&'static mut rbx_dom_weak::WeakDom>(unsafe{core::mem::transmute(&mut context.dom)});
#[cfg(feature="run-service")]
self.lua.set_app_data::<crate::scheduler::Scheduler>(crate::scheduler::Scheduler::default());
Ok(Runnable{
lua:self.lua,
_lifetime:std::marker::PhantomData
_lifetime:&std::marker::PhantomData
})
}
}
@@ -83,11 +77,11 @@ impl Runner{
//Runnable is the same thing but has context set, which it holds the lifetime for.
pub struct Runnable<'a>{
lua:mlua::Lua,
_lifetime:std::marker::PhantomData<&'a ()>
_lifetime:&'a std::marker::PhantomData<()>
}
impl Runnable<'_>{
pub fn drop_context(self)->Runner{
self.lua.remove_app_data::<crate::context::LuaAppData>();
self.lua.remove_app_data::<&'static mut rbx_dom_weak::WeakDom>();
#[cfg(feature="run-service")]
self.lua.remove_app_data::<crate::scheduler::Scheduler>();
Runner{
@@ -101,19 +95,7 @@ impl Runnable<'_>{
.set_name(name).into_function().map_err(Error::RustLua)?;
// TODO: set_environment without losing the ability to print from Lua
let thread=self.lua.create_thread(f).map_err(Error::RustLua)?;
// 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})?;
self.lua.remove_interrupt();
// wait() is called from inside Lua and goes to a rust function that schedules the thread and then yields
// No need to schedule the thread here
Ok(())

View File

@@ -98,9 +98,12 @@ impl ScriptConnection{
impl mlua::UserData for ScriptSignal{
fn add_methods<M:mlua::UserDataMethods<Self>>(methods:&mut M){
methods.add_method("connect",|_lua,this,f:mlua::Function|Ok(this.connect(f)));
methods.add_method("Connect",|_lua,this,f:mlua::Function|Ok(this.connect(f)));
methods.add_method("Once",|_lua,this,f:mlua::Function|Ok(this.once(f)));
methods.add_method("Connect",|_lua,this,f:mlua::Function|
Ok(this.connect(f))
);
methods.add_method("Once",|_lua,this,f:mlua::Function|
Ok(this.once(f))
);
// Fire is not allowed to be called from Lua
// methods.add_method("Fire",|_lua,this,args:mlua::MultiValue|
// Ok(this.fire(args))
@@ -117,7 +120,7 @@ impl mlua::FromLua for ScriptSignal{
}
impl mlua::UserData for ScriptConnection{
fn add_fields<F:UserDataFields<Self>>(fields:&mut F){
fn add_fields<F:mlua::UserDataFields<Self>>(fields:&mut F){
fields.add_field_method_get("Connected",|_,this|{
Ok(this.position().is_some())
});
@@ -161,7 +164,6 @@ pub fn set_globals(lua:&mlua::Lua,globals:&mlua::Table)->Result<(),mlua::Error>{
.call::<mlua::Function>(())?;
lua.register_userdata_type::<ScriptSignal>(|reg|{
reg.add_field("wait",wait.clone());
reg.add_field("Wait",wait);
mlua::UserData::register(reg);
})?;

View File

@@ -1,42 +0,0 @@
#[cfg(not(feature="run-service"))]
fn no_op(_lua:&mlua::Lua,_time:Option<super::number::Number>)->mlua::Result<f64>{
Ok(0.0)
}
fn tick(_lua:&mlua::Lua,_:())->mlua::Result<f64>{
Ok(0.0)
}
// This is used to avoid calling coroutine.yield from the rust side.
const LUA_WAIT:&str=
"local coroutine_yield=coroutine.yield
local schedule_thread=schedule_thread
return function(dt)
schedule_thread(dt)
return coroutine_yield()
end";
pub fn set_globals(lua:&mlua::Lua,globals:&mlua::Table)->Result<(),mlua::Error>{
let coroutine_table=globals.get::<mlua::Table>("coroutine")?;
#[cfg(feature="run-service")]
let schedule_thread=lua.create_function(crate::scheduler::schedule_thread)?;
#[cfg(not(feature="run-service"))]
let schedule_thread=lua.create_function(no_op)?;
//create wait function environment
let wait_env=lua.create_table()?;
wait_env.raw_set("coroutine",coroutine_table)?;
wait_env.raw_set("schedule_thread",schedule_thread)?;
//construct wait function from Lua code
let wait=lua.load(LUA_WAIT)
.set_name("wait")
.set_environment(wait_env)
.call::<mlua::Function>(())?;
globals.raw_set("wait",wait)?;
// TODO: move this somewhere it belongs
let tick=lua.create_function(tick)?;
globals.raw_set("tick",tick)?;
Ok(())
}

View File

@@ -1,35 +0,0 @@
use super::instance::Instance;
use super::tween_info::TweenInfo;
#[expect(dead_code)]
#[derive(Clone)]
pub struct Tween{
instance:Instance,
tween_info:TweenInfo,
goal:mlua::Table,
playback_state:rbx_types::Enum,
}
impl Tween{
pub fn create(
instance:Instance,
tween_info:TweenInfo,
goal:mlua::Table,
)->Self{
Self{
instance,
tween_info,
goal,
// Enum.PlaybackState.Begin
playback_state:rbx_types::Enum::from_u32(0),
}
}
pub fn play(&mut self){
}
}
impl mlua::UserData for Tween{
fn add_methods<M:mlua::UserDataMethods<Self>>(methods:&mut M){
methods.add_method_mut("Play",|_,this,()|Ok(this.play()))
}
}
type_from_lua_userdata_clone!(Tween);

View File

@@ -1,45 +0,0 @@
use super::number::Number;
use super::r#enum::{CoerceEnum,Enums};
#[expect(dead_code)]
#[derive(Clone)]
pub struct TweenInfo{
time:f64,
easing_style:rbx_types::Enum,
easing_direction:rbx_types::Enum,
repeat_count:u32,
reverses:bool,
delay_time:f64,
}
pub fn set_globals(lua:&mlua::Lua,globals:&mlua::Table)->Result<(),mlua::Error>{
let table=lua.create_table()?;
table.raw_set("new",
lua.create_function(|_,(time,easing_style,easing_direction,repeat_count,reverses,delay_time):(Option<Number>,Option<CoerceEnum>,Option<CoerceEnum>,Option<u32>,Option<bool>,Option<Number>)|{
Ok(TweenInfo{
time:time.map_or(1.0,Number::to_f64),
easing_style:match easing_style{
// Enum.EasingStyle.Quad
None=>rbx_types::Enum::from_u32(3),
Some(e)=>e.coerce_to(Enums.get("EasingStyle").unwrap())?.into(),
},
easing_direction:match easing_direction{
// Enum.EasingDirection.Out
None=>rbx_types::Enum::from_u32(1),
Some(e)=>e.coerce_to(Enums.get("EasingDirection").unwrap())?.into(),
},
repeat_count:repeat_count.unwrap_or(0),
reverses:reverses.unwrap_or(false),
delay_time:delay_time.map_or(0.0,Number::to_f64),
})
})?
)?;
globals.set("TweenInfo",table)?;
Ok(())
}
impl mlua::UserData for TweenInfo{}
type_from_lua_userdata_clone!(TweenInfo);

View File

@@ -1,36 +0,0 @@
use super::number::Number;
#[derive(Clone,Copy)]
pub struct UDim(rbx_types::UDim);
impl UDim{
pub fn new(scale:f32,offset:i32)->Self{
UDim(rbx_types::UDim::new(scale,offset))
}
}
impl From<rbx_types::UDim> for UDim{
fn from(value:rbx_types::UDim)->Self{
Self(value)
}
}
pub fn set_globals(lua:&mlua::Lua,globals:&mlua::Table)->Result<(),mlua::Error>{
let table=lua.create_table()?;
table.raw_set("new",
lua.create_function(|_,(scale,offset):(Number,i32)|
Ok(UDim::new(scale.into(),offset))
)?
)?;
globals.set("UDim",table)?;
Ok(())
}
impl mlua::UserData for UDim{
fn add_fields<F:mlua::UserDataFields<Self>>(fields:&mut F){
fields.add_field_method_get("Scale",|_,UDim(this)|Ok(this.scale));
fields.add_field_method_get("Offset",|_,UDim(this)|Ok(this.offset));
}
}
type_from_lua_userdata!(UDim);

View File

@@ -1,40 +0,0 @@
use super::udim::UDim;
use super::number::Number;
#[derive(Clone,Copy)]
pub struct UDim2(rbx_types::UDim2);
impl UDim2{
pub fn new(sx:f32,ox:i32,sy:f32,oy:i32)->Self{
UDim2(rbx_types::UDim2::new(
rbx_types::UDim::new(sx,ox),
rbx_types::UDim::new(sy,oy),
))
}
}
impl From<UDim2> for rbx_types::UDim2{
fn from(UDim2(value):UDim2)->rbx_types::UDim2{
value
}
}
pub fn set_globals(lua:&mlua::Lua,globals:&mlua::Table)->Result<(),mlua::Error>{
let table=lua.create_table()?;
table.raw_set("new",
lua.create_function(|_,(sx,ox,sy,oy):(Number,i32,Number,i32)|
Ok(UDim2::new(sx.into(),ox,sy.into(),oy))
)?
)?;
globals.set("UDim2",table)?;
Ok(())
}
impl mlua::UserData for UDim2{
fn add_fields<F:mlua::UserDataFields<Self>>(fields:&mut F){
fields.add_field_method_get("X",|_,UDim2(this)|Ok(UDim::from(this.x)));
fields.add_field_method_get("Y",|_,UDim2(this)|Ok(UDim::from(this.y)));
}
}
type_from_lua_userdata!(UDim2);

View File

@@ -1,93 +0,0 @@
use mlua::FromLua;
use super::number::Number;
#[derive(Clone,Copy)]
pub struct Vector2(glam::Vec2);
impl Vector2{
pub const fn new(x:f32,y:f32)->Self{
Self(glam::vec2(x,y))
}
}
pub fn set_globals(lua:&mlua::Lua,globals:&mlua::Table)->Result<(),mlua::Error>{
let table=lua.create_table()?;
//Vector2.new
table.raw_set("new",
lua.create_function(|_,(x,y):(Option<Number>,Option<Number>)|
match (x,y){
(Some(x),Some(y))=>Ok(Vector2::new(x.into(),y.into())),
(None,None)=>Ok(Vector2(glam::Vec2::ZERO)),
_=>Err(mlua::Error::runtime("Unsupported arguments to Vector2.new")),
}
)?
)?;
globals.set("Vector2",table)?;
Ok(())
}
impl From<Vector2> for rbx_types::Vector2{
fn from(Vector2(v):Vector2)->rbx_types::Vector2{
rbx_types::Vector2::new(v.x,v.y)
}
}
impl From<rbx_types::Vector2> for Vector2{
fn from(value:rbx_types::Vector2)->Vector2{
Vector2::new(value.x,value.y)
}
}
impl mlua::UserData for Vector2{
fn add_fields<F:mlua::UserDataFields<Self>>(fields:&mut F){
fields.add_field_method_get("magnitude",|_,Vector2(this)|Ok(this.length()));
fields.add_field_method_get("Magnitude",|_,Vector2(this)|Ok(this.length()));
fields.add_field_method_get("unit",|_,Vector2(this)|Ok(Vector2(this.normalize())));
fields.add_field_method_get("Unit",|_,Vector2(this)|Ok(Vector2(this.normalize())));
fields.add_field_method_get("x",|_,Vector2(this)|Ok(this.x));
fields.add_field_method_get("X",|_,Vector2(this)|Ok(this.x));
fields.add_field_method_get("y",|_,Vector2(this)|Ok(this.y));
fields.add_field_method_get("Y",|_,Vector2(this)|Ok(this.y));
}
fn add_methods<M:mlua::UserDataMethods<Self>>(methods:&mut M){
//methods.add_method("area",|_,this,()| Ok(this.length * this.width));
methods.add_meta_function(mlua::MetaMethod::Add,|_,(Vector2(this),Vector2(val)):(Self,Self)|Ok(Self(this+val)));
methods.add_meta_function(mlua::MetaMethod::Sub,|_,(Vector2(this),Vector2(val)):(Self,Self)|Ok(Self(this-val)));
methods.add_meta_function(mlua::MetaMethod::Mul,|lua,(lhs,rhs):(mlua::Value,mlua::Value)|{
match (lhs,rhs){
(mlua::Value::UserData(lhs),mlua::Value::UserData(rhs))=>lhs.borrow_scoped(|Vector2(lhs):&Vector2|rhs.borrow_scoped(|Vector2(rhs):&Vector2|Self(lhs*rhs)))?,
(lhs,mlua::Value::UserData(rhs))=>{
let lhs=Number::from_lua(lhs,lua)?;
rhs.borrow_scoped(|Vector2(rhs):&Vector2|Self(lhs.to_f32()*rhs))
},
(mlua::Value::UserData(lhs),rhs)=>{
let rhs=Number::from_lua(rhs,lua)?;
lhs.borrow_scoped(|Vector2(lhs):&Vector2|Self(lhs*rhs.to_f32()))
},
_=>Err(mlua::Error::runtime(format!("Expected Vector2")))
}
});
methods.add_meta_function(mlua::MetaMethod::Div,|_,(Vector2(this),val):(Self,mlua::Value)|{
match val{
mlua::Value::Integer(n)=>Ok(Self(this/(n as f32))),
mlua::Value::Number(n)=>Ok(Self(this/(n as f32))),
mlua::Value::UserData(ud)=>ud.borrow_scoped(|Vector2(rhs):&Vector2|Self(this/rhs)),
other=>Err(mlua::Error::runtime(format!("Attempt to divide Vector2 by {other:?}"))),
}
});
methods.add_meta_function(mlua::MetaMethod::ToString,|_,Vector2(this):Self|
Ok(format!("Vector2.new({},{})",
this.x,
this.y,
))
);
}
}
type_from_lua_userdata!(Vector2);

View File

@@ -1,7 +1,3 @@
use mlua::FromLua;
use super::number::Number;
#[derive(Clone,Copy)]
pub struct Vector3(pub(crate)glam::Vec3A);
@@ -12,27 +8,23 @@ impl Vector3{
}
pub fn set_globals(lua:&mlua::Lua,globals:&mlua::Table)->Result<(),mlua::Error>{
let table=lua.create_table()?;
let vector3_table=lua.create_table()?;
//Vector3.new
table.raw_set("new",
lua.create_function(|_,(x,y,z):(Option<Number>,Option<Number>,Option<Number>)|
match (x,y,z){
(Some(x),Some(y),Some(z))=>Ok(Vector3::new(x.into(),y.into(),z.into())),
(None,None,None)=>Ok(Vector3(glam::Vec3A::ZERO)),
_=>Err(mlua::Error::runtime("Unsupported arguments to Vector3.new")),
}
vector3_table.raw_set("new",
lua.create_function(|_,(x,y,z):(f32,f32,f32)|
Ok(Vector3::new(x,y,z))
)?
)?;
globals.set("Vector3",table)?;
globals.set("Vector3",vector3_table)?;
Ok(())
}
impl From<Vector3> for rbx_types::Vector3{
fn from(Vector3(v):Vector3)->rbx_types::Vector3{
rbx_types::Vector3::new(v.x,v.y,v.z)
impl Into<rbx_types::Vector3> for Vector3{
fn into(self)->rbx_types::Vector3{
rbx_types::Vector3::new(self.0.x,self.0.y,self.0.z)
}
}
@@ -44,50 +36,44 @@ impl From<rbx_types::Vector3> for Vector3{
impl mlua::UserData for Vector3{
fn add_fields<F:mlua::UserDataFields<Self>>(fields:&mut F){
fields.add_field_method_get("magnitude",|_,Vector3(this)|Ok(this.length()));
fields.add_field_method_get("Magnitude",|_,Vector3(this)|Ok(this.length()));
fields.add_field_method_get("unit",|_,Vector3(this)|Ok(Vector3(this.normalize())));
fields.add_field_method_get("Unit",|_,Vector3(this)|Ok(Vector3(this.normalize())));
fields.add_field_method_get("x",|_,Vector3(this)|Ok(this.x));
fields.add_field_method_get("X",|_,Vector3(this)|Ok(this.x));
fields.add_field_method_get("y",|_,Vector3(this)|Ok(this.y));
fields.add_field_method_get("Y",|_,Vector3(this)|Ok(this.y));
fields.add_field_method_get("z",|_,Vector3(this)|Ok(this.z));
fields.add_field_method_get("Z",|_,Vector3(this)|Ok(this.z));
fields.add_field_method_get("magnitude",|_,this|Ok(this.0.length()));
fields.add_field_method_get("x",|_,this|Ok(this.0.x));
fields.add_field_method_set("x",|_,this,val|{
this.0.x=val;
Ok(())
});
fields.add_field_method_get("y",|_,this|Ok(this.0.y));
fields.add_field_method_set("y",|_,this,val|{
this.0.y=val;
Ok(())
});
fields.add_field_method_get("z",|_,this|Ok(this.0.z));
fields.add_field_method_set("z",|_,this,val|{
this.0.z=val;
Ok(())
});
}
fn add_methods<M:mlua::UserDataMethods<Self>>(methods:&mut M){
//methods.add_method("area",|_,this,()| Ok(this.length * this.width));
methods.add_meta_function(mlua::MetaMethod::Add,|_,(Vector3(this),Vector3(val)):(Self,Self)|Ok(Self(this+val)));
methods.add_meta_function(mlua::MetaMethod::Sub,|_,(Vector3(this),Vector3(val)):(Self,Self)|Ok(Self(this-val)));
methods.add_meta_function(mlua::MetaMethod::Mul,|lua,(lhs,rhs):(mlua::Value,mlua::Value)|{
match (lhs,rhs){
(mlua::Value::UserData(lhs),mlua::Value::UserData(rhs))=>lhs.borrow_scoped(|Vector3(lhs):&Vector3|rhs.borrow_scoped(|Vector3(rhs):&Vector3|Self(lhs*rhs)))?,
(lhs,mlua::Value::UserData(rhs))=>{
let lhs=Number::from_lua(lhs,lua)?;
rhs.borrow_scoped(|Vector3(rhs):&Vector3|Self(lhs.to_f32()*rhs))
},
(mlua::Value::UserData(lhs),rhs)=>{
let rhs=Number::from_lua(rhs,lua)?;
lhs.borrow_scoped(|Vector3(lhs):&Vector3|Self(lhs*rhs.to_f32()))
},
_=>Err(mlua::Error::runtime(format!("Expected Vector3")))
}
});
methods.add_meta_function(mlua::MetaMethod::Div,|_,(Vector3(this),val):(Self,mlua::Value)|{
methods.add_meta_function(mlua::MetaMethod::Add,|_,(this,val):(Self,Self)|Ok(Self(this.0+val.0)));
methods.add_meta_function(mlua::MetaMethod::Div,|_,(this,val):(Self,mlua::Value)|{
match val{
mlua::Value::Integer(n)=>Ok(Self(this/(n as f32))),
mlua::Value::Number(n)=>Ok(Self(this/(n as f32))),
mlua::Value::UserData(ud)=>ud.borrow_scoped(|Vector3(rhs):&Vector3|Self(this/rhs)),
mlua::Value::Integer(n)=>Ok(Self(this.0/(n as f32))),
mlua::Value::Number(n)=>Ok(Self(this.0/(n as f32))),
mlua::Value::UserData(ud)=>{
let rhs:Vector3=ud.take()?;
Ok(Self(this.0/rhs.0))
},
other=>Err(mlua::Error::runtime(format!("Attempt to divide Vector3 by {other:?}"))),
}
});
methods.add_meta_function(mlua::MetaMethod::ToString,|_,Vector3(this):Self|
methods.add_meta_function(mlua::MetaMethod::ToString,|_,this:Self|
Ok(format!("Vector3.new({},{},{})",
this.x,
this.y,
this.z,
this.0.x,
this.0.y,
this.0.z,
))
);
}

View File

@@ -46,12 +46,12 @@ impl Scheduler{
}
}
pub fn scheduler_mut<T>(lua:&mlua::Lua,mut f:impl FnMut(&mut Scheduler)->mlua::Result<T>)->mlua::Result<T>{
let mut scheduler=lua.app_data_mut::<Scheduler>().ok_or_else(||mlua::Error::runtime("Scheduler missing"))?;
pub fn scheduler_mut<T>(lua:&mlua::Lua,mut f:impl FnMut(&mut crate::scheduler::Scheduler)->mlua::Result<T>)->mlua::Result<T>{
let mut scheduler=lua.app_data_mut::<crate::scheduler::Scheduler>().ok_or_else(||mlua::Error::runtime("Scheduler missing"))?;
f(&mut *scheduler)
}
pub fn schedule_thread(lua:&mlua::Lua,dt:mlua::Value)->Result<(),mlua::Error>{
fn schedule_thread(lua:&mlua::Lua,dt:mlua::Value)->Result<(),mlua::Error>{
let delay=match dt{
mlua::Value::Integer(i)=>i.max(0) as u64*60,
mlua::Value::Number(f)=>{
@@ -75,3 +75,32 @@ pub fn schedule_thread(lua:&mlua::Lua,dt:mlua::Value)->Result<(),mlua::Error>{
Ok(())
})
}
// This is used to avoid calling coroutine.yield from the rust side.
const LUA_WAIT:&str=
"local coroutine_yield=coroutine.yield
local schedule_thread=schedule_thread
return function(dt)
schedule_thread(dt)
return coroutine_yield()
end";
pub fn set_globals(lua:&mlua::Lua,globals:&mlua::Table)->Result<(),mlua::Error>{
let coroutine_table=globals.get::<mlua::Table>("coroutine")?;
let schedule_thread=lua.create_function(schedule_thread)?;
//create wait function environment
let wait_env=lua.create_table()?;
wait_env.raw_set("coroutine",coroutine_table)?;
wait_env.raw_set("schedule_thread",schedule_thread)?;
//construct wait function from Lua code
let wait=lua.load(LUA_WAIT)
.set_name("wait")
.set_environment(wait_env)
.call::<mlua::Function>(())?;
globals.raw_set("wait",wait)?;
Ok(())
}

View File

@@ -1,3 +0,0 @@
pub fn static_ustr(s:&'static str)->rbx_dom_weak::Ustr{
rbx_dom_weak::ustr(s)
}

View File

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

View File

@@ -6,7 +6,7 @@ use strafesnet_common::physics::Time;
const VERSION:u32=0;
type TimedPhysicsInstruction=strafesnet_common::instruction::TimedInstruction<strafesnet_common::physics::Instruction,Time>;
type TimedPhysicsInstruction=strafesnet_common::instruction::TimedInstruction<strafesnet_common::physics::Instruction,strafesnet_common::physics::Time>;
#[derive(Debug)]
pub enum Error{
@@ -85,7 +85,6 @@ pub struct Segment{
#[derive(Clone,Copy,Debug)]
pub struct SegmentInfo{
/// time of the first instruction in this segment.
#[expect(dead_code)]
time:Time,
instruction_count:u32,
/// How many total instructions in segments up to and including this segment
@@ -117,7 +116,6 @@ impl<R:BinReaderExt> StreamableBot<R>{
segment_map,
})
}
#[expect(dead_code)]
fn get_segment_info(&self,segment_id:SegmentId)->Result<SegmentInfo,Error>{
Ok(*self.segment_map.get(segment_id.get() as usize).ok_or(Error::InvalidSegmentId(segment_id))?)
}
@@ -274,7 +272,7 @@ pub fn write_bot<W:BinWriterExt>(mut writer:W,physics_version:u32,instructions:i
//probe header length
let mut bot_header_data=Vec::new();
header.write_le(&mut std::io::Cursor::new(&mut bot_header_data)).map_err(Error::InvalidData)?;
binrw::BinWrite::write_le(&header,&mut std::io::Cursor::new(&mut bot_header_data)).map_err(Error::InvalidData)?;
// the first block location is the map header
block_location.push(offset);

View File

@@ -53,6 +53,8 @@ pub(crate) enum FourCC{
Map,
#[brw(magic=b"SNFB")]
Bot,
#[brw(magic=b"SNFD")]
Demo,
}
#[binrw]
#[brw(little)]

Some files were not shown because too many files have changed in this diff Show More