Compare commits

..

36 Commits

Author SHA1 Message Date
6516b3ee30 cleaner 2025-12-22 14:09:36 -08:00
32d0e96958 stop sending instructions when paused 2025-12-22 14:06:59 -08:00
087e95b1f7 delete TogglePaused 2025-12-22 13:54:35 -08:00
e46a51319f delete unused models 2025-12-20 16:31:05 -08:00
a3b0306430 rbx_loader: fix regex 2025-12-19 13:10:04 -08:00
e024f37843 update deps 2025-12-18 10:58:50 -08:00
4059cfa527 update wgpu 2025-12-18 10:57:19 -08:00
e4f3418bc6 document PhysicsMesh 2025-12-11 09:36:21 -08:00
6ca6d5e484 expect dead code 2025-12-10 18:05:16 -08:00
0668ac2def use allow instead of expect 2025-12-09 14:39:42 -08:00
73e3181d0c roblox_emulator: v0.5.2 2025-11-27 16:42:01 -08:00
19ba8f2445 update deps 2025-11-27 15:50:19 -08:00
0495d07e26 update rbx-dom 2025-11-27 15:48:17 -08:00
0ea353b27d common: fixed_wide: min max 2025-11-24 13:04:44 -08:00
99706079d9 common: fixed_wide: add mul_sign div_sign 2025-11-24 13:04:44 -08:00
730c5fb7dd common: integer: generic zero 2025-11-22 08:47:16 -08:00
d1b61bb997 push_solve: remove epsilon 2025-11-21 10:52:34 -08:00
0343ad19cf MeshQuery::hint_point returns any point inside the mesh 2025-11-20 10:59:08 -08:00
43210b1417 less access to TouchingState private fields 2025-11-19 13:39:07 -08:00
e9d28cf15f document jank 2025-11-19 13:15:31 -08:00
452bac4049 change collision_end_contact & collision_end_intersect fn signatures 2025-11-19 10:57:44 -08:00
48aad78f59 change contact_normal function signature to reduce copies 2025-11-19 10:20:33 -08:00
d45a42f5aa change ContactCollision struct layout
Match TouchingState contacts HashMap K,V layout to try to get lucky with compiler optimization.
2025-11-19 10:20:33 -08:00
c219fec3bc specialize touching member access 2025-11-19 10:08:40 -08:00
2a05d50abb check touching before testing collision 2025-11-19 10:08:40 -08:00
fbb047f8d4 combine call chain 2025-11-19 09:01:51 -08:00
c4d837a552 Fix infinite loop with intersects when allowing 0s collisions 2025-11-19 09:01:51 -08:00
a08bd44789 Generic ConvexMeshId 2025-11-19 09:01:51 -08:00
4ae5359046 rename not_spawn_at to is_not_spawn_at 2025-11-19 09:01:27 -08:00
15ecaf602a deep match 2025-11-18 12:29:46 -08:00
1e0511a7ba remove intermediate allocation 2025-11-18 12:23:05 -08:00
a9e4705d89 remove (some) fixed point implicit conversion
They may be convenient, but they cannot be done at compile-time.
TODO: remove more of them i.e. impl_multiplicative_operator
2025-11-18 11:53:52 -08:00
98069859b5 Gracefully handle 0 acceleration for walking targets 2025-11-18 19:47:04 +00:00
64d3996fa9 use From instead of Into 2025-11-18 11:46:32 -08:00
49c0c16e35 Use a From implementation instead of manual conversion
If the contacts and intersects map ever change in the future to not be 1:1 with gaps but instead something else, this guarantees that this implicit use of the relationship will flag at a compiler level
2025-11-18 19:25:44 +00:00
255bed4803 Ensure the PhysicsData's bvh respects the original model ordering
There's no importance in worrying about the core HashMap ordering since it's not used as an iterator except for outside of this very function for bvh purposes
2025-11-18 19:25:44 +00:00
29 changed files with 1009 additions and 13042 deletions

908
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

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

View File

@@ -616,7 +616,7 @@ impl GraphicsState{
address_mode_w:wgpu::AddressMode::ClampToEdge,
mag_filter:wgpu::FilterMode::Linear,
min_filter:wgpu::FilterMode::Linear,
mipmap_filter:wgpu::FilterMode::Linear,
mipmap_filter:wgpu::MipmapFilterMode::Linear,
..Default::default()
});
let repeat_sampler=device.create_sampler(&wgpu::SamplerDescriptor{
@@ -626,7 +626,7 @@ impl GraphicsState{
address_mode_w:wgpu::AddressMode::Repeat,
mag_filter:wgpu::FilterMode::Linear,
min_filter:wgpu::FilterMode::Linear,
mipmap_filter:wgpu::FilterMode::Linear,
mipmap_filter:wgpu::MipmapFilterMode::Linear,
anisotropy_clamp:16,
..Default::default()
});
@@ -754,7 +754,7 @@ impl GraphicsState{
&skybox_texture_bind_group_layout,
&model_bind_group_layout,
],
push_constant_ranges:&[],
immediate_size:0,
});
let sky_pipeline_layout=device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor{
label:None,
@@ -762,7 +762,7 @@ impl GraphicsState{
&camera_bind_group_layout,
&skybox_texture_bind_group_layout,
],
push_constant_ranges:&[],
immediate_size:0,
});
// Create the render pipelines
@@ -793,7 +793,7 @@ impl GraphicsState{
bias:wgpu::DepthBiasState::default(),
}),
multisample:wgpu::MultisampleState::default(),
multiview:None,
multiview_mask:None,
cache:None,
});
let model_pipeline=device.create_render_pipeline(&wgpu::RenderPipelineDescriptor{
@@ -828,7 +828,7 @@ impl GraphicsState{
bias:wgpu::DepthBiasState::default(),
}),
multisample:wgpu::MultisampleState::default(),
multiview:None,
multiview_mask:None,
cache:None,
});
@@ -880,7 +880,7 @@ impl GraphicsState{
camera_buf,
models:Vec::new(),
depth_view,
staging_belt:wgpu::util::StagingBelt::new(0x100),
staging_belt:wgpu::util::StagingBelt::new(device.clone(),0x100),
bind_group_layouts:GraphicsBindGroupLayouts{model:model_bind_group_layout},
samplers:GraphicsSamplers{repeat:repeat_sampler},
temp_squid_texture_view:squid_texture_view,
@@ -918,7 +918,6 @@ impl GraphicsState{
&self.camera_buf,
0,
wgpu::BufferSize::new((camera_uniforms.len() * 4) as wgpu::BufferAddress).unwrap(),
device,
)
.copy_from_slice(bytemuck::cast_slice(&camera_uniforms));
//This code only needs to run when the uniforms change
@@ -965,6 +964,7 @@ impl GraphicsState{
}),
timestamp_writes:Default::default(),
occlusion_query_set:Default::default(),
multiview_mask:None,
});
rpass.set_bind_group(0,&self.bind_groups.camera,&[]);

View File

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

View File

