Compare commits

..

38 Commits

Author SHA1 Message Date
d8804c0901 break to outer 2025-11-17 14:02:01 -08:00
865d1756b9 fix lint 2025-11-17 14:02:01 -08:00
3188f78744 document failure case 2025-11-17 14:02:01 -08:00
3e167af521 cool 2025-11-17 14:02:01 -08:00
8cf69933b1 wip 2025-11-17 14:02:01 -08:00
caef37dc36 wip 2025-11-17 14:02:01 -08:00
1832ce07ee wip new algorithm 2025-11-17 14:02:01 -08:00
5ac65ad845 new MeshQuery helper function 2025-11-17 14:02:01 -08:00
76696a3162 delete old algorithm 2025-11-17 14:02:01 -08:00
128e137829 remove redundant code 2025-11-17 13:22:46 -08:00
1e19f804cc custom hex Debug print for Fixed 2025-11-17 12:45:55 -08:00
f6f35c5f54 fix lints 2025-11-17 12:41:34 -08:00
4e7d580918 add lints to workspace 2025-11-16 14:53:23 -08:00
8d5a100a2e update deps 2025-11-09 05:48:31 -08:00
91208db706 drop lazy_regex dep 2025-11-09 05:47:33 -08:00
5a320b514e fix style 2025-11-07 16:52:50 -08:00
661d706a22 common: aabb: area_weight fn 2025-10-17 15:48:59 +01:00
5550d5771e common: bvh: reduce variable scope 2025-10-17 15:03:54 +01:00
c834d1d1ca common: bvh: name constant 2025-10-17 14:57:53 +01:00
ca9d2238a7 common: bvh: tweak code style 2025-10-17 14:57:53 +01:00
f3bb8dd067 update deps 2025-10-01 23:37:07 -07:00
e58f9b9ff2 rbx_loader: silently filter vertices which fail to convert 2025-09-29 19:45:16 -07:00
54c4ed6bad update deps 2025-08-30 15:20:23 -07:00
9aceafa0df roblox_emulator: fix lints 2025-08-30 15:15:23 -07:00
d065bac130 Revert "roblox_emulator: use extended instances"
This reverts commit bb8e131464.
2025-08-30 15:13:28 -07:00
a4d0393556 update rbx-dom 2025-08-30 15:12:22 -07:00
3692d7f79e it: fix bug 3 test 2025-08-29 19:07:57 -07:00
7e49840768 it: set gravity to 0 2025-08-29 19:06:51 -07:00
4ecdd547c6 it: use instruction iter 2025-08-29 18:30:28 -07:00
b0365165e8 physics: create iterator over internal instructions 2025-08-29 18:30:18 -07:00
c2ff52a2ae instruction: iterator 2025-08-29 18:30:18 -07:00
6e778869e8 it: bug 3 test scene 2025-08-29 18:30:18 -07:00
6509bef070 it: add test scene 2025-08-29 16:47:46 -07:00
0fa097a004 aabb: tweak Aabb.contains 2025-08-29 15:40:00 -07:00
55d4b1d264 physics: PhysicsData is immutable after construction 2025-08-28 16:37:48 -07:00
ea28663e95 physics: move code 2025-08-28 16:02:23 -07:00
bac9be9684 physics: add edge case tests 2025-08-28 14:49:36 -07:00
7e76f3309b ignore debugger config 2025-08-26 16:54:36 -07:00
59 changed files with 1496 additions and 1092 deletions

1
.gitignore vendored
View File

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

View File

@@ -1,29 +0,0 @@
// Project-local debug tasks
//
// For more documentation on how to configure debug tasks,
// see: https://zed.dev/docs/debugger
[
{
"label": "Strafe Client",
"adapter": "CodeLLDB",
"program": "target/debug/strafe-client",
"args": [
"tools/bhop_maps/5692113331.snfm"
],
"request": "launch",
"build": {
"command": "cargo",
"args": ["build","-p strafe-client"]
}
},
{
"label": "Integration Testing",
"adapter": "CodeLLDB",
"program": "target/release/integration-testing",
"request": "launch",
"build": {
"command": "cargo",
"args": ["test","-p integration-testing"]
}
}
]

1341
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -24,3 +24,14 @@ resolver = "2"
#lto = true #lto = true
strip = true strip = true
codegen-units = 1 codegen-units = 1
[workspace.lints.rust]
# unsafe_code = "forbid"
# missing_docs = "warn"
# missing_debug_implementations = "warn"
single_use_lifetimes = "warn"
trivial_casts = "warn"
unused_lifetimes = "warn"
unused_qualifications = "warn"
# variant_size_differences = "warn"
unexpected_cfgs = "warn"

View File

@@ -11,4 +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 = "26.0.1" wgpu = "27.0.0"
[lints]
workspace = true

View File

