Compare commits

..

48 Commits

Author SHA1 Message Date
41a02e7b3c notes 2025-11-27 15:07:24 -08:00
4637891a21 euclidean point 2025-11-27 15:06:18 -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
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
60 changed files with 1359 additions and 1113 deletions

1339
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

@@ -94,7 +94,7 @@ impl GraphicsCamera{
raw
}
}
impl std::default::Default for GraphicsCamera{
impl Default for GraphicsCamera{
fn default()->Self{
Self{
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){
//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 image=match ddsfile::Dds::read(std::io::Cursor::new(texture_data)){
Ok(image)=>image,
@@ -803,7 +803,7 @@ impl GraphicsState{
module:&shader,
entry_point:Some("vs_entity_texture"),
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,
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"
id = { version = "0.1.0", registry = "strafesnet" }
strafesnet_common = { path = "../../lib/common", registry = "strafesnet" }
[lints]
workspace = true

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,6 +90,8 @@ 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)
}
/// This must return a point inside the mesh.
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]>;
@@ -447,6 +449,10 @@ 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;
@@ -500,7 +506,7 @@ impl TransformedMesh<'_>{
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))
}
fn farthest_vert(&self,dir:Planar64Vec3)->SubmeshVertId{
@@ -532,6 +538,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)
@@ -750,7 +759,7 @@ impl MinkowskiMesh<'_>{
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;
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()
})
@@ -778,10 +787,7 @@ impl MinkowskiMesh<'_>{
use crate::face_crawler::{low,upp};
//no algorithm needed, there is only one state and two cases (Edge,None)
//determine when it passes an edge ("sliding off" case)
let start_time=range.start_bound().map(|&t|{
let r=(t-relative_body.time).to_ratio();
Ratio::new(r.num,r.den)
});
let start_time=range.start_bound().map(|&t|(t-relative_body.time).to_ratio());
let mut best_time=range.end_bound().map(|&t|into_giga_time(t,relative_body.time));
let mut best_edge=None;
let face_n=self.face_nd(contact_face_id).0;
@@ -810,7 +816,7 @@ impl MinkowskiMesh<'_>{
infinity_fev.crawl(self,&infinity_body,Bound::Unbounded,Bound::Included(&infinity_body.time)).hit()
}
pub fn is_point_in_mesh(&self,point:Planar64Vec3)->bool{
let infinity_body=Body::new(point,vec3::Y,vec3::ZERO,Time::ZERO);
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)
@@ -856,6 +862,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