@@ -90,16 +90,9 @@ pub trait MeshQuery{
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)
}
/// 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]
})
}
/// This must return a point inside the mesh.
#[expect(dead_code)]
fn hint_point(&self)->Planar64Vec3;
fn vert(&self,vert_id:Self::Vert)->Planar64Vec3;
fn face_nd(&self,face_id:Self::Face)->(Self::Normal,Self::Offset);
fn face_edges(&self,face_id:Self::Face)->impl AsRef<[Self::Edge]>;
@@ -110,17 +103,21 @@ pub trait MeshQuery{
}
#[derive(Debug)]
struct FaceRefs{
// I didn't write it down, but I assume the edges are directed
// clockwise when looking towards the face normal, i.e. right hand rule.
edges:Vec<SubmeshDirectedEdgeId>,
//verts are redundant, use edge[i].verts[0]
//verts:Vec<VertId>,
}
#[derive(Debug)]
struct EdgeRefs{
faces:[SubmeshFaceId;2],//left, right
verts:[SubmeshVertId;2],//bottom, top
verts:[SubmeshVertId;2],//start, end
}
#[derive(Debug)]
struct VertRefs{
faces:Vec<SubmeshFaceId>,
// edges are always directed away from the vert
edges:Vec<SubmeshDirectedEdgeId>,
}
#[derive(Debug)]
@@ -457,13 +454,15 @@ impl MeshQuery for PhysicsMeshView<'_>{
let face_idx=self.topology.faces[face_id.get() as usize].get() as usize;
(self.data.faces[face_idx].normal,self.data.faces[face_idx].dot)
}
fn hint_point(&self)->Planar64Vec3{
// invariant: meshes always encompass the origin
vec3::zero()
}
//ideally I never calculate the vertex position, but I have to for the graphical meshes...
fn vert(&self,vert_id:SubmeshVertId)->Planar64Vec3{
let vert_idx=self.topology.verts[vert_id.get() as usize].get() as usize;
self.data.verts[vert_idx].0
}
/// 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]>{
self.topology.face_topology[face_id.get() as usize].edges.as_slice()
}
@@ -544,6 +543,9 @@ impl MeshQuery for TransformedMesh<'_>{
// wrap for speed
self.transform.vertex.transform_point3(self.view.vert(vert_id)).wrap_1()
}
fn hint_point(&self)->Planar64Vec3{
self.transform.vertex.translation
}
#[inline]
fn face_edges(&self,face_id:SubmeshFaceId)->impl AsRef<[SubmeshDirectedEdgeId]>{
self.view.face_edges(face_id)
@@ -626,6 +628,17 @@ pub struct MinkowskiMesh<'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 fn into_giga_time(time:Time,relative_to:Time)->GigaTime{
let r=(time-relative_to).to_ratio();
@@ -642,195 +655,122 @@ impl MinkowskiMesh<'_>{
fn farthest_vert(&self,dir:Planar64Vec3)->MinkowskiVert{
MinkowskiVert::VertVert(self.mesh0.farthest_vert(dir),self.mesh1.farthest_vert(-dir))
}
fn closest_fev_not_inside(&self,relative_position:Planar64Vec3)->Option<FEV<MinkowskiMesh<'_>>>{
// Make a fast guess as to what the closest point will be.
let MinkowskiVert::VertVert(mut v0,mut v1)=self.farthest_vert(relative_position);
// TODO: alternate naive vertex searches to improve the robustness
// in the "tall bipyramid" failure case
let mut m0v=self.mesh0.vert(v0);
let mut m1v=self.mesh1.vert(v1);
let mut best_distance_squared={
let diff=relative_position+m1v-m0v;
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{
let mut best_v0=None;
let mut best_v1=None;
// 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);
fn next_transition_vert(&self,vert_id:MinkowskiVert,best_distance_squared:&mut Fixed<2,64>,infinity_dir:Planar64Vec3,point:Planar64Vec3)->Transition{
let mut best_transition=Transition::Done;
for &directed_edge_id in self.vert_edges(vert_id).as_ref(){
let edge_n=self.directed_edge_n(directed_edge_id);
//is boundary uncrossable by a crawl from infinity
let edge_verts=self.edge_verts(directed_edge_id.as_undirected());
//select opposite vertex
let test_vert_id=edge_verts.as_ref()[directed_edge_id.parity() as usize];
//test if it's closer
let diff=point-self.vert(test_vert_id);
if edge_n.dot(infinity_dir).is_zero(){
let distance_squared=diff.dot(diff);
if distance_squared<*best_distance_squared{
best_transition=Transition::Vert(test_vert_id);
*best_distance_squared=distance_squared;
}
}
// check vertices adjacent to v0 against v1
for m0_test_vert in self.mesh0.directed_verts(v0e_ref){
let m0v_test=self.mesh0.vert(m0_test_vert);
let diff=relative_position+m1v-m0v_test;
let d=diff.dot(diff);
if d<best_distance_squared{
best_distance_squared=d;
best_v0=Some(m0_test_vert);
best_v1=None;
}
}
// check permutations of adjacent vertices
for m0_test_vert in self.mesh0.directed_verts(v0e_ref){
let m0v_test=self.mesh0.vert(m0_test_vert);
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_test;
let d=diff.dot(diff);
if d<best_distance_squared{
best_distance_squared=d;
best_v0=Some(m0_test_vert);
best_v1=Some(m1_test_vert);
}
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;
}
}
}
// 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;
}
best_transition
}
fn crawl_boundaries(&self,mut vert_id:MinkowskiVert,infinity_dir:Planar64Vec3,point:Planar64Vec3)->EV{
let mut best_distance_squared={
let diff=point-self.vert(vert_id);
diff.dot(diff)
};
loop{
match self.next_transition_vert(vert_id,&mut best_distance_squared,infinity_dir,point){
Transition::Done=>return self.final_ev(vert_id,&mut best_distance_squared,infinity_dir,point),
Transition::Vert(new_vert_id)=>vert_id=new_vert_id,
}
}
// now you have the two closest vertices
let mut best_fev=FEV::Vert(MinkowskiVert::VertVert(v0,v1));
// ==== FEV::Edge ====
// test VertEdges
for &e1 in v1e_ref{
let edge_verts=self.mesh1.edge_verts(e1.as_undirected());
let &[ev0_id,ev1_id]=edge_verts.as_ref();
let (ev0,ev1)=(self.mesh1.vert(ev0_id),self.mesh1.vert(ev1_id));
let edge_n=ev1-ev0;
// use relative coordinates to make including relative_position easier
let diff=relative_position+m0v-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()
}
/// This function drops a vertex down to an edge or a face if the path from infinity did not cross any vertex-edge boundaries but the point is supposed to have already crossed a boundary down from a vertex
fn infinity_fev(&self,infinity_dir:Planar64Vec3,point:Planar64Vec3)->FEV::<MinkowskiMesh<'_>>{
//start on any vertex
//cross uncrossable vertex-edge boundaries until you find the closest vertex or edge
//cross edge-face boundary if it's uncrossable
match self.crawl_boundaries(self.farthest_vert(infinity_dir),infinity_dir,point){
//if a vert is returned, it is the closest point to the infinity point
EV::Vert(vert_id)=>FEV::Vert(vert_id),
EV::Edge(edge_id)=>{
//cross to face if the boundary is not crossable and we are on the wrong side
let edge_n=self.edge_n(edge_id);
// point is multiplied by two because vert_sum sums two vertices.
let delta_pos=point*2-{
let &[v0,v1]=self.edge_verts(edge_id).as_ref();
self.vert(v0)+self.vert(v1)
};
if distance_squared<best_distance_squared{
best_distance_squared=distance_squared;
best_fev=FEV::Edge(MinkowskiEdge::VertEdge(v0,e1.as_undirected()))
for (i,&face_id) in self.edge_faces(edge_id).as_ref().iter().enumerate(){
let face_n=self.face_nd(face_id).0;
//edge-face boundary nd, n facing out of the face towards the edge
let boundary_n=face_n.cross(edge_n)*(i as i64*2-1);
let boundary_d=boundary_n.dot(delta_pos);
//check if time of collision is outside Time::MIN..Time::MAX
//infinity_dir can always be treated as a velocity
if !boundary_d.is_positive()&&boundary_n.dot(infinity_dir).is_zero(){
//both faces cannot pass this condition, return early if one does.
return FEV::Face(face_id);
}
}
}
FEV::Edge(edge_id)
},
}
// 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)
}
// TODO: fundamentally improve this algorithm.
// All it needs to do is find the closest point on the mesh
// and return the FEV which the point resides on.
//
// What it actually does is use the above functions to trace a ray in from infinity,
// crawling the closest point along the mesh surface until the ray reaches
// the starting point to discover the final FEV.
//
// The actual collision prediction probably does a single test
// and then immediately returns with 0 FEV transitions on average,
// because of the strict time_limit constraint.
//
// Most of the calculation time is just calculating the starting point
// for the "actual" crawling algorithm below (predict_collision_{in|out}).
fn closest_fev_not_inside(&self,mut infinity_body:Body,start_time:Bound<&Time>)->Option<FEV<MinkowskiMesh<'_>>>{
infinity_body.infinity_dir().and_then(|dir|{
let infinity_fev=self.infinity_fev(-dir,infinity_body.position);
//a line is simpler to solve than a parabola
infinity_body.velocity=dir;
infinity_body.acceleration=vec3::zero();
//crawl in from negative infinity along a tangent line to get the closest fev
infinity_fev.crawl(self,&infinity_body,Bound::Unbounded,start_time).miss()
})
}
pub fn predict_collision_in(&self,relative_body:&Body,range:impl RangeBounds<Time>)->Option<(MinkowskiFace,GigaTime)>{
self.closest_fev_not_inside(relative_body.position).and_then(|fev|{
self.closest_fev_not_inside(*relative_body,range.start_bound()).and_then(|fev|{
//continue forwards along the body parabola
fev.crawl(self,relative_body,range.start_bound(),range.end_bound()).hit()
})
@@ -839,9 +779,10 @@ impl MinkowskiMesh<'_>{
let (lower_bound,upper_bound)=(range.start_bound(),range.end_bound());
// swap and negate bounds to do a time inversion
let (lower_bound,upper_bound)=(upper_bound.map(|&t|-t),lower_bound.map(|&t|-t));
self.closest_fev_not_inside(relative_body.position).and_then(|fev|{
let infinity_body=-relative_body;
self.closest_fev_not_inside(infinity_body,lower_bound.as_ref()).and_then(|fev|{
//continue backwards along the body parabola
fev.crawl(self,&-relative_body,lower_bound.as_ref(),upper_bound.as_ref()).hit()
fev.crawl(self,&infinity_body,lower_bound.as_ref(),upper_bound.as_ref()).hit()
//no need to test -time<time_limit because of the first step
.map(|(face,time)|(face,-time))
})
@@ -874,10 +815,20 @@ impl MinkowskiMesh<'_>{
}
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{
// TODO
println!("Unimplemented is_point_in_mesh called! {point}");
false
let infinity_body=Body::new(point,vec3::Y,vec3::zero(),Time::ZERO);
//movement must escape the mesh forwards and backwards in time,
//otherwise the point is not inside the mesh
self.infinity_in(infinity_body)
.is_some_and(|_|
self.infinity_in(-infinity_body)
.is_some()
)
}
}
impl MeshQuery for MinkowskiMesh<'_>{
@@ -916,6 +867,10 @@ impl MeshQuery for MinkowskiMesh<'_>{
},
}
}
fn hint_point(&self)->Planar64Vec3{
self.mesh1.transform.vertex.translation-
self.mesh0.transform.vertex.translation
}
fn face_edges(&self,face_id:MinkowskiFace)->impl AsRef<[MinkowskiDirectedEdge]>{
match face_id{
MinkowskiFace::VertFace(v0,f1)=>{

View File

@@ -87,7 +87,7 @@ enum JumpDirection{
impl JumpDirection{
fn direction(&self,models:&PhysicsModels,hitbox_mesh:&HitboxMesh,contact:&ContactCollision)->Planar64Vec3{
match self{
JumpDirection::FromContactNormal=>contact_normal(models,hitbox_mesh,contact),
JumpDirection::FromContactNormal=>contact_normal(models,hitbox_mesh,&contact.convex_mesh_id,contact.face_id),
&JumpDirection::Exactly(dir)=>dir,
}
}
@@ -100,7 +100,6 @@ enum TransientAcceleration{
time:Time,
},
//walk target will never be reached
#[expect(dead_code)]
Unreachable{
acceleration:Planar64Vec3,
}
@@ -114,8 +113,12 @@ struct ContactMoveState{
}
impl TransientAcceleration{
fn with_target_diff(target_diff:Planar64Vec3,accel:Planar64,time:Time)->Self{
if target_diff==vec3::ZERO{
if target_diff==vec3::zero(){
TransientAcceleration::Reached
}else if accel==Planar64::ZERO{
TransientAcceleration::Unreachable{
acceleration:vec3::zero()
}
}else{
//normal friction acceleration is clippedAcceleration.dot(normal)*friction
TransientAcceleration::Reachable{
@@ -137,7 +140,7 @@ impl TransientAcceleration{
}
fn acceleration(&self)->Planar64Vec3{
match self{
TransientAcceleration::Reached=>vec3::ZERO,
TransientAcceleration::Reached=>vec3::zero(),
&TransientAcceleration::Reachable{acceleration,time:_}=>acceleration,
&TransientAcceleration::Unreachable{acceleration}=>acceleration,
}
@@ -160,7 +163,7 @@ impl ContactMoveState{
}
}
fn ground_things(walk_settings:&gameplay_style::WalkSettings,contact:&ContactCollision,touching:&TouchingState,models:&PhysicsModels,hitbox_mesh:&HitboxMesh,style:&StyleModifiers,camera:&PhysicsCamera,input_state:&InputState)->(Planar64Vec3,Planar64Vec3){
let normal=contact_normal(models,hitbox_mesh,contact);
let normal=contact_normal(models,hitbox_mesh,&contact.convex_mesh_id,contact.face_id);
let gravity=touching.base_acceleration(models,style,camera,input_state);
let control_dir=style.get_y_control_dir(camera,input_state.controls);
let target_velocity=walk_settings.get_walk_target_velocity(control_dir,normal);
@@ -168,7 +171,7 @@ fn ground_things(walk_settings:&gameplay_style::WalkSettings,contact:&ContactCol
(gravity,target_velocity_clipped)
}
fn ladder_things(ladder_settings:&gameplay_style::LadderSettings,contact:&ContactCollision,touching:&TouchingState,models:&PhysicsModels,hitbox_mesh:&HitboxMesh,style:&StyleModifiers,camera:&PhysicsCamera,input_state:&InputState)->(Planar64Vec3,Planar64Vec3){
let normal=contact_normal(models,hitbox_mesh,contact);
let normal=contact_normal(models,hitbox_mesh,&contact.convex_mesh_id,contact.face_id);
let gravity=touching.base_acceleration(models,style,camera,input_state);
let control_dir=style.get_y_control_dir(camera,input_state.controls);
let target_velocity=ladder_settings.get_ladder_target_velocity(control_dir,normal);
@@ -193,7 +196,7 @@ impl PhysicsModels{
self.contact_attributes.clear();
self.intersect_attributes.clear();
}
fn mesh(&self,convex_mesh_id:ConvexMeshId)->TransformedMesh<'_>{
fn mesh(&self,convex_mesh_id:ConvexMeshId<PhysicsModelId>)->TransformedMesh<'_>{
let (mesh_id,transform)=match convex_mesh_id.model_id{
PhysicsModelId::Contact(model_id)=>{
let model=&self.contact_models[&model_id];
@@ -210,25 +213,25 @@ impl PhysicsModels{
)
}
//it's a bit weird to have three functions, but it's always going to be one of these
fn contact_mesh(&self,contact:&ContactCollision)->TransformedMesh<'_>{
let model=&self.contact_models[&contact.model_id];
fn contact_mesh(&self,convex_mesh_id:&ConvexMeshId<ContactModelId>)->TransformedMesh<'_>{
let model=&self.contact_models[&convex_mesh_id.model_id];
TransformedMesh::new(
self.meshes[&model.mesh_id].submesh_view(contact.submesh_id),
self.meshes[&model.mesh_id].submesh_view(convex_mesh_id.submesh_id),
&model.transform
)
}
fn intersect_mesh(&self,intersect:&IntersectCollision)->TransformedMesh<'_>{
let model=&self.intersect_models[&intersect.model_id];
fn intersect_mesh(&self,convex_mesh_id:&ConvexMeshId<IntersectModelId>)->TransformedMesh<'_>{
let model=&self.intersect_models[&convex_mesh_id.model_id];
TransformedMesh::new(
self.meshes[&model.mesh_id].submesh_view(intersect.submesh_id),
self.meshes[&model.mesh_id].submesh_view(convex_mesh_id.submesh_id),
&model.transform
)
}
fn get_model_transform(&self,model_id:ModelId)->Option<&PhysicsMeshTransform>{
//ModelId can possibly be a decoration
match self.contact_models.get(&ContactModelId::new(model_id.get())){
match self.contact_models.get(&model_id.into()){
Some(model)=>Some(&model.transform),
None=>self.intersect_models.get(&IntersectModelId::new(model_id.get()))
None=>self.intersect_models.get(&model_id.into())
.map(|model|&model.transform),
}
}
@@ -440,7 +443,7 @@ impl StyleHelper for StyleModifiers{
fn get_control_dir(&self,controls:Controls)->Planar64Vec3{
//don't get fancy just do it
let mut control_dir:Planar64Vec3=vec3::ZERO;
let mut control_dir:Planar64Vec3=vec3::zero();
//Apply mask after held check so you can require non-allowed keys to be held for some reason
let controls=controls.intersection(self.controls_mask);
if controls.contains(Controls::MoveForward){
@@ -479,7 +482,7 @@ impl StyleHelper for StyleModifiers{
};
let transform=integer::Planar64Affine3::new(
mat3::from_diagonal(self.hitbox.halfsize),
vec3::ZERO
vec3::zero()
);
HitboxMesh::new(mesh,transform)
}
@@ -497,7 +500,7 @@ impl MoveState{
//call this after state.move_state is changed
fn apply_enum(&self,body:&mut Body,touching:&TouchingState,models:&PhysicsModels,hitbox_mesh:&HitboxMesh,style:&StyleModifiers,camera:&PhysicsCamera,input_state:&InputState){
match self{
MoveState::Fly=>body.acceleration=vec3::ZERO,
MoveState::Fly=>body.acceleration=vec3::zero(),
MoveState::Air=>{
//calculate base acceleration
let a=touching.base_acceleration(models,style,camera,input_state);
@@ -611,7 +614,7 @@ impl MoveState{
// TODO: unduplicate this code
match self.get_walk_state(){
// did you stop touching the thing you were walking on?
Some(walk_state)=>if !touching.contacts.contains(&walk_state.contact){
Some(walk_state)=>if !touching.contains_contact(&walk_state.contact.convex_mesh_id){
self.set_move_state(MoveState::Air,body,touching,models,hitbox_mesh,style,camera,input_state);
}else{
// stopped touching something else while walking
@@ -642,9 +645,9 @@ impl TryFrom<&gameplay_attributes::CollisionAttributes> for PhysicsCollisionAttr
}
#[derive(Clone,Copy,Hash,id::Id,Eq,PartialEq)]
struct ContactAttributesId(u32);
impl Into<CollisionAttributesId> for ContactAttributesId{
fn into(self)->CollisionAttributesId{
CollisionAttributesId::new(self.0)
impl From<ContactAttributesId> for CollisionAttributesId{
fn from(value:ContactAttributesId)->CollisionAttributesId{
CollisionAttributesId::new(value.0)
}
}
impl From<CollisionAttributesId> for ContactAttributesId{
@@ -654,9 +657,9 @@ impl From<CollisionAttributesId> for ContactAttributesId{
}
#[derive(Clone,Copy,Hash,id::Id,Eq,PartialEq)]
struct IntersectAttributesId(u32);
impl Into<CollisionAttributesId> for IntersectAttributesId{
fn into(self)->CollisionAttributesId{
CollisionAttributesId::new(self.0)
impl From<IntersectAttributesId> for CollisionAttributesId{
fn from(value:IntersectAttributesId)->CollisionAttributesId{
CollisionAttributesId::new(value.0)
}
}
impl From<CollisionAttributesId> for IntersectAttributesId{
@@ -666,16 +669,26 @@ impl From<CollisionAttributesId> for IntersectAttributesId{
}
#[derive(Debug,Clone,Copy,Hash,id::Id,Eq,PartialEq)]
struct ContactModelId(u32);
impl Into<ModelId> for ContactModelId{
fn into(self)->ModelId{
ModelId::new(self.get())
impl From<ContactModelId> for ModelId{
fn from(value:ContactModelId)->ModelId{
ModelId::new(value.get())
}
}
impl From<ModelId> for ContactModelId{
fn from(other: ModelId)->Self{
Self::new(other.get())
}
}
#[derive(Debug,Clone,Copy,Hash,id::Id,Eq,PartialEq)]
struct IntersectModelId(u32);
impl Into<ModelId> for IntersectModelId{
fn into(self)->ModelId{
ModelId::new(self.get())
impl From<IntersectModelId> for ModelId{
fn from(value:IntersectModelId)->ModelId{
ModelId::new(value.get())
}
}
impl From<ModelId> for IntersectModelId{
fn from(other: ModelId)->Self{
Self::new(other.get())
}
}
#[derive(Debug,Clone,Copy,Hash,Eq,PartialEq)]
@@ -683,9 +696,9 @@ enum PhysicsModelId{
Contact(ContactModelId),
Intersect(IntersectModelId),
}
impl Into<ModelId> for PhysicsModelId{
fn into(self)->ModelId{
ModelId::new(match self{
impl From<PhysicsModelId> for ModelId{
fn from(value:PhysicsModelId)->ModelId{
ModelId::new(match value{
PhysicsModelId::Contact(model_id)=>model_id.get(),
PhysicsModelId::Intersect(model_id)=>model_id.get(),
})
@@ -693,10 +706,18 @@ impl Into<ModelId> for PhysicsModelId{
}
//unique physics meshes indexed by this
#[derive(Debug,Clone,Copy,Eq,Hash,PartialEq)]
struct ConvexMeshId{
model_id:PhysicsModelId,
struct ConvexMeshId<Id>{
model_id:Id,
submesh_id:PhysicsSubmeshId,
}
impl<Id> ConvexMeshId<Id>{
fn map<NewId>(self,model_id:NewId)->ConvexMeshId<NewId>{
ConvexMeshId{
model_id,
submesh_id:self.submesh_id,
}
}
}
struct ContactModel{
mesh_id:PhysicsMeshId,
attr_id:ContactAttributesId,
@@ -710,14 +731,12 @@ struct IntersectModel{
#[derive(Debug,Clone,Copy,Eq,Hash,PartialEq)]
pub struct ContactCollision{
convex_mesh_id:ConvexMeshId<ContactModelId>,
face_id:model_physics::MinkowskiFace,
model_id:ContactModelId,
submesh_id:PhysicsSubmeshId,
}
#[derive(Debug,Clone,Copy,Eq,Hash,PartialEq)]
pub struct IntersectCollision{
model_id:IntersectModelId,
submesh_id:PhysicsSubmeshId,
convex_mesh_id:ConvexMeshId<IntersectModelId>,
}
#[derive(Debug,Clone,Eq,Hash,PartialEq)]
pub enum Collision{
@@ -725,33 +744,47 @@ pub enum Collision{
Intersect(IntersectCollision),
}
impl Collision{
const fn new(convex_mesh_id:ConvexMeshId,face_id:model_physics::MinkowskiFace)->Self{
fn new(convex_mesh_id:ConvexMeshId<PhysicsModelId>,face_id:model_physics::MinkowskiFace)->Self{
match convex_mesh_id.model_id{
PhysicsModelId::Contact(model_id)=>Collision::Contact(ContactCollision{model_id,submesh_id:convex_mesh_id.submesh_id,face_id}),
PhysicsModelId::Intersect(model_id)=>Collision::Intersect(IntersectCollision{model_id,submesh_id:convex_mesh_id.submesh_id}),
PhysicsModelId::Contact(model_id)=>Collision::Contact(ContactCollision{convex_mesh_id:convex_mesh_id.map(model_id),face_id}),
PhysicsModelId::Intersect(model_id)=>Collision::Intersect(IntersectCollision{convex_mesh_id:convex_mesh_id.map(model_id)}),
}
}
}
#[derive(Clone,Debug,Default)]
struct TouchingState{
contacts:HashSet<ContactCollision>,
intersects:HashSet<IntersectCollision>,
// This is kind of jank, it's a ContactCollision
// but split over the Key and Value of the HashMap.
contacts:HashMap<ConvexMeshId<ContactModelId>,model_physics::MinkowskiFace>,
intersects:HashSet<ConvexMeshId<IntersectModelId>>,
}
impl TouchingState{
fn clear(&mut self){
self.contacts.clear();
self.intersects.clear();
}
fn insert(&mut self,collision:Collision)->bool{
match collision{
Collision::Contact(collision)=>self.contacts.insert(collision),
Collision::Intersect(collision)=>self.intersects.insert(collision),
}
fn insert_contact(&mut self,contact:ContactCollision)->Option<model_physics::MinkowskiFace>{
self.contacts.insert(contact.convex_mesh_id,contact.face_id)
}
fn remove(&mut self,collision:&Collision)->bool{
match collision{
Collision::Contact(collision)=>self.contacts.remove(collision),
Collision::Intersect(collision)=>self.intersects.remove(collision),
fn insert_intersect(&mut self,intersect:IntersectCollision)->bool{
self.intersects.insert(intersect.convex_mesh_id)
}
fn remove_contact(&mut self,convex_mesh_id:&ConvexMeshId<ContactModelId>)->Option<model_physics::MinkowskiFace>{
self.contacts.remove(convex_mesh_id)
}
fn remove_intersect(&mut self,convex_mesh_id:&ConvexMeshId<IntersectModelId>)->bool{
self.intersects.remove(convex_mesh_id)
}
fn contains_contact(&self,convex_mesh_id:&ConvexMeshId<ContactModelId>)->bool{
self.contacts.contains_key(convex_mesh_id)
}
fn contains_intersect(&self,convex_mesh_id:&ConvexMeshId<IntersectModelId>)->bool{
self.intersects.contains(convex_mesh_id)
}
fn contains(&self,convex_mesh_id:&ConvexMeshId<PhysicsModelId>)->bool{
match convex_mesh_id.model_id{
PhysicsModelId::Contact(contact_model_id)=>self.contains_contact(&convex_mesh_id.map(contact_model_id)),
PhysicsModelId::Intersect(intersect_model_id)=>self.contains_intersect(&convex_mesh_id.map(intersect_model_id)),
}
}
fn base_acceleration(&self,models:&PhysicsModels,style:&StyleModifiers,camera:&PhysicsCamera,input_state:&InputState)->Planar64Vec3{
@@ -760,7 +793,7 @@ impl TouchingState{
a+=rocket_settings.acceleration(style.get_propulsion_control_dir(camera,input_state.controls));
}
//add accelerators
for contact in &self.contacts{
for contact in self.contacts.keys(){
if let Some(accelerator)=&models.contact_attr(contact.model_id).general.accelerator{
a+=accelerator.acceleration;
}
@@ -774,10 +807,10 @@ impl TouchingState{
a
}
fn constrain_velocity(&self,models:&PhysicsModels,hitbox_mesh:&HitboxMesh,velocity:Planar64Vec3)->Planar64Vec3{
let contacts:Vec<_>=self.contacts.iter().map(|contact|{
let n=contact_normal(models,hitbox_mesh,contact);
let contacts:Vec<_>=self.contacts.iter().map(|(convex_mesh_id,face_id)|{
let n=contact_normal(models,hitbox_mesh,convex_mesh_id,*face_id);
crate::push_solve::Contact{
position:vec3::ZERO,
position:vec3::zero(),
velocity:n,
normal:n,
}
@@ -785,10 +818,10 @@ impl TouchingState{
crate::push_solve::push_solve(&contacts,velocity)
}
fn constrain_acceleration(&self,models:&PhysicsModels,hitbox_mesh:&HitboxMesh,acceleration:Planar64Vec3)->Planar64Vec3{
let contacts:Vec<_>=self.contacts.iter().map(|contact|{
let n=contact_normal(models,hitbox_mesh,contact);
let contacts:Vec<_>=self.contacts.iter().map(|(convex_mesh_id,face_id)|{
let n=contact_normal(models,hitbox_mesh,convex_mesh_id,*face_id);
crate::push_solve::Contact{
position:vec3::ZERO,
position:vec3::zero(),
velocity:n,
normal:n,
}
@@ -798,29 +831,29 @@ impl TouchingState{
fn predict_collision_end(&self,collector:&mut instruction::InstructionCollector<InternalInstruction,Time>,models:&PhysicsModels,hitbox_mesh:&HitboxMesh,body:&Body,start_time:Time){
// let relative_body=body.relative_to(&Body::ZERO);
let relative_body=body;
for contact in &self.contacts{
for (convex_mesh_id,face_id) in &self.contacts{
//detect face slide off
let model_mesh=models.contact_mesh(contact);
let model_mesh=models.contact_mesh(convex_mesh_id);
let minkowski=model_physics::MinkowskiMesh::minkowski_sum(model_mesh,hitbox_mesh.transformed_mesh());
collector.collect(minkowski.predict_collision_face_out(&relative_body,start_time..collector.time(),contact.face_id).map(|(_face,time)|{
collector.collect(minkowski.predict_collision_face_out(&relative_body,start_time..collector.time(),*face_id).map(|(_face,time)|{
TimedInstruction{
time:relative_body.time+time.into(),
instruction:InternalInstruction::CollisionEnd(
Collision::Contact(*contact),
Collision::Contact(ContactCollision{face_id:*face_id,convex_mesh_id:*convex_mesh_id}),
time
),
}
}));
}
for intersect in &self.intersects{
for convex_mesh_id in &self.intersects{
//detect model collision in reverse
let model_mesh=models.intersect_mesh(intersect);
let model_mesh=models.intersect_mesh(convex_mesh_id);
let minkowski=model_physics::MinkowskiMesh::minkowski_sum(model_mesh,hitbox_mesh.transformed_mesh());
collector.collect(minkowski.predict_collision_out(&relative_body,start_time..collector.time()).map(|(_face,time)|{
TimedInstruction{
time:relative_body.time+time.into(),
instruction:InternalInstruction::CollisionEnd(
Collision::Intersect(*intersect),
Collision::Intersect(IntersectCollision{convex_mesh_id:*convex_mesh_id}),
time
),
}
@@ -947,7 +980,7 @@ impl PhysicsState{
// shared geometry for simulations
pub struct PhysicsData{
//permanent map data
bvh:bvh::BvhNode<ConvexMeshId>,
bvh:bvh::BvhNode<ConvexMeshId<PhysicsModelId>>,
//transient map/environment data (open world loads/unloads parts of this data)
models:PhysicsModels,
//semi-transient data
@@ -1033,14 +1066,16 @@ impl PhysicsData{
let transform=PhysicsMeshTransform::new(model.transform);
match attr_id{
PhysicsAttributesId::Contact(attr_id)=>{
contact_models.insert(ContactModelId::new(model_id as u32),ContactModel{
let contact_model_id=ContactModelId::new(model_id as u32);
contact_models.insert(contact_model_id,ContactModel{
mesh_id,
attr_id,
transform,
});
},
PhysicsAttributesId::Intersect(attr_id)=>{
intersect_models.insert(IntersectModelId::new(model_id as u32),IntersectModel{
let intersect_model_id=IntersectModelId::new(model_id as u32);
intersect_models.insert(intersect_model_id,IntersectModel{
mesh_id,
attr_id,
transform,
@@ -1054,12 +1089,23 @@ impl PhysicsData{
(PhysicsMeshId::new(mesh_id as u32),mesh)
).collect();
let convex_mesh_aabb_list=
//map the two lists into a single type so they can be processed with one closure
contact_models.iter().map(|(&model_id,model)|
(PhysicsModelId::Contact(model_id),&model.mesh_id,&model.transform)
).chain(intersect_models.iter().map(|(&model_id,model)|
(PhysicsModelId::Intersect(model_id),&model.mesh_id,&model.transform)
))
// use the map models iteration order to ensure that the
// order that the models are passed into bvh::generate_bvh is consistent
map.models.iter().enumerate().filter_map(|(model_id,model)|{
match map.attributes.get(model.attributes.get() as usize){
None|Some(gameplay_attributes::CollisionAttributes::Decoration)=>None,
Some(gameplay_attributes::CollisionAttributes::Contact(_))=>{
let model_id=ContactModelId::new(model_id as u32);
let model=contact_models.get(&model_id)?;
Some((PhysicsModelId::Contact(model_id),&model.mesh_id,&model.transform))
},
Some(gameplay_attributes::CollisionAttributes::Intersect(_))=>{
let model_id=IntersectModelId::new(model_id as u32);
let model=intersect_models.get(&model_id)?;
Some((PhysicsModelId::Intersect(model_id),&model.mesh_id,&model.transform))
},
}
})
.flat_map(|(model_id,mesh_id,transform)|{
meshes[mesh_id].submesh_views()
.enumerate().map(move|(submesh_id,view)|{
@@ -1161,21 +1207,19 @@ impl<'a> PhysicsContext<'a>{
//relative to moving platforms
//let relative_body=state.body.relative_to(&Body::ZERO);
let relative_body=&state.body;
data.bvh.sample_aabb(&aabb,&mut |&convex_mesh_id|{
data.bvh.sample_aabb(&aabb,&mut |convex_mesh_id|{
if state.touching.contains(convex_mesh_id){
return;
}
//no checks are needed because of the time limits.
let model_mesh=data.models.mesh(convex_mesh_id);
let model_mesh=data.models.mesh(*convex_mesh_id);
let minkowski=model_physics::MinkowskiMesh::minkowski_sum(model_mesh,data.hitbox_mesh.transformed_mesh());
collector.collect(minkowski.predict_collision_in(relative_body,state.time..collector.time())
//temp (?) code to avoid collision loops
.and_then(|(face,dt)|{
// this must be rounded to avoid the infinite loop when hitting the start zone
let time=relative_body.time+dt.into();
(state.time<time).then_some((time,face,dt))
}).map(|(time,face,dt)|
.map(|(face,dt)|
TimedInstruction{
time,
time:relative_body.time+dt.into(),
instruction:InternalInstruction::CollisionStart(
Collision::new(convex_mesh_id,face),
Collision::new(*convex_mesh_id,face),
dt
)
}
@@ -1186,12 +1230,17 @@ impl<'a> PhysicsContext<'a>{
}
fn contact_normal(models:&PhysicsModels,hitbox_mesh:&HitboxMesh,contact:&ContactCollision)->Planar64Vec3{
let model_mesh=models.contact_mesh(contact);
fn contact_normal(
models:&PhysicsModels,
hitbox_mesh:&HitboxMesh,
convex_mesh_id:&ConvexMeshId<ContactModelId>,
face_id:model_physics::MinkowskiFace,
)->Planar64Vec3{
let model_mesh=models.contact_mesh(convex_mesh_id);
let minkowski=model_physics::MinkowskiMesh::minkowski_sum(model_mesh,hitbox_mesh.transformed_mesh());
// TODO: normalize to i64::MAX>>1
// wrap for speed
minkowski.face_nd(contact.face_id).0.wrap_1()
minkowski.face_nd(face_id).0.wrap_1()
}
fn recalculate_touching(
@@ -1203,7 +1252,7 @@ fn recalculate_touching(
mode:Option<&gameplay_modes::Mode>,
models:&PhysicsModels,
hitbox_mesh:&HitboxMesh,
bvh:&bvh::BvhNode<ConvexMeshId>,
bvh:&bvh::BvhNode<ConvexMeshId<PhysicsModelId>>,
style:&StyleModifiers,
camera:&PhysicsCamera,
input_state:&InputState,
@@ -1212,11 +1261,11 @@ fn recalculate_touching(
//collision_end all existing contacts
//I would have preferred while let Some(contact)=contacts.pop()
//but there is no such method
while let Some(&contact)=touching.contacts.iter().next(){
collision_end_contact(move_state,body,touching,models,hitbox_mesh,style,camera,input_state,models.contact_attr(contact.model_id),contact)
while let Some((&convex_mesh_id,_face_id))=touching.contacts.iter().next(){
collision_end_contact(move_state,body,touching,models,hitbox_mesh,style,camera,input_state,models.contact_attr(convex_mesh_id.model_id),&convex_mesh_id)
}
while let Some(&intersect)=touching.intersects.iter().next(){
collision_end_intersect(move_state,body,touching,models,hitbox_mesh,style,camera,input_state,mode,run,models.intersect_attr(intersect.model_id),intersect,time);
while let Some(&convex_mesh_id)=touching.intersects.iter().next(){
collision_end_intersect(move_state,body,touching,models,hitbox_mesh,style,camera,input_state,mode,run,models.intersect_attr(convex_mesh_id.model_id),&convex_mesh_id,time);
}
//find all models in the teleport region
let mut aabb=aabb::Aabb::default();
@@ -1238,8 +1287,7 @@ fn recalculate_touching(
collision_start_intersect(move_state,body,mode_state,touching,mode,run,models,hitbox_mesh,bvh,style,camera,input_state,
models.intersect_attr(model_id),
IntersectCollision{
model_id,
submesh_id:convex_mesh_id.submesh_id,
convex_mesh_id:convex_mesh_id.map(model_id),
},
time,
),
@@ -1257,7 +1305,7 @@ fn set_position(
mode:Option<&gameplay_modes::Mode>,
models:&PhysicsModels,
hitbox_mesh:&HitboxMesh,
bvh:&bvh::BvhNode<ConvexMeshId>,
bvh:&bvh::BvhNode<ConvexMeshId<PhysicsModelId>>,
style:&StyleModifiers,
camera:&PhysicsCamera,
input_state:&InputState,
@@ -1273,8 +1321,8 @@ fn set_position(
fn set_velocity_cull(body:&mut Body,touching:&mut TouchingState,models:&PhysicsModels,hitbox_mesh:&HitboxMesh,v:Planar64Vec3)->bool{
//This is not correct but is better than what I have
let mut culled=false;
touching.contacts.retain(|contact|{
let n=contact_normal(models,hitbox_mesh,contact);
touching.contacts.retain(|convex_mesh_id,face_id|{
let n=contact_normal(models,hitbox_mesh,convex_mesh_id,*face_id);
let r=n.dot(v).is_positive();
if r{
culled=true;
@@ -1291,8 +1339,8 @@ fn set_velocity(body:&mut Body,touching:&TouchingState,models:&PhysicsModels,hit
fn set_acceleration_cull(body:&mut Body,touching:&mut TouchingState,models:&PhysicsModels,hitbox_mesh:&HitboxMesh,a:Planar64Vec3)->bool{
//This is not correct but is better than what I have
let mut culled=false;
touching.contacts.retain(|contact|{
let n=contact_normal(models,hitbox_mesh,contact);
touching.contacts.retain(|convex_mesh_id,face_id|{
let n=contact_normal(models,hitbox_mesh,convex_mesh_id,*face_id);
let r=n.dot(a).is_positive();
if r{
culled=true;
@@ -1316,7 +1364,7 @@ fn teleport(
mode:Option<&gameplay_modes::Mode>,
models:&PhysicsModels,
hitbox_mesh:&HitboxMesh,
bvh:&bvh::BvhNode<ConvexMeshId>,
bvh:&bvh::BvhNode<ConvexMeshId<PhysicsModelId>>,
style:&StyleModifiers,
camera:&PhysicsCamera,
input_state:&InputState,
@@ -1338,7 +1386,7 @@ fn teleport_to_spawn(
mode:&gameplay_modes::Mode,
models:&PhysicsModels,
hitbox_mesh:&HitboxMesh,
bvh:&bvh::BvhNode<ConvexMeshId>,
bvh:&bvh::BvhNode<ConvexMeshId<PhysicsModelId>>,
style:&StyleModifiers,
camera:&PhysicsCamera,
input_state:&InputState,
@@ -1427,7 +1475,7 @@ fn run_teleport_behaviour(
mode_state:&mut ModeState,
models:&PhysicsModels,
hitbox_mesh:&HitboxMesh,
bvh:&bvh::BvhNode<ConvexMeshId>,
bvh:&bvh::BvhNode<ConvexMeshId<PhysicsModelId>>,
style:&StyleModifiers,
camera:&PhysicsCamera,
input_state:&InputState,
@@ -1474,7 +1522,7 @@ fn run_teleport_behaviour(
}
}
fn not_spawn_at(
fn is_not_spawn_at(
mode:Option<&gameplay_modes::Mode>,
model_id:ModelId,
)->bool{
@@ -1495,7 +1543,7 @@ fn collision_start_contact(
mode:Option<&gameplay_modes::Mode>,
models:&PhysicsModels,
hitbox_mesh:&HitboxMesh,
bvh:&bvh::BvhNode<ConvexMeshId>,
bvh:&bvh::BvhNode<ConvexMeshId<PhysicsModelId>>,
style:&StyleModifiers,
camera:&PhysicsCamera,
input_state:&InputState,
@@ -1505,12 +1553,12 @@ fn collision_start_contact(
){
let incident_velocity=body.velocity;
//add to touching
touching.insert(Collision::Contact(contact));
touching.insert_contact(contact);
//clip v
set_velocity(body,touching,models,hitbox_mesh,incident_velocity);
let mut allow_jump=true;
let model_id=contact.model_id.into();
let mut allow_run_teleport_behaviour=not_spawn_at(mode,model_id);
let model_id=contact.convex_mesh_id.model_id.into();
let mut allow_run_teleport_behaviour=is_not_spawn_at(mode,model_id);
match &attr.contacting.contact_behaviour{
Some(gameplay_attributes::ContactingBehaviour::Surf)=>(),
Some(gameplay_attributes::ContactingBehaviour::Cling)=>println!("Unimplemented!"),
@@ -1524,7 +1572,7 @@ fn collision_start_contact(
//kill v
//actually you could do this with a booster attribute :thinking:
//it's a little bit different because maybe you want to chain ladders together
set_velocity(body,touching,models,hitbox_mesh,vec3::ZERO);//model.velocity
set_velocity(body,touching,models,hitbox_mesh,vec3::zero());//model.velocity
}
//ladder walkstate
let (gravity,target_velocity)=ladder_things(ladder_settings,&contact,touching,models,hitbox_mesh,style,camera,input_state);
@@ -1533,7 +1581,7 @@ fn collision_start_contact(
},
Some(gameplay_attributes::ContactingBehaviour::NoJump)=>allow_jump=false,
None=>if let Some(walk_settings)=&style.walk{
if walk_settings.is_slope_walkable(contact_normal(models,hitbox_mesh,&contact),vec3::Y){
if walk_settings.is_slope_walkable(contact_normal(models,hitbox_mesh,&contact.convex_mesh_id,contact.face_id),vec3::Y){
allow_run_teleport_behaviour=true;
//ground
let (gravity,target_velocity)=ground_things(walk_settings,&contact,touching,models,hitbox_mesh,style,camera,input_state);
@@ -1598,7 +1646,7 @@ fn collision_start_intersect(
run:&mut run::Run,
models:&PhysicsModels,
hitbox_mesh:&HitboxMesh,
bvh:&bvh::BvhNode<ConvexMeshId>,
bvh:&bvh::BvhNode<ConvexMeshId<PhysicsModelId>>,
style:&StyleModifiers,
camera:&PhysicsCamera,
input_state:&InputState,
@@ -1607,13 +1655,13 @@ fn collision_start_intersect(
time:Time,
){
//I think that setting the velocity to 0 was preventing surface contacts from entering an infinite loop
touching.insert(Collision::Intersect(intersect));
touching.insert_intersect(intersect);
//insta booster!
if let Some(booster)=&attr.general.booster{
move_state.cull_velocity(booster.boost(body.velocity),body,touching,models,hitbox_mesh,style,camera,input_state);
}
if let Some(mode)=mode{
let zone=mode.get_zone(intersect.model_id.into());
let zone=mode.get_zone(intersect.convex_mesh_id.model_id.into());
match zone{
Some(gameplay_modes::Zone::Start)=>{
println!("@@@@ Starting new run!");
@@ -1630,7 +1678,7 @@ fn collision_start_intersect(
}
}
move_state.apply_enum_and_body(body,touching,models,hitbox_mesh,style,camera,input_state);
run_teleport_behaviour(intersect.model_id.into(),attr.general.wormhole.as_ref(),mode,move_state,body,touching,run,mode_state,models,hitbox_mesh,bvh,style,camera,input_state,time);
run_teleport_behaviour(intersect.convex_mesh_id.model_id.into(),attr.general.wormhole.as_ref(),mode,move_state,body,touching,run,mode_state,models,hitbox_mesh,bvh,style,camera,input_state,time);
}
fn collision_end_contact(
@@ -1643,15 +1691,17 @@ fn collision_end_contact(
camera:&PhysicsCamera,
input_state:&InputState,
_attr:&gameplay_attributes::ContactAttributes,
contact:ContactCollision,
convex_mesh_id:&ConvexMeshId<ContactModelId>,
){
touching.remove(&Collision::Contact(contact));//remove contact before calling contact_constrain_acceleration
touching.remove_contact(convex_mesh_id);//remove contact before calling contact_constrain_acceleration
//check ground
//TODO do better
//this is inner code from move_state.cull_velocity
match move_state.get_walk_state(){
// did you stop touching the thing you were walking on?
Some(walk_state)=>if walk_state.contact==contact{
// This does not check the face! Is that a bad thing? It should be
// impossible to stop touching a different face than you started touching...
Some(walk_state)=>if &walk_state.contact.convex_mesh_id==convex_mesh_id{
move_state.set_move_state(MoveState::Air,body,touching,models,hitbox_mesh,style,camera,input_state);
}else{
// stopped touching something else while walking
@@ -1673,13 +1723,13 @@ fn collision_end_intersect(
mode:Option<&gameplay_modes::Mode>,
run:&mut run::Run,
_attr:&gameplay_attributes::IntersectAttributes,
intersect:IntersectCollision,
convex_mesh_id:&ConvexMeshId<IntersectModelId>,
time:Time,
){
touching.remove(&Collision::Intersect(intersect));
touching.remove_intersect(convex_mesh_id);
move_state.apply_enum_and_body(body,touching,models,hitbox_mesh,style,camera,input_state);
if let Some(mode)=mode{
let zone=mode.get_zone(intersect.model_id.into());
let zone=mode.get_zone(convex_mesh_id.model_id.into());
match zone{
Some(gameplay_modes::Zone::Start)=>{
match run.start(time){
@@ -1713,7 +1763,7 @@ fn atomic_internal_instruction(state:&mut PhysicsState,data:&PhysicsData,ins:Tim
&mut state.move_state,&mut state.body,&mut state.mode_state,&mut state.touching,&mut state.run,
mode,
&data.models,&data.hitbox_mesh,&data.bvh,&state.style,&state.camera,&state.input_state,
data.models.contact_attr(contact.model_id),
data.models.contact_attr(contact.convex_mesh_id.model_id),
contact,
state.time,
),
@@ -1721,7 +1771,7 @@ fn atomic_internal_instruction(state:&mut PhysicsState,data:&PhysicsData,ins:Tim
&mut state.move_state,&mut state.body,&mut state.mode_state,&mut state.touching,
mode,
&mut state.run,&data.models,&data.hitbox_mesh,&data.bvh,&state.style,&state.camera,&state.input_state,
data.models.intersect_attr(intersect.model_id),
data.models.intersect_attr(intersect.convex_mesh_id.model_id),
intersect,
state.time,
),
@@ -1730,15 +1780,15 @@ fn atomic_internal_instruction(state:&mut PhysicsState,data:&PhysicsData,ins:Tim
InternalInstruction::CollisionEnd(collision,_)=>match collision{
Collision::Contact(contact)=>collision_end_contact(
&mut state.move_state,&mut state.body,&mut state.touching,&data.models,&data.hitbox_mesh,&state.style,&state.camera,&state.input_state,
data.models.contact_attr(contact.model_id),
contact
data.models.contact_attr(contact.convex_mesh_id.model_id),
&contact.convex_mesh_id
),
Collision::Intersect(intersect)=>collision_end_intersect(
&mut state.move_state,&mut state.body,&mut state.touching,&data.models,&data.hitbox_mesh,&state.style,&state.camera,&state.input_state,
data.modes.get_mode(state.mode_state.get_mode_id()),
&mut state.run,
data.models.intersect_attr(intersect.model_id),
intersect,
data.models.intersect_attr(intersect.convex_mesh_id.model_id),
&intersect.convex_mesh_id,
state.time
),
},
@@ -1749,7 +1799,7 @@ fn atomic_internal_instruction(state:&mut PhysicsState,data:&PhysicsData,ins:Tim
if strafe_settings.activates(controls){
let masked_controls=strafe_settings.mask(controls);
let control_dir=state.style.get_control_dir(masked_controls);
if control_dir!=vec3::ZERO{
if control_dir!=vec3::zero(){
let camera_mat=state.camera.simulate_move_rotation_y(state.input_state.lerp_delta(state.time).x);
if let Some(ticked_velocity)=strafe_settings.tick_velocity(state.body.velocity,(camera_mat*control_dir).with_length(Planar64::ONE).divide().wrap_1()){
//this is wrong but will work ig
@@ -1772,7 +1822,7 @@ fn atomic_internal_instruction(state:&mut PhysicsState,data:&PhysicsData,ins:Tim
//which means that gravity can be fully cancelled
//ignore moving platforms for now
let target=core::mem::replace(&mut walk_state.target,TransientAcceleration::Reached);
set_acceleration(&mut state.body,&state.touching,&data.models,&data.hitbox_mesh,vec3::ZERO);
set_acceleration(&mut state.body,&state.touching,&data.models,&data.hitbox_mesh,vec3::zero());
// check what the target was to see if it was invalid
match target{
//you are not supposed to reach a walk target which is already reached!
@@ -1836,7 +1886,7 @@ fn atomic_input_instruction(state:&mut PhysicsState,data:&PhysicsData,ins:TimedI
if let Some(walk_state)=state.move_state.get_walk_state(){
if let Some(jump_settings)=&state.style.jump{
let jump_dir=walk_state.jump_direction.direction(&data.models,&data.hitbox_mesh,&walk_state.contact);
let booster_option=data.models.contact_attr(walk_state.contact.model_id).general.booster.as_ref();
let booster_option=data.models.contact_attr(walk_state.contact.convex_mesh_id.model_id).general.booster.as_ref();
let jumped_velocity=jump_settings.jumped_velocity(&state.style,jump_dir,state.body.velocity,booster_option);
state.cull_velocity(data,jumped_velocity);
}
@@ -1854,7 +1904,7 @@ fn atomic_input_instruction(state:&mut PhysicsState,data:&PhysicsData,ins:TimedI
},
Instruction::Mode(ModeInstruction::Restart(mode_id))=>{
//teleport to mode start zone
let mut spawn_point=vec3::ZERO;
let mut spawn_point=vec3::zero();
let mode=data.modes.get_mode(mode_id);
if let Some(mode)=mode{
// set style
@@ -1867,7 +1917,7 @@ fn atomic_input_instruction(state:&mut PhysicsState,data:&PhysicsData,ins:TimedI
}
}
set_position(spawn_point,&mut state.move_state,&mut state.body,&mut state.touching,&mut state.run,&mut state.mode_state,mode,&data.models,&data.hitbox_mesh,&data.bvh,&state.style,&state.camera,&state.input_state,state.time);
set_velocity(&mut state.body,&state.touching,&data.models,&data.hitbox_mesh,vec3::ZERO);
set_velocity(&mut state.body,&state.touching,&data.models,&data.hitbox_mesh,vec3::zero());
state.set_move_state(data,MoveState::Air);
b_refresh_walk_target=false;
}
@@ -1917,7 +1967,7 @@ mod test{
use strafesnet_common::integer::{vec3::{self,int as int3},mat3};
use super::*;
fn test_collision_axis_aligned(relative_body:Body,expected_collision_time:Option<Time>){
let h0=HitboxMesh::new(PhysicsMesh::unit_cube(),integer::Planar64Affine3::new(mat3::from_diagonal(int3(5,1,5)>>1),vec3::ZERO));
let h0=HitboxMesh::new(PhysicsMesh::unit_cube(),integer::Planar64Affine3::new(mat3::from_diagonal(int3(5,1,5)>>1),vec3::zero()));
let h1=StyleModifiers::roblox_bhop().calculate_mesh();
let hitbox_mesh=h1.transformed_mesh();
let platform_mesh=h0.transformed_mesh();
@@ -1933,7 +1983,7 @@ mod test{
int3(0,1,0)>>1,
int3(-1,0,5)>>1,
]),
vec3::ZERO
vec3::zero()
),
);
let h1=StyleModifiers::roblox_bhop().calculate_mesh();
@@ -1952,7 +2002,7 @@ mod test{
test_collision(Body::new(
int3(0,5,0),
int3(0,-1,0),
vec3::ZERO,
vec3::zero(),
Time::ZERO
),Some(Time::from_secs(2)));
}
@@ -1961,7 +2011,7 @@ mod test{
test_collision(Body::new(
int3(0,5,0),
int3(0,-1,0)+(vec3::X>>32),
vec3::ZERO,
vec3::zero(),
Time::ZERO
),Some(Time::from_secs(2)));
}
@@ -1970,7 +2020,7 @@ mod test{
test_collision(Body::new(
int3(3,5,0),
int3(0,-1,0),
vec3::ZERO,
vec3::zero(),
Time::ZERO
),Some(Time::from_secs(2)));
}
@@ -1979,7 +2029,7 @@ mod test{
test_collision(Body::new(
int3(0,5,3),
int3(0,-1,0),
vec3::ZERO,
vec3::zero(),
Time::ZERO
),Some(Time::from_secs(2)));
}
@@ -1988,7 +2038,7 @@ mod test{
test_collision(Body::new(
int3(-3,5,0),
int3(0,-1,0),
vec3::ZERO,
vec3::zero(),
Time::ZERO
),Some(Time::from_secs(2)));
}
@@ -1997,7 +2047,7 @@ mod test{
test_collision(Body::new(
int3(0,5,-3),
int3(0,-1,0),
vec3::ZERO,
vec3::zero(),
Time::ZERO
),Some(Time::from_secs(2)));
}
@@ -2114,7 +2164,7 @@ mod test{
test_collision(Body::new(
int3(0,5,0),
int3(1,-64,2)>>6,// /64
vec3::ZERO,
vec3::zero(),
Time::ZERO
),Some(Time::from_secs(2)));
}
@@ -2130,7 +2180,7 @@ mod test{
#[test]
fn already_inside_hit_nothing(){
test_collision(Body::new(
vec3::ZERO,
vec3::zero(),
int3(1,0,0),
int3(0,1,0),
Time::ZERO
@@ -2142,7 +2192,7 @@ mod test{
test_collision_axis_aligned(Body::new(
(int3(0,10,-7)>>1)+vec3::raw_xyz(0,0,1),
int3(0,-1,0),
vec3::ZERO,
vec3::zero(),
Time::ZERO
),Some(Time::from_secs(2)))
}
@@ -2151,7 +2201,7 @@ mod test{
test_collision_axis_aligned(Body::new(
(int3(7,10,0)>>1)+vec3::raw_xyz(-1,0,0),
int3(0,-1,0),
vec3::ZERO,
vec3::zero(),
Time::ZERO
),Some(Time::from_secs(2)))
}
@@ -2160,7 +2210,7 @@ mod test{
test_collision_axis_aligned(Body::new(
(int3(0,10,7)>>1)+vec3::raw_xyz(0,0,-1),
int3(0,-1,0),
vec3::ZERO,
vec3::zero(),
Time::ZERO
),Some(Time::from_secs(2)))
}
@@ -2169,7 +2219,7 @@ mod test{
test_collision_axis_aligned(Body::new(
(int3(-7,10,0)>>1)+vec3::raw_xyz(1,0,0),
int3(0,-1,0),
vec3::ZERO,
vec3::zero(),
Time::ZERO
),Some(Time::from_secs(2)))
}
@@ -2179,7 +2229,7 @@ mod test{
test_collision_axis_aligned(Body::new(
int3(0,10,-7)>>1,
int3(0,-1,0),
vec3::ZERO,
vec3::zero(),
Time::ZERO
),None)
}
@@ -2188,7 +2238,7 @@ mod test{
test_collision_axis_aligned(Body::new(
int3(7,10,0)>>1,
int3(0,-1,0),
vec3::ZERO,
vec3::zero(),
Time::ZERO
),None)
}
@@ -2197,7 +2247,7 @@ mod test{
test_collision_axis_aligned(Body::new(
int3(0,10,7)>>1,
int3(0,-1,0),
vec3::ZERO,
vec3::zero(),
Time::ZERO
),None)
}
@@ -2206,7 +2256,7 @@ mod test{
test_collision_axis_aligned(Body::new(
int3(-7,10,0)>>1,
int3(0,-1,0),
vec3::ZERO,
vec3::zero(),
Time::ZERO
),None)
}
@@ -2216,7 +2266,7 @@ mod test{
test_collision_axis_aligned(Body::new(
(int3(0,10,-7)>>1)-vec3::raw_xyz(0,0,1),
int3(0,-1,0),
vec3::ZERO,
vec3::zero(),
Time::ZERO
),None)
}
@@ -2225,7 +2275,7 @@ mod test{
test_collision_axis_aligned(Body::new(
(int3(7,10,0)>>1)-vec3::raw_xyz(-1,0,0),
int3(0,-1,0),
vec3::ZERO,
vec3::zero(),
Time::ZERO
),None)
}
@@ -2234,7 +2284,7 @@ mod test{
test_collision_axis_aligned(Body::new(
(int3(0,10,7)>>1)-vec3::raw_xyz(0,0,-1),
int3(0,-1,0),
vec3::ZERO,
vec3::zero(),
Time::ZERO
),None)
}
@@ -2243,7 +2293,7 @@ mod test{
test_collision_axis_aligned(Body::new(
(int3(-7,10,0)>>1)-vec3::raw_xyz(1,0,0),
int3(0,-1,0),
vec3::ZERO,
vec3::zero(),
Time::ZERO
),None)
}

View File

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

View File

@@ -52,7 +52,6 @@ pub enum SessionControlInstruction{
pub enum SessionPlaybackInstruction{
SkipForward,
SkipBack,
TogglePaused,
DecreaseTimescale,
IncreaseTimescale,
}
@@ -253,7 +252,14 @@ impl InstructionConsumer<Instruction<'_>> for Session{
// don't flush the buffered instructions in the mouse interpolator
// until the mouse is confirmed to be not moving at a later time
// what if they pause for 5ms lmao
_=self.simulation.timer.set_paused(ins.time,paused);
match &self.view_state{
ViewState::Play=>{
_=self.simulation.timer.set_paused(ins.time,paused);
},
ViewState::Replay(bot_id)=>if let Some(replay)=self.replays.get_mut(bot_id){
_=replay.simulation.timer.set_paused(ins.time,paused);
},
}
},
Instruction::Control(SessionControlInstruction::CopyRecordingIntoReplayAndSpectate)=> if let ViewState::Play=self.view_state{
// Bind: B
@@ -374,14 +380,6 @@ impl InstructionConsumer<Instruction<'_>> for Session{
},
}
},
Instruction::Playback(SessionPlaybackInstruction::TogglePaused)=>{
match &self.view_state{
ViewState::Play=>(),
ViewState::Replay(bot_id)=>if let Some(replay)=self.replays.get_mut(bot_id){
_=replay.simulation.timer.set_paused(ins.time,!replay.simulation.timer.is_paused());
},
}
}
Instruction::ChangeMap(complete_map)=>{
self.clear_recording();
self.change_map(complete_map);

View File

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

View File

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

View File

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

View File

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

View File

@@ -561,12 +561,6 @@ pub mod vec3{
pub use linear_ops::types::Vector3;
pub const MIN:Planar64Vec3=Planar64Vec3::new([Planar64::MIN;3]);
pub const MAX:Planar64Vec3=Planar64Vec3::new([Planar64::MAX;3]);
pub const ZERO:Planar64Vec3=Planar64Vec3::new([Planar64::ZERO;3]);
pub const ZERO_2:Vector3<Fixed::<2,64>>=Vector3::new([Fixed::<2,64>::ZERO;3]);
pub const ZERO_3:Vector3<Fixed::<3,96>>=Vector3::new([Fixed::<3,96>::ZERO;3]);
pub const ZERO_4:Vector3<Fixed::<4,128>>=Vector3::new([Fixed::<4,128>::ZERO;3]);
pub const ZERO_5:Vector3<Fixed::<5,160>>=Vector3::new([Fixed::<5,160>::ZERO;3]);
pub const ZERO_6:Vector3<Fixed::<6,192>>=Vector3::new([Fixed::<6,192>::ZERO;3]);
pub const X:Planar64Vec3=Planar64Vec3::new([Planar64::ONE,Planar64::ZERO,Planar64::ZERO]);
pub const Y:Planar64Vec3=Planar64Vec3::new([Planar64::ZERO,Planar64::ONE,Planar64::ZERO]);
pub const Z:Planar64Vec3=Planar64Vec3::new([Planar64::ZERO,Planar64::ZERO,Planar64::ONE]);
@@ -575,6 +569,10 @@ pub mod vec3{
pub const NEG_Y:Planar64Vec3=Planar64Vec3::new([Planar64::ZERO,Planar64::NEG_ONE,Planar64::ZERO]);
pub const NEG_Z:Planar64Vec3=Planar64Vec3::new([Planar64::ZERO,Planar64::ZERO,Planar64::NEG_ONE]);
pub const NEG_ONE:Planar64Vec3=Planar64Vec3::new([Planar64::NEG_ONE,Planar64::NEG_ONE,Planar64::NEG_ONE]);
// TODO: use #![feature(generic_const_items)] when stabilized https://github.com/rust-lang/rust/issues/113521
pub const fn zero<const N:usize,const F:usize>()->Vector3<Fixed<N,F>>{
Vector3::new([Fixed::ZERO;3])
}
#[inline]
pub const fn int(x:i32,y:i32,z:i32)->Planar64Vec3{
Planar64Vec3::new([Planar64::raw((x as i64)<<32),Planar64::raw((y as i64)<<32),Planar64::raw((z as i64)<<32)])
@@ -663,7 +661,7 @@ pub struct Planar64Affine3{
pub translation:Planar64Vec3,
}
impl Planar64Affine3{
pub const IDENTITY:Self=Self::new(mat3::identity(),vec3::ZERO);
pub const IDENTITY:Self=Self::new(mat3::identity(),vec3::zero());
#[inline]
pub const fn new(matrix3:Planar64Mat3,translation:Planar64Vec3)->Self{
Self{matrix3,translation}

View File

@@ -70,6 +70,34 @@ impl<const N:usize,const F:usize> Fixed<N,F>{
pub const fn midpoint(self,other:Self)->Self{
Self::from_bits(self.bits.midpoint(other.bits))
}
#[inline]
pub const fn min(self,other:Self)->Self{
Self::from_bits(self.bits.min(other.bits))
}
#[inline]
pub const fn max(self,other:Self)->Self{
Self::from_bits(self.bits.max(other.bits))
}
/// return the result of self*sign(other)
#[inline]
pub const fn mul_sign<const N1:usize,const F1:usize>(self,other:Fixed<N1,F1>)->Self{
if other.is_negative(){
Self::from_bits(self.bits.neg())
}else if other.is_zero(){
Fixed::ZERO
}else{
self
}
}
/// return the result of self/sign(other) (divide by zero does not change the sign)
#[inline]
pub const fn div_sign<const N1:usize,const F1:usize>(self,other:Fixed<N1,F1>)->Self{
if other.is_negative(){
Self::from_bits(self.bits.neg())
}else{
self
}
}
}
impl<const F:usize> Fixed<1,F>{
/// My old code called this function everywhere so let's provide it
@@ -101,28 +129,6 @@ impl_from!(
i8,i16,i32,i64,i128,isize
);
impl<const N:usize,const F:usize,T> PartialEq<T> for Fixed<N,F>
where
T:Copy,
BInt::<N>:From<T>,
{
#[inline]
fn eq(&self,&other:&T)->bool{
self.bits.eq(&other.into())
}
}
impl<const N:usize,const F:usize,T> PartialOrd<T> for Fixed<N,F>
where
T:Copy,
BInt::<N>:From<T>,
{
#[inline]
fn partial_cmp(&self,&other:&T)->Option<std::cmp::Ordering>{
self.bits.partial_cmp(&other.into())
}
}
impl<const N:usize,const F:usize> std::ops::Neg for Fixed<N,F>{
type Output=Self;
#[inline]
@@ -328,16 +334,6 @@ macro_rules! impl_additive_operator {
self.$method(other)
}
}
impl<const N:usize,const F:usize,U> core::ops::$trait<U> for $struct<N,F>
where
BInt::<N>:From<U>,
{
type Output = $output;
#[inline]
fn $method(self, other: U) -> Self::Output {
Self::from_bits(self.bits.$method(BInt::<N>::from(other).shl(F as u32)))
}
}
};
}
macro_rules! impl_additive_assign_operator {
@@ -348,15 +344,6 @@ macro_rules! impl_additive_assign_operator {
self.bits.$method(other.bits);
}
}
impl<const N:usize,const F:usize,U> core::ops::$trait<U> for $struct<N,F>
where
BInt::<N>:From<U>,
{
#[inline]
fn $method(&mut self, other: U) {
self.bits.$method(BInt::<N>::from(other).shl(F as u32));
}
}
};
}
@@ -379,7 +366,7 @@ impl_additive_operator!( Fixed, BitXor, bitxor, Self );
// non-wide operators. The result is the same width as the inputs.
// This macro is not used in the default configuration.
#[allow(unused_macros)]
#[expect(unused_macros)]
macro_rules! impl_multiplicative_operator_not_const_generic {
( ($struct: ident, $trait: ident, $method: ident, $output: ty ), $width:expr ) => {
impl<const F:usize> core::ops::$trait for $struct<$width,F>{
@@ -558,7 +545,7 @@ impl_shift_operator!( Fixed, Shr, shr, Self );
// wide operators. The result width is the sum of the input widths, i.e. none of the multiplication
#[allow(unused_macros)]
#[expect(unused_macros)]
macro_rules! impl_wide_operators{
($lhs:expr,$rhs:expr)=>{
impl core::ops::Mul<Fixed<$rhs,{$rhs*32}>> for Fixed<$lhs,{$lhs*32}>{

View File

@@ -12,17 +12,17 @@ authors = ["Rhys Lloyd <krakow20@gmail.com>"]
[dependencies]
bytemuck = "1.14.3"
glam = "0.30.0"
regex = { version = "1.11.3", default-features = false }
rbx_binary = { version = "1.0.1-sn5", registry = "strafesnet" }
rbx_dom_weak = { version = "3.0.1-sn5", registry = "strafesnet" }
regex = { version = "1.11.3", default-features = false, features = ["unicode-perl"] }
rbx_mesh = "0.5.0"
rbx_reflection = "5.0.0"
rbx_reflection_database = "1.0.0"
rbx_xml = { version = "1.0.1-sn5", registry = "strafesnet" }
rbxassetid = { version = "0.1.0", path = "../rbxassetid", registry = "strafesnet" }
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_deferred_loader = { version = "0.5.1", path = "../deferred_loader", registry = "strafesnet" }
rbx_binary = "2.0.1"
rbx_dom_weak = "4.1.0"
rbx_reflection = "6.1.0"
rbx_reflection_database = "2.0.2"
rbx_xml = "2.0.1"
[lints]
workspace = true

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,62 +0,0 @@
# Blender MTL File: 'teslacyberv3.0.blend'
# Material Count: 6
newmtl Material
Ns 65.476285
Ka 1.000000 1.000000 1.000000
Kd 0.411568 0.411568 0.411568
Ks 0.614679 0.614679 0.614679
Ke 0.000000 0.000000 0.000000
Ni 36.750000
d 1.000000
illum 3
newmtl Материал
Ns 323.999994
Ka 1.000000 1.000000 1.000000
Kd 0.800000 0.800000 0.800000
Ks 0.500000 0.500000 0.500000
Ke 0.000000 0.000000 0.000000
Ni 1.000000
d 1.000000
illum 2
newmtl Материал.001
Ns 900.000000
Ka 1.000000 1.000000 1.000000
Kd 0.026240 0.026240 0.026240
Ks 0.000000 0.000000 0.000000
Ke 0.000000 0.000000 0.000000
Ni 1.450000
d 1.000000
illum 1
newmtl Материал.002
Ns 0.000000
Ka 1.000000 1.000000 1.000000
Kd 0.031837 0.032429 0.029425
Ks 0.169725 0.169725 0.169725
Ke 0.000000 0.000000 0.000000
Ni 0.000000
d 1.000000
illum 2
newmtl Материал.003
Ns 900.000000
Ka 1.000000 1.000000 1.000000
Kd 0.023585 0.083235 0.095923
Ks 1.000000 1.000000 1.000000
Ke 0.000000 0.000000 0.000000
Ni 45.049999
d 1.000000
illum 3
newmtl Материал.004
Ns 323.999994
Ka 1.000000 1.000000 1.000000
Kd 0.800000 0.800000 0.800000
Ks 0.500000 0.500000 0.500000
Ke 0.000000 0.000000 0.000000
Ni 1.000000
d 1.000000
illum 2

File diff suppressed because it is too large Load Diff

View File

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

View File

@@ -15,6 +15,7 @@ pub enum Instruction{
struct WindowContext<'a>{
manual_mouse_lock:bool,
mouse_pos:glam::DVec2,
simulation_paused:bool,
screen_size:glam::UVec2,
window:&'a winit::window::Window,
physics_thread:crate::compat_worker::QNWorker<'a,TimedInstruction<PhysicsWorkerInstruction,SessionTime>>,
@@ -24,6 +25,35 @@ impl WindowContext<'_>{
fn get_middle_of_screen(&self)->winit::dpi::PhysicalPosition<u32>{
winit::dpi::PhysicalPosition::new(self.screen_size.x/2,self.screen_size.y/2)
}
fn free_mouse(&mut self){
self.manual_mouse_lock=false;
match self.window.set_cursor_position(self.get_middle_of_screen()){
Ok(())=>(),
Err(e)=>println!("Could not set cursor position: {:?}",e),
}
match self.window.set_cursor_grab(winit::window::CursorGrabMode::None){
Ok(())=>(),
Err(e)=>println!("Could not release cursor: {:?}",e),
}
self.window.set_cursor_visible(true);
}
fn lock_mouse(&mut self){
//if cursor is outside window don't lock but apparently there's no get pos function
//let pos=window.get_cursor_pos();
match self.window.set_cursor_grab(winit::window::CursorGrabMode::Locked){
Ok(())=>(),
Err(_)=>{
match self.window.set_cursor_grab(winit::window::CursorGrabMode::Confined){
Ok(())=>(),
Err(e)=>{
self.manual_mouse_lock=true;
println!("Could not confine cursor: {:?}",e)
},
}
}
}
self.window.set_cursor_visible(false);
}
fn window_event(&mut self,time:SessionTime,event:winit::event::WindowEvent){
match event{
winit::event::WindowEvent::DroppedFile(path)=>{
@@ -34,6 +64,10 @@ impl WindowContext<'_>{
}
},
winit::event::WindowEvent::Focused(state)=>{
// don't unpause if manually paused
if self.simulation_paused{
return;
}
//pause unpause
self.physics_thread.send(TimedInstruction{
time,
@@ -46,35 +80,8 @@ impl WindowContext<'_>{
..
}=>{
match (logical_key,state){
(winit::keyboard::Key::Named(winit::keyboard::NamedKey::Tab),winit::event::ElementState::Pressed)=>{
self.manual_mouse_lock=false;
match self.window.set_cursor_position(self.get_middle_of_screen()){
Ok(())=>(),
Err(e)=>println!("Could not set cursor position: {:?}",e),
}
match self.window.set_cursor_grab(winit::window::CursorGrabMode::None){
Ok(())=>(),
Err(e)=>println!("Could not release cursor: {:?}",e),
}
self.window.set_cursor_visible(state.is_pressed());
},
(winit::keyboard::Key::Named(winit::keyboard::NamedKey::Tab),winit::event::ElementState::Released)=>{
//if cursor is outside window don't lock but apparently there's no get pos function
//let pos=window.get_cursor_pos();
match self.window.set_cursor_grab(winit::window::CursorGrabMode::Locked){
Ok(())=>(),
Err(_)=>{
match self.window.set_cursor_grab(winit::window::CursorGrabMode::Confined){
Ok(())=>(),
Err(e)=>{
self.manual_mouse_lock=true;
println!("Could not confine cursor: {:?}",e)
},
}
}
}
self.window.set_cursor_visible(state.is_pressed());
},
(winit::keyboard::Key::Named(winit::keyboard::NamedKey::Tab),winit::event::ElementState::Pressed)=>self.free_mouse(),
(winit::keyboard::Key::Named(winit::keyboard::NamedKey::Tab),winit::event::ElementState::Released)=>self.lock_mouse(),
(winit::keyboard::Key::Named(winit::keyboard::NamedKey::F11),winit::event::ElementState::Pressed)=>{
if self.window.fullscreen().is_some(){
self.window.set_fullscreen(None);
@@ -90,7 +97,24 @@ impl WindowContext<'_>{
}
self.window.set_cursor_visible(true);
},
(winit::keyboard::Key::Named(winit::keyboard::NamedKey::Enter),winit::event::ElementState::Pressed)=>{
let paused=!self.simulation_paused;
self.simulation_paused=paused;
if paused{
self.free_mouse();
}else{
self.lock_mouse();
}
let instruction=PhysicsWorkerInstruction::SessionControl(SessionControlInstruction::SetPaused(paused));
self.physics_thread.send(TimedInstruction{
time,
instruction,
}).unwrap();
}
(keycode,state)=>{
if self.simulation_paused{
return;
}
let s=state.is_pressed();
// internal variants for this scope
@@ -132,7 +156,6 @@ impl WindowContext<'_>{
if let Some(session_instruction)=match keycode{
winit::keyboard::Key::Named(winit::keyboard::NamedKey::Space)=>input_ctrl!(SetJump,s),
// TODO: bind system so playback pausing can use spacebar
winit::keyboard::Key::Named(winit::keyboard::NamedKey::Enter)=>session_playback!(TogglePaused,s),
winit::keyboard::Key::Named(winit::keyboard::NamedKey::ArrowUp)=>session_playback!(IncreaseTimescale,s),
winit::keyboard::Key::Named(winit::keyboard::NamedKey::ArrowDown)=>session_playback!(DecreaseTimescale,s),
winit::keyboard::Key::Named(winit::keyboard::NamedKey::ArrowLeft)=>session_playback!(SkipBack,s),
@@ -191,7 +214,7 @@ impl WindowContext<'_>{
fn device_event(&mut self,time:SessionTime,event:winit::event::DeviceEvent){
match event{
winit::event::DeviceEvent::MouseMotion{
delta,
delta:(delta_x,delta_y),
}=>{
if self.manual_mouse_lock{
match self.window.set_cursor_position(self.get_middle_of_screen()){
@@ -199,7 +222,10 @@ impl WindowContext<'_>{
Err(e)=>println!("Could not set cursor position: {:?}",e),
}
}
self.mouse_pos+=glam::dvec2(delta.0,delta.1);
self.mouse_pos+=glam::dvec2(delta_x,delta_y);
if self.simulation_paused{
return;
}
self.physics_thread.send(TimedInstruction{
time,
instruction:PhysicsWorkerInstruction::SessionInput(SessionInputInstruction::Mouse(self.mouse_pos.as_ivec2())),
@@ -241,6 +267,7 @@ pub fn worker<'a>(
let mut window_context=WindowContext{
manual_mouse_lock:false,
mouse_pos:glam::DVec2::ZERO,
simulation_paused:false,
//make sure to update this!!!!!
screen_size,
window,