@@ -94,7 +94,7 @@ impl GraphicsCamera{
raw raw
} }
} }
impl std::default::Default for GraphicsCamera{ impl Default for GraphicsCamera{
fn default()->Self{ fn default()->Self{
Self{ Self{
screen_size:glam::UVec2::ONE, screen_size:glam::UVec2::ONE,
@@ -167,7 +167,7 @@ impl GraphicsState{
} }
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 texture view per texture //generate texture view per texture
let texture_views:HashMap<strafesnet_common::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);
let image=match ddsfile::Dds::read(std::io::Cursor::new(texture_data)){ let image=match ddsfile::Dds::read(std::io::Cursor::new(texture_data)){
Ok(image)=>image, Ok(image)=>image,
@@ -803,7 +803,7 @@ impl GraphicsState{
module:&shader, module:&shader,
entry_point:Some("vs_entity_texture"), entry_point:Some("vs_entity_texture"),
buffers:&[wgpu::VertexBufferLayout{ buffers:&[wgpu::VertexBufferLayout{
array_stride:std::mem::size_of::<GraphicsVertex>() as wgpu::BufferAddress, array_stride:size_of::<GraphicsVertex>() as wgpu::BufferAddress,
step_mode:wgpu::VertexStepMode::Vertex, step_mode:wgpu::VertexStepMode::Vertex,
attributes:&wgpu::vertex_attr_array![0=>Float32x3,1=>Float32x2,2=>Float32x3,3=>Float32x4], attributes:&wgpu::vertex_attr_array![0=>Float32x3,1=>Float32x2,2=>Float32x3,3=>Float32x4],
}], }],

View File

@@ -8,3 +8,6 @@ arrayvec = "0.7.6"
glam = "0.30.0" glam = "0.30.0"
id = { version = "0.1.0", registry = "strafesnet" } id = { version = "0.1.0", registry = "strafesnet" }
strafesnet_common = { path = "../../lib/common", registry = "strafesnet" } strafesnet_common = { path = "../../lib/common", registry = "strafesnet" }
[lints]
workspace = true

View File

@@ -90,6 +90,16 @@ 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)
} }
/// Returns an iterator over the vertices in the direction of the directed edges.
/// Intended to be used to find adjacent vertices:
/// `self.directed_verts(self.vert_edges(vert_id).as_ref())`
/// TODO: rewrite this function as `adjacent_vertices`
fn directed_verts(&self,edges:&[Self::Edge])->impl Iterator<Item=Self::Vert>{
edges.iter().map(|e|{
let edge_verts=self.edge_verts(e.as_undirected());
edge_verts.as_ref()[e.parity() as usize]
})
}
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]>;
@@ -452,6 +462,8 @@ impl MeshQuery for PhysicsMeshView<'_>{
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;
self.data.verts[vert_idx].0 self.data.verts[vert_idx].0
} }
/// Directed edges going clockwise when looking in the direction of the face normal.
/// (Edit this documentation if this is wrong!)
fn face_edges(&self,face_id:SubmeshFaceId)->impl AsRef<[SubmeshDirectedEdgeId]>{ fn face_edges(&self,face_id:SubmeshFaceId)->impl AsRef<[SubmeshDirectedEdgeId]>{
self.topology.face_topology[face_id.get() as usize].edges.as_slice() self.topology.face_topology[face_id.get() as usize].edges.as_slice()
} }
@@ -500,7 +512,7 @@ impl TransformedMesh<'_>{
transform, transform,
} }
} }
pub fn verts<'a>(&'a self)->impl Iterator<Item=vec3::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{ fn farthest_vert(&self,dir:Planar64Vec3)->SubmeshVertId{
@@ -614,17 +626,6 @@ 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();
@@ -641,122 +642,195 @@ impl MinkowskiMesh<'_>{
fn farthest_vert(&self,dir:Planar64Vec3)->MinkowskiVert{ fn farthest_vert(&self,dir:Planar64Vec3)->MinkowskiVert{
MinkowskiVert::VertVert(self.mesh0.farthest_vert(dir),self.mesh1.farthest_vert(-dir)) 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{ fn closest_fev_not_inside(&self,relative_position:Planar64Vec3)->Option<FEV<MinkowskiMesh<'_>>>{
let mut best_transition=Transition::Done; // Make a fast guess as to what the closest point will be.
for &directed_edge_id in self.vert_edges(vert_id).as_ref(){ let MinkowskiVert::VertVert(mut v0,mut v1)=self.farthest_vert(relative_position);
let edge_n=self.directed_edge_n(directed_edge_id); // TODO: alternate naive vertex searches to improve the robustness
//is boundary uncrossable by a crawl from infinity // in the "tall bipyramid" failure case
let edge_verts=self.edge_verts(directed_edge_id.as_undirected()); let mut m0v=self.mesh0.vert(v0);
//select opposite vertex let mut m1v=self.mesh1.vert(v1);
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 mut best_distance_squared={
let diff=point-self.vert(vert_id); let diff=relative_position+m1v-m0v;
diff.dot(diff) diff.dot(diff)
}; };
let mut v0e=self.mesh0.vert_edges(v0);
let mut v1e=self.mesh1.vert_edges(v1);
let mut v0e_ref=v0e.as_ref();
let mut v1e_ref=v1e.as_ref();
// repeatedly check adjacent vertex permutations to see if they are closer
loop{ loop{
match self.next_transition_vert(vert_id,&mut best_distance_squared,infinity_dir,point){ let mut best_v0=None;
Transition::Done=>return self.final_ev(vert_id,&mut best_distance_squared,infinity_dir,point), let mut best_v1=None;
Transition::Vert(new_vert_id)=>vert_id=new_vert_id,
// check vertices adjacent to v1 against v0
for m1_test_vert in self.mesh1.directed_verts(v1e_ref){
let m1v_test=self.mesh1.vert(m1_test_vert);
let diff=relative_position+m1v_test-m0v;
let d=diff.dot(diff);
if d<best_distance_squared{
best_distance_squared=d;
best_v0=None;
best_v1=Some(m1_test_vert);
}
} }
}
} // check vertices adjacent to v0 against v1
/// 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 for m0_test_vert in self.mesh0.directed_verts(v0e_ref){
fn infinity_fev(&self,infinity_dir:Planar64Vec3,point:Planar64Vec3)->FEV::<MinkowskiMesh<'_>>{ let m0v_test=self.mesh0.vert(m0_test_vert);
//start on any vertex let diff=relative_position+m1v-m0v_test;
//cross uncrossable vertex-edge boundaries until you find the closest vertex or edge let d=diff.dot(diff);
//cross edge-face boundary if it's uncrossable if d<best_distance_squared{
match self.crawl_boundaries(self.farthest_vert(infinity_dir),infinity_dir,point){ best_distance_squared=d;
//if a vert is returned, it is the closest point to the infinity point best_v0=Some(m0_test_vert);
EV::Vert(vert_id)=>FEV::Vert(vert_id), best_v1=None;
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. // check permutations of adjacent vertices
let delta_pos=point*2-{ for m0_test_vert in self.mesh0.directed_verts(v0e_ref){
let &[v0,v1]=self.edge_verts(edge_id).as_ref(); let m0v_test=self.mesh0.vert(m0_test_vert);
self.vert(v0)+self.vert(v1) for m1_test_vert in self.mesh1.directed_verts(v1e_ref){
}; let m1v_test=self.mesh1.vert(m1_test_vert);
for (i,&face_id) in self.edge_faces(edge_id).as_ref().iter().enumerate(){ let diff=relative_position+m1v_test-m0v_test;
let face_n=self.face_nd(face_id).0; let d=diff.dot(diff);
//edge-face boundary nd, n facing out of the face towards the edge if d<best_distance_squared{
let boundary_n=face_n.cross(edge_n)*(i as i64*2-1); best_distance_squared=d;
let boundary_d=boundary_n.dot(delta_pos); best_v0=Some(m0_test_vert);
//check if time of collision is outside Time::MIN..Time::MAX best_v1=Some(m1_test_vert);
//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) }
},
// end condition
let v0_changed=match best_v0{
Some(new_v0)=>{
v0=new_v0;
m0v=self.mesh0.vert(v0);
v0e=self.mesh0.vert_edges(v0);
v0e_ref=v0e.as_ref();
true
},
None=>false,
};
let v1_changed=match best_v1{
Some(new_v1)=>{
v1=new_v1;
m1v=self.mesh1.vert(v1);
v1e=self.mesh1.vert_edges(v1);
v1e_ref=v1e.as_ref();
true
},
None=>false,
};
// if neither vertex changes, we found the closest two vertices.
if !(v0_changed&&v1_changed){
break;
}
} }
} // now you have the two closest vertices
// TODO: fundamentally improve this algorithm. let mut best_fev=FEV::Vert(MinkowskiVert::VertVert(v0,v1));
// All it needs to do is find the closest point on the mesh // ==== FEV::Edge ====
// and return the FEV which the point resides on. // test VertEdges
// for &e1 in v1e_ref{
// What it actually does is use the above functions to trace a ray in from infinity, let edge_verts=self.mesh1.edge_verts(e1.as_undirected());
// crawling the closest point along the mesh surface until the ray reaches let &[ev0_id,ev1_id]=edge_verts.as_ref();
// the starting point to discover the final FEV. let (ev0,ev1)=(self.mesh1.vert(ev0_id),self.mesh1.vert(ev1_id));
// let edge_n=ev1-ev0;
// The actual collision prediction probably does a single test // use relative coordinates to make including relative_position easier
// and then immediately returns with 0 FEV transitions on average, let diff=relative_position+m0v-ev0;
// because of the strict time_limit constraint. let d=diff.dot(edge_n);
// // is test point between edge vertices
// Most of the calculation time is just calculating the starting point let edge_nn=edge_n.dot(edge_n);
// for the "actual" crawling algorithm below (predict_collision_{in|out}). if d.is_positive()&&d<edge_nn{
fn closest_fev_not_inside(&self,mut infinity_body:Body,start_time:Bound<&Time>)->Option<FEV<MinkowskiMesh<'_>>>{ let distance_squared={
infinity_body.infinity_dir().and_then(|dir|{ let c=diff.cross(edge_n);
let infinity_fev=self.infinity_fev(-dir,infinity_body.position); //wrap for speed
//a line is simpler to solve than a parabola (c.dot(c)/edge_nn).divide().wrap_2()
infinity_body.velocity=dir; };
infinity_body.acceleration=vec3::ZERO; if distance_squared<best_distance_squared{
//crawl in from negative infinity along a tangent line to get the closest fev best_distance_squared=distance_squared;
infinity_fev.crawl(self,&infinity_body,Bound::Unbounded,start_time).miss() best_fev=FEV::Edge(MinkowskiEdge::VertEdge(v0,e1.as_undirected()))
}) }
}
}
// test EdgeVerts
for &e0 in v0e_ref{
let edge_verts=self.mesh0.edge_verts(e0.as_undirected());
let &[ev0_id,ev1_id]=edge_verts.as_ref();
let (ev0,ev1)=(self.mesh0.vert(ev0_id),self.mesh0.vert(ev1_id));
let edge_n=ev1-ev0;
// use relative coordinates to make including relative_position easier
let diff=m1v-relative_position-ev0;
let d=diff.dot(edge_n);
// is test point between edge vertices
let edge_nn=edge_n.dot(edge_n);
if d.is_positive()&&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_distance_squared=distance_squared;
best_fev=FEV::Edge(MinkowskiEdge::EdgeVert(e0.as_undirected(),v1))
}
}
}
// ==== FEV::Face ====
// test VertFaces
'outer: for &f1 in self.mesh1.vert_faces(v1).as_ref(){
let (n,d)=self.mesh1.face_nd(f1);
// Test the face's voronoi column
for &e1 in self.mesh1.face_edges(f1).as_ref(){
let edge_n=self.mesh1.directed_edge_n(e1);
let boundary_n=n.cross(edge_n);
let &[ev0_id,ev1_id]=self.mesh1.edge_verts(e1.as_undirected()).as_ref();
let (ev0,ev1)=(self.mesh1.vert(ev0_id),self.mesh1.vert(ev1_id));
let diff=(relative_position+m0v)*2-(ev0+ev1);
if boundary_n.dot(diff).is_negative(){
// The test point is outside the face's voronoi column.
continue 'outer;
}
}
// Calculate distance
let d=n.dot(relative_position+m0v)-d;
// Wrap for speed
let distance_squared=(d*d).wrap_2();
if distance_squared<best_distance_squared{
best_distance_squared=distance_squared;
best_fev=FEV::Face(MinkowskiFace::VertFace(v0,f1));
}
}
// test FaceVerts
'outer: for &f0 in self.mesh0.vert_faces(v0).as_ref(){
let (n,d)=self.mesh0.face_nd(f0);
// Test the face's voronoi column
for &e0 in self.mesh0.face_edges(f0).as_ref(){
let edge_n=self.mesh0.directed_edge_n(e0);
let boundary_n=n.cross(edge_n);
let &[ev0_id,ev1_id]=self.mesh0.edge_verts(e0.as_undirected()).as_ref();
let (ev0,ev1)=(self.mesh0.vert(ev0_id),self.mesh0.vert(ev1_id));
let diff=(m1v-relative_position)*2-(ev0+ev1);
if boundary_n.dot(diff).is_negative(){
// The test point is outside the face's voronoi column.
continue 'outer;
}
}
// Calculate distance
let d=n.dot(relative_position+m0v)-d;
// Wrap for speed
let distance_squared=(d*d).wrap_2();
if distance_squared<best_distance_squared{
best_distance_squared=distance_squared;
best_fev=FEV::Face(MinkowskiFace::FaceVert(f0,v1));
}
}
// test EdgeEdges
Some(best_fev)
} }
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|{ self.closest_fev_not_inside(relative_body.position).and_then(|fev|{
//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()
}) })
@@ -765,10 +839,9 @@ impl MinkowskiMesh<'_>{
let (lower_bound,upper_bound)=(range.start_bound(),range.end_bound()); let (lower_bound,upper_bound)=(range.start_bound(),range.end_bound());
// 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; self.closest_fev_not_inside(relative_body.position).and_then(|fev|{
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,&-relative_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))
}) })
@@ -778,10 +851,7 @@ impl MinkowskiMesh<'_>{
use crate::face_crawler::{low,upp}; use crate::face_crawler::{low,upp};
//no algorithm needed, there is only one state and two cases (Edge,None) //no algorithm needed, there is only one state and two cases (Edge,None)
//determine when it passes an edge ("sliding off" case) //determine when it passes an edge ("sliding off" case)
let start_time=range.start_bound().map(|&t|{ let start_time=range.start_bound().map(|&t|(t-relative_body.time).to_ratio());
let r=(t-relative_body.time).to_ratio();
Ratio::new(r.num,r.den)
});
let mut best_time=range.end_bound().map(|&t|into_giga_time(t,relative_body.time)); let mut best_time=range.end_bound().map(|&t|into_giga_time(t,relative_body.time));
let mut best_edge=None; let mut best_edge=None;
let face_n=self.face_nd(contact_face_id).0; let face_n=self.face_nd(contact_face_id).0;
@@ -804,20 +874,10 @@ impl MinkowskiMesh<'_>{
} }
best_edge best_edge
} }
fn infinity_in(&self,infinity_body:Body)->Option<(MinkowskiFace,GigaTime)>{
let infinity_fev=self.infinity_fev(-infinity_body.velocity,infinity_body.position);
// 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{ pub fn is_point_in_mesh(&self,point:Planar64Vec3)->bool{
let infinity_body=Body::new(point,vec3::Y,vec3::ZERO,Time::ZERO); // TODO
//movement must escape the mesh forwards and backwards in time, println!("Unimplemented is_point_in_mesh called! {point}");
//otherwise the point is not inside the mesh false
self.infinity_in(infinity_body)
.is_some_and(|_|
self.infinity_in(-infinity_body)
.is_some()
)
} }
} }
impl MeshQuery for MinkowskiMesh<'_>{ impl MeshQuery for MinkowskiMesh<'_>{

View File

@@ -23,7 +23,7 @@ use strafesnet_common::physics::{Instruction,MouseInstruction,ModeInstruction,Mi
//internal influence //internal influence
//when the physics asks itself what happens next, this is how it's represented //when the physics asks itself what happens next, this is how it's represented
#[derive(Debug)] #[derive(Debug,Clone)]
pub enum InternalInstruction{ pub enum InternalInstruction{
CollisionStart(Collision,model_physics::GigaTime), CollisionStart(Collision,model_physics::GigaTime),
CollisionEnd(Collision,model_physics::GigaTime), CollisionEnd(Collision,model_physics::GigaTime),
@@ -36,7 +36,7 @@ pub enum InternalInstruction{
pub struct InputState{ pub struct InputState{
mouse:MouseState, mouse:MouseState,
next_mouse:MouseState, next_mouse:MouseState,
controls:strafesnet_common::controls_bitflag::Controls, controls:Controls,
} }
impl InputState{ impl InputState{
fn set_next_mouse(&mut self,next_mouse:MouseState){ fn set_next_mouse(&mut self,next_mouse:MouseState){
@@ -302,7 +302,7 @@ impl PhysicsCamera{
} }
} }
impl std::default::Default for PhysicsCamera{ impl Default for PhysicsCamera{
fn default()->Self{ fn default()->Self{
Self{ Self{
sensitivity:Ratio64Vec2::ONE*200_000, sensitivity:Ratio64Vec2::ONE*200_000,
@@ -385,7 +385,7 @@ mod gameplay{
self.unordered_checkpoints.clear(); self.unordered_checkpoints.clear();
} }
} }
impl std::default::Default for ModeState{ impl Default for ModeState{
fn default()->Self{ fn default()->Self{
Self{ Self{
mode_id:gameplay_modes::ModeId::MAIN, mode_id:gameplay_modes::ModeId::MAIN,
@@ -874,6 +874,9 @@ impl PhysicsState{
..Self::default() ..Self::default()
} }
} }
pub const fn body(&self)->&Body{
&self.body
}
pub fn camera_body(&self)->Body{ pub fn camera_body(&self)->Body{
Body{ Body{
position:self.body.position+self.style.camera_offset, position:self.body.position+self.style.camera_offset,
@@ -886,6 +889,9 @@ impl PhysicsState{
pub const fn mode(&self)->gameplay_modes::ModeId{ pub const fn mode(&self)->gameplay_modes::ModeId{
self.mode_state.get_mode_id() self.mode_state.get_mode_id()
} }
pub const fn style_mut(&mut self)->&mut StyleModifiers{
&mut self.style
}
pub fn get_finish_time(&self)->Option<run::Time>{ pub fn get_finish_time(&self)->Option<run::Time>{
self.run.get_finish_time() self.run.get_finish_time()
} }
@@ -949,8 +955,8 @@ pub struct PhysicsData{
//cached calculations //cached calculations
hitbox_mesh:HitboxMesh, hitbox_mesh:HitboxMesh,
} }
impl Default for PhysicsData{ impl PhysicsData{
fn default()->Self{ pub fn empty()->Self{
Self{ Self{
bvh:bvh::BvhNode::empty(), bvh:bvh::BvhNode::empty(),
models:Default::default(), models:Default::default(),
@@ -958,47 +964,7 @@ impl Default for PhysicsData{
hitbox_mesh:StyleModifiers::default().calculate_mesh(), hitbox_mesh:StyleModifiers::default().calculate_mesh(),
} }
} }
} pub fn new(map:&map::CompleteMap)->Self{
// the collection of information required to run physics
pub struct PhysicsContext<'a>{
state:&'a mut PhysicsState,//this captures the entire state of the physics.
data:&'a PhysicsData,//data currently loaded into memory which is needded for physics to run, but is not part of the state.
}
// the physics consumes both Instruction and PhysicsInternalInstruction,
// but can only emit PhysicsInternalInstruction
impl InstructionConsumer<InternalInstruction> for PhysicsContext<'_>{
type Time=Time;
fn process_instruction(&mut self,ins:TimedInstruction<InternalInstruction,Time>){
atomic_internal_instruction(&mut self.state,&self.data,ins)
}
}
impl InstructionConsumer<Instruction> for PhysicsContext<'_>{
type Time=Time;
fn process_instruction(&mut self,ins:TimedInstruction<Instruction,Time>){
atomic_input_instruction(&mut self.state,&self.data,ins)
}
}
impl InstructionEmitter<InternalInstruction> for PhysicsContext<'_>{
type Time=Time;
//this little next instruction function could cache its return value and invalidate the cached value by watching the State.
fn next_instruction(&self,time_limit:Time)->Option<TimedInstruction<InternalInstruction,Time>>{
next_instruction_internal(&self.state,&self.data,time_limit)
}
}
impl PhysicsContext<'_>{
pub fn run_input_instruction(
state:&mut PhysicsState,
data:&PhysicsData,
instruction:TimedInstruction<Instruction,Time>
){
let mut context=PhysicsContext{state,data};
context.process_exhaustive(instruction.time);
context.process_instruction(instruction);
}
}
impl PhysicsData{
/// use with caution, this is the only non-instruction way to mess with physics
pub fn generate_models(&mut self,map:&map::CompleteMap){
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();
let mut used_intersect_attributes=Vec::new(); let mut used_intersect_attributes=Vec::new();
@@ -1125,11 +1091,57 @@ impl PhysicsData{
(IntersectAttributesId::new(attr_id as u32),attr) (IntersectAttributesId::new(attr_id as u32),attr)
).collect(), ).collect(),
}; };
self.bvh=bvh;
self.models=models;
self.modes=modes;
//hitbox_mesh is unchanged
println!("Physics Objects: {}",model_count); println!("Physics Objects: {}",model_count);
Self{
hitbox_mesh:StyleModifiers::default().calculate_mesh(),
bvh,
models,
modes,
}
}
}
// the collection of information required to run physics
pub struct PhysicsContext<'a>{
state:&'a mut PhysicsState,//this captures the entire state of the physics.
data:&'a PhysicsData,//data currently loaded into memory which is needded for physics to run, but is not part of the state.
}
// the physics consumes both Instruction and PhysicsInternalInstruction,
// but can only emit PhysicsInternalInstruction
impl InstructionConsumer<InternalInstruction> for PhysicsContext<'_>{
type Time=Time;
fn process_instruction(&mut self,ins:TimedInstruction<InternalInstruction,Time>){
atomic_internal_instruction(&mut self.state,&self.data,ins)
}
}
impl InstructionConsumer<Instruction> for PhysicsContext<'_>{
type Time=Time;
fn process_instruction(&mut self,ins:TimedInstruction<Instruction,Time>){
atomic_input_instruction(&mut self.state,&self.data,ins)
}
}
impl InstructionEmitter<InternalInstruction> for PhysicsContext<'_>{
type Time=Time;
//this little next instruction function could cache its return value and invalidate the cached value by watching the State.
fn next_instruction(&self,time_limit:Time)->Option<TimedInstruction<InternalInstruction,Time>>{
next_instruction_internal(&self.state,&self.data,time_limit)
}
}
impl<'a> PhysicsContext<'a>{
pub fn run_input_instruction(
state:&mut PhysicsState,
data:&PhysicsData,
instruction:TimedInstruction<Instruction,Time>
){
let mut context=PhysicsContext{state,data};
context.process_exhaustive(instruction.time);
context.process_instruction(instruction);
}
pub fn iter_internal(
state:&'a mut PhysicsState,
data:&'a PhysicsData,
time_limit:Time,
)->instruction::InstructionIter<InternalInstruction,Time,Self>{
PhysicsContext{state,data}.into_iter(time_limit)
} }
} }
@@ -1916,7 +1928,7 @@ mod test{
fn test_collision_rotated(relative_body:Body,expected_collision_time:Option<Time>){ fn test_collision_rotated(relative_body:Body,expected_collision_time:Option<Time>){
let h0=HitboxMesh::new(PhysicsMesh::unit_cube(), let h0=HitboxMesh::new(PhysicsMesh::unit_cube(),
integer::Planar64Affine3::new( integer::Planar64Affine3::new(
integer::Planar64Mat3::from_cols([ Planar64Mat3::from_cols([
int3(5,0,1)>>1, int3(5,0,1)>>1,
int3(0,1,0)>>1, int3(0,1,0)>>1,
int3(-1,0,5)>>1, int3(-1,0,5)>>1,
@@ -2124,4 +2136,115 @@ mod test{
Time::ZERO Time::ZERO
),None); ),None);
} }
// overlap edges by 1 epsilon
#[test]
fn almost_miss_north(){
test_collision_axis_aligned(Body::new(
(int3(0,10,-7)>>1)+vec3::raw_xyz(0,0,1),
int3(0,-1,0),
vec3::ZERO,
Time::ZERO
),Some(Time::from_secs(2)))
}
#[test]
fn almost_miss_east(){
test_collision_axis_aligned(Body::new(
(int3(7,10,0)>>1)+vec3::raw_xyz(-1,0,0),
int3(0,-1,0),
vec3::ZERO,
Time::ZERO
),Some(Time::from_secs(2)))
}
#[test]
fn almost_miss_south(){
test_collision_axis_aligned(Body::new(
(int3(0,10,7)>>1)+vec3::raw_xyz(0,0,-1),
int3(0,-1,0),
vec3::ZERO,
Time::ZERO
),Some(Time::from_secs(2)))
}
#[test]
fn almost_miss_west(){
test_collision_axis_aligned(Body::new(
(int3(-7,10,0)>>1)+vec3::raw_xyz(1,0,0),
int3(0,-1,0),
vec3::ZERO,
Time::ZERO
),Some(Time::from_secs(2)))
}
// exactly miss edges
#[test]
fn exact_miss_north(){
test_collision_axis_aligned(Body::new(
int3(0,10,-7)>>1,
int3(0,-1,0),
vec3::ZERO,
Time::ZERO
),None)
}
#[test]
fn exact_miss_east(){
test_collision_axis_aligned(Body::new(
int3(7,10,0)>>1,
int3(0,-1,0),
vec3::ZERO,
Time::ZERO
),None)
}
#[test]
fn exact_miss_south(){
test_collision_axis_aligned(Body::new(
int3(0,10,7)>>1,
int3(0,-1,0),
vec3::ZERO,
Time::ZERO
),None)
}
#[test]
fn exact_miss_west(){
test_collision_axis_aligned(Body::new(
int3(-7,10,0)>>1,
int3(0,-1,0),
vec3::ZERO,
Time::ZERO
),None)
}
// miss edges by 1 epsilon
#[test]
fn narrow_miss_north(){
test_collision_axis_aligned(Body::new(
(int3(0,10,-7)>>1)-vec3::raw_xyz(0,0,1),
int3(0,-1,0),
vec3::ZERO,
Time::ZERO
),None)
}
#[test]
fn narrow_miss_east(){
test_collision_axis_aligned(Body::new(
(int3(7,10,0)>>1)-vec3::raw_xyz(-1,0,0),
int3(0,-1,0),
vec3::ZERO,
Time::ZERO
),None)
}
#[test]
fn narrow_miss_south(){
test_collision_axis_aligned(Body::new(
(int3(0,10,7)>>1)-vec3::raw_xyz(0,0,-1),
int3(0,-1,0),
vec3::ZERO,
Time::ZERO
),None)
}
#[test]
fn narrow_miss_west(){
test_collision_axis_aligned(Body::new(
(int3(-7,10,0)>>1)-vec3::raw_xyz(1,0,0),
int3(0,-1,0),
vec3::ZERO,
Time::ZERO
),None)
}
} }

View File

@@ -10,3 +10,6 @@ strafesnet_common = { path = "../../lib/common", registry = "strafesnet" }
strafesnet_physics = { path = "../physics", registry = "strafesnet" } strafesnet_physics = { path = "../physics", registry = "strafesnet" }
strafesnet_settings = { path = "../settings", registry = "strafesnet" } strafesnet_settings = { path = "../settings", registry = "strafesnet" }
strafesnet_snf = { path = "../../lib/snf", registry = "strafesnet" } strafesnet_snf = { path = "../../lib/snf", registry = "strafesnet" }
[lints]
workspace = true

View File

@@ -31,7 +31,7 @@ pub enum SessionInputInstruction{
Mouse(glam::IVec2), Mouse(glam::IVec2),
SetControl(strafesnet_common::physics::SetControlInstruction), SetControl(strafesnet_common::physics::SetControlInstruction),
Mode(ImplicitModeInstruction), Mode(ImplicitModeInstruction),
Misc(strafesnet_common::physics::MiscInstruction), Misc(MiscInstruction),
} }
/// Implicit mode instruction are fed separately to session. /// Implicit mode instruction are fed separately to session.
/// Session generates the explicit mode instructions interlaced with a SetSensitivity instruction /// Session generates the explicit mode instructions interlaced with a SetSensitivity instruction
@@ -152,10 +152,10 @@ enum ViewState{
pub struct Session{ pub struct Session{
directories:Directories, directories:Directories,
user_settings:UserSettings, user_settings:UserSettings,
mouse_interpolator:crate::mouse_interpolator::MouseInterpolator, mouse_interpolator:MouseInterpolator,
view_state:ViewState, view_state:ViewState,
//gui:GuiState //gui:GuiState
geometry_shared:physics::PhysicsData, geometry_shared:PhysicsData,
simulation:Simulation, simulation:Simulation,
// below fields not included in lite session // below fields not included in lite session
recording:Recording, recording:Recording,
@@ -172,7 +172,7 @@ impl Session{
user_settings, user_settings,
directories, directories,
mouse_interpolator:MouseInterpolator::new(), mouse_interpolator:MouseInterpolator::new(),
geometry_shared:Default::default(), geometry_shared:PhysicsData::empty(),
simulation, simulation,
view_state:ViewState::Play, view_state:ViewState::Play,
recording:Default::default(), recording:Default::default(),
@@ -184,7 +184,7 @@ impl Session{
} }
fn change_map(&mut self,map:&strafesnet_common::map::CompleteMap){ fn change_map(&mut self,map:&strafesnet_common::map::CompleteMap){
self.simulation.physics.clear(); self.simulation.physics.clear();
self.geometry_shared.generate_models(map); self.geometry_shared=PhysicsData::new(map);
} }
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{

View File

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

View File

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

View File

@@ -3,6 +3,8 @@ mod util;
#[cfg(test)] #[cfg(test)]
mod tests; mod tests;
#[cfg(test)]
mod test_scenes;
use std::time::Instant; use std::time::Instant;
@@ -29,9 +31,8 @@ fn run_replay()->Result<(),ReplayError>{
let bot=strafesnet_snf::read_bot(data)?.read_all()?; let bot=strafesnet_snf::read_bot(data)?.read_all()?;
// create recording // create recording
let mut physics_data=PhysicsData::default();
println!("generating models.."); println!("generating models..");
physics_data.generate_models(&map); let physics_data=PhysicsData::new(&map);
println!("simulating..."); println!("simulating...");
let mut physics=PhysicsState::default(); let mut physics=PhysicsState::default();
for ins in bot.instructions{ for ins in bot.instructions{
@@ -139,9 +140,8 @@ fn test_determinism()->Result<(),ReplayError>{
let data=read_entire_file("../tools/bhop_maps/5692113331.snfm")?; let data=read_entire_file("../tools/bhop_maps/5692113331.snfm")?;
let map=strafesnet_snf::read_map(data)?.into_complete_map()?; let map=strafesnet_snf::read_map(data)?.into_complete_map()?;
let mut physics_data=PhysicsData::default();
println!("generating models.."); println!("generating models..");
physics_data.generate_models(&map); let physics_data=PhysicsData::new(&map);
let (send,recv)=std::sync::mpsc::channel(); let (send,recv)=std::sync::mpsc::channel();

View File

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

View File

@@ -10,9 +10,8 @@ fn physics_bug_2()->Result<(),ReplayError>{
let map=strafesnet_snf::read_map(data)?.into_complete_map()?; let map=strafesnet_snf::read_map(data)?.into_complete_map()?;
// create recording // create recording
let mut physics_data=PhysicsData::default();
println!("generating models.."); println!("generating models..");
physics_data.generate_models(&map); let physics_data=PhysicsData::new(&map);
println!("simulating..."); println!("simulating...");
//teleport to bug //teleport to bug
@@ -30,7 +29,7 @@ fn physics_bug_2()->Result<(),ReplayError>{
// wait one second to activate the bug // wait one second to activate the bug
// hit=Some(ModelId(2262)) // hit=Some(ModelId(2262))
PhysicsContext::run_input_instruction(&mut physics,&physics_data,strafesnet_common::instruction::TimedInstruction{ PhysicsContext::run_input_instruction(&mut physics,&physics_data,strafesnet_common::instruction::TimedInstruction{
time:strafesnet_common::integer::Time::from_millis(500), time:Time::from_millis(500),
instruction:strafesnet_common::physics::Instruction::Idle, instruction:strafesnet_common::physics::Instruction::Idle,
}); });
@@ -45,9 +44,8 @@ fn physics_bug_3()->Result<(),ReplayError>{
let map=strafesnet_snf::read_map(data)?.into_complete_map()?; let map=strafesnet_snf::read_map(data)?.into_complete_map()?;
// create recording // create recording
let mut physics_data=PhysicsData::default();
println!("generating models.."); println!("generating models..");
physics_data.generate_models(&map); let physics_data=PhysicsData::new(&map);
println!("simulating..."); println!("simulating...");
//teleport to bug //teleport to bug
@@ -70,7 +68,7 @@ fn physics_bug_3()->Result<(),ReplayError>{
let mut physics=PhysicsState::new_with_body(body); let mut physics=PhysicsState::new_with_body(body);
// wait one second to activate the bug // wait one second to activate the bug
PhysicsContext::run_input_instruction(&mut physics,&physics_data,strafesnet_common::instruction::TimedInstruction{ PhysicsContext::run_input_instruction(&mut physics,&physics_data,strafesnet_common::instruction::TimedInstruction{
time:strafesnet_common::integer::Time::from_millis(500), time:Time::from_millis(500),
instruction:strafesnet_common::physics::Instruction::Idle, instruction:strafesnet_common::physics::Instruction::Idle,
}); });

View File

@@ -17,3 +17,6 @@ vbsp = "0.9.1"
vbsp-entities-css = "0.6.0" vbsp-entities-css = "0.6.0"
vmdl = "0.2.0" vmdl = "0.2.0"
vpk = "0.3.0" vpk = "0.3.0"
[lints]
workspace = true

View File

@@ -7,7 +7,7 @@ use crate::{valve_transform_normal,valve_transform_dist};
#[derive(Hash,Eq,PartialEq)] #[derive(Hash,Eq,PartialEq)]
struct Face{ struct Face{
normal:integer::Planar64Vec3, normal:integer::Planar64Vec3,
dot:integer::Planar64, dot:Planar64,
} }
#[derive(Debug)] #[derive(Debug)]

View File

@@ -347,7 +347,7 @@ pub struct PartialMap1{
modes:NormalizedModes, modes:NormalizedModes,
} }
impl PartialMap1{ impl PartialMap1{
pub fn add_prop_meshes<'a>( pub fn add_prop_meshes(
self, self,
prop_meshes:Meshes<model::Mesh>, prop_meshes:Meshes<model::Mesh>,
)->PartialMap2{ )->PartialMap2{

View File

@@ -31,7 +31,7 @@ impl Loader for TextureLoader{
type Error=TextureError; type Error=TextureError;
type Index<'a>=Cow<'a,str>; type Index<'a>=Cow<'a,str>;
type Resource=Texture; type Resource=Texture;
fn load<'a>(&mut self,index:Self::Index<'a>)->Result<Self::Resource,Self::Error>{ fn load(&mut self,index:Self::Index<'_>)->Result<Self::Resource,Self::Error>{
let file_name=format!("textures/{}.dds",index); let file_name=format!("textures/{}.dds",index);
let mut file=std::fs::File::open(file_name)?; let mut file=std::fs::File::open(file_name)?;
let mut data=Vec::new(); let mut data=Vec::new();
@@ -111,7 +111,7 @@ impl ModelLoader<'_,'_>{
} }
} }
} }
impl<'bsp,'vpk> Loader for ModelLoader<'bsp,'vpk>{ impl Loader for ModelLoader<'_,'_>{
type Error=MeshError; type Error=MeshError;
type Index<'a>=&'a str where Self:'a; type Index<'a>=&'a str where Self:'a;
type Resource=vmdl::Model; type Resource=vmdl::Model;
@@ -151,7 +151,7 @@ impl MeshLoader<'_,'_,'_,'_>{
} }
} }
} }
impl<'str,'bsp,'vpk,'load> Loader for MeshLoader<'bsp,'vpk,'load,'str>{ impl Loader for MeshLoader<'_,'_,'_,'_>{
type Error=MeshError; type Error=MeshError;
type Index<'a>=&'a str where Self:'a; type Index<'a>=&'a str where Self:'a;
type Resource=Mesh; type Resource=Mesh;

View File

@@ -61,7 +61,7 @@ pub fn convert_mesh(model:vmdl::Model,deferred_loader:&mut RenderConfigDeferredL
_=>None, _=>None,
} }
}) })
}).flat_map(|[v1,v2,v3]|{ }).filter_map(|[v1,v2,v3]|{
// this should probably be a fatal error :D // this should probably be a fatal error :D
let v1=model_vertices.get(v1)?; let v1=model_vertices.get(v1)?;
let v2=model_vertices.get(v2)?; let v2=model_vertices.get(v2)?;

View File

@@ -17,3 +17,6 @@ linear_ops = { version = "0.1.1", path = "../linear_ops", registry = "strafesnet
ratio_ops = { version = "0.1.0", path = "../ratio_ops", registry = "strafesnet" } ratio_ops = { version = "0.1.0", path = "../ratio_ops", registry = "strafesnet" }
glam = "0.30.0" glam = "0.30.0"
id = { version = "0.1.0", registry = "strafesnet" } id = { version = "0.1.0", registry = "strafesnet" }
[lints]
workspace = true

View File

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

View File

@@ -245,18 +245,19 @@ pub fn generate_bvh<T>(boxen:Vec<(T,Aabb)>)->BvhNode<T>{
fn generate_bvh_node<T>(boxen:Vec<(T,Aabb)>,force:bool)->BvhNode<T>{ fn generate_bvh_node<T>(boxen:Vec<(T,Aabb)>,force:bool)->BvhNode<T>{
let n=boxen.len(); let n=boxen.len();
if force||n<20{ const MAX_TERMINAL_BRANCH_LEAF_NODES:usize=20;
let mut aabb=Aabb::default(); if force||n<MAX_TERMINAL_BRANCH_LEAF_NODES{
let nodes=boxen.into_iter().map(|b|{ let mut aabb_outer=Aabb::default();
aabb.join(&b.1); let nodes=boxen.into_iter().map(|(data,aabb)|{
aabb_outer.join(&aabb);
BvhNode{ BvhNode{
content:RecursiveContent::Leaf(b.0), content:RecursiveContent::Leaf(data),
aabb:b.1, aabb,
} }
}).collect(); }).collect();
BvhNode{ BvhNode{
content:RecursiveContent::Branch(nodes), content:RecursiveContent::Branch(nodes),
aabb, aabb:aabb_outer,
} }
}else{ }else{
let mut sort_x=Vec::with_capacity(n); let mut sort_x=Vec::with_capacity(n);
@@ -272,9 +273,9 @@ fn generate_bvh_node<T>(boxen:Vec<(T,Aabb)>,force:bool)->BvhNode<T>{
sort_y.sort_by_key(|&(_,c)|c); sort_y.sort_by_key(|&(_,c)|c);
sort_z.sort_by_key(|&(_,c)|c); sort_z.sort_by_key(|&(_,c)|c);
let h=n/2; let h=n/2;
let median_x=sort_x[h].1; let (_,median_x)=sort_x[h];
let median_y=sort_y[h].1; let (_,median_y)=sort_y[h];
let median_z=sort_z[h].1; let (_,median_z)=sort_z[h];
//locate a run of values equal to the median //locate a run of values equal to the median
//partition point gives the first index for which the predicate evaluates to false //partition point gives the first index for which the predicate evaluates to false
let first_index_eq_median_x=sort_x.partition_point(|&(_,x)|x<median_x); let first_index_eq_median_x=sort_x.partition_point(|&(_,x)|x<median_x);
@@ -313,10 +314,10 @@ fn generate_bvh_node<T>(boxen:Vec<(T,Aabb)>,force:bool)->BvhNode<T>{
}; };
list_list[list_id].push((data,aabb)); list_list[list_id].push((data,aabb));
} }
let mut aabb=Aabb::default();
if list_list.len()==1{ if list_list.len()==1{
generate_bvh_node(list_list.remove(0),true) generate_bvh_node(list_list.remove(0),true)
}else{ }else{
let mut aabb=Aabb::default();
BvhNode{ BvhNode{
content:RecursiveContent::Branch( content:RecursiveContent::Branch(
list_list.into_iter().map(|b|{ list_list.into_iter().map(|b|{

View File

@@ -34,7 +34,7 @@ pub struct StyleModifiers{
//unused //unused
pub mass:Planar64, pub mass:Planar64,
} }
impl std::default::Default for StyleModifiers{ impl Default for StyleModifiers{
fn default()->Self{ fn default()->Self{
Self::roblox_bhop() Self::roblox_bhop()
} }

View File

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

View File

@@ -86,7 +86,7 @@ impl<T> std::fmt::Display for Time<T>{
write!(f,"{}s+{:09}ns",self.0/Self::ONE_SECOND.0,self.0%Self::ONE_SECOND.0) write!(f,"{}s+{:09}ns",self.0/Self::ONE_SECOND.0,self.0%Self::ONE_SECOND.0)
} }
} }
impl<T> std::default::Default for Time<T>{ impl<T> Default for Time<T>{
fn default()->Self{ fn default()->Self{
Self::raw(0) Self::raw(0)
} }
@@ -126,7 +126,7 @@ impl_time_additive_assign_operator!(core::ops::AddAssign,add_assign);
impl_time_additive_assign_operator!(core::ops::SubAssign,sub_assign); impl_time_additive_assign_operator!(core::ops::SubAssign,sub_assign);
impl_time_additive_assign_operator!(core::ops::RemAssign,rem_assign); impl_time_additive_assign_operator!(core::ops::RemAssign,rem_assign);
impl<T> std::ops::Mul for Time<T>{ impl<T> std::ops::Mul for Time<T>{
type Output=Ratio<fixed_wide::fixed::Fixed<2,64>,fixed_wide::fixed::Fixed<2,64>>; type Output=Ratio<Fixed<2,64>,Fixed<2,64>>;
#[inline] #[inline]
fn mul(self,rhs:Self)->Self::Output{ fn mul(self,rhs:Self)->Self::Output{
Ratio::new(Fixed::raw(self.0)*Fixed::raw(rhs.0),Fixed::raw_digit(1_000_000_000i64.pow(2))) Ratio::new(Fixed::raw(self.0)*Fixed::raw(rhs.0),Fixed::raw_digit(1_000_000_000i64.pow(2)))
@@ -156,7 +156,7 @@ impl<T> core::ops::Mul<Time<T>> for Planar64{
#[cfg(test)] #[cfg(test)]
mod test_time{ mod test_time{
use super::*; use super::*;
type Time=super::AbsoluteTime; type Time=AbsoluteTime;
#[test] #[test]
fn time_from_planar64(){ fn time_from_planar64(){
let a:Time=Planar64::from(1).into(); let a:Time=Planar64::from(1).into();
@@ -552,7 +552,7 @@ impl TryFrom<[f32;3]> for Unit32Vec3{
} }
*/ */
pub type Planar64TryFromFloatError=fixed_wide::fixed::FixedFromFloatError; pub type Planar64TryFromFloatError=FixedFromFloatError;
pub type Planar64=fixed_wide::types::I32F32; pub type Planar64=fixed_wide::types::I32F32;
pub type Planar64Vec3=linear_ops::types::Vector3<Planar64>; pub type Planar64Vec3=linear_ops::types::Vector3<Planar64>;
pub type Planar64Mat3=linear_ops::types::Matrix3<Planar64>; pub type Planar64Mat3=linear_ops::types::Matrix3<Planar64>;
@@ -562,11 +562,11 @@ pub mod vec3{
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:Planar64Vec3=Planar64Vec3::new([Planar64::ZERO;3]);
pub const ZERO_2:linear_ops::types::Vector3<Fixed::<2,64>>=linear_ops::types::Vector3::new([Fixed::<2,64>::ZERO;3]); pub const ZERO_2:Vector3<Fixed::<2,64>>=Vector3::new([Fixed::<2,64>::ZERO;3]);
pub const ZERO_3:linear_ops::types::Vector3<Fixed::<3,96>>=linear_ops::types::Vector3::new([Fixed::<3,96>::ZERO;3]); pub const ZERO_3:Vector3<Fixed::<3,96>>=Vector3::new([Fixed::<3,96>::ZERO;3]);
pub const ZERO_4:linear_ops::types::Vector3<Fixed::<4,128>>=linear_ops::types::Vector3::new([Fixed::<4,128>::ZERO;3]); pub const ZERO_4:Vector3<Fixed::<4,128>>=Vector3::new([Fixed::<4,128>::ZERO;3]);
pub const ZERO_5:linear_ops::types::Vector3<Fixed::<5,160>>=linear_ops::types::Vector3::new([Fixed::<5,160>::ZERO;3]); pub const ZERO_5:Vector3<Fixed::<5,160>>=Vector3::new([Fixed::<5,160>::ZERO;3]);
pub const ZERO_6:linear_ops::types::Vector3<Fixed::<6,192>>=linear_ops::types::Vector3::new([Fixed::<6,192>::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]);

View File

@@ -11,3 +11,6 @@ authors = ["Rhys Lloyd <krakow20@gmail.com>"]
[dependencies] [dependencies]
strafesnet_common = { version = "0.7.0", path = "../common", registry = "strafesnet" } strafesnet_common = { version = "0.7.0", path = "../common", registry = "strafesnet" }
[lints]
workspace = true

View File

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

View File

@@ -18,3 +18,6 @@ bnum = "0.13.0"
arrayvec = { version = "0.7.6", optional = true } arrayvec = { version = "0.7.6", optional = true }
paste = "1.0.15" paste = "1.0.15"
ratio_ops = { version = "0.1.0", path = "../ratio_ops", registry = "strafesnet", optional = true } ratio_ops = { version = "0.1.0", path = "../ratio_ops", registry = "strafesnet", optional = true }
[lints]
workspace = true

View File

@@ -1,6 +1,8 @@
use bnum::{BInt,cast::As}; use bnum::{BInt,cast::As};
#[derive(Clone,Copy,Debug,Default,Hash,PartialEq,Eq,PartialOrd,Ord)] const BNUM_DIGIT_WIDTH:usize=64;
#[derive(Clone,Copy,Default,Hash,PartialEq,Eq,PartialOrd,Ord)]
/// A Fixed point number for which multiply operations widen the bits in the output. (when the wide-mul feature is enabled) /// A Fixed point number for which multiply operations widen the bits in the output. (when the wide-mul feature is enabled)
/// N is the number of u64s to use /// N is the number of u64s to use
/// F is the number of fractional bits (always N*32 lol) /// F is the number of fractional bits (always N*32 lol)
@@ -286,6 +288,23 @@ macro_rules! impl_from_float {
impl_from_float!(integer_decode_f32,f32,24); impl_from_float!(integer_decode_f32,f32,24);
impl_from_float!(integer_decode_f64,f64,53); impl_from_float!(integer_decode_f64,f64,53);
impl<const N:usize,const F:usize> core::fmt::Debug for Fixed<N,F>{
#[inline]
fn fmt(&self,f:&mut core::fmt::Formatter)->Result<(),core::fmt::Error>{
let integral=self.as_bits().unsigned_abs()>>F;
let fractional=self.as_bits().unsigned_abs()&((bnum::BUint::<N>::ONE<<F)-bnum::BUint::<N>::ONE);
let leading_zeroes=(fractional.leading_zeros() as usize).saturating_sub(N*BNUM_DIGIT_WIDTH-F)>>2;
if self.is_negative(){
core::write!(f,"-")?;
}
if fractional.is_zero(){
core::write!(f,"{integral:x}.{}","0".repeat(leading_zeroes))
}else{
core::write!(f,"{integral:x}.{}{fractional:x}","0".repeat(leading_zeroes))
}
}
}
impl<const N:usize,const F:usize> core::fmt::Display for Fixed<N,F>{ impl<const N:usize,const F:usize> core::fmt::Display for Fixed<N,F>{
#[inline] #[inline]
fn fmt(&self,f:&mut core::fmt::Formatter)->Result<(),core::fmt::Error>{ fn fmt(&self,f:&mut core::fmt::Formatter)->Result<(),core::fmt::Error>{

View File

@@ -229,3 +229,16 @@ fn test_zeroes_deferred_division(){
]) ])
); );
} }
#[test]
fn test_debug(){
assert_eq!(format!("{:?}",I32F32::EPSILON),"0.00000001");
assert_eq!(format!("{:?}",I32F32::ONE),"1.00000000");
assert_eq!(format!("{:?}",I32F32::TWO),"2.00000000");
assert_eq!(format!("{:?}",I32F32::MAX),"7fffffff.ffffffff");
assert_eq!(format!("{:?}",I32F32::try_from(core::f64::consts::PI).unwrap()),"3.243f6a88");
assert_eq!(format!("{:?}",I32F32::NEG_EPSILON),"-0.00000001");
assert_eq!(format!("{:?}",I32F32::NEG_ONE),"-1.00000000");
assert_eq!(format!("{:?}",I32F32::NEG_TWO),"-2.00000000");
assert_eq!(format!("{:?}",I32F32::MIN),"-80000000.00000000");
}

View File

@@ -20,3 +20,6 @@ paste = { version = "1.0.15", optional = true }
[dev-dependencies] [dev-dependencies]
fixed_wide = { path = "../fixed_wide", registry = "strafesnet", features = ["wide-mul"] } fixed_wide = { path = "../fixed_wide", registry = "strafesnet", features = ["wide-mul"] }
[lints]
workspace = true

View File

@@ -205,7 +205,8 @@ macro_rules! impl_matrix_named_fields_shape {
#[inline] #[inline]
fn deref(&self)->&Self::Target{ fn deref(&self)->&Self::Target{
// This cast is valid because Matrix has #[repr(transparent)] // This cast is valid because Matrix has #[repr(transparent)]
let ptr=&self.array as *const [[T;$size_inner];$size_outer] as *const Self::Target; let ptr:*const [[T;$size_inner];$size_outer]=&self.array;
let ptr=ptr as *const Self::Target;
// SAFETY: this pointer is non-null because it comes from a reference // SAFETY: this pointer is non-null because it comes from a reference
unsafe{&*ptr} unsafe{&*ptr}
} }
@@ -214,7 +215,8 @@ macro_rules! impl_matrix_named_fields_shape {
#[inline] #[inline]
fn deref_mut(&mut self)->&mut Self::Target{ fn deref_mut(&mut self)->&mut Self::Target{
// This cast is valid because Matrix has #[repr(transparent)] // This cast is valid because Matrix has #[repr(transparent)]
let ptr=&mut self.array as *mut [[T;$size_inner];$size_outer] as *mut Self::Target; let ptr:*mut [[T;$size_inner];$size_outer]=&mut self.array;
let ptr=ptr as *mut Self::Target;
// SAFETY: this pointer is non-null because it comes from a reference // SAFETY: this pointer is non-null because it comes from a reference
unsafe{&mut*ptr} unsafe{&mut*ptr}
} }

View File

@@ -331,7 +331,8 @@ macro_rules! impl_vector_named_fields {
#[inline] #[inline]
fn deref(&self)->&Self::Target{ fn deref(&self)->&Self::Target{
// This cast is valid because Vector has #[repr(transparent)] // This cast is valid because Vector has #[repr(transparent)]
let ptr=&self.array as *const [T;$size] as *const Self::Target; let ptr:*const [T;$size]=&self.array;
let ptr=ptr as *const Self::Target;
// SAFETY: this pointer is non-null because it comes from a reference // SAFETY: this pointer is non-null because it comes from a reference
unsafe{&*ptr} unsafe{&*ptr}
} }
@@ -340,7 +341,8 @@ macro_rules! impl_vector_named_fields {
#[inline] #[inline]
fn deref_mut(&mut self)->&mut Self::Target{ fn deref_mut(&mut self)->&mut Self::Target{
// This cast is valid because Vector has #[repr(transparent)] // This cast is valid because Vector has #[repr(transparent)]
let ptr=&mut self.array as *mut [T;$size] as *mut Self::Target; let ptr:*mut [T;$size]=&mut self.array;
let ptr=ptr as *mut Self::Target;
// SAFETY: this pointer is non-null because it comes from a reference // SAFETY: this pointer is non-null because it comes from a reference
unsafe{&mut*ptr} unsafe{&mut*ptr}
} }

View File

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

View File

@@ -12,14 +12,17 @@ authors = ["Rhys Lloyd <krakow20@gmail.com>"]
[dependencies] [dependencies]
bytemuck = "1.14.3" bytemuck = "1.14.3"
glam = "0.30.0" glam = "0.30.0"
lazy-regex = "3.1.0" regex = { version = "1.11.3", default-features = false }
rbx_binary = { version = "1.1.0-sn4", registry = "strafesnet" } rbx_binary = { version = "1.0.1-sn5", registry = "strafesnet" }
rbx_dom_weak = { version = "3.1.0-sn4", registry = "strafesnet", features = ["instance-userdata"] } 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 = "5.0.0"
rbx_reflection_database = "1.0.0" rbx_reflection_database = "1.0.0"
rbx_xml = { version = "1.1.0-sn4", registry = "strafesnet" } 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" }
[lints]
workspace = true

View File

@@ -12,7 +12,7 @@ mod mesh;
mod error; mod error;
mod union; mod union;
pub mod loader; pub mod loader;
mod primitives; pub mod primitives;
pub mod data{ pub mod data{
pub struct RobloxMeshBytes(Vec<u8>); pub struct RobloxMeshBytes(Vec<u8>);

View File

@@ -50,7 +50,7 @@ impl Loader for TextureLoader{
type Error=TextureError; type Error=TextureError;
type Index<'a>=&'a str; type Index<'a>=&'a str;
type Resource=Texture; type Resource=Texture;
fn load<'a>(&mut self,index:Self::Index<'a>)->Result<Self::Resource,Self::Error>{ fn load(&mut self,index:Self::Index<'_>)->Result<Self::Resource,Self::Error>{
let RobloxAssetId(asset_id)=index.parse()?; let RobloxAssetId(asset_id)=index.parse()?;
let file_name=format!("textures/{}.dds",asset_id); let file_name=format!("textures/{}.dds",asset_id);
let data=read_entire_file(file_name)?; let data=read_entire_file(file_name)?;
@@ -157,7 +157,7 @@ impl Loader for MeshLoader{
type Error=MeshError; type Error=MeshError;
type Index<'a>=MeshIndex<'a>; type Index<'a>=MeshIndex<'a>;
type Resource=MeshWithSize; type Resource=MeshWithSize;
fn load<'a>(&mut self,index:Self::Index<'a>)->Result<Self::Resource,Self::Error>{ fn load(&mut self,index:Self::Index<'_>)->Result<Self::Resource,Self::Error>{
let mesh=match index.mesh_type{ let mesh=match index.mesh_type{
MeshType::FileMesh=>{ MeshType::FileMesh=>{
let RobloxAssetId(asset_id)=index.content.parse()?; let RobloxAssetId(asset_id)=index.content.parse()?;

View File

@@ -9,7 +9,6 @@ use crate::loader::MeshWithSize;
#[derive(Debug)] #[derive(Debug)]
pub enum Error{ pub enum Error{
Planar64Vec3(strafesnet_common::integer::Planar64TryFromFloatError),
RbxMesh(rbx_mesh::mesh::Error) RbxMesh(rbx_mesh::mesh::Error)
} }
impl std::fmt::Display for Error{ impl std::fmt::Display for Error{
@@ -22,41 +21,43 @@ impl std::error::Error for Error{}
fn ingest_vertices2( fn ingest_vertices2(
vertices:Vec<Vertex2>, vertices:Vec<Vertex2>,
mb:&mut model::MeshBuilder, mb:&mut model::MeshBuilder,
)->Result<HashMap<rbx_mesh::mesh::VertexId2,VertexId>,Error>{ )->HashMap<rbx_mesh::mesh::VertexId2,VertexId>{
//this monster is collecting a map of old_vertices_index -> unique_vertices_index //this monster is collecting a map of old_vertices_index -> unique_vertices_index
//while also doing the inserting unique entries into lists simultaneously //while also doing the inserting unique entries into lists simultaneously
Ok(vertices.into_iter().enumerate().map(|(vertex_id,vertex)|Ok(( // vertex positions that fail to convert are DROPPED
vertices.into_iter().enumerate().filter_map(|(vertex_id,vertex)|Some((
rbx_mesh::mesh::VertexId2(vertex_id as u32), rbx_mesh::mesh::VertexId2(vertex_id as u32),
{ {
let vertex=IndexedVertex{ let vertex=IndexedVertex{
pos:mb.acquire_pos_id(vec3::try_from_f32_array(vertex.pos)?), pos:mb.acquire_pos_id(vec3::try_from_f32_array(vertex.pos).ok()?),
tex:mb.acquire_tex_id(glam::Vec2::from_array(vertex.tex)), tex:mb.acquire_tex_id(glam::Vec2::from_array(vertex.tex)),
normal:mb.acquire_normal_id(vec3::try_from_f32_array(vertex.norm)?), normal:mb.acquire_normal_id(vec3::try_from_f32_array(vertex.norm).ok()?),
color:mb.acquire_color_id(glam::Vec4::from_array(vertex.color.map(|f|f as f32/255.0f32))) color:mb.acquire_color_id(glam::Vec4::from_array(vertex.color.map(|f|f as f32/255.0f32))),
}; };
mb.acquire_vertex_id(vertex) mb.acquire_vertex_id(vertex)
} }
))).collect::<Result<_,_>>().map_err(Error::Planar64Vec3)?) ))).collect()
} }
fn ingest_vertices_truncated2( fn ingest_vertices_truncated2(
vertices:Vec<Vertex2Truncated>, vertices:Vec<Vertex2Truncated>,
mb:&mut model::MeshBuilder, mb:&mut model::MeshBuilder,
static_color_id:ColorId,//pick one color and fill everything with it static_color_id:ColorId,//pick one color and fill everything with it
)->Result<HashMap<rbx_mesh::mesh::VertexId2,VertexId>,Error>{ )->HashMap<rbx_mesh::mesh::VertexId2,VertexId>{
//this monster is collecting a map of old_vertices_index -> unique_vertices_index //this monster is collecting a map of old_vertices_index -> unique_vertices_index
//while also doing the inserting unique entries into lists simultaneously //while also doing the inserting unique entries into lists simultaneously
Ok(vertices.into_iter().enumerate().map(|(vertex_id,vertex)|Ok(( // vertex positions that fail to convert are DROPPED
vertices.into_iter().enumerate().filter_map(|(vertex_id,vertex)|Some((
rbx_mesh::mesh::VertexId2(vertex_id as u32), rbx_mesh::mesh::VertexId2(vertex_id as u32),
{ {
let vertex=IndexedVertex{ let vertex=IndexedVertex{
pos:mb.acquire_pos_id(vec3::try_from_f32_array(vertex.pos)?), pos:mb.acquire_pos_id(vec3::try_from_f32_array(vertex.pos).ok()?),
tex:mb.acquire_tex_id(glam::Vec2::from_array(vertex.tex)), tex:mb.acquire_tex_id(glam::Vec2::from_array(vertex.tex)),
normal:mb.acquire_normal_id(vec3::try_from_f32_array(vertex.norm)?), normal:mb.acquire_normal_id(vec3::try_from_f32_array(vertex.norm).ok()?),
color:static_color_id, color:static_color_id,
}; };
mb.acquire_vertex_id(vertex) mb.acquire_vertex_id(vertex)
} }
))).collect::<Result<_,_>>().map_err(Error::Planar64Vec3)?) ))).collect()
} }
fn ingest_faces2_lods3( fn ingest_faces2_lods3(
@@ -67,8 +68,8 @@ fn ingest_faces2_lods3(
){ ){
//faces have to be split into polygon groups based on lod //faces have to be split into polygon groups based on lod
polygon_groups.extend(lods.windows(2).map(|lod_pair| polygon_groups.extend(lods.windows(2).map(|lod_pair|
PolygonGroup::PolygonList(PolygonList::new(faces[lod_pair[0].0 as usize..lod_pair[1].0 as usize].iter().map(|rbx_mesh::mesh::Face2(v0,v1,v2)| PolygonGroup::PolygonList(PolygonList::new(faces[lod_pair[0].0 as usize..lod_pair[1].0 as usize].iter().filter_map(|rbx_mesh::mesh::Face2(v0,v1,v2)|
vec![vertex_id_map[&v0],vertex_id_map[&v1],vertex_id_map[&v2]] Some(vec![*vertex_id_map.get(&v0)?,*vertex_id_map.get(&v1)?,*vertex_id_map.get(&v2)?])
).collect())) ).collect()))
)) ))
} }
@@ -80,18 +81,18 @@ pub fn convert(roblox_mesh_bytes:crate::data::RobloxMeshBytes)->Result<MeshWithS
match rbx_mesh::read_versioned(roblox_mesh_bytes.cursor()).map_err(Error::RbxMesh)?{ match rbx_mesh::read_versioned(roblox_mesh_bytes.cursor()).map_err(Error::RbxMesh)?{
rbx_mesh::mesh::Mesh::V1(mesh)=>{ rbx_mesh::mesh::Mesh::V1(mesh)=>{
let color_id=mb.acquire_color_id(glam::Vec4::ONE); let color_id=mb.acquire_color_id(glam::Vec4::ONE);
polygon_groups.push(PolygonGroup::PolygonList(PolygonList::new(mesh.vertices.chunks_exact(3).map(|trip|{ polygon_groups.push(PolygonGroup::PolygonList(PolygonList::new(mesh.vertices.chunks_exact(3).filter_map(|trip|{
let mut ingest_vertex1=|vertex:&rbx_mesh::mesh::Vertex1|{ let mut ingest_vertex1=|vertex:&rbx_mesh::mesh::Vertex1|{
let vertex=IndexedVertex{ let vertex=IndexedVertex{
pos:mb.acquire_pos_id(vec3::try_from_f32_array(vertex.pos)?), pos:mb.acquire_pos_id(vec3::try_from_f32_array(vertex.pos).ok()?),
tex:mb.acquire_tex_id(glam::vec2(vertex.tex[0],vertex.tex[1])), tex:mb.acquire_tex_id(glam::vec2(vertex.tex[0],vertex.tex[1])),
normal:mb.acquire_normal_id(vec3::try_from_f32_array(vertex.norm)?), normal:mb.acquire_normal_id(vec3::try_from_f32_array(vertex.norm).ok()?),
color:color_id, color:color_id,
}; };
Ok(mb.acquire_vertex_id(vertex)) Some(mb.acquire_vertex_id(vertex))
}; };
Ok(vec![ingest_vertex1(&trip[0])?,ingest_vertex1(&trip[1])?,ingest_vertex1(&trip[2])?]) Some(vec![ingest_vertex1(&trip[0])?,ingest_vertex1(&trip[1])?,ingest_vertex1(&trip[2])?])
}).collect::<Result<_,_>>().map_err(Error::Planar64Vec3)?))); }).collect())));
}, },
rbx_mesh::mesh::Mesh::V2(mesh)=>{ rbx_mesh::mesh::Mesh::V2(mesh)=>{
let vertex_id_map=match mesh.header.sizeof_vertex{ let vertex_id_map=match mesh.header.sizeof_vertex{
@@ -101,10 +102,10 @@ pub fn convert(roblox_mesh_bytes:crate::data::RobloxMeshBytes)->Result<MeshWithS
ingest_vertices_truncated2(mesh.vertices_truncated,&mut mb,color_id) ingest_vertices_truncated2(mesh.vertices_truncated,&mut mb,color_id)
}, },
rbx_mesh::mesh::SizeOfVertex2::Full=>ingest_vertices2(mesh.vertices,&mut mb), rbx_mesh::mesh::SizeOfVertex2::Full=>ingest_vertices2(mesh.vertices,&mut mb),
}?; };
//one big happy group for all the faces //one big happy group for all the faces
polygon_groups.push(PolygonGroup::PolygonList(PolygonList::new(mesh.faces.into_iter().map(|face| polygon_groups.push(PolygonGroup::PolygonList(PolygonList::new(mesh.faces.into_iter().filter_map(|face|
vec![vertex_id_map[&face.0],vertex_id_map[&face.1],vertex_id_map[&face.2]] Some(vec![*vertex_id_map.get(&face.0)?,*vertex_id_map.get(&face.1)?,*vertex_id_map.get(&face.2)?])
).collect()))); ).collect())));
}, },
rbx_mesh::mesh::Mesh::V3(mesh)=>{ rbx_mesh::mesh::Mesh::V3(mesh)=>{
@@ -114,15 +115,15 @@ pub fn convert(roblox_mesh_bytes:crate::data::RobloxMeshBytes)->Result<MeshWithS
ingest_vertices_truncated2(mesh.vertices_truncated,&mut mb,color_id) ingest_vertices_truncated2(mesh.vertices_truncated,&mut mb,color_id)
}, },
rbx_mesh::mesh::SizeOfVertex2::Full=>ingest_vertices2(mesh.vertices,&mut mb), rbx_mesh::mesh::SizeOfVertex2::Full=>ingest_vertices2(mesh.vertices,&mut mb),
}?; };
ingest_faces2_lods3(&mut polygon_groups,&vertex_id_map,&mesh.faces,&mesh.lods); ingest_faces2_lods3(&mut polygon_groups,&vertex_id_map,&mesh.faces,&mesh.lods);
}, },
rbx_mesh::mesh::Mesh::V4(mesh)=>{ rbx_mesh::mesh::Mesh::V4(mesh)=>{
let vertex_id_map=ingest_vertices2(mesh.vertices,&mut mb)?; let vertex_id_map=ingest_vertices2(mesh.vertices,&mut mb);
ingest_faces2_lods3(&mut polygon_groups,&vertex_id_map,&mesh.faces,&mesh.lods); ingest_faces2_lods3(&mut polygon_groups,&vertex_id_map,&mesh.faces,&mesh.lods);
}, },
rbx_mesh::mesh::Mesh::V5(mesh)=>{ rbx_mesh::mesh::Mesh::V5(mesh)=>{
let vertex_id_map=ingest_vertices2(mesh.vertices,&mut mb)?; let vertex_id_map=ingest_vertices2(mesh.vertices,&mut mb);
ingest_faces2_lods3(&mut polygon_groups,&vertex_id_map,&mesh.faces,&mesh.lods); ingest_faces2_lods3(&mut polygon_groups,&vertex_id_map,&mesh.faces,&mesh.lods);
}, },
} }

View File

@@ -18,6 +18,15 @@ fn static_ustr(s:&'static str)->rbx_dom_weak::Ustr{
rbx_dom_weak::ustr(s) rbx_dom_weak::ustr(s)
} }
macro_rules! lazy_regex{
($r:literal)=>{{
use regex::Regex;
use std::sync::LazyLock;
static RE:LazyLock<Regex>=LazyLock::new(||Regex::new($r).unwrap());
&RE
}};
}
fn planar64_affine3_from_roblox(cf:&rbx_dom_weak::types::CFrame,size:&rbx_dom_weak::types::Vector3)->Result<Planar64Affine3,Planar64TryFromFloatError>{ fn planar64_affine3_from_roblox(cf:&rbx_dom_weak::types::CFrame,size:&rbx_dom_weak::types::Vector3)->Result<Planar64Affine3,Planar64TryFromFloatError>{
Ok(Planar64Affine3::new( Ok(Planar64Affine3::new(
Planar64Mat3::from_cols([ Planar64Mat3::from_cols([
@@ -118,7 +127,7 @@ fn get_attributes(name:&str,can_collide:bool,velocity:Planar64Vec3,model_id:mode
); );
}, },
other=>{ other=>{
let regman=lazy_regex::regex!(r"^(BonusStart|WormholeOut)(\d+)$"); let regman=lazy_regex!(r"^(BonusStart|WormholeOut)(\d+)$");
if let Some(captures)=regman.captures(other){ if let Some(captures)=regman.captures(other){
match &captures[1]{ match &captures[1]{
"BonusStart"=>{ "BonusStart"=>{
@@ -144,7 +153,7 @@ fn get_attributes(name:&str,can_collide:bool,velocity:Planar64Vec3,model_id:mode
}, },
_=>(), _=>(),
} }
}else if let Some(captures)=lazy_regex::regex!(r"^(Force)?(Spawn|SpawnAt|Trigger|Teleport|Platform)(\d+)$") }else if let Some(captures)=lazy_regex!(r"^(Force)?(Spawn|SpawnAt|Trigger|Teleport|Platform)(\d+)$")
.captures(other){ .captures(other){
force_intersecting=true; force_intersecting=true;
let stage_id=StageId::new(ParseIntContext::parse(&captures[3]).map_err(GetAttributesError::StageIdParseInt)?); let stage_id=StageId::new(ParseIntContext::parse(&captures[3]).map_err(GetAttributesError::StageIdParseInt)?);
@@ -185,7 +194,7 @@ fn get_attributes(name:&str,can_collide:bool,velocity:Planar64Vec3,model_id:mode
stage_element, stage_element,
), ),
); );
}else if let Some(captures)=lazy_regex::regex!(r"^(Jump|WormholeIn)(\d+)$") }else if let Some(captures)=lazy_regex!(r"^(Jump|WormholeIn)(\d+)$")
.captures(other){ .captures(other){
match &captures[1]{ match &captures[1]{
"Jump"=>modes_builder.push_mode_update( "Jump"=>modes_builder.push_mode_update(
@@ -210,7 +219,7 @@ fn get_attributes(name:&str,can_collide:bool,velocity:Planar64Vec3,model_id:mode
}, },
_=>unreachable!("regex2[1] messed up bad"), _=>unreachable!("regex2[1] messed up bad"),
} }
}else if let Some(captures)=lazy_regex::regex!(r"^Bonus(Finish|Anticheat)(\d+)$") }else if let Some(captures)=lazy_regex!(r"^Bonus(Finish|Anticheat)(\d+)$")
.captures(other){ .captures(other){
force_can_collide=false; force_can_collide=false;
force_intersecting=true; force_intersecting=true;
@@ -335,12 +344,12 @@ pub struct RobloxFaceTextureDescription{
pub color:glam::Vec4, pub color:glam::Vec4,
pub transform:RobloxTextureTransform, pub transform:RobloxTextureTransform,
} }
impl core::cmp::PartialEq for RobloxFaceTextureDescription{ impl PartialEq for RobloxFaceTextureDescription{
fn eq(&self,other:&Self)->bool{ fn eq(&self,other:&Self)->bool{
self.to_bits().eq(&other.to_bits()) self.to_bits().eq(&other.to_bits())
} }
} }
impl core::cmp::Eq for RobloxFaceTextureDescription{} impl Eq for RobloxFaceTextureDescription{}
impl core::hash::Hash for RobloxFaceTextureDescription{ impl core::hash::Hash for RobloxFaceTextureDescription{
fn hash<H:core::hash::Hasher>(&self,state:&mut H){ fn hash<H:core::hash::Hasher>(&self,state:&mut H){
self.to_bits().hash(state); self.to_bits().hash(state);
@@ -766,10 +775,10 @@ struct MeshIdWithSize{
mesh:model::MeshId, mesh:model::MeshId,
size:Planar64Vec3, size:Planar64Vec3,
} }
fn acquire_mesh_id_from_render_config_id<'a>( fn acquire_mesh_id_from_render_config_id(
primitive_meshes:&mut Vec<model::Mesh>, primitive_meshes:&mut Vec<model::Mesh>,
mesh_id_from_render_config_id:&mut HashMap<model::MeshId,HashMap<RenderConfigId,model::MeshId>>, mesh_id_from_render_config_id:&mut HashMap<model::MeshId,HashMap<RenderConfigId,model::MeshId>>,
loaded_meshes:&'a HashMap<model::MeshId,MeshWithSize>, loaded_meshes:&HashMap<model::MeshId,MeshWithSize>,
old_mesh_id:model::MeshId, old_mesh_id:model::MeshId,
render:RenderConfigId, render:RenderConfigId,
)->Option<MeshIdWithSize>{ )->Option<MeshIdWithSize>{
@@ -789,10 +798,10 @@ fn acquire_mesh_id_from_render_config_id<'a>(
size, size,
}) })
} }
fn acquire_union_id_from_render_config_id<'a>( fn acquire_union_id_from_render_config_id(
primitive_meshes:&mut Vec<model::Mesh>, primitive_meshes:&mut Vec<model::Mesh>,
union_id_from_render_config_id:&mut HashMap<model::MeshId,HashMap<RobloxPartDescription,model::MeshId>>, union_id_from_render_config_id:&mut HashMap<model::MeshId,HashMap<RobloxPartDescription,model::MeshId>>,
loaded_meshes:&'a HashMap<model::MeshId,MeshWithSize>, loaded_meshes:&HashMap<model::MeshId,MeshWithSize>,
old_union_id:model::MeshId, old_union_id:model::MeshId,
part_texture_description:RobloxPartDescription, part_texture_description:RobloxPartDescription,
)->Option<MeshIdWithSize>{ )->Option<MeshIdWithSize>{

View File

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

View File

@@ -13,9 +13,12 @@ run-service=[]
[dependencies] [dependencies]
glam = "0.30.0" glam = "0.30.0"
mlua = { version = "0.10.1", features = ["luau"] } mlua = { version = "0.11.3", features = ["luau"] }
phf = { version = "0.12.1", features = ["macros"] } phf = { version = "0.13.1", features = ["macros"] }
rbx_dom_weak = { version = "3.1.0-sn4", registry = "strafesnet", features = ["instance-userdata"] } rbx_dom_weak = { version = "3.0.1-sn5", registry = "strafesnet" }
rbx_reflection = "5.0.0" rbx_reflection = "5.0.0"
rbx_reflection_database = "1.0.0" rbx_reflection_database = "1.0.0"
rbx_types = "2.0.0" rbx_types = "2.0.0"
[lints]
workspace = true

View File

@@ -8,7 +8,7 @@ impl<'a> EnumItem<'a>{
Self{name:Some(name.as_ref()),value} Self{name:Some(name.as_ref()),value}
} }
} }
impl<'a> From<rbx_types::Enum> for EnumItem<'a>{ impl From<rbx_types::Enum> for EnumItem<'_>{
fn from(e:rbx_types::Enum)->Self{ fn from(e:rbx_types::Enum)->Self{
EnumItem{ EnumItem{
name:None, name:None,
@@ -65,7 +65,7 @@ impl<'a> EnumItems<'a>{
} }
pub enum CoerceEnum<'a>{ pub enum CoerceEnum<'a>{
Integer(i32), Integer(i64),
String(mlua::String), String(mlua::String),
Enum(EnumItem<'a>), Enum(EnumItem<'a>),
} }

View File

@@ -5,12 +5,12 @@ use rbx_types::Ref;
use rbx_dom_weak::{Ustr,InstanceBuilder,WeakDom}; use rbx_dom_weak::{Ustr,InstanceBuilder,WeakDom};
use crate::util::static_ustr; use crate::util::static_ustr;
use crate::runner::vector3::Vector3;
use crate::runner::number::Number; use crate::runner::number::Number;
pub fn set_globals(lua:&mlua::Lua,globals:&mlua::Table)->Result<(),mlua::Error>{ pub fn set_globals(lua:&mlua::Lua,globals:&mlua::Table)->Result<(),mlua::Error>{
//class functions store //class functions store
lua.set_app_data(ClassMethodsStore::default()); lua.set_app_data(ClassMethodsStore::default());
lua.set_app_data(InstanceValueStore::default());
let table=lua.create_table()?; let table=lua.create_table()?;
@@ -43,7 +43,7 @@ pub fn class_is_a(class:&str,superclass:&str)->bool{
}; };
db.has_superclass(class,superclass) db.has_superclass(class,superclass)
} }
fn get_full_name(dom:&rbx_dom_weak::WeakDom,instance:&rbx_dom_weak::Instance)->String{ fn get_full_name(dom:&WeakDom,instance:&rbx_dom_weak::Instance)->String{
let mut full_name=instance.name.clone(); let mut full_name=instance.name.clone();
let mut pref=instance.parent(); let mut pref=instance.parent();
while let Some(parent)=dom.get_by_ref(pref){ while let Some(parent)=dom.get_by_ref(pref){
@@ -65,28 +65,28 @@ pub fn get_name_source(lua:&mlua::Lua,script:Instance)->Result<(String,String),m
}) })
} }
pub fn find_first_child<'a>(dom:&'a rbx_dom_weak::WeakDom,instance:&rbx_dom_weak::Instance,name:&str)->Option<&'a rbx_dom_weak::Instance>{ pub fn find_first_child<'a>(dom:&'a WeakDom,instance:&rbx_dom_weak::Instance,name:&str)->Option<&'a rbx_dom_weak::Instance>{
instance.children().iter().filter_map(|&r|dom.get_by_ref(r)).find(|inst|inst.name==name) instance.children().iter().filter_map(|&r|dom.get_by_ref(r)).find(|inst|inst.name==name)
} }
pub fn find_first_descendant<'a>(dom:&'a rbx_dom_weak::WeakDom,instance:&rbx_dom_weak::Instance,name:&str)->Option<&'a rbx_dom_weak::Instance>{ pub fn find_first_descendant<'a>(dom:&'a WeakDom,instance:&rbx_dom_weak::Instance,name:&str)->Option<&'a rbx_dom_weak::Instance>{
dom.descendants_of(instance.referent()).find(|&inst|inst.name==name) dom.descendants_of(instance.referent()).find(|&inst|inst.name==name)
} }
pub fn find_first_child_of_class<'a>(dom:&'a rbx_dom_weak::WeakDom,instance:&rbx_dom_weak::Instance,class:&str)->Option<&'a rbx_dom_weak::Instance>{ pub fn find_first_child_of_class<'a>(dom:&'a WeakDom,instance:&rbx_dom_weak::Instance,class:&str)->Option<&'a rbx_dom_weak::Instance>{
instance.children().iter().filter_map(|&r|dom.get_by_ref(r)).find(|inst|inst.class==class) instance.children().iter().filter_map(|&r|dom.get_by_ref(r)).find(|inst|inst.class==class)
} }
pub fn find_first_descendant_of_class<'a>(dom:&'a rbx_dom_weak::WeakDom,instance:&rbx_dom_weak::Instance,class:&str)->Option<&'a rbx_dom_weak::Instance>{ pub fn find_first_descendant_of_class<'a>(dom:&'a WeakDom,instance:&rbx_dom_weak::Instance,class:&str)->Option<&'a rbx_dom_weak::Instance>{
dom.descendants_of(instance.referent()).find(|&inst|inst.class==class) dom.descendants_of(instance.referent()).find(|&inst|inst.class==class)
} }
pub fn find_first_child_which_is_a<'a>(dom:&'a rbx_dom_weak::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();
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 rbx_dom_weak::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();
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|{
@@ -325,13 +325,16 @@ impl mlua::UserData for Instance{
} }
//find or create an associated userdata object //find or create an associated userdata object
let instance=this.get_mut(dom)?; if let Some(value)=instance_value_store_mut(lua,|ivs|{
if let Some(value)=get_or_create_userdata(instance,lua,index_str)?{ //TODO: walk class tree somehow
match ivs.get_or_create_instance_values(&instance){
Some(mut instance_values)=>instance_values.get_or_create_value(lua,index_str),
None=>Ok(None)
}
})?{
return value.into_lua(lua); return value.into_lua(lua);
} }
// drop mutable borrow
//find a child with a matching name //find a child with a matching name
let instance=this.get(dom)?;
find_first_child(dom,instance,index_str) find_first_child(dom,instance,index_str)
.map(|instance|Instance::new_unchecked(instance.referent())) .map(|instance|Instance::new_unchecked(instance.referent()))
.into_lua(lua) .into_lua(lua)
@@ -419,7 +422,7 @@ impl mlua::UserData for Instance{
rbx_types::Variant::CFrame(typed_value.clone().into()) rbx_types::Variant::CFrame(typed_value.clone().into())
}, },
rbx_reflection::DataType::Value(rbx_types::VariantType::ContentId)=>{ rbx_reflection::DataType::Value(rbx_types::VariantType::ContentId)=>{
let typed_value=value.as_str().ok_or_else(||mlua::Error::runtime("Expected string"))?.to_owned(); let typed_value=value.as_string().ok_or_else(||mlua::Error::runtime("Expected string"))?.to_str()?.to_owned();
rbx_types::Variant::ContentId(typed_value.into()) rbx_types::Variant::ContentId(typed_value.into())
}, },
rbx_reflection::DataType::Value(rbx_types::VariantType::Ref)=>{ rbx_reflection::DataType::Value(rbx_types::VariantType::Ref)=>{
@@ -484,8 +487,8 @@ static CLASS_FUNCTION_DATABASE:CFD=phf::phf_map!{
"GetService"=>GET_SERVICE, "GetService"=>GET_SERVICE,
}, },
"Terrain"=>phf::phf_map!{ "Terrain"=>phf::phf_map!{
"FillBall"=>cf!(|_lua,_,_:(Vector3,Number,crate::runner::r#enum::CoerceEnum)|mlua::Result::Ok(())), "FillBall"=>cf!(|_lua,_,_:(crate::runner::vector3::Vector3,Number,crate::runner::r#enum::CoerceEnum)|mlua::Result::Ok(())),
"FillBlock"=>cf!(|_lua,_,_:(crate::runner::cframe::CFrame,Vector3,crate::runner::r#enum::CoerceEnum)|mlua::Result::Ok(())), "FillBlock"=>cf!(|_lua,_,_:(crate::runner::cframe::CFrame,crate::runner::vector3::Vector3,crate::runner::r#enum::CoerceEnum)|mlua::Result::Ok(())),
"FillCylinder"=>cf!(|_lua,_,_:(crate::runner::cframe::CFrame,Number,Number,crate::runner::r#enum::CoerceEnum)|mlua::Result::Ok(())), "FillCylinder"=>cf!(|_lua,_,_:(crate::runner::cframe::CFrame,Number,Number,crate::runner::r#enum::CoerceEnum)|mlua::Result::Ok(())),
"SetMaterialColor"=>cf!(|_lua,_,_:(crate::runner::r#enum::CoerceEnum,crate::runner::color3::Color3)|mlua::Result::Ok(())), "SetMaterialColor"=>cf!(|_lua,_,_:(crate::runner::r#enum::CoerceEnum,crate::runner::color3::Color3)|mlua::Result::Ok(())),
}, },
@@ -606,6 +609,8 @@ fn find_virtual_property(
} }
// lazy-loaded per-instance userdata values // lazy-loaded per-instance userdata values
// This whole thing is a bad idea and a garbage collection nightmare.
// TODO: recreate rbx_dom_weak with my own instance type that owns this data.
type CreateUserData=fn(&mlua::Lua)->mlua::Result<mlua::AnyUserData>; type CreateUserData=fn(&mlua::Lua)->mlua::Result<mlua::AnyUserData>;
type LUD=phf::Map<&'static str,// Class name type LUD=phf::Map<&'static str,// Class name
phf::Map<&'static str,// Value name phf::Map<&'static str,// Value name
@@ -638,22 +643,47 @@ static LAZY_USER_DATA:LUD=phf::phf_map!{
"MouseClick"=>create_script_signal, "MouseClick"=>create_script_signal,
}, },
}; };
fn get_or_create_userdata(instance:&mut rbx_dom_weak::Instance,lua:&mlua::Lua,index:&str)->mlua::Result<Option<mlua::AnyUserData>>{ #[derive(Default)]
use std::collections::hash_map::Entry; pub struct InstanceValueStore{
let db=rbx_reflection_database::get(); values:HashMap<Ref,
let Some(class)=db.classes.get(instance.class.as_str())else{ HashMap<&'static str,
return Ok(None) mlua::AnyUserData
}; >
if let Some((&static_str,create_userdata))=db.superclasses_iter(class).find_map(|superclass| >,
// find pair (class,index) }
LAZY_USER_DATA.get(&superclass.name) pub struct InstanceValues<'a>{
.and_then(|map|map.get_entry(index)) named_values:&'static phf::Map<&'static str,CreateUserData>,
){ values:&'a mut HashMap<&'static str,mlua::AnyUserData>,
let index_ustr=static_ustr(static_str); }
return Ok(Some(match instance.userdata.entry(index_ustr){ impl InstanceValueStore{
Entry::Occupied(entry)=>entry.get().clone(), pub fn get_or_create_instance_values(&mut self,instance:&rbx_dom_weak::Instance)->Option<InstanceValues<'_>>{
Entry::Vacant(entry)=>entry.insert(create_userdata(lua)?).clone(), LAZY_USER_DATA.get(instance.class.as_str())
})); .map(|named_values|
} InstanceValues{
Ok(None) named_values,
values:self.values.entry(instance.referent())
.or_insert_with(||HashMap::new()),
}
)
}
}
impl InstanceValues<'_>{
pub fn get_or_create_value(&mut self,lua:&mlua::Lua,index:&str)->mlua::Result<Option<mlua::AnyUserData>>{
Ok(match self.named_values.get_entry(index){
Some((&static_index_str,&function_pointer))=>Some(
match self.values.entry(static_index_str){
Entry::Occupied(entry)=>entry.get().clone(),
Entry::Vacant(entry)=>entry.insert(
function_pointer(lua)?
).clone(),
}
),
None=>None,
})
}
}
pub fn instance_value_store_mut<T>(lua:&mlua::Lua,mut f:impl FnMut(&mut InstanceValueStore)->mlua::Result<T>)->mlua::Result<T>{
let mut cf=lua.app_data_mut::<InstanceValueStore>().ok_or_else(||mlua::Error::runtime("InstanceValueStore missing"))?;
f(&mut *cf)
} }

View File

@@ -4,7 +4,7 @@
#[derive(Clone,Copy)] #[derive(Clone,Copy)]
pub enum Number{ pub enum Number{
Integer(i32), Integer(i64),
Number(f64), Number(f64),
} }
macro_rules! impl_ty{ macro_rules! impl_ty{

View File

@@ -1,5 +1,4 @@
use crate::context::Context; use crate::context::Context;
use crate::util::static_ustr;
#[cfg(feature="run-service")] #[cfg(feature="run-service")]
use crate::scheduler::scheduler_mut; use crate::scheduler::scheduler_mut;
@@ -49,6 +48,11 @@ fn init(lua:&mlua::Lua)->mlua::Result<()>{
Ok(()) Ok(())
} }
unsafe fn extend_lifetime_mut<'a,T>(src:&mut T)->&'a mut T{
let ptr:*mut T=src;
unsafe{&mut*ptr}
}
impl Runner{ impl Runner{
pub fn new()->Result<Self,Error>{ pub fn new()->Result<Self,Error>{
let runner=Self{ let runner=Self{
@@ -66,8 +70,7 @@ impl Runner{
// SAFETY: This is not a &'static mut WeakDom, // SAFETY: This is not a &'static mut WeakDom,
// but as long as Runnable<'a> holds the lifetime of &'a mut Context // but as long as Runnable<'a> holds the lifetime of &'a mut Context
// it is a valid unique reference. // it is a valid unique reference.
let ptr=&mut context.dom as *mut rbx_dom_weak::WeakDom; self.lua.set_app_data::<crate::context::LuaAppData>(unsafe{extend_lifetime_mut(&mut context.dom)});
self.lua.set_app_data::<crate::context::LuaAppData>(unsafe{&mut*ptr});
#[cfg(feature="run-service")] #[cfg(feature="run-service")]
self.lua.set_app_data::<crate::scheduler::Scheduler>(crate::scheduler::Scheduler::default()); self.lua.set_app_data::<crate::scheduler::Scheduler>(crate::scheduler::Scheduler::default());
Ok(Runnable{ Ok(Runnable{
@@ -126,15 +129,20 @@ impl Runnable<'_>{
} }
#[cfg(feature="run-service")] #[cfg(feature="run-service")]
pub fn run_service_step(&self)->Result<(),mlua::Error>{ pub fn run_service_step(&self)->Result<(),mlua::Error>{
let render_stepped_signal=super::instance::instance::dom_mut(&self.lua,|dom|{ let render_stepped=super::instance::instance::dom_mut(&self.lua,|dom|{
let run_service=super::instance::instance::find_first_child_of_class(dom,dom.root(),"RunService").ok_or_else(||mlua::Error::runtime("RunService missing"))?; let run_service=super::instance::instance::find_first_child_of_class(dom,dom.root(),"RunService").ok_or_else(||mlua::Error::runtime("RunService missing"))?;
Ok(match run_service.userdata.get(&static_ustr("RenderStepped")){ super::instance::instance::instance_value_store_mut(&self.lua,|instance_value_store|{
Some(render_stepped)=>Some(render_stepped.borrow::<super::script_signal::ScriptSignal>()?.clone()), //unwrap because I trust my find_first_child_of_class function to
None=>None let mut instance_values=instance_value_store.get_or_create_instance_values(run_service).ok_or_else(||mlua::Error::runtime("RunService InstanceValues missing"))?;
let render_stepped=instance_values.get_or_create_value(&self.lua,"RenderStepped")?;
//let stepped=instance_values.get_or_create_value(&self.lua,"Stepped")?;
//let heartbeat=instance_values.get_or_create_value(&self.lua,"Heartbeat")?;
Ok(render_stepped)
}) })
})?; })?;
if let Some(render_stepped_signal)=render_stepped_signal{ if let Some(render_stepped)=render_stepped{
render_stepped_signal.fire(&mlua::MultiValue::new()); let signal:&super::script_signal::ScriptSignal=&*render_stepped.borrow()?;
signal.fire(&mlua::MultiValue::new());
} }
Ok(()) Ok(())
} }

View File

@@ -117,7 +117,7 @@ impl mlua::FromLua for ScriptSignal{
} }
impl mlua::UserData for ScriptConnection{ impl mlua::UserData for ScriptConnection{
fn add_fields<F:mlua::UserDataFields<Self>>(fields:&mut F){ fn add_fields<F:UserDataFields<Self>>(fields:&mut F){
fields.add_field_method_get("Connected",|_,this|{ fields.add_field_method_get("Connected",|_,this|{
Ok(this.position().is_some()) Ok(this.position().is_some())
}); });

View File

@@ -46,8 +46,8 @@ impl Scheduler{
} }
} }
pub fn scheduler_mut<T>(lua:&mlua::Lua,mut f:impl FnMut(&mut crate::scheduler::Scheduler)->mlua::Result<T>)->mlua::Result<T>{ pub fn scheduler_mut<T>(lua:&mlua::Lua,mut f:impl FnMut(&mut Scheduler)->mlua::Result<T>)->mlua::Result<T>{
let mut scheduler=lua.app_data_mut::<crate::scheduler::Scheduler>().ok_or_else(||mlua::Error::runtime("Scheduler missing"))?; let mut scheduler=lua.app_data_mut::<Scheduler>().ok_or_else(||mlua::Error::runtime("Scheduler missing"))?;
f(&mut *scheduler) f(&mut *scheduler)
} }

View File

@@ -9,3 +9,6 @@ edition = "2024"
binrw = "0.15.0" binrw = "0.15.0"
id = { version = "0.1.0", registry = "strafesnet" } id = { version = "0.1.0", registry = "strafesnet" }
strafesnet_common = { version = "0.7.0", path = "../common", registry = "strafesnet" } strafesnet_common = { version = "0.7.0", path = "../common", registry = "strafesnet" }
[lints]
workspace = true

View File

@@ -6,7 +6,7 @@ use strafesnet_common::physics::Time;
const VERSION:u32=0; const VERSION:u32=0;
type TimedPhysicsInstruction=strafesnet_common::instruction::TimedInstruction<strafesnet_common::physics::Instruction,strafesnet_common::physics::Time>; type TimedPhysicsInstruction=strafesnet_common::instruction::TimedInstruction<strafesnet_common::physics::Instruction,Time>;
#[derive(Debug)] #[derive(Debug)]
pub enum Error{ pub enum Error{
@@ -274,7 +274,7 @@ pub fn write_bot<W:BinWriterExt>(mut writer:W,physics_version:u32,instructions:i
//probe header length //probe header length
let mut bot_header_data=Vec::new(); let mut bot_header_data=Vec::new();
binrw::BinWrite::write_le(&header,&mut std::io::Cursor::new(&mut bot_header_data)).map_err(Error::InvalidData)?; header.write_le(&mut std::io::Cursor::new(&mut bot_header_data)).map_err(Error::InvalidData)?;
// the first block location is the map header // the first block location is the map header
block_location.push(offset); block_location.push(offset);

View File

@@ -97,8 +97,8 @@ enum ResourceType{
} }
struct ResourceMap<T>{ struct ResourceMap<T>{
meshes:HashMap<strafesnet_common::model::MeshId,T>, meshes:HashMap<model::MeshId,T>,
textures:HashMap<strafesnet_common::model::TextureId,T>, textures:HashMap<model::TextureId,T>,
} }
impl<T> Default for ResourceMap<T>{ impl<T> Default for ResourceMap<T>{
fn default()->Self{ fn default()->Self{
@@ -185,7 +185,7 @@ pub struct StreamableMap<R:BinReaderExt>{
//this is every possible attribute... need some sort of streaming system //this is every possible attribute... need some sort of streaming system
attributes:Vec<strafesnet_common::gameplay_attributes::CollisionAttributes>, attributes:Vec<strafesnet_common::gameplay_attributes::CollisionAttributes>,
//this is every possible render configuration... shaders and such... need streaming //this is every possible render configuration... shaders and such... need streaming
render_configs:Vec<strafesnet_common::model::RenderConfig>, render_configs:Vec<model::RenderConfig>,
//this makes sense to keep in memory for streaming, a map of which blocks occupy what space //this makes sense to keep in memory for streaming, a map of which blocks occupy what space
bvh:BvhNode<BlockId>, bvh:BvhNode<BlockId>,
//something something resources hashmaps //something something resources hashmaps
@@ -223,7 +223,7 @@ impl<R:BinReaderExt> StreamableMap<R>{
} }
Ok(Self{ Ok(Self{
file, file,
modes:strafesnet_common::gameplay_modes::NormalizedModes::new(modes), modes:gameplay_modes::NormalizedModes::new(modes),
attributes, attributes,
render_configs, render_configs,
bvh:strafesnet_common::bvh::generate_bvh(bvh), bvh:strafesnet_common::bvh::generate_bvh(bvh),
@@ -366,12 +366,12 @@ fn collect_spacial_blocks(
block_location.push(sequential_block_data.position()); block_location.push(sequential_block_data.position());
}else{ }else{
match bvh_node.into_content(){ match bvh_node.into_content(){
strafesnet_common::bvh::RecursiveContent::Branch(bvh_node_list)=>{ RecursiveContent::Branch(bvh_node_list)=>{
for bvh_node in bvh_node_list{ for bvh_node in bvh_node_list{
collect_spacial_blocks(block_location,block_headers,sequential_block_data,bvh_node)?; collect_spacial_blocks(block_location,block_headers,sequential_block_data,bvh_node)?;
} }
}, },
strafesnet_common::bvh::RecursiveContent::Leaf(_)=>panic!(),//bvh branches are 20 leaves minimum RecursiveContent::Leaf(_)=>panic!(),//bvh branches are 20 leaves minimum
} }
} }
Ok(()) Ok(())
@@ -384,13 +384,13 @@ pub fn write_map<W:BinWriterExt>(mut writer:W,map:strafesnet_common::map::Comple
let boxen=map.models.into_iter().enumerate().map(|(model_id,model)|{ let boxen=map.models.into_iter().enumerate().map(|(model_id,model)|{
//grow your own aabb //grow your own aabb
let mesh=map.meshes.get(model.mesh.get() as usize).ok_or(Error::InvalidMeshId(model.mesh))?; let mesh=map.meshes.get(model.mesh.get() as usize).ok_or(Error::InvalidMeshId(model.mesh))?;
let mut aabb=strafesnet_common::aabb::Aabb::default(); let mut aabb=Aabb::default();
for &pos in &mesh.unique_pos{ for &pos in &mesh.unique_pos{
aabb.grow(model.transform.transform_point3(pos).narrow_1().unwrap()); aabb.grow(model.transform.transform_point3(pos).narrow_1().unwrap());
} }
Ok(((model::ModelId::new(model_id as u32),model.into()),aabb)) Ok(((model::ModelId::new(model_id as u32),model.into()),aabb))
}).collect::<Result<Vec<_>,_>>()?; }).collect::<Result<Vec<_>,_>>()?;
let bvh=weigh_contents(strafesnet_common::bvh::generate_bvh(boxen),&|_|std::mem::size_of::<newtypes::model::Model>()); let bvh=weigh_contents(strafesnet_common::bvh::generate_bvh(boxen),&|_|size_of::<newtypes::model::Model>());
//build blocks //build blocks
//block location is initialized with two values //block location is initialized with two values
//the first value represents the location of the first byte after the file header //the first value represents the location of the first byte after the file header

View File

@@ -20,7 +20,7 @@ impl TryInto<TimedPhysicsInstruction> for TimedInstruction{
} }
} }
impl TryFrom<TimedPhysicsInstruction> for TimedInstruction{ impl TryFrom<TimedPhysicsInstruction> for TimedInstruction{
type Error=super::physics::InstructionConvert; type Error=InstructionConvert;
fn try_from(value:TimedPhysicsInstruction)->Result<Self,Self::Error>{ fn try_from(value:TimedPhysicsInstruction)->Result<Self,Self::Error>{
Ok(Self{ Ok(Self{
time:value.time.get(), time:value.time.get(),

View File

@@ -12,12 +12,11 @@ flate2 = "1.0.27"
futures = "0.3.31" futures = "0.3.31"
image = "0.25.2" image = "0.25.2"
image_dds = "0.7.1" image_dds = "0.7.1"
lazy-regex = "3.1.0" rbx_asset = { version = "0.5.0", registry = "strafesnet" }
rbx_asset = { version = "0.4.4", registry = "strafesnet" } rbx_binary = { version = "1.0.1-sn5", registry = "strafesnet" }
rbx_binary = { version = "1.1.0-sn4", registry = "strafesnet" } rbx_dom_weak = { version = "3.0.1-sn5", registry = "strafesnet" }
rbx_dom_weak = { version = "3.1.0-sn4", registry = "strafesnet" }
rbx_reflection_database = "1.0.0" rbx_reflection_database = "1.0.0"
rbx_xml = { version = "1.1.0-sn4", registry = "strafesnet" } rbx_xml = { version = "1.0.1-sn5", registry = "strafesnet" }
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" }
@@ -36,3 +35,6 @@ vtf = "0.3.0"
#lto = true #lto = true
#strip = true #strip = true
#codegen-units = 1 #codegen-units = 1
[lints]
workspace = true

View File

@@ -465,7 +465,7 @@ fn convert_to_snf(path:&Path,output_folder:PathBuf)->Result<Errors,ConvertError>
}) })
} }
async fn roblox_to_snf(paths:Vec<std::path::PathBuf>,output_folder:PathBuf)->AResult<()>{ async fn roblox_to_snf(paths:Vec<PathBuf>,output_folder:PathBuf)->AResult<()>{
let start=std::time::Instant::now(); let start=std::time::Instant::now();
let thread_limit=std::thread::available_parallelism()?.get(); let thread_limit=std::thread::available_parallelism()?.get();

View File

@@ -484,7 +484,7 @@ async fn convert_to_snf(path:&Path,vpk_list:&[strafesnet_bsp_loader::Vpk],output
Ok(()) Ok(())
} }
async fn source_to_snf(paths:Vec<std::path::PathBuf>,output_folder:PathBuf,vpk_paths:Vec<PathBuf>)->AResult<()>{ async fn source_to_snf(paths:Vec<PathBuf>,output_folder:PathBuf,vpk_paths:Vec<PathBuf>)->AResult<()>{
let start=std::time::Instant::now(); let start=std::time::Instant::now();
let thread_limit=std::thread::available_parallelism()?.get(); let thread_limit=std::thread::available_parallelism()?.get();

View File

@@ -28,9 +28,12 @@ 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 = "26.0.1" wgpu = "27.0.0"
winit = "0.30.7" winit = "0.30.7"
[profile.dev] [profile.dev]
strip = false strip = false
opt-level = 3 opt-level = 3
[lints]
workspace = true

View File

@@ -119,12 +119,13 @@ impl<'a> SetupContextPartial3<'a>{
let (device, queue)=pollster::block_on(self.adapter let (device, queue)=pollster::block_on(self.adapter
.request_device( .request_device(
&wgpu::DeviceDescriptor { &wgpu::DeviceDescriptor{
label: None, label:None,
required_features: (optional_features & self.adapter.features()) | required_features, required_features:(optional_features&self.adapter.features())|required_features,
required_limits: needed_limits, required_limits:needed_limits,
memory_hints:wgpu::MemoryHints::Performance, memory_hints:wgpu::MemoryHints::Performance,
trace: wgpu::Trace::Off, trace:wgpu::Trace::Off,
experimental_features:wgpu::ExperimentalFeatures::disabled(),
}, },
)) ))
.expect("Unable to find a suitable GPU adapter!"); .expect("Unable to find a suitable GPU adapter!");

View File

@@ -188,7 +188,7 @@ impl WindowContext<'_>{
} }
} }
fn device_event(&mut self,time:SessionTime,event: winit::event::DeviceEvent){ fn device_event(&mut self,time:SessionTime,event:winit::event::DeviceEvent){
match event{ match event{
winit::event::DeviceEvent::MouseMotion{ winit::event::DeviceEvent::MouseMotion{
delta, delta,