@@ -23,15 +23,10 @@ use strafesnet_common::physics::{Instruction,MouseInstruction,ModeInstruction,Mi
//internal influence
//when the physics asks itself what happens next, this is how it's represented
#[derive(Debug)]
#[derive(Debug,Clone)]
pub enum InternalInstruction{
// begin accepting touch updates
OpenMultiCollision(model_physics::GigaTime),
// mutliple touch updates
CollisionStart(Collision),
CollisionEnd(Collision),
// confirm there will be no more touch updates and apply the transaction
CloseMultiCollision,
CollisionStart(Collision,model_physics::GigaTime),
CollisionEnd(Collision,model_physics::GigaTime),
StrafeTick,
ReachWalkTargetVelocity,
// Water,
@@ -41,7 +36,7 @@ pub enum InternalInstruction{
pub struct InputState{
mouse:MouseState,
next_mouse:MouseState,
controls:strafesnet_common::controls_bitflag::Controls,
controls:Controls,
}
impl InputState{
fn set_next_mouse(&mut self,next_mouse:MouseState){
@@ -92,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,
}
}
@@ -105,7 +100,6 @@ enum TransientAcceleration{
time:Time,
},
//walk target will never be reached
#[expect(dead_code)]
Unreachable{
acceleration:Planar64Vec3,
}
@@ -119,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{
@@ -142,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,
}
@@ -165,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);
@@ -173,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);
@@ -198,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];
@@ -215,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),
}
}
@@ -307,7 +305,7 @@ impl PhysicsCamera{
}
}
impl std::default::Default for PhysicsCamera{
impl Default for PhysicsCamera{
fn default()->Self{
Self{
sensitivity:Ratio64Vec2::ONE*200_000,
@@ -390,7 +388,7 @@ mod gameplay{
self.unordered_checkpoints.clear();
}
}
impl std::default::Default for ModeState{
impl Default for ModeState{
fn default()->Self{
Self{
mode_id:gameplay_modes::ModeId::MAIN,
@@ -445,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){
@@ -484,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)
}
@@ -502,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);
@@ -616,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
@@ -647,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{
@@ -659,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{
@@ -671,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)]
@@ -688,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(),
})
@@ -698,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,
@@ -715,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{
@@ -730,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{
@@ -765,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;
}
@@ -779,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,
}
@@ -790,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,
}
@@ -803,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
),
}
@@ -894,6 +922,9 @@ impl PhysicsState{
pub const fn mode(&self)->gameplay_modes::ModeId{
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>{
self.run.get_finish_time()
}
@@ -949,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
@@ -1035,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,
@@ -1056,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)|{
@@ -1128,7 +1172,7 @@ impl InstructionEmitter<InternalInstruction> for PhysicsContext<'_>{
next_instruction_internal(&self.state,&self.data,time_limit)
}
}
impl PhysicsContext<'_>{
impl<'a> PhysicsContext<'a>{
pub fn run_input_instruction(
state:&mut PhysicsState,
data:&PhysicsData,
@@ -1138,6 +1182,13 @@ impl PhysicsContext<'_>{
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)
}
}
//this is the one who asks
@@ -1156,21 +1207,19 @@ impl PhysicsContext<'_>{
//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
)
}
@@ -1181,12 +1230,17 @@ impl PhysicsContext<'_>{
}
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(
@@ -1198,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,
@@ -1207,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();
@@ -1233,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,
),
@@ -1252,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,
@@ -1268,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;
@@ -1286,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;
@@ -1311,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,
@@ -1333,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,
@@ -1422,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,
@@ -1469,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{
@@ -1490,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,
@@ -1500,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!"),
@@ -1519,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);
@@ -1528,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);
@@ -1593,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,
@@ -1602,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!");
@@ -1625,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(
@@ -1638,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
@@ -1668,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){
@@ -1708,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,
),
@@ -1716,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,
),
@@ -1725,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
),
},
@@ -1744,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
@@ -1767,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!
@@ -1831,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);
}
@@ -1849,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
@@ -1862,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;
}
@@ -1912,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();
@@ -1923,12 +1978,12 @@ mod test{
fn test_collision_rotated(relative_body:Body,expected_collision_time:Option<Time>){
let h0=HitboxMesh::new(PhysicsMesh::unit_cube(),
integer::Planar64Affine3::new(
integer::Planar64Mat3::from_cols([
Planar64Mat3::from_cols([
int3(5,0,1)>>1,
int3(0,1,0)>>1,
int3(-1,0,5)>>1,
]),
vec3::ZERO
vec3::zero()
),
);
let h1=StyleModifiers::roblox_bhop().calculate_mesh();
@@ -1947,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)));
}
@@ -1956,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)));
}
@@ -1965,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)));
}
@@ -1974,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)));
}
@@ -1983,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)));
}
@@ -1992,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)));
}
@@ -2109,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)));
}
@@ -2125,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
@@ -2137,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)))
}
@@ -2146,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)))
}
@@ -2155,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)))
}
@@ -2164,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)))
}
@@ -2174,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)
}
@@ -2183,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)
}
@@ -2192,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)
}
@@ -2201,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)
}
@@ -2211,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)
}
@@ -2220,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)
}
@@ -2229,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)
}
@@ -2238,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

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

View File

