Compare commits

...

103 Commits

Author SHA1 Message Date
32f8f9caed fix lints 2025-12-18 11:03:30 -08:00
697958080c fix rebase 2025-12-18 11:03:29 -08:00
6896800c75 debug physics models visually 2025-12-18 11:03:29 -08:00
72a23f784a Session::debug_raycast_print_model_id_if_changed 2025-12-18 11:03:06 -08:00
78b9a4fdce skip faces early 2025-12-18 11:03:06 -08:00
bd6bb4b27a physics: PhysicsData::trace_ray 2025-12-18 11:03:06 -08:00
9b3a9ce437 narrow directions more carefully 2025-12-18 11:03:06 -08:00
73f0a85f81 fix predict_collision_out 2025-12-18 11:03:06 -08:00
332e11a627 add loop 2025-12-18 11:03:06 -08:00
2d7f5cdc7d add test points 2025-12-18 11:03:06 -08:00
822f4571d2 add unit test 2025-12-18 11:03:06 -08:00
ac4c84f562 partially implement md generic 2025-12-18 11:03:06 -08:00
3a7aaa20f8 fix constraints epsilon
these were supposed to be 3 voxels but were on the order of 3 units
2025-12-18 11:03:06 -08:00
2e77366a17 handle non-canonnical multi-edge spanning edges 2025-12-18 11:03:06 -08:00
8a1f434a2a Meshquery::farthest_vert 2025-12-18 11:03:06 -08:00
80d3df4659 todo 2025-12-18 11:03:06 -08:00
47f94fe359 why 2025-12-18 11:03:06 -08:00
c4a2811627 think through simplex constraints 2025-12-18 11:03:06 -08:00
b23d4d590d refactor algorithm to use a struct 2025-12-18 11:03:06 -08:00
cb9307bdf2 comments 2025-12-18 11:03:05 -08:00
ae7582704b comment todos 2025-12-18 11:03:05 -08:00
7f0f63570e add unfortunate algorithm 2025-12-18 11:03:05 -08:00
dbe96a0451 remove Eq for MinkowskiFace 2025-12-18 11:03:05 -08:00
291cedff3f breakout 2025-12-18 11:03:05 -08:00
43a2c76906 change on_exact signature 2025-12-18 11:03:05 -08:00
7d8dbf7e82 fail without crash 2025-12-18 11:03:05 -08:00
7640ea824d no print 2025-12-18 11:03:05 -08:00
f3b02bba92 use new algorithm 2025-12-18 11:03:05 -08:00
9d04df4894 convert to fev using dumbest algorithm possible 2025-12-18 11:03:05 -08:00
f90436f0cc negate minkowski input to minimum_difference 2025-12-18 11:03:05 -08:00
5e14a85d84 make hint_point consistent with vert 2025-12-18 11:03:05 -08:00
da718e4bd2 rename variable 2025-12-18 11:03:05 -08:00
5641e9a26f physics: derive Eq for Minkowski FEV 2025-12-18 11:03:05 -08:00
7e7839f4aa remove indexing 2025-12-18 11:03:05 -08:00
6448d7cc57 put comment back in 2025-12-18 11:03:05 -08:00
b8be169092 use struct 2025-12-18 11:03:05 -08:00
9c4c14c5dc reduce member fn 2025-12-18 11:03:05 -08:00
ca40e65060 split reduce 2025-12-18 11:03:05 -08:00
492e72c1bc deconstruct 2025-12-18 11:03:05 -08:00
03bf2650fd refactor using Simplex enum 2025-12-18 11:03:05 -08:00
0c8cf02287 simplify perp 2025-12-18 11:03:05 -08:00
af1374906b fix algorithm 2025-12-18 11:03:05 -08:00
570d33a030 rename 2025-12-18 11:03:05 -08:00
d0c38a6e66 style 2025-12-18 11:03:05 -08:00
dd7a636fa9 careful relative point opti 2025-12-18 11:03:05 -08:00
2483abe2ad work 2025-12-18 11:03:05 -08:00
6621e369f2 include relative point 2025-12-18 11:03:05 -08:00
1b833ef6b3 fix degenerate case 2025-12-18 11:03:05 -08:00
8a1ab4e03c fix wrong 2025-12-18 11:03:05 -08:00
069db75d3a use min diff 2025-12-18 11:03:05 -08:00
de54bcfc36 implement contains_point 2025-12-18 11:03:05 -08:00
e777b89c6d rename variable 2025-12-18 11:03:05 -08:00
d17153d17d work 2025-12-18 11:03:05 -08:00
e514c27675 reduce min dist bit width 2025-12-18 11:03:05 -08:00
d1c13757e0 FnOnce 2025-12-18 11:03:04 -08:00
d93e558678 fast fail fn 2025-12-18 11:03:04 -08:00
0048306236 more naming things 2025-12-18 11:03:04 -08:00
ee50f8dc1e refine naming 2025-12-18 11:03:04 -08:00
e4966b037f names 2025-12-18 11:03:04 -08:00
96cdd684d1 stuff 2025-12-18 11:03:04 -08:00
838130fec4 switch trait to closures 2025-12-18 11:03:04 -08:00
8a3badc270 insane trait just to remove if statements 2025-12-18 11:03:04 -08:00
4470e88d7b refactor calculation result 2025-12-18 11:03:04 -08:00
df7bee6cd1 details later 2025-12-18 11:03:04 -08:00
4fdd254f2a remove is more clear 2025-12-18 11:03:04 -08:00
8a9db203fa remove unused 2025-12-18 11:03:04 -08:00
be05fd108a work 2025-12-18 11:03:04 -08:00
6160872469 work 2025-12-18 11:03:04 -08:00
2c1fa5da22 work 2025-12-18 11:03:04 -08:00
6fe45f4873 work 2025-12-18 11:03:04 -08:00
e1dac67aa0 notes 2025-12-18 11:03:04 -08:00
f9ed33073e zero 2025-12-18 11:03:04 -08:00
91636747d4 idea 2025-12-18 11:03:04 -08:00
03b72301a3 eugh 2025-12-18 11:03:04 -08:00
214b23f780 work 2025-12-18 11:03:04 -08:00
6d98407830 work 2025-12-18 11:03:04 -08:00
978659e8c6 work 2025-12-18 11:03:04 -08:00
d00871f87f work 2025-12-18 11:03:04 -08:00
d2ed97fcf2 wip paste fns 2025-12-18 11:03:04 -08:00
ab3c693f84 no hold ref 2025-12-18 11:03:04 -08:00
f0c7677a77 work 2025-12-18 11:03:04 -08:00
eed0abcc2c work 2025-12-18 11:03:04 -08:00
bc5cdc7313 work 2025-12-18 11:03:04 -08:00
14a5a3f964 work 2025-12-18 11:03:04 -08:00
18b7bba901 work 2025-12-18 11:03:04 -08:00
1770ac7292 work 2025-12-18 11:03:04 -08:00
7384886512 work 2025-12-18 11:03:04 -08:00
1dbde609cc wip 2025-12-18 11:03:04 -08:00
e024f37843 update deps 2025-12-18 10:58:50 -08:00
4059cfa527 update wgpu 2025-12-18 10:57:19 -08:00
e4f3418bc6 document PhysicsMesh 2025-12-11 09:36:21 -08:00
6ca6d5e484 expect dead code 2025-12-10 18:05:16 -08:00
0668ac2def use allow instead of expect 2025-12-09 14:39:42 -08:00
73e3181d0c roblox_emulator: v0.5.2 2025-11-27 16:42:01 -08:00
19ba8f2445 update deps 2025-11-27 15:50:19 -08:00
0495d07e26 update rbx-dom 2025-11-27 15:48:17 -08:00
0ea353b27d common: fixed_wide: min max 2025-11-24 13:04:44 -08:00
99706079d9 common: fixed_wide: add mul_sign div_sign 2025-11-24 13:04:44 -08:00
730c5fb7dd common: integer: generic zero 2025-11-22 08:47:16 -08:00
d1b61bb997 push_solve: remove epsilon 2025-11-21 10:52:34 -08:00
0343ad19cf MeshQuery::hint_point returns any point inside the mesh 2025-11-20 10:59:08 -08:00
43210b1417 less access to TouchingState private fields 2025-11-19 13:39:07 -08:00
e9d28cf15f document jank 2025-11-19 13:15:31 -08:00
30 changed files with 1904 additions and 707 deletions

908
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -11,7 +11,7 @@ id = { version = "0.1.0", registry = "strafesnet" }
strafesnet_common = { path = "../../lib/common", registry = "strafesnet" } strafesnet_common = { path = "../../lib/common", registry = "strafesnet" }
strafesnet_session = { path = "../session", registry = "strafesnet" } strafesnet_session = { path = "../session", registry = "strafesnet" }
strafesnet_settings = { path = "../settings", registry = "strafesnet" } strafesnet_settings = { path = "../settings", registry = "strafesnet" }
wgpu = "27.0.0" wgpu = "28.0.0"
[lints] [lints]
workspace = true workspace = true

View File

@@ -5,7 +5,7 @@ use strafesnet_settings::settings;
use strafesnet_session::session; use strafesnet_session::session;
use strafesnet_common::model::{self, ColorId, NormalId, PolygonIter, PositionId, RenderConfigId, TextureCoordinateId, VertexId}; use strafesnet_common::model::{self, ColorId, NormalId, PolygonIter, PositionId, RenderConfigId, TextureCoordinateId, VertexId};
use wgpu::{util::DeviceExt,AstcBlock,AstcChannel}; use wgpu::{util::DeviceExt,AstcBlock,AstcChannel};
use crate::model::{self as model_graphics,IndexedGraphicsMeshOwnedRenderConfig,IndexedGraphicsMeshOwnedRenderConfigId,GraphicsMeshOwnedRenderConfig,GraphicsModelColor4,GraphicsModelOwned,GraphicsVertex}; use crate::model::{self as model_graphics,IndexedGraphicsMeshOwnedRenderConfig,IndexedGraphicsMeshOwnedRenderConfigId,GraphicsMeshOwnedRenderConfig,GraphicsModelColor4,GraphicsModelOwned,GraphicsVertex,DebugGraphicsVertex};
pub fn required_limits()->wgpu::Limits{ pub fn required_limits()->wgpu::Limits{
wgpu::Limits::default() wgpu::Limits::default()
@@ -36,12 +36,22 @@ struct GraphicsModel{
instance_count:u32, instance_count:u32,
} }
struct DebugGraphicsMesh{
indices:Indices,
vertex_buf:wgpu::Buffer,
}
struct DebugGraphicsModel{
debug_mesh_id:u32,
bind_group:wgpu::BindGroup,
}
struct GraphicsSamplers{ struct GraphicsSamplers{
repeat:wgpu::Sampler, repeat:wgpu::Sampler,
} }
struct GraphicsBindGroupLayouts{ struct GraphicsBindGroupLayouts{
model:wgpu::BindGroupLayout, model:wgpu::BindGroupLayout,
debug_model:wgpu::BindGroupLayout,
} }
struct GraphicsBindGroups{ struct GraphicsBindGroups{
@@ -52,6 +62,7 @@ struct GraphicsBindGroups{
struct GraphicsPipelines{ struct GraphicsPipelines{
skybox:wgpu::RenderPipeline, skybox:wgpu::RenderPipeline,
model:wgpu::RenderPipeline, model:wgpu::RenderPipeline,
debug:wgpu::RenderPipeline,
} }
struct GraphicsCamera{ struct GraphicsCamera{
@@ -132,6 +143,8 @@ pub struct GraphicsState{
camera_buf:wgpu::Buffer, camera_buf:wgpu::Buffer,
temp_squid_texture_view:wgpu::TextureView, temp_squid_texture_view:wgpu::TextureView,
models:Vec<GraphicsModel>, models:Vec<GraphicsModel>,
debug_meshes:Vec<DebugGraphicsMesh>,
debug_models:Vec<DebugGraphicsModel>,
depth_view:wgpu::TextureView, depth_view:wgpu::TextureView,
staging_belt:wgpu::util::StagingBelt, staging_belt:wgpu::util::StagingBelt,
} }
@@ -166,6 +179,76 @@ impl GraphicsState{
self.camera.fov=user_settings.calculate_fov(1.0,&self.camera.screen_size).as_vec2(); 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){ pub fn generate_models(&mut self,device:&wgpu::Device,queue:&wgpu::Queue,map:&map::CompleteMap){
//generate debug meshes, each debug model refers to one
self.debug_meshes=map.meshes.iter().map(|mesh|{
let vertices:Vec<DebugGraphicsVertex>=mesh.unique_vertices.iter().map(|vertex|{
DebugGraphicsVertex{
pos:mesh.unique_pos[vertex.pos.get() as usize].to_array().map(Into::into),
normal:mesh.unique_normal[vertex.normal.get() as usize].to_array().map(Into::into),
}
}).collect();
let vertex_buf=device.create_buffer_init(&wgpu::util::BufferInitDescriptor{
label:Some("Vertex"),
contents:bytemuck::cast_slice(&vertices),
usage:wgpu::BufferUsages::VERTEX,
});
let mut indices=Vec::new();
for physics_group in &mesh.physics_groups{
for polygon_group_id in &physics_group.groups{
for poly in mesh.polygon_groups[polygon_group_id.get() as usize].polys(){
// triangulate
let mut poly_vertices=poly.into_iter().copied();
if let (Some(a),Some(mut b))=(poly_vertices.next(),poly_vertices.next()){
for c in poly_vertices{
indices.extend([a,b,c]);
b=c;
}
}
}
}
}
DebugGraphicsMesh{
indices:if (u32::MAX as usize)<vertices.len(){
panic!("Model has too many vertices!")
}else if (u16::MAX as usize)<vertices.len(){
Indices::new(device,&indices.into_iter().map(|vertex_idx|vertex_idx.get() as u32).collect(),wgpu::IndexFormat::Uint32)
}else{
Indices::new(device,&indices.into_iter().map(|vertex_idx|vertex_idx.get() as u16).collect(),wgpu::IndexFormat::Uint16)
},
vertex_buf,
}
}).collect();
//generate debug models, only one will be rendered at a time
self.debug_models=map.models.iter().enumerate().map(|(model_id,model)|{
let model_uniforms=get_instances_buffer_data(&[GraphicsModelOwned{
transform:model.transform.into(),
normal_transform:glam::Mat3::from_cols_array_2d(&model.transform.matrix3.to_array().map(|row|row.map(Into::into))).inverse().transpose(),
color:GraphicsModelColor4::new(glam::vec4(1.0,0.0,0.0,0.2)),
}]);
let model_buf=device.create_buffer_init(&wgpu::util::BufferInitDescriptor{
label:Some(format!("Debug Model{} Buf",model_id).as_str()),
contents:bytemuck::cast_slice(&model_uniforms),
usage:wgpu::BufferUsages::UNIFORM|wgpu::BufferUsages::COPY_DST,
});
let bind_group=device.create_bind_group(&wgpu::BindGroupDescriptor{
layout:&self.bind_group_layouts.debug_model,
entries:&[
wgpu::BindGroupEntry{
binding:0,
resource:model_buf.as_entire_binding(),
},
],
label:Some(format!("Debug Model{} Bind Group",model_id).as_str()),
});
DebugGraphicsModel{
debug_mesh_id:model.mesh.get(),
bind_group,
}
}).collect();
//generate texture view per texture //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<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 texture_id=model::TextureId::new(texture_id as u32);
@@ -608,6 +691,21 @@ impl GraphicsState{
}, },
], ],
}); });
let debug_model_bind_group_layout=device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor{
label:Some("Debug Model Bind Group Layout"),
entries:&[
wgpu::BindGroupLayoutEntry{
binding:0,
visibility:wgpu::ShaderStages::VERTEX_FRAGMENT,
ty:wgpu::BindingType::Buffer{
ty:wgpu::BufferBindingType::Uniform,
has_dynamic_offset:false,
min_binding_size:None,
},
count:None,
},
],
});
let clamp_sampler=device.create_sampler(&wgpu::SamplerDescriptor{ let clamp_sampler=device.create_sampler(&wgpu::SamplerDescriptor{
label:Some("Clamp Sampler"), label:Some("Clamp Sampler"),
@@ -616,7 +714,7 @@ impl GraphicsState{
address_mode_w:wgpu::AddressMode::ClampToEdge, address_mode_w:wgpu::AddressMode::ClampToEdge,
mag_filter:wgpu::FilterMode::Linear, mag_filter:wgpu::FilterMode::Linear,
min_filter:wgpu::FilterMode::Linear, min_filter:wgpu::FilterMode::Linear,
mipmap_filter:wgpu::FilterMode::Linear, mipmap_filter:wgpu::MipmapFilterMode::Linear,
..Default::default() ..Default::default()
}); });
let repeat_sampler=device.create_sampler(&wgpu::SamplerDescriptor{ let repeat_sampler=device.create_sampler(&wgpu::SamplerDescriptor{
@@ -626,7 +724,7 @@ impl GraphicsState{
address_mode_w:wgpu::AddressMode::Repeat, address_mode_w:wgpu::AddressMode::Repeat,
mag_filter:wgpu::FilterMode::Linear, mag_filter:wgpu::FilterMode::Linear,
min_filter:wgpu::FilterMode::Linear, min_filter:wgpu::FilterMode::Linear,
mipmap_filter:wgpu::FilterMode::Linear, mipmap_filter:wgpu::MipmapFilterMode::Linear,
anisotropy_clamp:16, anisotropy_clamp:16,
..Default::default() ..Default::default()
}); });
@@ -754,15 +852,23 @@ impl GraphicsState{
&skybox_texture_bind_group_layout, &skybox_texture_bind_group_layout,
&model_bind_group_layout, &model_bind_group_layout,
], ],
push_constant_ranges:&[], immediate_size:0,
}); });
let debug_model_pipeline_layout=device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor{
label:None,
bind_group_layouts:&[
&camera_bind_group_layout,
&debug_model_bind_group_layout,
],
push_constant_ranges:&[],
});
let sky_pipeline_layout=device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor{ let sky_pipeline_layout=device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor{
label:None, label:None,
bind_group_layouts:&[ bind_group_layouts:&[
&camera_bind_group_layout, &camera_bind_group_layout,
&skybox_texture_bind_group_layout, &skybox_texture_bind_group_layout,
], ],
push_constant_ranges:&[], immediate_size:0,
}); });
// Create the render pipelines // Create the render pipelines
@@ -793,7 +899,7 @@ impl GraphicsState{
bias:wgpu::DepthBiasState::default(), bias:wgpu::DepthBiasState::default(),
}), }),
multisample:wgpu::MultisampleState::default(), multisample:wgpu::MultisampleState::default(),
multiview:None, multiview_mask:None,
cache:None, cache:None,
}); });
let model_pipeline=device.create_render_pipeline(&wgpu::RenderPipelineDescriptor{ let model_pipeline=device.create_render_pipeline(&wgpu::RenderPipelineDescriptor{
@@ -828,6 +934,45 @@ impl GraphicsState{
bias:wgpu::DepthBiasState::default(), bias:wgpu::DepthBiasState::default(),
}), }),
multisample:wgpu::MultisampleState::default(), multisample:wgpu::MultisampleState::default(),
multiview_mask:None,
cache:None,
});
let debug_model_pipeline=device.create_render_pipeline(&wgpu::RenderPipelineDescriptor{
label:Some("Debug Model Pipeline"),
layout:Some(&debug_model_pipeline_layout),
vertex:wgpu::VertexState{
module:&shader,
entry_point:Some("vs_debug"),
buffers:&[wgpu::VertexBufferLayout{
array_stride:size_of::<DebugGraphicsVertex>() as wgpu::BufferAddress,
step_mode:wgpu::VertexStepMode::Vertex,
attributes:&wgpu::vertex_attr_array![0=>Float32x3,1=>Float32x3],
}],
compilation_options:wgpu::PipelineCompilationOptions::default(),
},
fragment:Some(wgpu::FragmentState{
module:&shader,
entry_point:Some("fs_debug"),
targets:&[Some(wgpu::ColorTargetState{
format:config.view_formats[0],
blend:Some(wgpu::BlendState::ALPHA_BLENDING),
write_mask:wgpu::ColorWrites::default(),
})],
compilation_options:wgpu::PipelineCompilationOptions::default(),
}),
primitive:wgpu::PrimitiveState{
front_face:wgpu::FrontFace::Cw,
cull_mode:Some(wgpu::Face::Front),
..Default::default()
},
depth_stencil:Some(wgpu::DepthStencilState{
format:Self::DEPTH_FORMAT,
depth_write_enabled:true,
depth_compare:wgpu::CompareFunction::Always,
stencil:wgpu::StencilState::default(),
bias:wgpu::DepthBiasState::default(),
}),
multisample:wgpu::MultisampleState::default(),
multiview:None, multiview:None,
cache:None, cache:None,
}); });
@@ -870,7 +1015,8 @@ impl GraphicsState{
Self{ Self{
pipelines:GraphicsPipelines{ pipelines:GraphicsPipelines{
skybox:sky_pipeline, skybox:sky_pipeline,
model:model_pipeline model:model_pipeline,
debug:debug_model_pipeline,
}, },
bind_groups:GraphicsBindGroups{ bind_groups:GraphicsBindGroups{
camera:camera_bind_group, camera:camera_bind_group,
@@ -879,9 +1025,14 @@ impl GraphicsState{
camera, camera,
camera_buf, camera_buf,
models:Vec::new(), models:Vec::new(),
debug_meshes:Vec::new(),
debug_models:Vec::new(),
depth_view, depth_view,
staging_belt:wgpu::util::StagingBelt::new(0x100), staging_belt:wgpu::util::StagingBelt::new(device.clone(),0x100),
bind_group_layouts:GraphicsBindGroupLayouts{model:model_bind_group_layout}, bind_group_layouts:GraphicsBindGroupLayouts{
model:model_bind_group_layout,
debug_model:debug_model_bind_group_layout,
},
samplers:GraphicsSamplers{repeat:repeat_sampler}, samplers:GraphicsSamplers{repeat:repeat_sampler},
temp_squid_texture_view:squid_texture_view, temp_squid_texture_view:squid_texture_view,
} }
@@ -918,7 +1069,6 @@ impl GraphicsState{
&self.camera_buf, &self.camera_buf,
0, 0,
wgpu::BufferSize::new((camera_uniforms.len() * 4) as wgpu::BufferAddress).unwrap(), wgpu::BufferSize::new((camera_uniforms.len() * 4) as wgpu::BufferAddress).unwrap(),
device,
) )
.copy_from_slice(bytemuck::cast_slice(&camera_uniforms)); .copy_from_slice(bytemuck::cast_slice(&camera_uniforms));
//This code only needs to run when the uniforms change //This code only needs to run when the uniforms change
@@ -965,11 +1115,13 @@ impl GraphicsState{
}), }),
timestamp_writes:Default::default(), timestamp_writes:Default::default(),
occlusion_query_set:Default::default(), occlusion_query_set:Default::default(),
multiview_mask:None,
}); });
rpass.set_bind_group(0,&self.bind_groups.camera,&[]); rpass.set_bind_group(0,&self.bind_groups.camera,&[]);
rpass.set_bind_group(1,&self.bind_groups.skybox_texture,&[]); rpass.set_bind_group(1,&self.bind_groups.skybox_texture,&[]);
// Draw all models.
rpass.set_pipeline(&self.pipelines.model); rpass.set_pipeline(&self.pipelines.model);
for model in &self.models{ for model in &self.models{
rpass.set_bind_group(2,&model.bind_group,&[]); rpass.set_bind_group(2,&model.bind_group,&[]);
@@ -981,6 +1133,19 @@ impl GraphicsState{
rpass.set_pipeline(&self.pipelines.skybox); rpass.set_pipeline(&self.pipelines.skybox);
rpass.draw(0..3,0..1); rpass.draw(0..3,0..1);
// render a single debug_model in red
if let Some(model_id)=frame_state.debug_model{
if let Some(model)=self.debug_models.get(model_id.get() as usize){
let mesh=&self.debug_meshes[model.debug_mesh_id as usize];
rpass.set_pipeline(&self.pipelines.debug);
rpass.set_bind_group(1,&model.bind_group,&[]);
rpass.set_vertex_buffer(0,mesh.vertex_buf.slice(..));
rpass.set_index_buffer(mesh.indices.buf.slice(..),mesh.indices.format);
//TODO: loop over triangle strips
rpass.draw_indexed(0..mesh.indices.count,0,0..1);
}
}
} }
queue.submit(std::iter::once(encoder.finish())); queue.submit(std::iter::once(encoder.finish()));

View File

@@ -8,6 +8,12 @@ pub struct GraphicsVertex{
pub normal:[f32;3], pub normal:[f32;3],
pub color:[f32;4], pub color:[f32;4],
} }
#[derive(Clone,Copy,Pod,Zeroable)]
#[repr(C)]
pub struct DebugGraphicsVertex{
pub pos:[f32;3],
pub normal:[f32;3],
}
#[derive(Clone,Copy,id::Id)] #[derive(Clone,Copy,id::Id)]
pub struct IndexedGraphicsMeshOwnedRenderConfigId(u32); pub struct IndexedGraphicsMeshOwnedRenderConfigId(u32);
pub struct IndexedGraphicsMeshOwnedRenderConfig{ pub struct IndexedGraphicsMeshOwnedRenderConfig{

View File

@@ -33,7 +33,7 @@ impl<T:Copy> std::ops::Neg for &Body<T>{
impl<T> Body<T> impl<T> Body<T>
where Time<T>:Copy, 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{ pub const fn new(position:Planar64Vec3,velocity:Planar64Vec3,acceleration:Planar64Vec3,time:Time<T>)->Self{
Self{ Self{
position, position,
@@ -107,8 +107,8 @@ impl<T> Body<T>
self.time+=dt.into(); self.time+=dt.into();
} }
pub fn infinity_dir(&self)->Option<Planar64Vec3>{ pub fn infinity_dir(&self)->Option<Planar64Vec3>{
if self.velocity==vec3::ZERO{ if self.velocity==vec3::zero(){
if self.acceleration==vec3::ZERO{ if self.acceleration==vec3::zero(){
None None
}else{ }else{
Some(self.acceleration) Some(self.acceleration)

View File

@@ -21,12 +21,6 @@ impl<M:MeshQuery> CrawlResult<M>{
CrawlResult::Hit(face,time)=>Some((face,time)), CrawlResult::Hit(face,time)=>Some((face,time)),
} }
} }
pub fn miss(self)->Option<FEV<M>>{
match self{
CrawlResult::Miss(fev)=>Some(fev),
CrawlResult::Hit(_,_)=>None,
}
}
} }
// TODO: move predict_collision_face_out algorithm in here or something // TODO: move predict_collision_face_out algorithm in here or something

View File

@@ -1,7 +1,8 @@
mod body; mod body;
mod push_solve;
mod face_crawler; mod face_crawler;
mod model; mod model;
mod push_solve;
mod minimum_difference;
pub mod physics; pub mod physics;

View File

@@ -0,0 +1,913 @@
use strafesnet_common::integer::vec3;
use strafesnet_common::integer::vec3::Vector3;
use strafesnet_common::integer::{Fixed,Planar64,Planar64Vec3};
use crate::model::{DirectedEdge,FEV,MeshQuery};
// TODO: remove mesh invert
use crate::model::{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)]
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>>(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<Fixed<2,64>>)->Planar64Vec3{
if dir==vec3::zero(){
return dir.narrow_1().unwrap();
}
let x=dir.x.as_bits().unsigned_abs().bits();
let y=dir.y.as_bits().unsigned_abs().bits();
let z=dir.z.as_bits().unsigned_abs().bits();
let big=x.max(y).max(z);
const MAX_BITS:u32=64+31;
if MAX_BITS<big{
dir>>(big-MAX_BITS)
}else{
dir
}.narrow_1().unwrap()
}
fn narrow_dir3(dir:Vector3<Fixed<3,96>>)->Planar64Vec3{
if dir==vec3::zero(){
return dir.narrow_1().unwrap();
}
let x=dir.x.as_bits().unsigned_abs().bits();
let y=dir.y.as_bits().unsigned_abs().bits();
let z=dir.z.as_bits().unsigned_abs().bits();
let big=x.max(y).max(z);
const MAX_BITS:u32=96+31;
if MAX_BITS<big{
dir>>(big-MAX_BITS)
}else{
dir
}.narrow_1().unwrap()
}
fn reduce1<M:MeshQuery>(
[v0]:Simplex<1,M::Vert>,
mesh:&M,
point:Planar64Vec3,
)->Reduced<M::Vert>{
// --debug.profilebegin("reduceSimplex0")
// local a = a1 - a0
let p0=mesh.vert(v0);
// local p = -a
let p=-(p0+point);
// 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>(
[v0,v1]:Simplex<2,M::Vert>,
mesh:&M,
point:Planar64Vec3,
)->Reduced<M::Vert>{
// --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_negative(){
// 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>(
[v0,mut v1,v2]:Simplex<3,M::Vert>,
mesh:&M,
point:Planar64Vec3,
)->Reduced<M::Vert>{
// --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_negative()&&!uv_pv.is_negative(){
// 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_negative(){
// 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>(
[v0,mut v1,mut v2,v3]:Simplex<4,M::Vert>,
mesh:&M,
point:Planar64Vec3,
)->Reduce<M::Vert>{
// --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_negative()
||!up_w.div_sign(uv_w).is_negative()
||!uv_p.div_sign(uv_w).is_negative(){
// 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{
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]
}else{
v2=v3;
}
}
// 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_negative()&&!uv_pv.is_negative(){
// local direction = uvw < 0 and uv or -uv
// return direction, a0, a1, b0, b1, c0, c1
if uv_w.is_negative(){
return Reduce::Reduced(Reduced{
dir:narrow_dir2(uv),
simplex:Simplex1_3::Simplex3([v0,v1,v2]),
});
}else{
return Reduce::Reduced(Reduced{
dir:narrow_dir2(-uv),
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_negative(){
// 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
if uv_w.is_negative(){
return Reduce::Reduced(Reduced{
dir:narrow_dir2(uv),
simplex:Simplex1_3::Simplex2([v0,v1]),
});
}else{
return Reduce::Reduced(Reduced{
dir:narrow_dir2(-uv),
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
if uv_w.is_negative(){
return Reduce::Reduced(Reduced{
dir:narrow_dir2(uv),
simplex:Simplex1_3::Simplex1([v0]),
});
}else{
return Reduce::Reduced(Reduced{
dir:narrow_dir2(-uv),
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>>(self,mesh:&M,point:Planar64Vec3)->Reduce<Vert>{
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),
}
}
}
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
)
}
//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:MeshQuery>{
Vert(M::Vert),
Edge(<M::Edge as DirectedEdge>::UndirectedEdge),
}
impl<M:MeshQuery> 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<Fixed<2,64>>,
epsilon:Fixed<3,96>,
}
impl ThickPlane{
fn new<M:MeshQuery>(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_3();
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:Fixed<4,128>,
}
impl ThickLine{
fn new<M:MeshQuery>(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_4();
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:Fixed<2,64>,
}
impl<M:MeshQuery,C:Contains> EVFinder<'_,M,C>{
fn next_transition_vert(&mut self,vert_id:M::Vert,point:Planar64Vec3)->Transition<M::Vert>{
let mut best_transition=Transition::Done;
for &directed_edge_id in self.mesh.vert_edges(vert_id).as_ref(){
//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;
for &directed_edge_id in self.mesh.vert_edges(vert_id).as_ref(){
//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_2()
};
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>{
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 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 crawl_to_closest_ev<M:MeshQuery>(mesh:&M,simplex:Simplex<2,M::Vert>,point:Planar64Vec3)->EV<M>{
// 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 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 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(){
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);
}
}
FEV::Edge(edge_id)
},
}
}
pub fn closest_fev_not_inside<'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|{
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!()
)
}
// local function minimumDifference(
// queryP, radiusP,
// queryQ, radiusQ,
// exitRadius, testIntersection
// )
fn minimum_difference<const ENABLE_FAST_FAIL:bool,T,M:MeshQuery>(
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{
// 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();
}
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{
// 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
if ENABLE_FAST_FAIL&&direction.dot(next_pos+point).is_negative(){
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
if !direction.dot(next_pos-last_pos).is_positive()
||simplex_big.det_is_zero(mesh){
// 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)=>{
// 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

@@ -90,6 +90,10 @@ pub trait MeshQuery{
let &[v0,v1]=self.edge_verts(directed_edge_id.as_undirected()).as_ref(); 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) (self.vert(v1)-self.vert(v0))*((directed_edge_id.parity() as i64)*2-1)
} }
/// This must return a point inside the mesh.
#[expect(dead_code)]
fn hint_point(&self)->Planar64Vec3;
fn farthest_vert(&self,dir:Planar64Vec3)->Self::Vert;
fn vert(&self,vert_id:Self::Vert)->Planar64Vec3; fn vert(&self,vert_id:Self::Vert)->Planar64Vec3;
fn face_nd(&self,face_id:Self::Face)->(Self::Normal,Self::Offset); fn face_nd(&self,face_id:Self::Face)->(Self::Normal,Self::Offset);
fn face_edges(&self,face_id:Self::Face)->impl AsRef<[Self::Edge]>; fn face_edges(&self,face_id:Self::Face)->impl AsRef<[Self::Edge]>;
@@ -100,17 +104,21 @@ pub trait MeshQuery{
} }
#[derive(Debug)] #[derive(Debug)]
struct FaceRefs{ 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>, edges:Vec<SubmeshDirectedEdgeId>,
//verts are redundant, use edge[i].verts[0]
//verts:Vec<VertId>, //verts:Vec<VertId>,
} }
#[derive(Debug)] #[derive(Debug)]
struct EdgeRefs{ struct EdgeRefs{
faces:[SubmeshFaceId;2],//left, right faces:[SubmeshFaceId;2],//left, right
verts:[SubmeshVertId;2],//bottom, top verts:[SubmeshVertId;2],//start, end
} }
#[derive(Debug)] #[derive(Debug)]
struct VertRefs{ struct VertRefs{
faces:Vec<SubmeshFaceId>, faces:Vec<SubmeshFaceId>,
// edges are always directed away from the vert
edges:Vec<SubmeshDirectedEdgeId>, edges:Vec<SubmeshDirectedEdgeId>,
} }
#[derive(Debug)] #[derive(Debug)]
@@ -432,7 +440,7 @@ impl TryFrom<&model::Mesh> for PhysicsMesh{
} }
} }
#[derive(Debug)] #[derive(Debug,Clone,Copy)]
pub struct PhysicsMeshView<'a>{ pub struct PhysicsMeshView<'a>{
data:&'a PhysicsMeshData, data:&'a PhysicsMeshData,
topology:&'a PhysicsMeshTopology, topology:&'a PhysicsMeshTopology,
@@ -447,6 +455,22 @@ impl MeshQuery for PhysicsMeshView<'_>{
let face_idx=self.topology.faces[face_id.get() as usize].get() as usize; 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) (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... //ideally I never calculate the vertex position, but I have to for the graphical meshes...
fn vert(&self,vert_id:SubmeshVertId)->Planar64Vec3{ fn vert(&self,vert_id:SubmeshVertId)->Planar64Vec3{
let vert_idx=self.topology.verts[vert_id.get() as usize].get() as usize; let vert_idx=self.topology.verts[vert_id.get() as usize].get() as usize;
@@ -485,7 +509,7 @@ impl PhysicsMeshTransform{
} }
} }
#[derive(Debug)] #[derive(Debug,Clone,Copy)]
pub struct TransformedMesh<'a>{ pub struct TransformedMesh<'a>{
view:PhysicsMeshView<'a>, view:PhysicsMeshView<'a>,
transform:&'a PhysicsMeshTransform, transform:&'a PhysicsMeshTransform,
@@ -503,17 +527,8 @@ impl TransformedMesh<'_>{
pub fn verts<'a>(&'a self)->impl Iterator<Item=Vector3<Fixed<2,64>>>+'a{ pub fn verts<'a>(&'a self)->impl Iterator<Item=Vector3<Fixed<2,64>>>+'a{
self.view.data.verts.iter().map(|&Vert(pos)|self.transform.vertex.transform_point3(pos)) self.view.data.verts.iter().map(|&Vert(pos)|self.transform.vertex.transform_point3(pos))
} }
fn farthest_vert(&self,dir:Planar64Vec3)->SubmeshVertId{ pub fn faces(&self)->impl Iterator<Item=SubmeshFaceId>{
//this happens to be well-defined. there are no virtual virtices (0..self.view.topology.faces.len() as u32).map(SubmeshFaceId::new)
SubmeshVertId::new(
self.view.topology.verts.iter()
.enumerate()
.max_by_key(|&(_,&vert_id)|
dir.dot(self.transform.vertex.transform_point3(self.view.data.verts[vert_id.get() as usize].0))
)
//assume there is more than zero vertices.
.unwrap().0 as u32
)
} }
} }
impl MeshQuery for TransformedMesh<'_>{ impl MeshQuery for TransformedMesh<'_>{
@@ -532,6 +547,21 @@ impl MeshQuery for TransformedMesh<'_>{
// wrap for speed // wrap for speed
self.transform.vertex.transform_point3(self.view.vert(vert_id)).wrap_1() self.transform.vertex.transform_point3(self.view.vert(vert_id)).wrap_1()
} }
fn hint_point(&self)->Planar64Vec3{
self.transform.vertex.translation
}
fn farthest_vert(&self,dir:Planar64Vec3)->SubmeshVertId{
//this happens to be well-defined. there are no virtual virtices
SubmeshVertId::new(
self.view.topology.verts.iter()
.enumerate()
.max_by_key(|&(_,&vert_id)|
dir.dot(self.transform.vertex.transform_point3(self.view.data.verts[vert_id.get() as usize].0))
)
//assume there is more than zero vertices.
.unwrap().0 as u32
)
}
#[inline] #[inline]
fn face_edges(&self,face_id:SubmeshFaceId)->impl AsRef<[SubmeshDirectedEdgeId]>{ fn face_edges(&self,face_id:SubmeshFaceId)->impl AsRef<[SubmeshDirectedEdgeId]>{
self.view.face_edges(face_id) self.view.face_edges(face_id)
@@ -558,11 +588,20 @@ impl MeshQuery for TransformedMesh<'_>{
//(face,vertex) //(face,vertex)
//(edge,edge) //(edge,edge)
//(vertex,face) //(vertex,face)
#[derive(Clone,Copy,Debug)] #[derive(Clone,Copy,Debug,Eq,PartialEq)]
pub enum MinkowskiVert{ pub enum MinkowskiVert{
VertVert(SubmeshVertId,SubmeshVertId), VertVert(SubmeshVertId,SubmeshVertId),
} }
#[derive(Clone,Copy,Debug)] // 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,Eq,PartialEq)]
pub enum MinkowskiEdge{ pub enum MinkowskiEdge{
VertEdge(SubmeshVertId,SubmeshEdgeId), VertEdge(SubmeshVertId,SubmeshEdgeId),
EdgeVert(SubmeshEdgeId,SubmeshVertId), EdgeVert(SubmeshEdgeId,SubmeshVertId),
@@ -577,7 +616,7 @@ impl UndirectedEdge for MinkowskiEdge{
} }
} }
} }
#[derive(Clone,Copy,Debug)] #[derive(Clone,Copy,Debug,Eq,PartialEq)]
pub enum MinkowskiDirectedEdge{ pub enum MinkowskiDirectedEdge{
VertEdge(SubmeshVertId,SubmeshDirectedEdgeId), VertEdge(SubmeshVertId,SubmeshDirectedEdgeId),
EdgeVert(SubmeshDirectedEdgeId,SubmeshVertId), EdgeVert(SubmeshDirectedEdgeId,SubmeshVertId),
@@ -598,7 +637,7 @@ impl DirectedEdge for MinkowskiDirectedEdge{
} }
} }
} }
#[derive(Clone,Copy,Debug,Hash,Eq,PartialEq)] #[derive(Clone,Copy,Debug,Hash)]
pub enum MinkowskiFace{ pub enum MinkowskiFace{
VertFace(SubmeshVertId,SubmeshFaceId), VertFace(SubmeshVertId,SubmeshFaceId),
EdgeEdge(SubmeshEdgeId,SubmeshEdgeId,bool), EdgeEdge(SubmeshEdgeId,SubmeshEdgeId,bool),
@@ -614,23 +653,20 @@ pub struct MinkowskiMesh<'a>{
mesh1: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>>; pub type GigaTime=Ratio<Fixed<4,128>,Fixed<4,128>>;
pub fn into_giga_time(time:Time,relative_to:Time)->GigaTime{ pub fn into_giga_time(time:Time,relative_to:Time)->GigaTime{
let r=(time-relative_to).to_ratio(); let r=(time-relative_to).to_ratio();
Ratio::new(r.num.widen_4(),r.den.widen_4()) Ratio::new(r.num.widen_4(),r.den.widen_4())
} }
// 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<'_>{ impl MinkowskiMesh<'_>{
pub fn minkowski_sum<'a>(mesh0:TransformedMesh<'a>,mesh1:TransformedMesh<'a>)->MinkowskiMesh<'a>{ pub fn minkowski_sum<'a>(mesh0:TransformedMesh<'a>,mesh1:TransformedMesh<'a>)->MinkowskiMesh<'a>{
MinkowskiMesh{ MinkowskiMesh{
@@ -638,140 +674,27 @@ impl MinkowskiMesh<'_>{
mesh1, 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).as_ref(){
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.as_ref()[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).as_ref(){
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);
//wrap for speed
(c.dot(c)/edge_nn).divide().wrap_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).as_ref();
self.vert(v0)+self.vert(v1)
};
for (i,&face_id) in self.edge_faces(edge_id).as_ref().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:Bound<&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
infinity_fev.crawl(self,&infinity_body,Bound::Unbounded,start_time).miss()
})
}
pub fn predict_collision_in(&self,relative_body:&Body,range:impl RangeBounds<Time>)->Option<(MinkowskiFace,GigaTime)>{ pub fn predict_collision_in(&self,relative_body:&Body,range:impl RangeBounds<Time>)->Option<(MinkowskiFace,GigaTime)>{
self.closest_fev_not_inside(*relative_body,range.start_bound()).and_then(|fev|{ let fev=crate::minimum_difference::closest_fev_not_inside(self,relative_body.position)?;
//continue forwards along the body parabola //continue forwards along the body parabola
fev.crawl(self,relative_body,range.start_bound(),range.end_bound()).hit() fev.crawl(self,relative_body,range.start_bound(),range.end_bound()).hit()
})
} }
pub fn predict_collision_out(&self,relative_body:&Body,range:impl RangeBounds<Time>)->Option<(MinkowskiFace,GigaTime)>{ pub fn predict_collision_out(&self,relative_body:&Body,range:impl RangeBounds<Time>)->Option<(MinkowskiFace,GigaTime)>{
let (lower_bound,upper_bound)=(range.start_bound(),range.end_bound()); let (lower_bound,upper_bound)=(range.start_bound(),range.end_bound());
// TODO: handle unbounded collision using infinity fev
let time=match upper_bound{
Bound::Included(&time)=>time,
Bound::Excluded(&time)=>time,
Bound::Unbounded=>unimplemented!("unbounded collision out"),
};
let fev=crate::minimum_difference::closest_fev_not_inside(self,relative_body.extrapolated_position(time))?;
// swap and negate bounds to do a time inversion // 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 (lower_bound,upper_bound)=(upper_bound.map(|&t|-t),lower_bound.map(|&t|-t));
let infinity_body=-relative_body; let infinity_body=-relative_body;
self.closest_fev_not_inside(infinity_body,lower_bound.as_ref()).and_then(|fev|{ //continue backwards along the body parabola
//continue backwards along the body parabola fev.crawl(self,&infinity_body,lower_bound.as_ref(),upper_bound.as_ref()).hit()
fev.crawl(self,&infinity_body,lower_bound.as_ref(),upper_bound.as_ref()).hit() //no need to test -time<time_limit because of the first step
//no need to test -time<time_limit because of the first step .map(|(face,time)|(face,-time))
.map(|(face,time)|(face,-time))
})
} }
pub fn predict_collision_face_out(&self,relative_body:&Body,range:impl RangeBounds<Time>,contact_face_id:MinkowskiFace)->Option<(MinkowskiDirectedEdge,GigaTime)>{ pub fn predict_collision_face_out(&self,relative_body:&Body,range:impl RangeBounds<Time>,contact_face_id:MinkowskiFace)->Option<(MinkowskiDirectedEdge,GigaTime)>{
// TODO: make better // TODO: make better
@@ -801,20 +724,8 @@ impl MinkowskiMesh<'_>{
} }
best_edge best_edge
} }
fn infinity_in(&self,infinity_body:Body)->Option<(MinkowskiFace,GigaTime)>{ pub fn contains_point(&self,point:Planar64Vec3)->bool{
let infinity_fev=self.infinity_fev(-infinity_body.velocity,infinity_body.position); crate::minimum_difference::contains_point(self,point)
// Bound::Included means that the surface of the mesh is included in the mesh
infinity_fev.crawl(self,&infinity_body,Bound::Unbounded,Bound::Included(&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<'_>{ impl MeshQuery for MinkowskiMesh<'_>{
@@ -853,6 +764,12 @@ impl MeshQuery for MinkowskiMesh<'_>{
}, },
} }
} }
fn hint_point(&self)->Planar64Vec3{
self.mesh0.transform.vertex.translation-self.mesh1.transform.vertex.translation
}
fn farthest_vert(&self,dir:Planar64Vec3)->MinkowskiVert{
MinkowskiVert::VertVert(self.mesh0.farthest_vert(dir),self.mesh1.farthest_vert(-dir))
}
fn face_edges(&self,face_id:MinkowskiFace)->impl AsRef<[MinkowskiDirectedEdge]>{ fn face_edges(&self,face_id:MinkowskiFace)->impl AsRef<[MinkowskiDirectedEdge]>{
match face_id{ match face_id{
MinkowskiFace::VertFace(v0,f1)=>{ MinkowskiFace::VertFace(v0,f1)=>{

View File

@@ -1,4 +1,5 @@
use std::collections::{HashMap,HashSet}; use std::collections::{HashMap,HashSet};
use crate::model::DirectedEdge;
use crate::model::{self as model_physics,PhysicsMesh,PhysicsMeshTransform,TransformedMesh,MeshQuery,PhysicsMeshId,PhysicsSubmeshId}; use crate::model::{self as model_physics,PhysicsMesh,PhysicsMeshTransform,TransformedMesh,MeshQuery,PhysicsMeshId,PhysicsSubmeshId};
use strafesnet_common::bvh; use strafesnet_common::bvh;
use strafesnet_common::map; use strafesnet_common::map;
@@ -113,11 +114,11 @@ struct ContactMoveState{
} }
impl TransientAcceleration{ impl TransientAcceleration{
fn with_target_diff(target_diff:Planar64Vec3,accel:Planar64,time:Time)->Self{ fn with_target_diff(target_diff:Planar64Vec3,accel:Planar64,time:Time)->Self{
if target_diff==vec3::ZERO{ if target_diff==vec3::zero(){
TransientAcceleration::Reached TransientAcceleration::Reached
}else if accel==Planar64::ZERO{ }else if accel==Planar64::ZERO{
TransientAcceleration::Unreachable{ TransientAcceleration::Unreachable{
acceleration:vec3::ZERO acceleration:vec3::zero()
} }
}else{ }else{
//normal friction acceleration is clippedAcceleration.dot(normal)*friction //normal friction acceleration is clippedAcceleration.dot(normal)*friction
@@ -140,7 +141,7 @@ impl TransientAcceleration{
} }
fn acceleration(&self)->Planar64Vec3{ fn acceleration(&self)->Planar64Vec3{
match self{ match self{
TransientAcceleration::Reached=>vec3::ZERO, TransientAcceleration::Reached=>vec3::zero(),
&TransientAcceleration::Reachable{acceleration,time:_}=>acceleration, &TransientAcceleration::Reachable{acceleration,time:_}=>acceleration,
&TransientAcceleration::Unreachable{acceleration}=>acceleration, &TransientAcceleration::Unreachable{acceleration}=>acceleration,
} }
@@ -286,7 +287,8 @@ impl PhysicsCamera{
.clamp(Self::ANGLE_PITCH_LOWER_LIMIT,Self::ANGLE_PITCH_UPPER_LIMIT); .clamp(Self::ANGLE_PITCH_LOWER_LIMIT,Self::ANGLE_PITCH_UPPER_LIMIT);
mat3::from_rotation_yx(ax,ay) mat3::from_rotation_yx(ax,ay)
} }
fn rotation(&self)->Planar64Mat3{ #[inline]
pub fn rotation(&self)->Planar64Mat3{
self.get_rotation(self.clamped_mouse_pos) self.get_rotation(self.clamped_mouse_pos)
} }
#[expect(dead_code)] #[expect(dead_code)]
@@ -443,7 +445,7 @@ impl StyleHelper for StyleModifiers{
fn get_control_dir(&self,controls:Controls)->Planar64Vec3{ fn get_control_dir(&self,controls:Controls)->Planar64Vec3{
//don't get fancy just do it //don't get fancy just do it
let mut control_dir:Planar64Vec3=vec3::ZERO; let mut control_dir:Planar64Vec3=vec3::zero();
//Apply mask after held check so you can require non-allowed keys to be held for some reason //Apply mask after held check so you can require non-allowed keys to be held for some reason
let controls=controls.intersection(self.controls_mask); let controls=controls.intersection(self.controls_mask);
if controls.contains(Controls::MoveForward){ if controls.contains(Controls::MoveForward){
@@ -482,7 +484,7 @@ impl StyleHelper for StyleModifiers{
}; };
let transform=integer::Planar64Affine3::new( let transform=integer::Planar64Affine3::new(
mat3::from_diagonal(self.hitbox.halfsize), mat3::from_diagonal(self.hitbox.halfsize),
vec3::ZERO vec3::zero()
); );
HitboxMesh::new(mesh,transform) HitboxMesh::new(mesh,transform)
} }
@@ -500,7 +502,7 @@ impl MoveState{
//call this after state.move_state is changed //call this after state.move_state is changed
fn apply_enum(&self,body:&mut Body,touching:&TouchingState,models:&PhysicsModels,hitbox_mesh:&HitboxMesh,style:&StyleModifiers,camera:&PhysicsCamera,input_state:&InputState){ fn apply_enum(&self,body:&mut Body,touching:&TouchingState,models:&PhysicsModels,hitbox_mesh:&HitboxMesh,style:&StyleModifiers,camera:&PhysicsCamera,input_state:&InputState){
match self{ match self{
MoveState::Fly=>body.acceleration=vec3::ZERO, MoveState::Fly=>body.acceleration=vec3::zero(),
MoveState::Air=>{ MoveState::Air=>{
//calculate base acceleration //calculate base acceleration
let a=touching.base_acceleration(models,style,camera,input_state); let a=touching.base_acceleration(models,style,camera,input_state);
@@ -614,7 +616,7 @@ impl MoveState{
// TODO: unduplicate this code // TODO: unduplicate this code
match self.get_walk_state(){ match self.get_walk_state(){
// did you stop touching the thing you were walking on? // did you stop touching the thing you were walking on?
Some(walk_state)=>if !touching.contacts.contains_key(&walk_state.contact.convex_mesh_id){ Some(walk_state)=>if !touching.contains_contact(&walk_state.contact.convex_mesh_id){
self.set_move_state(MoveState::Air,body,touching,models,hitbox_mesh,style,camera,input_state); self.set_move_state(MoveState::Air,body,touching,models,hitbox_mesh,style,camera,input_state);
}else{ }else{
// stopped touching something else while walking // stopped touching something else while walking
@@ -729,7 +731,7 @@ struct IntersectModel{
transform:PhysicsMeshTransform, transform:PhysicsMeshTransform,
} }
#[derive(Debug,Clone,Copy,Eq,Hash,PartialEq)] #[derive(Debug,Clone,Copy,Hash)]
pub struct ContactCollision{ pub struct ContactCollision{
convex_mesh_id:ConvexMeshId<ContactModelId>, convex_mesh_id:ConvexMeshId<ContactModelId>,
face_id:model_physics::MinkowskiFace, face_id:model_physics::MinkowskiFace,
@@ -738,7 +740,7 @@ pub struct ContactCollision{
pub struct IntersectCollision{ pub struct IntersectCollision{
convex_mesh_id:ConvexMeshId<IntersectModelId>, convex_mesh_id:ConvexMeshId<IntersectModelId>,
} }
#[derive(Debug,Clone,Eq,Hash,PartialEq)] #[derive(Debug,Clone,Hash)]
pub enum Collision{ pub enum Collision{
Contact(ContactCollision), Contact(ContactCollision),
Intersect(IntersectCollision), Intersect(IntersectCollision),
@@ -753,6 +755,8 @@ impl Collision{
} }
#[derive(Clone,Debug,Default)] #[derive(Clone,Debug,Default)]
struct TouchingState{ struct TouchingState{
// This is kind of jank, it's a ContactCollision
// but split over the Key and Value of the HashMap.
contacts:HashMap<ConvexMeshId<ContactModelId>,model_physics::MinkowskiFace>, contacts:HashMap<ConvexMeshId<ContactModelId>,model_physics::MinkowskiFace>,
intersects:HashSet<ConvexMeshId<IntersectModelId>>, intersects:HashSet<ConvexMeshId<IntersectModelId>>,
} }
@@ -773,10 +777,16 @@ impl TouchingState{
fn remove_intersect(&mut self,convex_mesh_id:&ConvexMeshId<IntersectModelId>)->bool{ fn remove_intersect(&mut self,convex_mesh_id:&ConvexMeshId<IntersectModelId>)->bool{
self.intersects.remove(convex_mesh_id) self.intersects.remove(convex_mesh_id)
} }
fn contains_contact(&self,convex_mesh_id:&ConvexMeshId<ContactModelId>)->bool{
self.contacts.contains_key(convex_mesh_id)
}
fn contains_intersect(&self,convex_mesh_id:&ConvexMeshId<IntersectModelId>)->bool{
self.intersects.contains(convex_mesh_id)
}
fn contains(&self,convex_mesh_id:&ConvexMeshId<PhysicsModelId>)->bool{ fn contains(&self,convex_mesh_id:&ConvexMeshId<PhysicsModelId>)->bool{
match convex_mesh_id.model_id{ match convex_mesh_id.model_id{
PhysicsModelId::Contact(contact_model_id)=>self.contacts.contains_key(&convex_mesh_id.map(contact_model_id)), PhysicsModelId::Contact(contact_model_id)=>self.contains_contact(&convex_mesh_id.map(contact_model_id)),
PhysicsModelId::Intersect(intersect_model_id)=>self.intersects.contains(&convex_mesh_id.map(intersect_model_id)), PhysicsModelId::Intersect(intersect_model_id)=>self.contains_intersect(&convex_mesh_id.map(intersect_model_id)),
} }
} }
fn base_acceleration(&self,models:&PhysicsModels,style:&StyleModifiers,camera:&PhysicsCamera,input_state:&InputState)->Planar64Vec3{ fn base_acceleration(&self,models:&PhysicsModels,style:&StyleModifiers,camera:&PhysicsCamera,input_state:&InputState)->Planar64Vec3{
@@ -802,7 +812,7 @@ impl TouchingState{
let contacts:Vec<_>=self.contacts.iter().map(|(convex_mesh_id,face_id)|{ let contacts:Vec<_>=self.contacts.iter().map(|(convex_mesh_id,face_id)|{
let n=contact_normal(models,hitbox_mesh,convex_mesh_id,*face_id); let n=contact_normal(models,hitbox_mesh,convex_mesh_id,*face_id);
crate::push_solve::Contact{ crate::push_solve::Contact{
position:vec3::ZERO, position:vec3::zero(),
velocity:n, velocity:n,
normal:n, normal:n,
} }
@@ -813,7 +823,7 @@ impl TouchingState{
let contacts:Vec<_>=self.contacts.iter().map(|(convex_mesh_id,face_id)|{ let contacts:Vec<_>=self.contacts.iter().map(|(convex_mesh_id,face_id)|{
let n=contact_normal(models,hitbox_mesh,convex_mesh_id,*face_id); let n=contact_normal(models,hitbox_mesh,convex_mesh_id,*face_id);
crate::push_solve::Contact{ crate::push_solve::Contact{
position:vec3::ZERO, position:vec3::zero(),
velocity:n, velocity:n,
normal:n, normal:n,
} }
@@ -989,6 +999,45 @@ impl PhysicsData{
hitbox_mesh:StyleModifiers::default().calculate_mesh(), hitbox_mesh:StyleModifiers::default().calculate_mesh(),
} }
} }
pub fn trace_ray(&self,ray:strafesnet_common::ray::Ray)->Option<ModelId>{
let (_time,convex_mesh_id)=self.bvh.sample_ray(&ray,Time::ZERO,Time::MAX/4,|&model,ray|{
let mesh=self.models.mesh(model);
// brute force trace every face
let faces=mesh.faces().filter_map(|face_id|{
let (n,d)=mesh.face_nd(face_id);
// trace ray onto face
// n.(o+d*t)==n.p
// n.o + n.d * t == n.p
// t == (n.p - n.o)/n.d
let nd=n.dot(ray.direction);
if nd.is_zero(){
return None;
}
let t=(d-n.dot(ray.origin))/nd;
Some((face_id,t,n))
});
let mut min=None;
for (face_id,t,n) in faces{
if let Some(min)=min&&min<t{
continue;
}
// check if point of intersection is behind face edges
// *2 because average of 2 vertices
let p=ray.extrapolate(t)*2;
let is_inside_face=mesh.face_edges(face_id).as_ref().iter().all(|&directed_edge_id|{
let edge_n=mesh.directed_edge_n(directed_edge_id);
let cross_n=edge_n.cross(n);
let &[vert0,vert1]=mesh.edge_verts(directed_edge_id.as_undirected()).as_ref();
cross_n.dot(p)<cross_n.dot(mesh.vert(vert0)+mesh.vert(vert1))
});
if is_inside_face{
min=Some(t);
}
}
min.map(Into::into)
})?;
Some(convex_mesh_id.model_id.into())
}
pub fn new(map:&map::CompleteMap)->Self{ pub fn new(map:&map::CompleteMap)->Self{
let modes=map.modes.clone().denormalize(); let modes=map.modes.clone().denormalize();
let mut used_contact_attributes=Vec::new(); let mut used_contact_attributes=Vec::new();
@@ -1269,7 +1318,7 @@ fn recalculate_touching(
//no checks are needed because of the time limits. //no checks are needed because of the time limits.
let model_mesh=models.mesh(convex_mesh_id); let model_mesh=models.mesh(convex_mesh_id);
let minkowski=model_physics::MinkowskiMesh::minkowski_sum(model_mesh,hitbox_mesh.transformed_mesh()); let minkowski=model_physics::MinkowskiMesh::minkowski_sum(model_mesh,hitbox_mesh.transformed_mesh());
if minkowski.is_point_in_mesh(body.position){ if minkowski.contains_point(body.position){
match convex_mesh_id.model_id{ match convex_mesh_id.model_id{
//being inside of contact objects is an invalid physics state //being inside of contact objects is an invalid physics state
//but the physics isn't advanced enough to do anything about it yet //but the physics isn't advanced enough to do anything about it yet
@@ -1564,7 +1613,7 @@ fn collision_start_contact(
//kill v //kill v
//actually you could do this with a booster attribute :thinking: //actually you could do this with a booster attribute :thinking:
//it's a little bit different because maybe you want to chain ladders together //it's a little bit different because maybe you want to chain ladders together
set_velocity(body,touching,models,hitbox_mesh,vec3::ZERO);//model.velocity set_velocity(body,touching,models,hitbox_mesh,vec3::zero());//model.velocity
} }
//ladder walkstate //ladder walkstate
let (gravity,target_velocity)=ladder_things(ladder_settings,&contact,touching,models,hitbox_mesh,style,camera,input_state); let (gravity,target_velocity)=ladder_things(ladder_settings,&contact,touching,models,hitbox_mesh,style,camera,input_state);
@@ -1791,7 +1840,7 @@ fn atomic_internal_instruction(state:&mut PhysicsState,data:&PhysicsData,ins:Tim
if strafe_settings.activates(controls){ if strafe_settings.activates(controls){
let masked_controls=strafe_settings.mask(controls); let masked_controls=strafe_settings.mask(controls);
let control_dir=state.style.get_control_dir(masked_controls); let control_dir=state.style.get_control_dir(masked_controls);
if control_dir!=vec3::ZERO{ if control_dir!=vec3::zero(){
let camera_mat=state.camera.simulate_move_rotation_y(state.input_state.lerp_delta(state.time).x); let camera_mat=state.camera.simulate_move_rotation_y(state.input_state.lerp_delta(state.time).x);
if let Some(ticked_velocity)=strafe_settings.tick_velocity(state.body.velocity,(camera_mat*control_dir).with_length(Planar64::ONE).divide().wrap_1()){ if let Some(ticked_velocity)=strafe_settings.tick_velocity(state.body.velocity,(camera_mat*control_dir).with_length(Planar64::ONE).divide().wrap_1()){
//this is wrong but will work ig //this is wrong but will work ig
@@ -1814,7 +1863,7 @@ fn atomic_internal_instruction(state:&mut PhysicsState,data:&PhysicsData,ins:Tim
//which means that gravity can be fully cancelled //which means that gravity can be fully cancelled
//ignore moving platforms for now //ignore moving platforms for now
let target=core::mem::replace(&mut walk_state.target,TransientAcceleration::Reached); let target=core::mem::replace(&mut walk_state.target,TransientAcceleration::Reached);
set_acceleration(&mut state.body,&state.touching,&data.models,&data.hitbox_mesh,vec3::ZERO); set_acceleration(&mut state.body,&state.touching,&data.models,&data.hitbox_mesh,vec3::zero());
// check what the target was to see if it was invalid // check what the target was to see if it was invalid
match target{ match target{
//you are not supposed to reach a walk target which is already reached! //you are not supposed to reach a walk target which is already reached!
@@ -1896,7 +1945,7 @@ fn atomic_input_instruction(state:&mut PhysicsState,data:&PhysicsData,ins:TimedI
}, },
Instruction::Mode(ModeInstruction::Restart(mode_id))=>{ Instruction::Mode(ModeInstruction::Restart(mode_id))=>{
//teleport to mode start zone //teleport to mode start zone
let mut spawn_point=vec3::ZERO; let mut spawn_point=vec3::zero();
let mode=data.modes.get_mode(mode_id); let mode=data.modes.get_mode(mode_id);
if let Some(mode)=mode{ if let Some(mode)=mode{
// set style // set style
@@ -1909,7 +1958,7 @@ fn atomic_input_instruction(state:&mut PhysicsState,data:&PhysicsData,ins:TimedI
} }
} }
set_position(spawn_point,&mut state.move_state,&mut state.body,&mut state.touching,&mut state.run,&mut state.mode_state,mode,&data.models,&data.hitbox_mesh,&data.bvh,&state.style,&state.camera,&state.input_state,state.time); set_position(spawn_point,&mut state.move_state,&mut state.body,&mut state.touching,&mut state.run,&mut state.mode_state,mode,&data.models,&data.hitbox_mesh,&data.bvh,&state.style,&state.camera,&state.input_state,state.time);
set_velocity(&mut state.body,&state.touching,&data.models,&data.hitbox_mesh,vec3::ZERO); set_velocity(&mut state.body,&state.touching,&data.models,&data.hitbox_mesh,vec3::zero());
state.set_move_state(data,MoveState::Air); state.set_move_state(data,MoveState::Air);
b_refresh_walk_target=false; b_refresh_walk_target=false;
} }
@@ -1959,7 +2008,7 @@ mod test{
use strafesnet_common::integer::{vec3::{self,int as int3},mat3}; use strafesnet_common::integer::{vec3::{self,int as int3},mat3};
use super::*; use super::*;
fn test_collision_axis_aligned(relative_body:Body,expected_collision_time:Option<Time>){ fn test_collision_axis_aligned(relative_body:Body,expected_collision_time:Option<Time>){
let h0=HitboxMesh::new(PhysicsMesh::unit_cube(),integer::Planar64Affine3::new(mat3::from_diagonal(int3(5,1,5)>>1),vec3::ZERO)); let h0=HitboxMesh::new(PhysicsMesh::unit_cube(),integer::Planar64Affine3::new(mat3::from_diagonal(int3(5,1,5)>>1),vec3::zero()));
let h1=StyleModifiers::roblox_bhop().calculate_mesh(); let h1=StyleModifiers::roblox_bhop().calculate_mesh();
let hitbox_mesh=h1.transformed_mesh(); let hitbox_mesh=h1.transformed_mesh();
let platform_mesh=h0.transformed_mesh(); let platform_mesh=h0.transformed_mesh();
@@ -1975,7 +2024,7 @@ mod test{
int3(0,1,0)>>1, int3(0,1,0)>>1,
int3(-1,0,5)>>1, int3(-1,0,5)>>1,
]), ]),
vec3::ZERO vec3::zero()
), ),
); );
let h1=StyleModifiers::roblox_bhop().calculate_mesh(); let h1=StyleModifiers::roblox_bhop().calculate_mesh();
@@ -1994,7 +2043,7 @@ mod test{
test_collision(Body::new( test_collision(Body::new(
int3(0,5,0), int3(0,5,0),
int3(0,-1,0), int3(0,-1,0),
vec3::ZERO, vec3::zero(),
Time::ZERO Time::ZERO
),Some(Time::from_secs(2))); ),Some(Time::from_secs(2)));
} }
@@ -2003,7 +2052,7 @@ mod test{
test_collision(Body::new( test_collision(Body::new(
int3(0,5,0), int3(0,5,0),
int3(0,-1,0)+(vec3::X>>32), int3(0,-1,0)+(vec3::X>>32),
vec3::ZERO, vec3::zero(),
Time::ZERO Time::ZERO
),Some(Time::from_secs(2))); ),Some(Time::from_secs(2)));
} }
@@ -2012,7 +2061,7 @@ mod test{
test_collision(Body::new( test_collision(Body::new(
int3(3,5,0), int3(3,5,0),
int3(0,-1,0), int3(0,-1,0),
vec3::ZERO, vec3::zero(),
Time::ZERO Time::ZERO
),Some(Time::from_secs(2))); ),Some(Time::from_secs(2)));
} }
@@ -2021,7 +2070,7 @@ mod test{
test_collision(Body::new( test_collision(Body::new(
int3(0,5,3), int3(0,5,3),
int3(0,-1,0), int3(0,-1,0),
vec3::ZERO, vec3::zero(),
Time::ZERO Time::ZERO
),Some(Time::from_secs(2))); ),Some(Time::from_secs(2)));
} }
@@ -2030,7 +2079,7 @@ mod test{
test_collision(Body::new( test_collision(Body::new(
int3(-3,5,0), int3(-3,5,0),
int3(0,-1,0), int3(0,-1,0),
vec3::ZERO, vec3::zero(),
Time::ZERO Time::ZERO
),Some(Time::from_secs(2))); ),Some(Time::from_secs(2)));
} }
@@ -2039,7 +2088,7 @@ mod test{
test_collision(Body::new( test_collision(Body::new(
int3(0,5,-3), int3(0,5,-3),
int3(0,-1,0), int3(0,-1,0),
vec3::ZERO, vec3::zero(),
Time::ZERO Time::ZERO
),Some(Time::from_secs(2))); ),Some(Time::from_secs(2)));
} }
@@ -2156,7 +2205,7 @@ mod test{
test_collision(Body::new( test_collision(Body::new(
int3(0,5,0), int3(0,5,0),
int3(1,-64,2)>>6,// /64 int3(1,-64,2)>>6,// /64
vec3::ZERO, vec3::zero(),
Time::ZERO Time::ZERO
),Some(Time::from_secs(2))); ),Some(Time::from_secs(2)));
} }
@@ -2172,7 +2221,7 @@ mod test{
#[test] #[test]
fn already_inside_hit_nothing(){ fn already_inside_hit_nothing(){
test_collision(Body::new( test_collision(Body::new(
vec3::ZERO, vec3::zero(),
int3(1,0,0), int3(1,0,0),
int3(0,1,0), int3(0,1,0),
Time::ZERO Time::ZERO
@@ -2184,7 +2233,7 @@ mod test{
test_collision_axis_aligned(Body::new( test_collision_axis_aligned(Body::new(
(int3(0,10,-7)>>1)+vec3::raw_xyz(0,0,1), (int3(0,10,-7)>>1)+vec3::raw_xyz(0,0,1),
int3(0,-1,0), int3(0,-1,0),
vec3::ZERO, vec3::zero(),
Time::ZERO Time::ZERO
),Some(Time::from_secs(2))) ),Some(Time::from_secs(2)))
} }
@@ -2193,7 +2242,7 @@ mod test{
test_collision_axis_aligned(Body::new( test_collision_axis_aligned(Body::new(
(int3(7,10,0)>>1)+vec3::raw_xyz(-1,0,0), (int3(7,10,0)>>1)+vec3::raw_xyz(-1,0,0),
int3(0,-1,0), int3(0,-1,0),
vec3::ZERO, vec3::zero(),
Time::ZERO Time::ZERO
),Some(Time::from_secs(2))) ),Some(Time::from_secs(2)))
} }
@@ -2202,7 +2251,7 @@ mod test{
test_collision_axis_aligned(Body::new( test_collision_axis_aligned(Body::new(
(int3(0,10,7)>>1)+vec3::raw_xyz(0,0,-1), (int3(0,10,7)>>1)+vec3::raw_xyz(0,0,-1),
int3(0,-1,0), int3(0,-1,0),
vec3::ZERO, vec3::zero(),
Time::ZERO Time::ZERO
),Some(Time::from_secs(2))) ),Some(Time::from_secs(2)))
} }
@@ -2211,7 +2260,7 @@ mod test{
test_collision_axis_aligned(Body::new( test_collision_axis_aligned(Body::new(
(int3(-7,10,0)>>1)+vec3::raw_xyz(1,0,0), (int3(-7,10,0)>>1)+vec3::raw_xyz(1,0,0),
int3(0,-1,0), int3(0,-1,0),
vec3::ZERO, vec3::zero(),
Time::ZERO Time::ZERO
),Some(Time::from_secs(2))) ),Some(Time::from_secs(2)))
} }
@@ -2221,7 +2270,7 @@ mod test{
test_collision_axis_aligned(Body::new( test_collision_axis_aligned(Body::new(
int3(0,10,-7)>>1, int3(0,10,-7)>>1,
int3(0,-1,0), int3(0,-1,0),
vec3::ZERO, vec3::zero(),
Time::ZERO Time::ZERO
),None) ),None)
} }
@@ -2230,7 +2279,7 @@ mod test{
test_collision_axis_aligned(Body::new( test_collision_axis_aligned(Body::new(
int3(7,10,0)>>1, int3(7,10,0)>>1,
int3(0,-1,0), int3(0,-1,0),
vec3::ZERO, vec3::zero(),
Time::ZERO Time::ZERO
),None) ),None)
} }
@@ -2239,7 +2288,7 @@ mod test{
test_collision_axis_aligned(Body::new( test_collision_axis_aligned(Body::new(
int3(0,10,7)>>1, int3(0,10,7)>>1,
int3(0,-1,0), int3(0,-1,0),
vec3::ZERO, vec3::zero(),
Time::ZERO Time::ZERO
),None) ),None)
} }
@@ -2248,7 +2297,7 @@ mod test{
test_collision_axis_aligned(Body::new( test_collision_axis_aligned(Body::new(
int3(-7,10,0)>>1, int3(-7,10,0)>>1,
int3(0,-1,0), int3(0,-1,0),
vec3::ZERO, vec3::zero(),
Time::ZERO Time::ZERO
),None) ),None)
} }
@@ -2258,7 +2307,7 @@ mod test{
test_collision_axis_aligned(Body::new( test_collision_axis_aligned(Body::new(
(int3(0,10,-7)>>1)-vec3::raw_xyz(0,0,1), (int3(0,10,-7)>>1)-vec3::raw_xyz(0,0,1),
int3(0,-1,0), int3(0,-1,0),
vec3::ZERO, vec3::zero(),
Time::ZERO Time::ZERO
),None) ),None)
} }
@@ -2267,7 +2316,7 @@ mod test{
test_collision_axis_aligned(Body::new( test_collision_axis_aligned(Body::new(
(int3(7,10,0)>>1)-vec3::raw_xyz(-1,0,0), (int3(7,10,0)>>1)-vec3::raw_xyz(-1,0,0),
int3(0,-1,0), int3(0,-1,0),
vec3::ZERO, vec3::zero(),
Time::ZERO Time::ZERO
),None) ),None)
} }
@@ -2276,7 +2325,7 @@ mod test{
test_collision_axis_aligned(Body::new( test_collision_axis_aligned(Body::new(
(int3(0,10,7)>>1)-vec3::raw_xyz(0,0,-1), (int3(0,10,7)>>1)-vec3::raw_xyz(0,0,-1),
int3(0,-1,0), int3(0,-1,0),
vec3::ZERO, vec3::zero(),
Time::ZERO Time::ZERO
),None) ),None)
} }
@@ -2285,7 +2334,7 @@ mod test{
test_collision_axis_aligned(Body::new( test_collision_axis_aligned(Body::new(
(int3(-7,10,0)>>1)-vec3::raw_xyz(1,0,0), (int3(-7,10,0)>>1)-vec3::raw_xyz(1,0,0),
int3(0,-1,0), int3(0,-1,0),
vec3::ZERO, vec3::zero(),
Time::ZERO Time::ZERO
),None) ),None)
} }

View File

@@ -39,20 +39,18 @@ impl Contact{
//note that this is horrible with fixed point arithmetic //note that this is horrible with fixed point arithmetic
fn solve1(c0:&Contact)->Option<Ratio<Vector3<Fixed<3,96>>,Fixed<2,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); let det=c0.normal.dot(c0.velocity);
if det.abs()<EPSILON{ if det.abs()==Fixed::ZERO{
return None; return None;
} }
let d0=c0.normal.dot(c0.position); let d0=c0.normal.dot(c0.position);
Some(c0.normal*d0/det) Some(c0.normal*d0/det)
} }
fn solve2(c0:&Contact,c1:&Contact)->Option<Ratio<Vector3<Fixed<5,160>>,Fixed<4,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 u0_u1=c0.velocity.cross(c1.velocity);
let n0_n1=c0.normal.cross(c1.normal); let n0_n1=c0.normal.cross(c1.normal);
let det=u0_u1.dot(n0_n1); let det=u0_u1.dot(n0_n1);
if det.abs()<EPSILON{ if det.abs()==Fixed::ZERO{
return None; return None;
} }
let d0=c0.normal.dot(c0.position); let d0=c0.normal.dot(c0.position);
@@ -60,10 +58,9 @@ fn solve2(c0:&Contact,c1:&Contact)->Option<Ratio<Vector3<Fixed<5,160>>,Fixed<4,1
Some((c1.normal.cross(u0_u1)*d0+u0_u1.cross(c0.normal)*d1)/det) Some((c1.normal.cross(u0_u1)*d0+u0_u1.cross(c0.normal)*d1)/det)
} }
fn solve3(c0:&Contact,c1:&Contact,c2:&Contact)->Option<Ratio<Vector3<Fixed<4,128>>,Fixed<3,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 n0_n1=c0.normal.cross(c1.normal);
let det=c2.normal.dot(n0_n1); let det=c2.normal.dot(n0_n1);
if det.abs()<EPSILON{ if det.abs()==Fixed::ZERO{
return None; return None;
} }
let d0=c0.normal.dot(c0.position); let d0=c0.normal.dot(c0.position);
@@ -149,7 +146,7 @@ fn is_space_enclosed_4(
} }
const fn get_push_ray_0(point:Planar64Vec3)->Ray{ 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>{ fn get_push_ray_1(point:Planar64Vec3,c0:&Contact)->Option<Ray>{
//wrap for speed //wrap for speed
@@ -321,13 +318,13 @@ mod tests{
fn test_push_solve(){ fn test_push_solve(){
let contacts=vec![ let contacts=vec![
Contact{ Contact{
position:vec3::ZERO, position:vec3::zero(),
velocity:vec3::Y, velocity:vec3::Y,
normal:vec3::Y, normal:vec3::Y,
} }
]; ];
assert_eq!( assert_eq!(
vec3::ZERO, vec3::zero(),
push_solve(&contacts,vec3::NEG_Y) push_solve(&contacts,vec3::NEG_Y)
); );
} }

View File

@@ -2,6 +2,7 @@ use std::collections::HashMap;
use strafesnet_common::gameplay_modes::{ModeId,StageId}; use strafesnet_common::gameplay_modes::{ModeId,StageId};
use strafesnet_common::instruction::{InstructionConsumer,InstructionEmitter,InstructionFeedback,TimedInstruction}; use strafesnet_common::instruction::{InstructionConsumer,InstructionEmitter,InstructionFeedback,TimedInstruction};
use strafesnet_common::model::ModelId;
// session represents the non-hardware state of the client. // session represents the non-hardware state of the client.
// Ideally it is a deterministic state which is atomically updated by instructions, same as the simulation state. // Ideally it is a deterministic state which is atomically updated by instructions, same as the simulation state.
use strafesnet_common::physics::{ use strafesnet_common::physics::{
@@ -61,6 +62,7 @@ pub struct FrameState{
pub body:physics::Body, pub body:physics::Body,
pub camera:physics::PhysicsCamera, pub camera:physics::PhysicsCamera,
pub time:PhysicsTime, pub time:PhysicsTime,
pub debug_model:Option<ModelId>,
} }
pub struct Simulation{ pub struct Simulation{
@@ -77,11 +79,12 @@ impl Simulation{
physics, physics,
} }
} }
pub fn get_frame_state(&self,time:SessionTime)->FrameState{ pub fn get_frame_state(&self,time:SessionTime,debug_model:Option<ModelId>)->FrameState{
FrameState{ FrameState{
body:self.physics.camera_body(), body:self.physics.camera_body(),
camera:self.physics.camera(), camera:self.physics.camera(),
time:self.timer.time(time), time:self.timer.time(time),
debug_model,
} }
} }
} }
@@ -161,6 +164,7 @@ pub struct Session{
recording:Recording, recording:Recording,
//players:HashMap<PlayerId,Simulation>, //players:HashMap<PlayerId,Simulation>,
replays:HashMap<BotId,Replay>, replays:HashMap<BotId,Replay>,
last_ray_hit:Option<ModelId>,
} }
impl Session{ impl Session{
pub fn new( pub fn new(
@@ -177,6 +181,7 @@ impl Session{
view_state:ViewState::Play, view_state:ViewState::Play,
recording:Default::default(), recording:Default::default(),
replays:HashMap::new(), replays:HashMap::new(),
last_ray_hit:None,
} }
} }
fn clear_recording(&mut self){ fn clear_recording(&mut self){
@@ -188,12 +193,25 @@ impl Session{
} }
pub fn get_frame_state(&self,time:SessionTime)->Option<FrameState>{ pub fn get_frame_state(&self,time:SessionTime)->Option<FrameState>{
match &self.view_state{ match &self.view_state{
ViewState::Play=>Some(self.simulation.get_frame_state(time)), ViewState::Play=>Some(self.simulation.get_frame_state(time,self.last_ray_hit)),
ViewState::Replay(bot_id)=>self.replays.get(bot_id).map(|replay| ViewState::Replay(bot_id)=>self.replays.get(bot_id).map(|replay|
replay.simulation.get_frame_state(time) replay.simulation.get_frame_state(time,None)
), ),
} }
} }
pub fn debug_raycast_print_model_id_if_changed(&mut self,time:SessionTime){
if let Some(frame_state)=self.get_frame_state(time){
let ray=strafesnet_common::ray::Ray{
origin:frame_state.body.extrapolated_position(self.simulation.timer.time(time)),
direction:-frame_state.camera.rotation().z_axis,
};
let model_id=self.geometry_shared.trace_ray(ray);
if model_id!=self.last_ray_hit{
println!("hit={model_id:?}");
self.last_ray_hit=model_id;
}
}
}
pub fn user_settings(&self)->&UserSettings{ pub fn user_settings(&self)->&UserSettings{
&self.user_settings &self.user_settings
} }

View File

@@ -77,7 +77,7 @@ fn simultaneous_collision(){
Time::ZERO, Time::ZERO,
); );
let mut physics=PhysicsState::new_with_body(body); let mut physics=PhysicsState::new_with_body(body);
physics.style_mut().gravity=vec3::ZERO; physics.style_mut().gravity=vec3::zero();
let mut phys_iter=PhysicsContext::iter_internal(&mut physics,&physics_data,Time::from_secs(2)) let mut phys_iter=PhysicsContext::iter_internal(&mut physics,&physics_data,Time::from_secs(2))
.filter(|ins|!matches!(ins.instruction,InternalInstruction::StrafeTick)); .filter(|ins|!matches!(ins.instruction,InternalInstruction::StrafeTick));
// the order that they hit does matter, but we aren't currently worrying about that. // the order that they hit does matter, but we aren't currently worrying about that.
@@ -101,7 +101,7 @@ fn bug_3(){
Time::ZERO, Time::ZERO,
); );
let mut physics=PhysicsState::new_with_body(body); let mut physics=PhysicsState::new_with_body(body);
physics.style_mut().gravity=vec3::ZERO; physics.style_mut().gravity=vec3::zero();
let mut phys_iter=PhysicsContext::iter_internal(&mut physics,&physics_data,Time::from_secs(3)) let mut phys_iter=PhysicsContext::iter_internal(&mut physics,&physics_data,Time::from_secs(3))
.filter(|ins|!matches!(ins.instruction,InternalInstruction::StrafeTick)); .filter(|ins|!matches!(ins.instruction,InternalInstruction::StrafeTick));
// touch side of part at 0,0,0 // touch side of part at 0,0,0

View File

@@ -210,7 +210,7 @@ pub fn faces_to_mesh(faces:Vec<Vec<integer::Planar64Vec3>>)->model::Mesh{
let color=mb.acquire_color_id(glam::Vec4::ONE); let color=mb.acquire_color_id(glam::Vec4::ONE);
let tex=mb.acquire_tex_id(glam::Vec2::ZERO); let tex=mb.acquire_tex_id(glam::Vec2::ZERO);
// normals are ignored by physics // 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|{ let polygon_list=faces.into_iter().map(|face|{
face.into_iter().map(|pos|{ face.into_iter().map(|pos|{

View File

@@ -105,7 +105,7 @@ pub fn convert<'a>(
water:Some(attr::IntersectingWater{ water:Some(attr::IntersectingWater{
viscosity:integer::Planar64::ONE, viscosity:integer::Planar64::ONE,
density:integer::Planar64::ONE, density:integer::Planar64::ONE,
velocity:integer::vec3::ZERO, velocity:integer::vec3::zero(),
}), }),
}, },
general:attr::GeneralAttributes::default(), general:attr::GeneralAttributes::default(),
@@ -295,7 +295,7 @@ pub fn convert<'a>(
attributes, attributes,
transform:integer::Planar64Affine3::new( transform:integer::Planar64Affine3::new(
integer::mat3::identity(), integer::mat3::identity(),
integer::vec3::ZERO, integer::vec3::zero(),
), ),
color:glam::Vec4::ONE, color:glam::Vec4::ONE,
}); });

View File

@@ -319,7 +319,7 @@ impl WalkSettings{
self.accelerate.accel.min((-gravity.y*friction).clamp_1()) self.accelerate.accel.min((-gravity.y*friction).clamp_1())
} }
pub fn get_walk_target_velocity(&self,control_dir:Planar64Vec3,normal:Planar64Vec3)->Planar64Vec3{ pub fn get_walk_target_velocity(&self,control_dir:Planar64Vec3,normal:Planar64Vec3)->Planar64Vec3{
if control_dir==crate::integer::vec3::ZERO{ if control_dir==crate::integer::vec3::zero(){
return control_dir; return control_dir;
} }
let nn=normal.length_squared(); let nn=normal.length_squared();
@@ -329,13 +329,13 @@ impl WalkSettings{
let dd=d*d; let dd=d*d;
if dd<nnmm{ if dd<nnmm{
let cr=normal.cross(control_dir); let cr=normal.cross(control_dir);
if cr==crate::integer::vec3::ZERO_2{ if cr==crate::integer::vec3::zero(){
crate::integer::vec3::ZERO crate::integer::vec3::zero()
}else{ }else{
(cr.cross(normal)*self.accelerate.topspeed/((nn*(nnmm-dd)).sqrt())).divide().clamp_1() (cr.cross(normal)*self.accelerate.topspeed/((nn*(nnmm-dd)).sqrt())).divide().clamp_1()
} }
}else{ }else{
crate::integer::vec3::ZERO crate::integer::vec3::zero()
} }
} }
pub fn is_slope_walkable(&self,normal:Planar64Vec3,up:Planar64Vec3)->bool{ pub fn is_slope_walkable(&self,normal:Planar64Vec3,up:Planar64Vec3)->bool{
@@ -360,7 +360,7 @@ impl LadderSettings{
self.accelerate.accel self.accelerate.accel
} }
pub fn get_ladder_target_velocity(&self,mut control_dir:Planar64Vec3,normal:Planar64Vec3)->Planar64Vec3{ pub fn get_ladder_target_velocity(&self,mut control_dir:Planar64Vec3,normal:Planar64Vec3)->Planar64Vec3{
if control_dir==crate::integer::vec3::ZERO{ if control_dir==crate::integer::vec3::zero(){
return control_dir; return control_dir;
} }
let nn=normal.length_squared(); let nn=normal.length_squared();
@@ -382,13 +382,13 @@ impl LadderSettings{
//- fix the underlying issue //- fix the underlying issue
if dd<nnmm{ if dd<nnmm{
let cr=normal.cross(control_dir); let cr=normal.cross(control_dir);
if cr==crate::integer::vec3::ZERO_2{ if cr==crate::integer::vec3::zero(){
crate::integer::vec3::ZERO crate::integer::vec3::zero()
}else{ }else{
(cr.cross(normal)*self.accelerate.topspeed/((nn*(nnmm-dd)).sqrt())).divide().clamp_1() (cr.cross(normal)*self.accelerate.topspeed/((nn*(nnmm-dd)).sqrt())).divide().clamp_1()
} }
}else{ }else{
crate::integer::vec3::ZERO crate::integer::vec3::zero()
} }
} }
} }

View File

@@ -561,12 +561,6 @@ pub mod vec3{
pub use linear_ops::types::Vector3; pub use linear_ops::types::Vector3;
pub const MIN:Planar64Vec3=Planar64Vec3::new([Planar64::MIN;3]); pub const MIN:Planar64Vec3=Planar64Vec3::new([Planar64::MIN;3]);
pub const MAX:Planar64Vec3=Planar64Vec3::new([Planar64::MAX;3]); pub const MAX:Planar64Vec3=Planar64Vec3::new([Planar64::MAX;3]);
pub const ZERO:Planar64Vec3=Planar64Vec3::new([Planar64::ZERO;3]);
pub const ZERO_2:Vector3<Fixed::<2,64>>=Vector3::new([Fixed::<2,64>::ZERO;3]);
pub const ZERO_3:Vector3<Fixed::<3,96>>=Vector3::new([Fixed::<3,96>::ZERO;3]);
pub const ZERO_4:Vector3<Fixed::<4,128>>=Vector3::new([Fixed::<4,128>::ZERO;3]);
pub const ZERO_5:Vector3<Fixed::<5,160>>=Vector3::new([Fixed::<5,160>::ZERO;3]);
pub const ZERO_6:Vector3<Fixed::<6,192>>=Vector3::new([Fixed::<6,192>::ZERO;3]);
pub const X:Planar64Vec3=Planar64Vec3::new([Planar64::ONE,Planar64::ZERO,Planar64::ZERO]); 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 Y:Planar64Vec3=Planar64Vec3::new([Planar64::ZERO,Planar64::ONE,Planar64::ZERO]);
pub const Z:Planar64Vec3=Planar64Vec3::new([Planar64::ZERO,Planar64::ZERO,Planar64::ONE]); pub const Z:Planar64Vec3=Planar64Vec3::new([Planar64::ZERO,Planar64::ZERO,Planar64::ONE]);
@@ -575,6 +569,10 @@ pub mod vec3{
pub const NEG_Y:Planar64Vec3=Planar64Vec3::new([Planar64::ZERO,Planar64::NEG_ONE,Planar64::ZERO]); pub const NEG_Y:Planar64Vec3=Planar64Vec3::new([Planar64::ZERO,Planar64::NEG_ONE,Planar64::ZERO]);
pub const NEG_Z:Planar64Vec3=Planar64Vec3::new([Planar64::ZERO,Planar64::ZERO,Planar64::NEG_ONE]); pub const NEG_Z:Planar64Vec3=Planar64Vec3::new([Planar64::ZERO,Planar64::ZERO,Planar64::NEG_ONE]);
pub const NEG_ONE:Planar64Vec3=Planar64Vec3::new([Planar64::NEG_ONE,Planar64::NEG_ONE,Planar64::NEG_ONE]); pub const NEG_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] #[inline]
pub const fn int(x:i32,y:i32,z:i32)->Planar64Vec3{ pub const fn int(x:i32,y:i32,z:i32)->Planar64Vec3{
Planar64Vec3::new([Planar64::raw((x as i64)<<32),Planar64::raw((y as i64)<<32),Planar64::raw((z as i64)<<32)]) Planar64Vec3::new([Planar64::raw((x as i64)<<32),Planar64::raw((y as i64)<<32),Planar64::raw((z as i64)<<32)])
@@ -663,7 +661,7 @@ pub struct Planar64Affine3{
pub translation:Planar64Vec3, pub translation:Planar64Vec3,
} }
impl Planar64Affine3{ impl Planar64Affine3{
pub const IDENTITY:Self=Self::new(mat3::identity(),vec3::ZERO); pub const IDENTITY:Self=Self::new(mat3::identity(),vec3::zero());
#[inline] #[inline]
pub const fn new(matrix3:Planar64Mat3,translation:Planar64Vec3)->Self{ pub const fn new(matrix3:Planar64Mat3,translation:Planar64Vec3)->Self{
Self{matrix3,translation} Self{matrix3,translation}

View File

@@ -70,6 +70,34 @@ impl<const N:usize,const F:usize> Fixed<N,F>{
pub const fn midpoint(self,other:Self)->Self{ pub const fn midpoint(self,other:Self)->Self{
Self::from_bits(self.bits.midpoint(other.bits)) Self::from_bits(self.bits.midpoint(other.bits))
} }
#[inline]
pub const fn min(self,other:Self)->Self{
Self::from_bits(self.bits.min(other.bits))
}
#[inline]
pub const fn max(self,other:Self)->Self{
Self::from_bits(self.bits.max(other.bits))
}
/// return the result of self*sign(other)
#[inline]
pub const fn mul_sign<const N1:usize,const F1:usize>(self,other:Fixed<N1,F1>)->Self{
if other.is_negative(){
Self::from_bits(self.bits.neg())
}else if other.is_zero(){
Fixed::ZERO
}else{
self
}
}
/// return the result of self/sign(other) (divide by zero does not change the sign)
#[inline]
pub const fn div_sign<const N1:usize,const F1:usize>(self,other:Fixed<N1,F1>)->Self{
if other.is_negative(){
Self::from_bits(self.bits.neg())
}else{
self
}
}
} }
impl<const F:usize> Fixed<1,F>{ impl<const F:usize> Fixed<1,F>{
/// My old code called this function everywhere so let's provide it /// My old code called this function everywhere so let's provide it
@@ -338,7 +366,7 @@ impl_additive_operator!( Fixed, BitXor, bitxor, Self );
// non-wide operators. The result is the same width as the inputs. // non-wide operators. The result is the same width as the inputs.
// This macro is not used in the default configuration. // This macro is not used in the default configuration.
#[allow(unused_macros)] #[expect(unused_macros)]
macro_rules! impl_multiplicative_operator_not_const_generic { macro_rules! impl_multiplicative_operator_not_const_generic {
( ($struct: ident, $trait: ident, $method: ident, $output: ty ), $width:expr ) => { ( ($struct: ident, $trait: ident, $method: ident, $output: ty ), $width:expr ) => {
impl<const F:usize> core::ops::$trait for $struct<$width,F>{ impl<const F:usize> core::ops::$trait for $struct<$width,F>{
@@ -517,7 +545,7 @@ impl_shift_operator!( Fixed, Shr, shr, Self );
// wide operators. The result width is the sum of the input widths, i.e. none of the multiplication // wide operators. The result width is the sum of the input widths, i.e. none of the multiplication
#[allow(unused_macros)] #[expect(unused_macros)]
macro_rules! impl_wide_operators{ macro_rules! impl_wide_operators{
($lhs:expr,$rhs:expr)=>{ ($lhs:expr,$rhs:expr)=>{
impl core::ops::Mul<Fixed<$rhs,{$rhs*32}>> for Fixed<$lhs,{$lhs*32}>{ impl core::ops::Mul<Fixed<$rhs,{$rhs*32}>> for Fixed<$lhs,{$lhs*32}>{

View File

@@ -13,16 +13,16 @@ authors = ["Rhys Lloyd <krakow20@gmail.com>"]
bytemuck = "1.14.3" bytemuck = "1.14.3"
glam = "0.30.0" glam = "0.30.0"
regex = { version = "1.11.3", default-features = false } regex = { version = "1.11.3", default-features = false }
rbx_binary = { version = "1.0.1-sn5", registry = "strafesnet" }
rbx_dom_weak = { version = "3.0.1-sn5", registry = "strafesnet" }
rbx_mesh = "0.5.0" rbx_mesh = "0.5.0"
rbx_reflection = "5.0.0"
rbx_reflection_database = "1.0.0"
rbx_xml = { version = "1.0.1-sn5", registry = "strafesnet" }
rbxassetid = { version = "0.1.0", path = "../rbxassetid", registry = "strafesnet" } rbxassetid = { version = "0.1.0", path = "../rbxassetid", registry = "strafesnet" }
roblox_emulator = { version = "0.5.1", path = "../roblox_emulator", default-features = false, registry = "strafesnet" } roblox_emulator = { version = "0.5.1", path = "../roblox_emulator", default-features = false, registry = "strafesnet" }
strafesnet_common = { version = "0.7.0", path = "../common", registry = "strafesnet" } strafesnet_common = { version = "0.7.0", path = "../common", registry = "strafesnet" }
strafesnet_deferred_loader = { version = "0.5.1", path = "../deferred_loader", registry = "strafesnet" } strafesnet_deferred_loader = { version = "0.5.1", path = "../deferred_loader", registry = "strafesnet" }
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] [lints]
workspace = true workspace = true

View File

@@ -251,7 +251,7 @@ fn get_attributes(name:&str,can_collide:bool,velocity:Planar64Vec3,model_id:mode
} }
} }
//need some way to skip this //need some way to skip this
if allow_booster&&velocity!=vec3::ZERO{ if allow_booster&&velocity!=vec3::zero(){
general.booster=Some(attr::Booster::Velocity(velocity)); general.booster=Some(attr::Booster::Velocity(velocity));
} }
Ok(match force_can_collide{ Ok(match force_can_collide{
@@ -559,7 +559,7 @@ pub fn convert<'a>(
//just going to leave it like this for now instead of reworking the data structures for this whole thing //just going to leave it like this for now instead of reworking the data structures for this whole thing
let textureless_render_group=render_config_deferred_loader.acquire_render_config_id(None); let textureless_render_group=render_config_deferred_loader.acquire_render_config_id(None);
let db=rbx_reflection_database::get(); let db=rbx_reflection_database::get().unwrap();
let basepart=&db.classes["BasePart"]; let basepart=&db.classes["BasePart"];
let baseparts=dom.descendants().filter(|&instance| let baseparts=dom.descendants().filter(|&instance|
db.classes.get(instance.class.as_str()).is_some_and(|class| db.classes.get(instance.class.as_str()).is_some_and(|class|

View File

@@ -250,7 +250,7 @@ pub fn convert(
// generate a unit cube as default physics // generate a unit cube as default physics
let pos_list=CUBE_DEFAULT_VERTICES.map(|pos|mb.acquire_pos_id(pos>>1)); let pos_list=CUBE_DEFAULT_VERTICES.map(|pos|mb.acquire_pos_id(pos>>1));
let tex=mb.acquire_tex_id(glam::Vec2::ZERO); let tex=mb.acquire_tex_id(glam::Vec2::ZERO);
let normal=mb.acquire_normal_id(vec3::ZERO); let normal=mb.acquire_normal_id(vec3::zero());
let color=mb.acquire_color_id(glam::Vec4::ONE); 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,_]| let polygon_group=PolygonGroup::PolygonList(PolygonList::new(CUBE_DEFAULT_POLYS.map(|poly|poly.map(|[pos_id,_]|
mb.acquire_vertex_id(IndexedVertex{pos:pos_list[pos_id as usize],tex,normal,color}) mb.acquire_vertex_id(IndexedVertex{pos:pos_list[pos_id as usize],tex,normal,color})

View File

@@ -1,6 +1,6 @@
[package] [package]
name = "roblox_emulator" name = "roblox_emulator"
version = "0.5.1" version = "0.5.2"
edition = "2024" edition = "2024"
repository = "https://git.itzana.me/StrafesNET/strafe-project" repository = "https://git.itzana.me/StrafesNET/strafe-project"
license = "MIT OR Apache-2.0" license = "MIT OR Apache-2.0"
@@ -15,10 +15,10 @@ run-service=[]
glam = "0.30.0" glam = "0.30.0"
mlua = { version = "0.11.3", features = ["luau"] } mlua = { version = "0.11.3", features = ["luau"] }
phf = { version = "0.13.1", features = ["macros"] } phf = { version = "0.13.1", features = ["macros"] }
rbx_dom_weak = { version = "3.0.1-sn5", registry = "strafesnet" } rbx_dom_weak = "4.1.0"
rbx_reflection = "5.0.0" rbx_reflection = "6.1.0"
rbx_reflection_database = "1.0.0" rbx_reflection_database = "2.0.2"
rbx_types = "2.0.0" rbx_types = "3.1.0"
[lints] [lints]
workspace = true workspace = true

View File

@@ -52,7 +52,7 @@ impl Context{
} }
/// Creates an iterator over all items of a particular class. /// 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{ pub fn superclass_iter<'a>(&'a self,superclass:&'a str)->impl Iterator<Item=Ref>+'a{
let db=rbx_reflection_database::get(); let db=rbx_reflection_database::get().unwrap();
let Some(superclass)=db.classes.get(superclass)else{ let Some(superclass)=db.classes.get(superclass)else{
panic!("Invalid class"); panic!("Invalid class");
}; };

View File

@@ -37,7 +37,7 @@ impl PartialEq for EnumItem<'_>{
pub struct Enums; pub struct Enums;
impl Enums{ impl Enums{
pub fn get(&self,index:&str)->Option<EnumItems<'static>>{ pub fn get(&self,index:&str)->Option<EnumItems<'static>>{
let db=rbx_reflection_database::get(); let db=rbx_reflection_database::get().unwrap();
db.enums.get(index).map(|ed|EnumItems{ed}) db.enums.get(index).map(|ed|EnumItems{ed})
} }
} }

View File

@@ -37,7 +37,7 @@ pub fn dom_mut<T>(lua:&mlua::Lua,mut f:impl FnMut(&mut WeakDom)->mlua::Result<T>
} }
pub fn class_is_a(class:&str,superclass:&str)->bool{ pub fn class_is_a(class:&str,superclass:&str)->bool{
let db=rbx_reflection_database::get(); let db=rbx_reflection_database::get().unwrap();
let (Some(class),Some(superclass))=(db.classes.get(class),db.classes.get(superclass))else{ let (Some(class),Some(superclass))=(db.classes.get(class),db.classes.get(superclass))else{
return false; return false;
}; };
@@ -80,14 +80,14 @@ pub fn find_first_descendant_of_class<'a>(dom:&'a WeakDom,instance:&rbx_dom_weak
} }
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>{ 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(); let db=rbx_reflection_database::get().unwrap();
let superclass_descriptor=db.classes.get(superclass)?; let superclass_descriptor=db.classes.get(superclass)?;
instance.children().iter().filter_map(|&r|dom.get_by_ref(r)).find(|inst|{ 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)) 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>{ 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(); let db=rbx_reflection_database::get().unwrap();
let superclass_descriptor=db.classes.get(superclass)?; let superclass_descriptor=db.classes.get(superclass)?;
dom.descendants_of(instance.referent()).find(|inst|{ dom.descendants_of(instance.referent()).find(|inst|{
db.classes.get(inst.class.as_str()).is_some_and(|descriptor|db.has_superclass(descriptor,superclass_descriptor)) db.classes.get(inst.class.as_str()).is_some_and(|descriptor|db.has_superclass(descriptor,superclass_descriptor))
@@ -282,7 +282,7 @@ impl mlua::UserData for Instance{
dom_mut(lua,|dom|{ dom_mut(lua,|dom|{
let instance=this.get(dom)?; let instance=this.get(dom)?;
//println!("__index t={} i={index:?}",instance.name); //println!("__index t={} i={index:?}",instance.name);
let db=rbx_reflection_database::get(); let db=rbx_reflection_database::get().unwrap();
let class=db.classes.get(instance.class.as_str()).ok_or_else(||mlua::Error::runtime("Class missing"))?; let class=db.classes.get(instance.class.as_str()).ok_or_else(||mlua::Error::runtime("Class missing"))?;
// Find existing property // Find existing property
// Interestingly, ustr can know ahead of time if // Interestingly, ustr can know ahead of time if
@@ -344,7 +344,7 @@ impl mlua::UserData for Instance{
let index_str=&*index.to_str()?; let index_str=&*index.to_str()?;
dom_mut(lua,|dom|{ dom_mut(lua,|dom|{
let instance=this.get_mut(dom)?; let instance=this.get_mut(dom)?;
let db=rbx_reflection_database::get(); let db=rbx_reflection_database::get().unwrap();
let class=db.classes.get(instance.class.as_str()).ok_or_else(||mlua::Error::runtime("Class missing"))?; let class=db.classes.get(instance.class.as_str()).ok_or_else(||mlua::Error::runtime("Class missing"))?;
let property=db.superclasses_iter(class).find_map(|cls| let property=db.superclasses_iter(class).find_map(|cls|
cls.properties.get(index_str) cls.properties.get(index_str)

View File

@@ -13,10 +13,10 @@ futures = "0.3.31"
image = "0.25.2" image = "0.25.2"
image_dds = "0.7.1" image_dds = "0.7.1"
rbx_asset = { version = "0.5.0", registry = "strafesnet" } rbx_asset = { version = "0.5.0", registry = "strafesnet" }
rbx_binary = { version = "1.0.1-sn5", registry = "strafesnet" } rbx_binary = "2.0.1"
rbx_dom_weak = { version = "3.0.1-sn5", registry = "strafesnet" } rbx_dom_weak = "4.1.0"
rbx_reflection_database = "1.0.0" rbx_reflection_database = "2.0.2"
rbx_xml = { version = "1.0.1-sn5", registry = "strafesnet" } rbx_xml = "2.0.1"
rbxassetid = { version = "0.1.0", registry = "strafesnet" } rbxassetid = { version = "0.1.0", registry = "strafesnet" }
strafesnet_bsp_loader = { version = "0.3.1", path = "../lib/bsp_loader", registry = "strafesnet" } strafesnet_bsp_loader = { version = "0.3.1", path = "../lib/bsp_loader", registry = "strafesnet" }
strafesnet_deferred_loader = { version = "0.5.1", path = "../lib/deferred_loader", registry = "strafesnet" } strafesnet_deferred_loader = { version = "0.5.1", path = "../lib/deferred_loader", registry = "strafesnet" }

View File

@@ -28,7 +28,7 @@ strafesnet_rbx_loader = { path = "../lib/rbx_loader", registry = "strafesnet", o
strafesnet_session = { path = "../engine/session", registry = "strafesnet" } strafesnet_session = { path = "../engine/session", registry = "strafesnet" }
strafesnet_settings = { path = "../engine/settings", registry = "strafesnet" } strafesnet_settings = { path = "../engine/settings", registry = "strafesnet" }
strafesnet_snf = { path = "../lib/snf", registry = "strafesnet", optional = true } strafesnet_snf = { path = "../lib/snf", registry = "strafesnet", optional = true }
wgpu = "27.0.0" wgpu = "28.0.0"
winit = "0.30.7" winit = "0.30.7"
[profile.dev] [profile.dev]

View File

@@ -77,5 +77,8 @@ pub fn new<'a>(
run_session_instruction!(ins.time,SessionInstruction::LoadReplay(bot)); run_session_instruction!(ins.time,SessionInstruction::LoadReplay(bot));
} }
} }
//whatever just do it
session.debug_raycast_print_model_id_if_changed(ins.time);
}) })
} }

View File

@@ -52,7 +52,7 @@ impl<'a> SetupContextPartial2<'a>{
let required_features=required_features(); let required_features=required_features();
//no helper function smh gotta write it myself //no helper function smh gotta write it myself
let adapters=self.instance.enumerate_adapters(self.backends); let adapters=pollster::block_on(self.instance.enumerate_adapters(self.backends));
let mut chosen_adapter=None; let mut chosen_adapter=None;
let mut chosen_adapter_score=0; let mut chosen_adapter_score=0;

View File

@@ -86,6 +86,29 @@ fn vs_entity_texture(
return result; return result;
} }
@group(1)
@binding(0)
var<uniform> model_instance: ModelInstance;
struct DebugEntityOutput {
@builtin(position) position: vec4<f32>,
@location(1) normal: vec3<f32>,
@location(2) view: vec3<f32>,
};
@vertex
fn vs_debug(
@location(0) pos: vec3<f32>,
@location(1) normal: vec3<f32>,
) -> DebugEntityOutput {
var position: vec4<f32> = model_instance.transform * vec4<f32>(pos, 1.0);
var result: DebugEntityOutput;
result.normal = model_instance.normal_transform * normal;
result.view = position.xyz - camera.view_inv[3].xyz;//col(3)
result.position = camera.proj * camera.view * position;
return result;
}
//group 2 is the skybox texture //group 2 is the skybox texture
@group(1) @group(1)
@binding(0) @binding(0)
@@ -110,3 +133,8 @@ fn fs_entity_texture(vertex: EntityOutputTexture) -> @location(0) vec4<f32> {
let reflected_color = textureSample(cube_texture, cube_sampler, reflected).rgb; let reflected_color = textureSample(cube_texture, cube_sampler, reflected).rgb;
return mix(vec4<f32>(vec3<f32>(0.05) + 0.2 * reflected_color,1.0),mix(vertex.model_color,vec4<f32>(fragment_color.rgb,1.0),fragment_color.a),0.5+0.5*abs(d)); return mix(vec4<f32>(vec3<f32>(0.05) + 0.2 * reflected_color,1.0),mix(vertex.model_color,vec4<f32>(fragment_color.rgb,1.0),fragment_color.a),0.5+0.5*abs(d));
} }
@fragment
fn fs_debug(vertex: DebugEntityOutput) -> @location(0) vec4<f32> {
return model_instance.color;
}