@@ -31,7 +31,7 @@ pub enum SessionInputInstruction{
Mouse(glam::IVec2),
SetControl(strafesnet_common::physics::SetControlInstruction),
Mode(ImplicitModeInstruction),
Misc(strafesnet_common::physics::MiscInstruction),
Misc(MiscInstruction),
}
/// Implicit mode instruction are fed separately to session.
/// Session generates the explicit mode instructions interlaced with a SetSensitivity instruction
@@ -152,10 +152,10 @@ enum ViewState{
pub struct Session{
directories:Directories,
user_settings:UserSettings,
mouse_interpolator:crate::mouse_interpolator::MouseInterpolator,
mouse_interpolator:MouseInterpolator,
view_state:ViewState,
//gui:GuiState
geometry_shared:physics::PhysicsData,
geometry_shared:PhysicsData,
simulation:Simulation,
// below fields not included in lite session
recording:Recording,

View File

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

View File

@@ -10,3 +10,6 @@ strafesnet_physics = { path = "../engine/physics", 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

@@ -1,11 +1,9 @@
use strafesnet_physics::physics::{PhysicsData,PhysicsState,PhysicsContext};
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_common::physics::Instruction;
use strafesnet_common::instruction::TimedInstruction;
use strafesnet_rbx_loader::primitives::{unit_cube,CubeFaceDescription};
struct TestSceneBuilder{
@@ -73,19 +71,47 @@ fn test_scene()->PhysicsData{
fn simultaneous_collision(){
let physics_data=test_scene();
let body=strafesnet_physics::physics::Body::new(
vec3::int(5+1,1,0),
(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);
PhysicsContext::run_input_instruction(&mut physics,&physics_data,TimedInstruction{
time:Time::from_secs(2),
instruction:Instruction::Idle,
});
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::ONE_SECOND);
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

@@ -29,7 +29,7 @@ fn physics_bug_2()->Result<(),ReplayError>{
// wait one second to activate the bug
// hit=Some(ModelId(2262))
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,
});
@@ -68,7 +68,7 @@ fn physics_bug_3()->Result<(),ReplayError>{
let mut physics=PhysicsState::new_with_body(body);
// wait one second to activate the bug
PhysicsContext::run_input_instruction(&mut physics,&physics_data,strafesnet_common::instruction::TimedInstruction{
time:strafesnet_common::integer::Time::from_millis(500),
time:Time::from_millis(500),
instruction:strafesnet_common::physics::Instruction::Idle,
});

View File

@@ -17,3 +17,6 @@ vbsp = "0.9.1"
vbsp-entities-css = "0.6.0"
vmdl = "0.2.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)]
struct Face{
normal:integer::Planar64Vec3,
dot:integer::Planar64,
dot:Planar64,
}
#[derive(Debug)]
@@ -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,
});
@@ -347,7 +347,7 @@ pub struct PartialMap1{
modes:NormalizedModes,
}
impl PartialMap1{
pub fn add_prop_meshes<'a>(
pub fn add_prop_meshes(
self,
prop_meshes:Meshes<model::Mesh>,
)->PartialMap2{

View File

@@ -31,7 +31,7 @@ impl Loader for TextureLoader{
type Error=TextureError;
type Index<'a>=Cow<'a,str>;
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 mut file=std::fs::File::open(file_name)?;
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 Index<'a>=&'a str where Self:'a;
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 Index<'a>=&'a str where Self:'a;
type Resource=Mesh;

View File

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

View File

@@ -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" }
glam = "0.30.0"
id = { version = "0.1.0", registry = "strafesnet" }
[lints]
workspace = true

View File

@@ -61,11 +61,11 @@ impl Aabb{
pub fn center(&self)->Planar64Vec3{
self.min.map_zip(self.max,|(min,max)|min.midpoint(max))
}
//probably use floats for area & volume because we don't care about precision
// pub fn area_weight(&self)->f32{
// let d=self.max-self.min;
// d.x*d.y+d.y*d.z+d.z*d.x
// }
#[inline]
pub fn area_weight(&self)->fixed_wide::fixed::Fixed<2,64>{
let d=self.max-self.min;
d.x*d.y+d.y*d.z+d.z*d.x
}
// pub fn volume(&self)->f32{
// let d=self.max-self.min;
// d.x*d.y*d.z

View File

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

View File

@@ -0,0 +1,45 @@
/// Euclidean point.
/// Newtype of ideal point.
/// Euclidean points can be offset by ideal points to create a new euclidean point.
/// Two euclidean points cannot be added together.
/// Subtracting two euclidean points gives an ideal point.
// TODO: Affine*Point translates but Affine*Vector does not translate
pub struct Point<P>(P);
use core::ops::Add;
use core::ops::Sub;
use core::ops::AddAssign;
use core::ops::SubAssign;
// Offset euclidean point by ideal point
impl<P:Add> Add<P> for Point<P>{
type Output=Point<P::Output>;
fn add(self,rhs:P)->Self::Output{
Point(self.0+rhs)
}
}
impl<P:Sub> Sub<P> for Point<P>{
type Output=Point<P::Output>;
fn sub(self,rhs:P)->Self::Output{
Point(self.0-rhs)
}
}
impl<P:AddAssign> AddAssign<P> for Point<P>{
fn add_assign(&mut self,rhs:P){
self.0+=rhs;
}
}
impl<P:SubAssign> SubAssign<P> for Point<P>{
fn sub_assign(&mut self,rhs:P){
self.0-=rhs;
}
}
// Extract ideal point from euclidean point
impl<P:Sub> Sub for Point<P>{
type Output=P::Output;
fn sub(self,rhs:Point<P>)->Self::Output{
self.0-rhs.0
}
}

View File

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

@@ -34,12 +34,41 @@ pub trait InstructionFeedback<I,T>:InstructionEmitter<I,Time=T>+InstructionConsu
self.process_instruction(instruction);
}
}
#[inline]
fn into_iter(self,time_limit:T)->InstructionIter<I,T,Self>
where
Self:Sized
{
InstructionIter{
time_limit,
feedback:self,
_phantom:core::marker::PhantomData,
}
}
}
impl<I,T,X> InstructionFeedback<I,T> for X
impl<I,T,F> InstructionFeedback<I,T> for F
where
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!!!
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)
}
}
impl<T> std::default::Default for Time<T>{
impl<T> Default for Time<T>{
fn default()->Self{
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::RemAssign,rem_assign);
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]
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)))
@@ -156,7 +156,7 @@ impl<T> core::ops::Mul<Time<T>> for Planar64{
#[cfg(test)]
mod test_time{
use super::*;
type Time=super::AbsoluteTime;
type Time=AbsoluteTime;
#[test]
fn time_from_planar64(){
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 Planar64Vec3=linear_ops::types::Vector3<Planar64>;
pub type Planar64Mat3=linear_ops::types::Matrix3<Planar64>;
@@ -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:linear_ops::types::Vector3<Fixed::<2,64>>=linear_ops::types::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_4:linear_ops::types::Vector3<Fixed::<4,128>>=linear_ops::types::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_6:linear_ops::types::Vector3<Fixed::<6,192>>=linear_ops::types::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

@@ -5,6 +5,7 @@ pub mod run;
pub mod aabb;
pub mod model;
pub mod mouse;
pub mod euclidean_point;
pub mod timer;
pub mod integer;
pub mod physics;

View File

@@ -11,3 +11,6 @@ authors = ["Rhys Lloyd <krakow20@gmail.com>"]
[dependencies]
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 Index<'a> where Self:'a;
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 }
paste = "1.0.15"
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};
#[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)
/// N is the number of u64s to use
/// F is the number of fractional bits (always N*32 lol)
@@ -68,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
@@ -99,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]
@@ -286,6 +294,23 @@ macro_rules! impl_from_float {
impl_from_float!(integer_decode_f32,f32,24);
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>{
#[inline]
fn fmt(&self,f:&mut core::fmt::Formatter)->Result<(),core::fmt::Error>{
@@ -309,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 {
@@ -329,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));
}
}
};
}

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]
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]
fn deref(&self)->&Self::Target{
// 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
unsafe{&*ptr}
}
@@ -214,7 +215,8 @@ macro_rules! impl_matrix_named_fields_shape {
#[inline]
fn deref_mut(&mut self)->&mut Self::Target{
// 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
unsafe{&mut*ptr}
}

View File

@@ -331,7 +331,8 @@ macro_rules! impl_vector_named_fields {
#[inline]
fn deref(&self)->&Self::Target{
// 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
unsafe{&*ptr}
}
@@ -340,7 +341,8 @@ macro_rules! impl_vector_named_fields {
#[inline]
fn deref_mut(&mut self)->&mut Self::Target{
// 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
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>"]
[dependencies]
[lints]
workspace = true

View File

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

View File

@@ -50,7 +50,7 @@ impl Loader for TextureLoader{
type Error=TextureError;
type Index<'a>=&'a str;
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 file_name=format!("textures/{}.dds",asset_id);
let data=read_entire_file(file_name)?;
@@ -157,7 +157,7 @@ impl Loader for MeshLoader{
type Error=MeshError;
type Index<'a>=MeshIndex<'a>;
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{
MeshType::FileMesh=>{
let RobloxAssetId(asset_id)=index.content.parse()?;

View File

@@ -9,7 +9,6 @@ use crate::loader::MeshWithSize;
#[derive(Debug)]
pub enum Error{
Planar64Vec3(strafesnet_common::integer::Planar64TryFromFloatError),
RbxMesh(rbx_mesh::mesh::Error)
}
impl std::fmt::Display for Error{
@@ -22,41 +21,43 @@ impl std::error::Error for Error{}
fn ingest_vertices2(
vertices:Vec<Vertex2>,
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
//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),
{
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)),
normal:mb.acquire_normal_id(vec3::try_from_f32_array(vertex.norm)?),
color:mb.acquire_color_id(glam::Vec4::from_array(vertex.color.map(|f|f as f32/255.0f32)))
normal:mb.acquire_normal_id(vec3::try_from_f32_array(vertex.norm).ok()?),
color:mb.acquire_color_id(glam::Vec4::from_array(vertex.color.map(|f|f as f32/255.0f32))),
};
mb.acquire_vertex_id(vertex)
}
))).collect::<Result<_,_>>().map_err(Error::Planar64Vec3)?)
))).collect()
}
fn ingest_vertices_truncated2(
vertices:Vec<Vertex2Truncated>,
mb:&mut model::MeshBuilder,
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
//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),
{
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)),
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,
};
mb.acquire_vertex_id(vertex)
}
))).collect::<Result<_,_>>().map_err(Error::Planar64Vec3)?)
))).collect()
}
fn ingest_faces2_lods3(
@@ -67,8 +68,8 @@ fn ingest_faces2_lods3(
){
//faces have to be split into polygon groups based on lod
polygon_groups.extend(lods.windows(2).map(|lod_pair|
PolygonGroup::PolygonList(PolygonList::new(faces[lod_pair[0].0 as usize..lod_pair[1].0 as usize].iter().map(|rbx_mesh::mesh::Face2(v0,v1,v2)|
vec![vertex_id_map[&v0],vertex_id_map[&v1],vertex_id_map[&v2]]
PolygonGroup::PolygonList(PolygonList::new(faces[lod_pair[0].0 as usize..lod_pair[1].0 as usize].iter().filter_map(|rbx_mesh::mesh::Face2(v0,v1,v2)|
Some(vec![*vertex_id_map.get(&v0)?,*vertex_id_map.get(&v1)?,*vertex_id_map.get(&v2)?])
).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)?{
rbx_mesh::mesh::Mesh::V1(mesh)=>{
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 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])),
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,
};
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])?])
}).collect::<Result<_,_>>().map_err(Error::Planar64Vec3)?)));
Some(vec![ingest_vertex1(&trip[0])?,ingest_vertex1(&trip[1])?,ingest_vertex1(&trip[2])?])
}).collect())));
},
rbx_mesh::mesh::Mesh::V2(mesh)=>{
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)
},
rbx_mesh::mesh::SizeOfVertex2::Full=>ingest_vertices2(mesh.vertices,&mut mb),
}?;
};
//one big happy group for all the faces
polygon_groups.push(PolygonGroup::PolygonList(PolygonList::new(mesh.faces.into_iter().map(|face|
vec![vertex_id_map[&face.0],vertex_id_map[&face.1],vertex_id_map[&face.2]]
polygon_groups.push(PolygonGroup::PolygonList(PolygonList::new(mesh.faces.into_iter().filter_map(|face|
Some(vec![*vertex_id_map.get(&face.0)?,*vertex_id_map.get(&face.1)?,*vertex_id_map.get(&face.2)?])
).collect())));
},
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)
},
rbx_mesh::mesh::SizeOfVertex2::Full=>ingest_vertices2(mesh.vertices,&mut mb),
}?;
};
ingest_faces2_lods3(&mut polygon_groups,&vertex_id_map,&mesh.faces,&mesh.lods);
},
rbx_mesh::mesh::Mesh::V4(mesh)=>{
let vertex_id_map=ingest_vertices2(mesh.vertices,&mut mb)?;
let vertex_id_map=ingest_vertices2(mesh.vertices,&mut mb);
ingest_faces2_lods3(&mut polygon_groups,&vertex_id_map,&mesh.faces,&mesh.lods);
},
rbx_mesh::mesh::Mesh::V5(mesh)=>{
let vertex_id_map=ingest_vertices2(mesh.vertices,&mut mb)?;
let vertex_id_map=ingest_vertices2(mesh.vertices,&mut mb);
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)
}
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>{
Ok(Planar64Affine3::new(
Planar64Mat3::from_cols([
@@ -118,7 +127,7 @@ fn get_attributes(name:&str,can_collide:bool,velocity:Planar64Vec3,model_id:mode
);
},
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){
match &captures[1]{
"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){
force_intersecting=true;
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,
),
);
}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){
match &captures[1]{
"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"),
}
}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){
force_can_collide=false;
force_intersecting=true;
@@ -242,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{
@@ -335,12 +344,12 @@ pub struct RobloxFaceTextureDescription{
pub color:glam::Vec4,
pub transform:RobloxTextureTransform,
}
impl core::cmp::PartialEq for RobloxFaceTextureDescription{
impl PartialEq for RobloxFaceTextureDescription{
fn eq(&self,other:&Self)->bool{
self.to_bits().eq(&other.to_bits())
}
}
impl core::cmp::Eq for RobloxFaceTextureDescription{}
impl Eq for RobloxFaceTextureDescription{}
impl core::hash::Hash for RobloxFaceTextureDescription{
fn hash<H:core::hash::Hasher>(&self,state:&mut H){
self.to_bits().hash(state);
@@ -766,10 +775,10 @@ struct MeshIdWithSize{
mesh:model::MeshId,
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>,
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,
render:RenderConfigId,
)->Option<MeshIdWithSize>{
@@ -789,10 +798,10 @@ fn acquire_mesh_id_from_render_config_id<'a>(
size,
})
}
fn acquire_union_id_from_render_config_id<'a>(
fn acquire_union_id_from_render_config_id(
primitive_meshes:&mut Vec<model::Mesh>,
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,
part_texture_description:RobloxPartDescription,
)->Option<MeshIdWithSize>{

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

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

View File

@@ -13,9 +13,12 @@ run-service=[]
[dependencies]
glam = "0.30.0"
mlua = { version = "0.10.1", features = ["luau"] }
phf = { version = "0.12.1", features = ["macros"] }
rbx_dom_weak = { version = "3.1.0-sn4", registry = "strafesnet", features = ["instance-userdata"] }
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"
[lints]
workspace = true

View File

@@ -8,7 +8,7 @@ impl<'a> EnumItem<'a>{
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{
EnumItem{
name:None,
@@ -65,7 +65,7 @@ impl<'a> EnumItems<'a>{
}
pub enum CoerceEnum<'a>{
Integer(i32),
Integer(i64),
String(mlua::String),
Enum(EnumItem<'a>),
}

View File

@@ -5,12 +5,12 @@ use rbx_types::Ref;
use rbx_dom_weak::{Ustr,InstanceBuilder,WeakDom};
use crate::util::static_ustr;
use crate::runner::vector3::Vector3;
use crate::runner::number::Number;
pub fn set_globals(lua:&mlua::Lua,globals:&mlua::Table)->Result<(),mlua::Error>{
//class functions store
lua.set_app_data(ClassMethodsStore::default());
lua.set_app_data(InstanceValueStore::default());
let table=lua.create_table()?;
@@ -43,7 +43,7 @@ pub fn class_is_a(class:&str,superclass:&str)->bool{
};
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 pref=instance.parent();
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)
}
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)
}
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)
}
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)
}
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 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 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 superclass_descriptor=db.classes.get(superclass)?;
dom.descendants_of(instance.referent()).find(|inst|{
@@ -325,13 +325,16 @@ impl mlua::UserData for Instance{
}
//find or create an associated userdata object
let instance=this.get_mut(dom)?;
if let Some(value)=get_or_create_userdata(instance,lua,index_str)?{
if let Some(value)=instance_value_store_mut(lua,|ivs|{
//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);
}
// drop mutable borrow
//find a child with a matching name
let instance=this.get(dom)?;
find_first_child(dom,instance,index_str)
.map(|instance|Instance::new_unchecked(instance.referent()))
.into_lua(lua)
@@ -419,7 +422,7 @@ impl mlua::UserData for Instance{
rbx_types::Variant::CFrame(typed_value.clone().into())
},
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_reflection::DataType::Value(rbx_types::VariantType::Ref)=>{
@@ -484,8 +487,8 @@ static CLASS_FUNCTION_DATABASE:CFD=phf::phf_map!{
"GetService"=>GET_SERVICE,
},
"Terrain"=>phf::phf_map!{
"FillBall"=>cf!(|_lua,_,_:(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(())),
"FillBall"=>cf!(|_lua,_,_:(crate::runner::vector3::Vector3,Number,crate::runner::r#enum::CoerceEnum)|mlua::Result::Ok(())),
"FillBlock"=>cf!(|_lua,_,_:(crate::runner::cframe::CFrame,crate::runner::vector3::Vector3,crate::runner::r#enum::CoerceEnum)|mlua::Result::Ok(())),
"FillCylinder"=>cf!(|_lua,_,_:(crate::runner::cframe::CFrame,Number,Number,crate::runner::r#enum::CoerceEnum)|mlua::Result::Ok(())),
"SetMaterialColor"=>cf!(|_lua,_,_:(crate::runner::r#enum::CoerceEnum,crate::runner::color3::Color3)|mlua::Result::Ok(())),
},
@@ -606,6 +609,8 @@ fn find_virtual_property(
}
// 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 LUD=phf::Map<&'static str,// Class name
phf::Map<&'static str,// Value name
@@ -638,22 +643,47 @@ static LAZY_USER_DATA:LUD=phf::phf_map!{
"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>>{
use std::collections::hash_map::Entry;
let db=rbx_reflection_database::get();
let Some(class)=db.classes.get(instance.class.as_str())else{
return Ok(None)
};
if let Some((&static_str,create_userdata))=db.superclasses_iter(class).find_map(|superclass|
// find pair (class,index)
LAZY_USER_DATA.get(&superclass.name)
.and_then(|map|map.get_entry(index))
){
let index_ustr=static_ustr(static_str);
return Ok(Some(match instance.userdata.entry(index_ustr){
Entry::Occupied(entry)=>entry.get().clone(),
Entry::Vacant(entry)=>entry.insert(create_userdata(lua)?).clone(),
}));
}
Ok(None)
#[derive(Default)]
pub struct InstanceValueStore{
values:HashMap<Ref,
HashMap<&'static str,
mlua::AnyUserData
>
>,
}
pub struct InstanceValues<'a>{
named_values:&'static phf::Map<&'static str,CreateUserData>,
values:&'a mut HashMap<&'static str,mlua::AnyUserData>,
}
impl InstanceValueStore{
pub fn get_or_create_instance_values(&mut self,instance:&rbx_dom_weak::Instance)->Option<InstanceValues<'_>>{
LAZY_USER_DATA.get(instance.class.as_str())
.map(|named_values|
InstanceValues{
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)]
pub enum Number{
Integer(i32),
Integer(i64),
Number(f64),
}
macro_rules! impl_ty{

View File

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

View File

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

View File

@@ -9,3 +9,6 @@ edition = "2024"
binrw = "0.15.0"
id = { version = "0.1.0", 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;
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)]
pub enum Error{
@@ -274,7 +274,7 @@ pub fn write_bot<W:BinWriterExt>(mut writer:W,physics_version:u32,instructions:i
//probe header length
let mut bot_header_data=Vec::new();
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
block_location.push(offset);

View File

@@ -97,8 +97,8 @@ enum ResourceType{
}
struct ResourceMap<T>{
meshes:HashMap<strafesnet_common::model::MeshId,T>,
textures:HashMap<strafesnet_common::model::TextureId,T>,
meshes:HashMap<model::MeshId,T>,
textures:HashMap<model::TextureId,T>,
}
impl<T> Default for ResourceMap<T>{
fn default()->Self{
@@ -185,7 +185,7 @@ pub struct StreamableMap<R:BinReaderExt>{
//this is every possible attribute... need some sort of streaming system
attributes:Vec<strafesnet_common::gameplay_attributes::CollisionAttributes>,
//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
bvh:BvhNode<BlockId>,
//something something resources hashmaps
@@ -223,7 +223,7 @@ impl<R:BinReaderExt> StreamableMap<R>{
}
Ok(Self{
file,
modes:strafesnet_common::gameplay_modes::NormalizedModes::new(modes),
modes:gameplay_modes::NormalizedModes::new(modes),
attributes,
render_configs,
bvh:strafesnet_common::bvh::generate_bvh(bvh),
@@ -366,12 +366,12 @@ fn collect_spacial_blocks(
block_location.push(sequential_block_data.position());
}else{
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{
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(())
@@ -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)|{
//grow your own aabb
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{
aabb.grow(model.transform.transform_point3(pos).narrow_1().unwrap());
}
Ok(((model::ModelId::new(model_id as u32),model.into()),aabb))
}).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
//block location is initialized with two values
//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{
type Error=super::physics::InstructionConvert;
type Error=InstructionConvert;
fn try_from(value:TimedPhysicsInstruction)->Result<Self,Self::Error>{
Ok(Self{
time:value.time.get(),

View File

@@ -12,12 +12,11 @@ flate2 = "1.0.27"
futures = "0.3.31"
image = "0.25.2"
image_dds = "0.7.1"
lazy-regex = "3.1.0"
rbx_asset = { version = "0.4.4", registry = "strafesnet" }
rbx_binary = { version = "1.1.0-sn4", registry = "strafesnet" }
rbx_dom_weak = { version = "3.1.0-sn4", registry = "strafesnet" }
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.1.0-sn4", registry = "strafesnet" }
rbx_xml = { version = "1.0.1-sn5", registry = "strafesnet" }
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" }
@@ -36,3 +35,6 @@ vtf = "0.3.0"
#lto = true
#strip = true
#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 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(())
}
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 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_settings = { path = "../engine/settings", registry = "strafesnet" }
strafesnet_snf = { path = "../lib/snf", registry = "strafesnet", optional = true }
wgpu = "26.0.1"
wgpu = "27.0.0"
winit = "0.30.7"
[profile.dev]
strip = false
opt-level = 3
[lints]
workspace = true

View File

@@ -119,12 +119,13 @@ impl<'a> SetupContextPartial3<'a>{
let (device, queue)=pollster::block_on(self.adapter
.request_device(
&wgpu::DeviceDescriptor {
label: None,
required_features: (optional_features & self.adapter.features()) | required_features,
required_limits: needed_limits,
&wgpu::DeviceDescriptor{
label:None,
required_features:(optional_features&self.adapter.features())|required_features,
required_limits:needed_limits,
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!");

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{
winit::event::DeviceEvent::MouseMotion{
delta,