Compare commits

..

53 Commits

Author SHA1 Message Date
f52399a931 Session::debug_raycast_print_model_id_if_changed 2025-03-11 17:45:26 -07:00
7be7d40c8a physics: PhysicsData::trace_ray 2025-03-11 17:45:26 -07:00
78c53ae429 physics: set style on reset and spawn 2025-03-11 17:09:29 -07:00
e794b2649c bsp_loader: implement ladders and water 2025-03-11 16:41:46 -07:00
50f6fe5bd8 common: fix source styles 2025-03-11 15:26:45 -07:00
fc3681f41f it: read_entire_file use built in 2025-03-11 14:07:24 -07:00
a2369b4211 rbx_loader: never do &Vec + deconstruct Face2 (style) 2025-03-09 23:47:23 -07:00
bfea49ffae it: measure simulate speed 2025-03-08 17:39:47 -08:00
8c7063d340 common: aabb: make function inline-able 2025-03-08 14:34:54 -08:00
651150760c common: aabb: use midpoint for center 2025-03-08 14:34:54 -08:00
3d203e2da9 fixed_wide: add midpoint 2025-03-08 14:34:54 -08:00
3798f438cf use as_bits_mut to optimize sqrt 2025-03-08 13:58:56 -08:00
4ace8317fe update deps 2025-03-08 13:55:14 -08:00
ab4a9bb922 bsp_loader: generate prop matrix with euler angles 2025-03-02 18:23:47 -08:00
a9ef07ce78 update vbsp 2025-03-02 18:23:47 -08:00
561e41c760 common: delete unused ModesUpdate 2025-02-28 13:15:55 -08:00
9896a09576 common: delete updatable trait 2025-02-28 13:15:55 -08:00
82840fa6cb refactor type safety for modes data normalization 2025-02-28 13:15:55 -08:00
2c87cb71df common: gameplay_modes: rename internal field on ModeBuilder 2025-02-28 12:37:49 -08:00
709f622547 common: don't use .0 for newtypes 2025-02-28 12:10:16 -08:00
b20264b382 common: move ModesBuilder to common 2025-02-28 11:08:38 -08:00
17cce2d702 silence lints 2025-02-28 10:50:53 -08:00
a2b2ddc9ce bsp_loader: use texture_info function 2025-02-26 15:29:23 -08:00
cb57d893e0 update vbsp fork 2025-02-26 15:26:56 -08:00
9e8d66cec1 map-tool: gimme_them_textures iterate over a shorter list of prop model paths 2025-02-26 12:00:51 -08:00
39fe833198 map-tool: grab brush models 2025-02-26 12:00:51 -08:00
e7688a95fd bsp_loader: generate physics from brushes 2025-02-26 12:00:51 -08:00
d2cc98b04d deps: fork vbsp 2025-02-26 11:59:59 -08:00
9e887580af bsp_loader: valve_transform_{dist|normal} 2025-02-21 13:18:20 -08:00
92feac572e bsp_loader: include missing model path in error 2025-02-21 13:18:20 -08:00
fd02a40783 common: CollisionAttributes::intersect_default 2025-02-21 13:18:20 -08:00
7c787a0e0f linear_ops: repr(transparent) to ward off UB 2025-02-21 13:18:20 -08:00
6a7c076203 Planar64Affine3::IDENTITY 2025-02-21 13:18:20 -08:00
af3abbcb4d Planar64Affine3::from_translation 2025-02-21 13:14:57 -08:00
4859c37780 common: BvhNode::sample_ray 2025-02-21 12:54:13 -08:00
19e65802f6 common: Aabb::contains(point) 2025-02-21 12:54:13 -08:00
2cf1547423 common: Time is Ord 2025-02-21 12:54:13 -08:00
63305f91c7 common: Ray module 2025-02-21 12:54:13 -08:00
e875826250 TransformedMesh::faces 2025-02-21 12:54:13 -08:00
0a44c1630f ratio_ops v0.1.1 fix Ord 2025-02-21 12:54:13 -08:00
5cffc03ef6 ratio_ops: fix Ord for Ratio 2025-02-21 12:53:14 -08:00
d638e633ba update deps 2025-02-20 19:02:03 -08:00
61e44f2aba upgrade rust edition to 2024 2025-02-20 18:58:01 -08:00
347b1176d2 update quat pc symbolic links 2025-02-13 09:50:42 -08:00
7c0ad5b601 common: integer: Time <-> Ratio 2025-02-06 13:35:43 -08:00
62851bbd60 common: instruction: relax InstructionCollector trait bounds 2025-02-06 12:39:38 -08:00
b3a6d08656 silence millions of lints in unused module 2025-02-06 12:26:56 -08:00
5409548348 common: don't use TimeInner as Instruction generics 2025-02-06 12:26:17 -08:00
57552c1a6a common: bvh: rename generics 2025-02-06 12:12:16 -08:00
aace3bb2a3 common: bvh: use sort_by_key 2025-02-06 11:20:35 -08:00
4899003766 common: bvh: rip the_tester 2025-02-06 10:37:22 -08:00
0c991715ab common: bvh: empty not Default 2025-02-06 10:27:00 -08:00
72e0caa84a common: evict cursed BvhWeightNode 2025-02-06 10:21:24 -08:00
60 changed files with 1527 additions and 988 deletions

646
Cargo.lock generated

File diff suppressed because it is too large Load Diff

@ -1,12 +1,12 @@
[package] [package]
name = "strafesnet_graphics" name = "strafesnet_graphics"
version = "0.1.0" version = "0.1.0"
edition = "2021" edition = "2024"
[dependencies] [dependencies]
bytemuck = { version = "1.13.1", features = ["derive"] } bytemuck = { version = "1.13.1", features = ["derive"] }
ddsfile = "0.5.1" ddsfile = "0.5.1"
glam = "0.29.0" glam = "0.30.0"
id = { version = "0.1.0", registry = "strafesnet" } id = { version = "0.1.0", registry = "strafesnet" }
strafesnet_common = { path = "../../lib/common", registry = "strafesnet" } strafesnet_common = { path = "../../lib/common", registry = "strafesnet" }
strafesnet_session = { path = "../session", registry = "strafesnet" } strafesnet_session = { path = "../session", registry = "strafesnet" }

@ -1,10 +1,10 @@
[package] [package]
name = "strafesnet_physics" name = "strafesnet_physics"
version = "0.1.0" version = "0.1.0"
edition = "2021" edition = "2024"
[dependencies] [dependencies]
arrayvec = "0.7.6" arrayvec = "0.7.6"
glam = "0.29.0" glam = "0.30.0"
id = { version = "0.1.0", registry = "strafesnet" } id = { version = "0.1.0", registry = "strafesnet" }
strafesnet_common = { path = "../../lib/common", registry = "strafesnet" } strafesnet_common = { path = "../../lib/common", registry = "strafesnet" }

@ -484,12 +484,15 @@ impl TransformedMesh<'_>{
pub fn verts<'a>(&'a self)->impl Iterator<Item=vec3::Vector3<Fixed<2,64>>>+'a{ pub fn verts<'a>(&'a self)->impl Iterator<Item=vec3::Vector3<Fixed<2,64>>>+'a{
self.view.data.verts.iter().map(|&Vert(pos)|self.transform.vertex.transform_point3(pos)) self.view.data.verts.iter().map(|&Vert(pos)|self.transform.vertex.transform_point3(pos))
} }
pub fn faces(&self)->impl Iterator<Item=SubmeshFaceId>{
(0..self.view.topology.faces.len() as u32).map(SubmeshFaceId::new)
}
fn farthest_vert(&self,dir:Planar64Vec3)->SubmeshVertId{ fn farthest_vert(&self,dir:Planar64Vec3)->SubmeshVertId{
//this happens to be well-defined. there are no virtual virtices //this happens to be well-defined. there are no virtual virtices
SubmeshVertId::new( SubmeshVertId::new(
self.view.topology.verts.iter() self.view.topology.verts.iter()
.enumerate() .enumerate()
.max_by_key(|(_,&vert_id)| .max_by_key(|&(_,&vert_id)|
dir.dot(self.transform.vertex.transform_point3(self.view.data.verts[vert_id.get() as usize].0)) dir.dot(self.transform.vertex.transform_point3(self.view.data.verts[vert_id.get() as usize].0))
) )
//assume there is more than zero vertices. //assume there is more than zero vertices.

@ -1,4 +1,5 @@
use std::collections::{HashMap,HashSet}; use std::collections::{HashMap,HashSet};
use crate::model::DirectedEdge;
use crate::model::{self as model_physics,PhysicsMesh,PhysicsMeshTransform,TransformedMesh,MeshQuery,PhysicsMeshId,PhysicsSubmeshId}; use crate::model::{self as model_physics,PhysicsMesh,PhysicsMeshTransform,TransformedMesh,MeshQuery,PhysicsMeshId,PhysicsSubmeshId};
use strafesnet_common::bvh; use strafesnet_common::bvh;
use strafesnet_common::map; use strafesnet_common::map;
@ -280,7 +281,8 @@ impl PhysicsCamera{
.clamp(Self::ANGLE_PITCH_LOWER_LIMIT,Self::ANGLE_PITCH_UPPER_LIMIT); .clamp(Self::ANGLE_PITCH_LOWER_LIMIT,Self::ANGLE_PITCH_UPPER_LIMIT);
mat3::from_rotation_yx(ax,ay) mat3::from_rotation_yx(ax,ay)
} }
fn rotation(&self)->Planar64Mat3{ #[inline]
pub fn rotation(&self)->Planar64Mat3{
self.get_rotation(self.clamped_mouse_pos) self.get_rotation(self.clamped_mouse_pos)
} }
fn simulate_move_rotation(&self,mouse_delta:glam::IVec2)->Planar64Mat3{ fn simulate_move_rotation(&self,mouse_delta:glam::IVec2)->Planar64Mat3{
@ -556,7 +558,7 @@ impl MoveState{
=>None, =>None,
} }
} }
fn next_move_instruction(&self,strafe:&Option<gameplay_style::StrafeSettings>,time:Time)->Option<TimedInstruction<InternalInstruction,TimeInner>>{ fn next_move_instruction(&self,strafe:&Option<gameplay_style::StrafeSettings>,time:Time)->Option<TimedInstruction<InternalInstruction,Time>>{
//check if you have a valid walk state and create an instruction //check if you have a valid walk state and create an instruction
match self{ match self{
MoveState::Walk(walk_state)|MoveState::Ladder(walk_state)=>match &walk_state.target{ MoveState::Walk(walk_state)|MoveState::Ladder(walk_state)=>match &walk_state.target{
@ -784,7 +786,7 @@ impl TouchingState{
}).collect(); }).collect();
crate::push_solve::push_solve(&contacts,acceleration) crate::push_solve::push_solve(&contacts,acceleration)
} }
fn predict_collision_end(&self,collector:&mut instruction::InstructionCollector<InternalInstruction,TimeInner>,models:&PhysicsModels,hitbox_mesh:&HitboxMesh,body:&Body,start_time:Time){ 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.relative_to(&Body::ZERO);
let relative_body=body; let relative_body=body;
for contact in &self.contacts{ for contact in &self.contacts{
@ -878,7 +880,7 @@ impl PhysicsState{
fn reset_to_default(&mut self){ fn reset_to_default(&mut self){
*self=Self::default(); *self=Self::default();
} }
fn next_move_instruction(&self)->Option<TimedInstruction<InternalInstruction,TimeInner>>{ fn next_move_instruction(&self)->Option<TimedInstruction<InternalInstruction,Time>>{
self.move_state.next_move_instruction(&self.style.strafe,self.time) self.move_state.next_move_instruction(&self.style.strafe,self.time)
} }
fn cull_velocity(&mut self,data:&PhysicsData,velocity:Planar64Vec3){ fn cull_velocity(&mut self,data:&PhysicsData,velocity:Planar64Vec3){
@ -935,7 +937,7 @@ pub struct PhysicsData{
impl Default for PhysicsData{ impl Default for PhysicsData{
fn default()->Self{ fn default()->Self{
Self{ Self{
bvh:bvh::BvhNode::default(), bvh:bvh::BvhNode::empty(),
models:Default::default(), models:Default::default(),
modes:Default::default(), modes:Default::default(),
hitbox_mesh:StyleModifiers::default().calculate_mesh(), hitbox_mesh:StyleModifiers::default().calculate_mesh(),
@ -950,21 +952,21 @@ pub struct PhysicsContext<'a>{
// the physics consumes both Instruction and PhysicsInternalInstruction, // the physics consumes both Instruction and PhysicsInternalInstruction,
// but can only emit PhysicsInternalInstruction // but can only emit PhysicsInternalInstruction
impl InstructionConsumer<InternalInstruction> for PhysicsContext<'_>{ impl InstructionConsumer<InternalInstruction> for PhysicsContext<'_>{
type TimeInner=TimeInner; type Time=Time;
fn process_instruction(&mut self,ins:TimedInstruction<InternalInstruction,TimeInner>){ fn process_instruction(&mut self,ins:TimedInstruction<InternalInstruction,Time>){
atomic_internal_instruction(&mut self.state,&self.data,ins) atomic_internal_instruction(&mut self.state,&self.data,ins)
} }
} }
impl InstructionConsumer<Instruction> for PhysicsContext<'_>{ impl InstructionConsumer<Instruction> for PhysicsContext<'_>{
type TimeInner=TimeInner; type Time=Time;
fn process_instruction(&mut self,ins:TimedInstruction<Instruction,TimeInner>){ fn process_instruction(&mut self,ins:TimedInstruction<Instruction,Time>){
atomic_input_instruction(&mut self.state,&self.data,ins) atomic_input_instruction(&mut self.state,&self.data,ins)
} }
} }
impl InstructionEmitter<InternalInstruction> for PhysicsContext<'_>{ impl InstructionEmitter<InternalInstruction> for PhysicsContext<'_>{
type TimeInner=TimeInner; type Time=Time;
//this little next instruction function could cache its return value and invalidate the cached value by watching the State. //this little next instruction function could cache its return value and invalidate the cached value by watching the State.
fn next_instruction(&self,time_limit:Time)->Option<TimedInstruction<InternalInstruction,TimeInner>>{ fn next_instruction(&self,time_limit:Time)->Option<TimedInstruction<InternalInstruction,Time>>{
next_instruction_internal(&self.state,&self.data,time_limit) next_instruction_internal(&self.state,&self.data,time_limit)
} }
} }
@ -972,7 +974,7 @@ impl PhysicsContext<'_>{
pub fn run_input_instruction( pub fn run_input_instruction(
state:&mut PhysicsState, state:&mut PhysicsState,
data:&PhysicsData, data:&PhysicsData,
instruction:TimedInstruction<Instruction,TimeInner> instruction:TimedInstruction<Instruction,Time>
){ ){
let mut context=PhysicsContext{state,data}; let mut context=PhysicsContext{state,data};
context.process_exhaustive(instruction.time); context.process_exhaustive(instruction.time);
@ -980,12 +982,37 @@ impl PhysicsContext<'_>{
} }
} }
impl PhysicsData{ impl PhysicsData{
pub fn trace_ray(&self,ray:strafesnet_common::ray::Ray)->Option<ModelId>{
let (_time,convex_mesh_id)=self.bvh.sample_ray(&ray,Time::ZERO,Time::MAX/4,|&model,ray|{
let mesh=self.models.mesh(model);
// brute force trace every face
mesh.faces().filter_map(|face_id|{
let (n,d)=mesh.face_nd(face_id);
// trace ray onto face
// n.(o+d*t)==n.p
// n.o + n.d * t == n.p
// t == (n.p - n.o)/n.d
let nd=n.dot(ray.direction);
if nd.is_zero(){
return None;
}
let t=(d-n.dot(ray.origin))/nd;
// check if point of intersection is behind face edges
// *2 because average of 2 vertices
let p=ray.extrapolate(t)*2;
mesh.face_edges(face_id).iter().all(|&directed_edge_id|{
let edge_n=mesh.directed_edge_n(directed_edge_id);
let cross_n=edge_n.cross(n);
let &[vert0,vert1]=mesh.edge_verts(directed_edge_id.as_undirected()).as_ref();
cross_n.dot(p)<cross_n.dot(mesh.vert(vert0)+mesh.vert(vert1))
}).then(||t)
}).min().map(Into::into)
})?;
Some(convex_mesh_id.model_id.into())
}
/// use with caution, this is the only non-instruction way to mess with physics /// use with caution, this is the only non-instruction way to mess with physics
pub fn generate_models(&mut self,map:&map::CompleteMap){ pub fn generate_models(&mut self,map:&map::CompleteMap){
let mut modes=map.modes.clone(); let mut modes=map.modes.clone().denormalize();
for mode in &mut modes.modes{
mode.denormalize_data();
}
let mut used_contact_attributes=Vec::new(); let mut used_contact_attributes=Vec::new();
let mut used_intersect_attributes=Vec::new(); let mut used_intersect_attributes=Vec::new();
@ -1121,7 +1148,7 @@ impl PhysicsData{
} }
//this is the one who asks //this is the one who asks
fn next_instruction_internal(state:&PhysicsState,data:&PhysicsData,time_limit:Time)->Option<TimedInstruction<InternalInstruction,TimeInner>>{ fn next_instruction_internal(state:&PhysicsState,data:&PhysicsData,time_limit:Time)->Option<TimedInstruction<InternalInstruction,Time>>{
//JUST POLLING!!! NO MUTATION //JUST POLLING!!! NO MUTATION
let mut collector=instruction::InstructionCollector::new(time_limit); let mut collector=instruction::InstructionCollector::new(time_limit);
@ -1136,7 +1163,7 @@ impl PhysicsData{
//relative to moving platforms //relative to moving platforms
//let relative_body=state.body.relative_to(&Body::ZERO); //let relative_body=state.body.relative_to(&Body::ZERO);
let relative_body=&state.body; let relative_body=&state.body;
data.bvh.the_tester(&aabb,&mut |&convex_mesh_id|{ data.bvh.sample_aabb(&aabb,&mut |&convex_mesh_id|{
//no checks are needed because of the time limits. //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()); let minkowski=model_physics::MinkowskiMesh::minkowski_sum(model_mesh,data.hitbox_mesh.transformed_mesh());
@ -1198,7 +1225,7 @@ fn recalculate_touching(
aabb.inflate(hitbox_mesh.halfsize); aabb.inflate(hitbox_mesh.halfsize);
//relative to moving platforms //relative to moving platforms
//let relative_body=state.body.relative_to(&Body::ZERO); //let relative_body=state.body.relative_to(&Body::ZERO);
bvh.the_tester(&aabb,&mut |&convex_mesh_id|{ bvh.sample_aabb(&aabb,&mut |&convex_mesh_id|{
//no checks are needed because of the time limits. //no checks are needed because of the time limits.
let model_mesh=models.mesh(convex_mesh_id); let model_mesh=models.mesh(convex_mesh_id);
let minkowski=model_physics::MinkowskiMesh::minkowski_sum(model_mesh,hitbox_mesh.transformed_mesh()); let minkowski=model_physics::MinkowskiMesh::minkowski_sum(model_mesh,hitbox_mesh.transformed_mesh());
@ -1259,7 +1286,7 @@ fn set_velocity_cull(body:&mut Body,touching:&mut TouchingState,models:&PhysicsM
culled culled
} }
fn set_velocity(body:&mut Body,touching:&TouchingState,models:&PhysicsModels,hitbox_mesh:&HitboxMesh,v:Planar64Vec3){ fn set_velocity(body:&mut Body,touching:&TouchingState,models:&PhysicsModels,hitbox_mesh:&HitboxMesh,v:Planar64Vec3){
body.velocity=touching.constrain_velocity(models,hitbox_mesh,v);; body.velocity=touching.constrain_velocity(models,hitbox_mesh,v);
} }
fn set_acceleration_cull(body:&mut Body,touching:&mut TouchingState,models:&PhysicsModels,hitbox_mesh:&HitboxMesh,a:Planar64Vec3)->bool{ 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 //This is not correct but is better than what I have
@ -1651,7 +1678,7 @@ fn collision_end_intersect(
} }
} }
} }
fn atomic_internal_instruction(state:&mut PhysicsState,data:&PhysicsData,ins:TimedInstruction<InternalInstruction,TimeInner>){ fn atomic_internal_instruction(state:&mut PhysicsState,data:&PhysicsData,ins:TimedInstruction<InternalInstruction,Time>){
state.time=ins.time; state.time=ins.time;
let (should_advance_body,goober_time)=match ins.instruction{ let (should_advance_body,goober_time)=match ins.instruction{
InternalInstruction::CollisionStart(_,dt) InternalInstruction::CollisionStart(_,dt)
@ -1747,7 +1774,7 @@ fn atomic_internal_instruction(state:&mut PhysicsState,data:&PhysicsData,ins:Tim
} }
} }
fn atomic_input_instruction(state:&mut PhysicsState,data:&PhysicsData,ins:TimedInstruction<Instruction,TimeInner>){ fn atomic_input_instruction(state:&mut PhysicsState,data:&PhysicsData,ins:TimedInstruction<Instruction,Time>){
state.time=ins.time; state.time=ins.time;
let should_advance_body=match ins.instruction{ let should_advance_body=match ins.instruction{
//the body may as well be a quantum wave function //the body may as well be a quantum wave function
@ -1814,14 +1841,18 @@ fn atomic_input_instruction(state:&mut PhysicsState,data:&PhysicsData,ins:TimedI
}, },
Instruction::Mode(ModeInstruction::Restart(mode_id))=>{ Instruction::Mode(ModeInstruction::Restart(mode_id))=>{
//teleport to mode start zone //teleport to mode start zone
let mut spawn_point=vec3::ZERO;
let mode=data.modes.get_mode(mode_id); let mode=data.modes.get_mode(mode_id);
let spawn_point=mode.and_then(|mode| if let Some(mode)=mode{
// set style
state.style=mode.get_style().clone();
//TODO: spawn at the bottom of the start zone plus the hitbox size //TODO: spawn at the bottom of the start zone plus the hitbox size
//TODO: set camera andles to face the same way as the start zone //TODO: set camera angles to face the same way as the start zone
data.models.get_model_transform(mode.get_start().into()).map(|transform| if let Some(transform)=data.models.get_model_transform(mode.get_start()){
transform.vertex.translation // NOTE: this value may be 0,0,0 on source maps
) spawn_point=transform.vertex.translation;
).unwrap_or(vec3::ZERO); }
}
set_position(spawn_point,&mut state.move_state,&mut state.body,&mut state.touching,&mut state.run,&mut state.mode_state,mode,&data.models,&data.hitbox_mesh,&data.bvh,&state.style,&state.camera,&state.input_state,state.time); set_position(spawn_point,&mut state.move_state,&mut state.body,&mut state.touching,&mut state.run,&mut state.mode_state,mode,&data.models,&data.hitbox_mesh,&data.bvh,&state.style,&state.camera,&state.input_state,state.time);
set_velocity(&mut state.body,&state.touching,&data.models,&data.hitbox_mesh,vec3::ZERO); set_velocity(&mut state.body,&state.touching,&data.models,&data.hitbox_mesh,vec3::ZERO);
state.set_move_state(data,MoveState::Air); state.set_move_state(data,MoveState::Air);
@ -1831,6 +1862,9 @@ fn atomic_input_instruction(state:&mut PhysicsState,data:&PhysicsData,ins:TimedI
Instruction::Mode(ModeInstruction::Spawn(mode_id,stage_id))=>{ Instruction::Mode(ModeInstruction::Spawn(mode_id,stage_id))=>{
//spawn at a particular stage //spawn at a particular stage
if let Some(mode)=data.modes.get_mode(mode_id){ if let Some(mode)=data.modes.get_mode(mode_id){
// set style
state.style=mode.get_style().clone();
// teleport to stage in mode
if let Some(stage)=mode.get_stage(stage_id){ if let Some(stage)=mode.get_stage(stage_id){
let _=teleport_to_spawn( let _=teleport_to_spawn(
stage.spawn(), stage.spawn(),

@ -1,4 +1,6 @@
use strafesnet_common::integer::{self,vec3::{self,Vector3},Fixed,Planar64,Planar64Vec3,Ratio}; use strafesnet_common::integer::vec3::{self,Vector3};
use strafesnet_common::integer::{Fixed,Planar64Vec3,Ratio};
use strafesnet_common::ray::Ray;
// This algorithm is based on Lua code // This algorithm is based on Lua code
// written by Trey Reynolds in 2021 // written by Trey Reynolds in 2021
@ -12,24 +14,6 @@ type Conts<'a>=arrayvec::ArrayVec<&'a Contact,4>;
// hack to allow comparing ratios to zero // hack to allow comparing ratios to zero
const RATIO_ZERO:Ratio<Fixed<1,32>,Fixed<1,32>>=Ratio::new(Fixed::ZERO,Fixed::EPSILON); const RATIO_ZERO:Ratio<Fixed<1,32>,Fixed<1,32>>=Ratio::new(Fixed::ZERO,Fixed::EPSILON);
struct Ray{
origin:Planar64Vec3,
direction:Planar64Vec3,
}
impl Ray{
fn extrapolate<Num,Den,N1,T1>(&self,t:Ratio<Num,Den>)->Planar64Vec3
where
Num:Copy,
Den:Copy,
Num:core::ops::Mul<Planar64,Output=N1>,
Planar64:core::ops::Mul<Den,Output=N1>,
N1:integer::Divide<Den,Output=T1>,
T1:integer::Fix<Planar64>,
{
self.origin+self.direction.map(|elem|(t*elem).divide().fix())
}
}
/// Information about a contact restriction /// Information about a contact restriction
pub struct Contact{ pub struct Contact{
pub position:Planar64Vec3, pub position:Planar64Vec3,

@ -1,10 +1,10 @@
[package] [package]
name = "strafesnet_session" name = "strafesnet_session"
version = "0.1.0" version = "0.1.0"
edition = "2021" edition = "2024"
[dependencies] [dependencies]
glam = "0.29.0" glam = "0.30.0"
replace_with = "0.1.7" replace_with = "0.1.7"
strafesnet_common = { path = "../../lib/common", registry = "strafesnet" } strafesnet_common = { path = "../../lib/common", registry = "strafesnet" }
strafesnet_physics = { path = "../physics", registry = "strafesnet" } strafesnet_physics = { path = "../physics", registry = "strafesnet" }

@ -5,13 +5,13 @@ use strafesnet_common::physics::{
TimeInner as PhysicsTimeInner, TimeInner as PhysicsTimeInner,
Time as PhysicsTime, Time as PhysicsTime,
}; };
use strafesnet_common::session::{Time as SessionTime,TimeInner as SessionTimeInner}; use strafesnet_common::session::Time as SessionTime;
use strafesnet_common::instruction::{InstructionConsumer,InstructionEmitter,TimedInstruction}; use strafesnet_common::instruction::{InstructionConsumer,InstructionEmitter,TimedInstruction};
type TimedSelfInstruction=TimedInstruction<Instruction,PhysicsTimeInner>; type TimedSelfInstruction=TimedInstruction<Instruction,PhysicsTime>;
type DoubleTimedSelfInstruction=TimedInstruction<TimedSelfInstruction,SessionTimeInner>; type DoubleTimedSelfInstruction=TimedInstruction<TimedSelfInstruction,SessionTime>;
type TimedPhysicsInstruction=TimedInstruction<PhysicsInstruction,PhysicsTimeInner>; type TimedPhysicsInstruction=TimedInstruction<PhysicsInstruction,PhysicsTime>;
const MOUSE_TIMEOUT:SessionTime=SessionTime::from_millis(10); const MOUSE_TIMEOUT:SessionTime=SessionTime::from_millis(10);
@ -89,14 +89,14 @@ pub struct MouseInterpolator{
// Maybe MouseInterpolator manipulation is better expressed using impls // Maybe MouseInterpolator manipulation is better expressed using impls
// and called from Instruction trait impls in session // and called from Instruction trait impls in session
impl InstructionConsumer<TimedSelfInstruction> for MouseInterpolator{ impl InstructionConsumer<TimedSelfInstruction> for MouseInterpolator{
type TimeInner=SessionTimeInner; type Time=SessionTime;
fn process_instruction(&mut self,ins:DoubleTimedSelfInstruction){ fn process_instruction(&mut self,ins:DoubleTimedSelfInstruction){
self.push_unbuffered_input(ins.time,ins.instruction.time,ins.instruction.instruction.into()) self.push_unbuffered_input(ins.time,ins.instruction.time,ins.instruction.instruction.into())
} }
} }
impl InstructionEmitter<StepInstruction> for MouseInterpolator{ impl InstructionEmitter<StepInstruction> for MouseInterpolator{
type TimeInner=SessionTimeInner; type Time=SessionTime;
fn next_instruction(&self,time_limit:SessionTime)->Option<TimedInstruction<StepInstruction,Self::TimeInner>>{ fn next_instruction(&self,time_limit:SessionTime)->Option<TimedInstruction<StepInstruction,Self::Time>>{
self.buffered_instruction_with_timeout(time_limit) self.buffered_instruction_with_timeout(time_limit)
} }
} }
@ -108,7 +108,7 @@ impl MouseInterpolator{
output:std::collections::VecDeque::new(), output:std::collections::VecDeque::new(),
} }
} }
fn push_mouse_and_flush_buffer(&mut self,ins:TimedInstruction<MouseInstruction,PhysicsTimeInner>){ fn push_mouse_and_flush_buffer(&mut self,ins:TimedInstruction<MouseInstruction,PhysicsTime>){
self.buffer.push_front(TimedInstruction{ self.buffer.push_front(TimedInstruction{
time:ins.time, time:ins.time,
instruction:BufferedInstruction::Mouse(ins.instruction).into(), instruction:BufferedInstruction::Mouse(ins.instruction).into(),
@ -219,7 +219,7 @@ impl MouseInterpolator{
} }
} }
} }
fn buffered_instruction_with_timeout(&self,time_limit:SessionTime)->Option<TimedInstruction<StepInstruction,SessionTimeInner>>{ fn buffered_instruction_with_timeout(&self,time_limit:SessionTime)->Option<TimedInstruction<StepInstruction,SessionTime>>{
match self.get_mouse_timedout_at(time_limit){ match self.get_mouse_timedout_at(time_limit){
Some(timeout)=>Some(TimedInstruction{ Some(timeout)=>Some(TimedInstruction{
time:timeout, time:timeout,
@ -232,7 +232,7 @@ impl MouseInterpolator{
}), }),
} }
} }
pub fn pop_buffered_instruction(&mut self,ins:TimedInstruction<StepInstruction,PhysicsTimeInner>)->Option<TimedPhysicsInstruction>{ pub fn pop_buffered_instruction(&mut self,ins:TimedInstruction<StepInstruction,PhysicsTime>)->Option<TimedPhysicsInstruction>{
match ins.instruction{ match ins.instruction{
StepInstruction::Pop=>(), StepInstruction::Pop=>(),
StepInstruction::Timeout=>self.timeout_mouse(ins.time), StepInstruction::Timeout=>self.timeout_mouse(ins.time),
@ -244,6 +244,7 @@ impl MouseInterpolator{
#[cfg(test)] #[cfg(test)]
mod test{ mod test{
use super::*; use super::*;
use strafesnet_common::session::TimeInner as SessionTimeInner;
#[test] #[test]
fn test(){ fn test(){
let mut interpolator=MouseInterpolator::new(); let mut interpolator=MouseInterpolator::new();

@ -88,11 +88,11 @@ impl Simulation{
#[derive(Default)] #[derive(Default)]
pub struct Recording{ pub struct Recording{
instructions:Vec<TimedInstruction<PhysicsInputInstruction,PhysicsTimeInner>>, instructions:Vec<TimedInstruction<PhysicsInputInstruction,PhysicsTime>>,
} }
impl Recording{ impl Recording{
pub fn new( pub fn new(
instructions:Vec<TimedInstruction<PhysicsInputInstruction,PhysicsTimeInner>>, instructions:Vec<TimedInstruction<PhysicsInputInstruction,PhysicsTime>>,
)->Self{ )->Self{
Self{instructions} Self{instructions}
} }
@ -161,6 +161,7 @@ pub struct Session{
recording:Recording, recording:Recording,
//players:HashMap<PlayerId,Simulation>, //players:HashMap<PlayerId,Simulation>,
replays:HashMap<BotId,Replay>, replays:HashMap<BotId,Replay>,
last_ray_hit:Option<strafesnet_common::model::ModelId>,
} }
impl Session{ impl Session{
pub fn new( pub fn new(
@ -177,6 +178,7 @@ impl Session{
view_state:ViewState::Play, view_state:ViewState::Play,
recording:Default::default(), recording:Default::default(),
replays:HashMap::new(), replays:HashMap::new(),
last_ray_hit:None,
} }
} }
fn clear_recording(&mut self){ fn clear_recording(&mut self){
@ -194,6 +196,19 @@ impl Session{
), ),
} }
} }
pub fn debug_raycast_print_model_id_if_changed(&mut self,time:SessionTime){
if let Some(frame_state)=self.get_frame_state(time){
let ray=strafesnet_common::ray::Ray{
origin:frame_state.body.extrapolated_position(self.simulation.timer.time(time)),
direction:-frame_state.camera.rotation().z_axis,
};
let model_id=self.geometry_shared.trace_ray(ray);
if model_id!=self.last_ray_hit{
println!("hit={model_id:?}");
self.last_ray_hit=model_id;
}
}
}
pub fn user_settings(&self)->&UserSettings{ pub fn user_settings(&self)->&UserSettings{
&self.user_settings &self.user_settings
} }
@ -207,8 +222,8 @@ impl Session{
// Session emits DoStep // Session emits DoStep
impl InstructionConsumer<Instruction<'_>> for Session{ impl InstructionConsumer<Instruction<'_>> for Session{
type TimeInner=SessionTimeInner; type Time=SessionTime;
fn process_instruction(&mut self,ins:TimedInstruction<Instruction,Self::TimeInner>){ fn process_instruction(&mut self,ins:TimedInstruction<Instruction,Self::Time>){
// repetitive procedure macro // repetitive procedure macro
macro_rules! run_mouse_interpolator_instruction{ macro_rules! run_mouse_interpolator_instruction{
($instruction:expr)=>{ ($instruction:expr)=>{
@ -425,8 +440,8 @@ impl InstructionConsumer<Instruction<'_>> for Session{
} }
} }
impl InstructionConsumer<StepInstruction> for Session{ impl InstructionConsumer<StepInstruction> for Session{
type TimeInner=SessionTimeInner; type Time=SessionTime;
fn process_instruction(&mut self,ins:TimedInstruction<StepInstruction,Self::TimeInner>){ fn process_instruction(&mut self,ins:TimedInstruction<StepInstruction,Self::Time>){
let time=self.simulation.timer.time(ins.time); let time=self.simulation.timer.time(ins.time);
if let Some(instruction)=self.mouse_interpolator.pop_buffered_instruction(ins.set_time(time)){ if let Some(instruction)=self.mouse_interpolator.pop_buffered_instruction(ins.set_time(time)){
//record //record
@ -436,8 +451,8 @@ impl InstructionConsumer<StepInstruction> for Session{
} }
} }
impl InstructionEmitter<StepInstruction> for Session{ impl InstructionEmitter<StepInstruction> for Session{
type TimeInner=SessionTimeInner; type Time=SessionTime;
fn next_instruction(&self,time_limit:SessionTime)->Option<TimedInstruction<StepInstruction,Self::TimeInner>>{ fn next_instruction(&self,time_limit:SessionTime)->Option<TimedInstruction<StepInstruction,Self::Time>>{
self.mouse_interpolator.next_instruction(time_limit) self.mouse_interpolator.next_instruction(time_limit)
} }
} }

@ -1,10 +1,10 @@
[package] [package]
name = "strafesnet_settings" name = "strafesnet_settings"
version = "0.1.0" version = "0.1.0"
edition = "2021" edition = "2024"
[dependencies] [dependencies]
configparser = "3.0.2" configparser = "3.0.2"
directories = "6.0.0" directories = "6.0.0"
glam = "0.29.0" glam = "0.30.0"
strafesnet_common = { path = "../../lib/common", registry = "strafesnet" } strafesnet_common = { path = "../../lib/common", registry = "strafesnet" }

@ -1,7 +1,7 @@
[package] [package]
name = "integration-testing" name = "integration-testing"
version = "0.1.0" version = "0.1.0"
edition = "2021" edition = "2024"
[dependencies] [dependencies]
strafesnet_common = { path = "../lib/common", registry = "strafesnet" } strafesnet_common = { path = "../lib/common", registry = "strafesnet" }

@ -1,5 +1,7 @@
use std::{io::{Cursor,Read},path::Path}; use std::io::Cursor;
use std::path::Path;
use std::time::Instant;
use strafesnet_physics::physics::{PhysicsData,PhysicsState,PhysicsContext}; use strafesnet_physics::physics::{PhysicsData,PhysicsState,PhysicsContext};
@ -37,9 +39,7 @@ impl From<strafesnet_snf::bot::Error> for ReplayError{
} }
fn read_entire_file(path:impl AsRef<Path>)->Result<Cursor<Vec<u8>>,std::io::Error>{ fn read_entire_file(path:impl AsRef<Path>)->Result<Cursor<Vec<u8>>,std::io::Error>{
let mut file=std::fs::File::open(path)?; let data=std::fs::read(path)?;
let mut data=Vec::new();
file.read_to_end(&mut data)?;
Ok(Cursor::new(data)) Ok(Cursor::new(data))
} }
@ -72,7 +72,12 @@ enum DeterminismResult{
Deterministic, Deterministic,
NonDeterministic, NonDeterministic,
} }
fn segment_determinism(bot:strafesnet_snf::bot::Segment,physics_data:&PhysicsData)->DeterminismResult{ struct SegmentResult{
determinism:DeterminismResult,
ticks:u64,
nanoseconds:u64,
}
fn segment_determinism(bot:strafesnet_snf::bot::Segment,physics_data:&PhysicsData)->SegmentResult{
// create default physics state // create default physics state
let mut physics_deterministic=PhysicsState::default(); let mut physics_deterministic=PhysicsState::default();
// create a second physics state // create a second physics state
@ -82,6 +87,9 @@ fn segment_determinism(bot:strafesnet_snf::bot::Segment,physics_data:&PhysicsDat
println!("simulating..."); println!("simulating...");
let mut non_idle_count=0; let mut non_idle_count=0;
let instruction_count=bot.instructions.len();
let start=Instant::now();
for (i,ins) in bot.instructions.into_iter().enumerate(){ for (i,ins) in bot.instructions.into_iter().enumerate(){
let state_deterministic=physics_deterministic.clone(); let state_deterministic=physics_deterministic.clone();
@ -97,6 +105,7 @@ fn segment_determinism(bot:strafesnet_snf::bot::Segment,physics_data:&PhysicsDat
let b0=physics_deterministic.camera_body(); let b0=physics_deterministic.camera_body();
let b1=physics_filtered.camera_body(); let b1=physics_filtered.camera_body();
if b0.position!=b1.position{ if b0.position!=b1.position{
let nanoseconds=start.elapsed().as_nanos() as u64;
println!("desync at instruction #{}",i); println!("desync at instruction #{}",i);
println!("non idle instructions completed={non_idle_count}"); println!("non idle instructions completed={non_idle_count}");
println!("instruction #{i}={:?}",other); println!("instruction #{i}={:?}",other);
@ -104,11 +113,16 @@ fn segment_determinism(bot:strafesnet_snf::bot::Segment,physics_data:&PhysicsDat
println!("filtered state0:\n{state_filtered:?}"); println!("filtered state0:\n{state_filtered:?}");
println!("deterministic state1:\n{:?}",physics_deterministic); println!("deterministic state1:\n{:?}",physics_deterministic);
println!("filtered state1:\n{:?}",physics_filtered); println!("filtered state1:\n{:?}",physics_filtered);
return DeterminismResult::NonDeterministic; return SegmentResult{
determinism:DeterminismResult::NonDeterministic,
ticks:1+i as u64+non_idle_count,
nanoseconds,
};
} }
}, },
} }
} }
let nanoseconds=start.elapsed().as_nanos() as u64;
match physics_deterministic.get_finish_time(){ match physics_deterministic.get_finish_time(){
Some(time)=>println!("[with idle] finish time:{}",time), Some(time)=>println!("[with idle] finish time:{}",time),
None=>println!("[with idle] simulation did not end in finished state"), None=>println!("[with idle] simulation did not end in finished state"),
@ -117,9 +131,14 @@ fn segment_determinism(bot:strafesnet_snf::bot::Segment,physics_data:&PhysicsDat
Some(time)=>println!("[filtered] finish time:{}",time), Some(time)=>println!("[filtered] finish time:{}",time),
None=>println!("[filtered] simulation did not end in finished state"), None=>println!("[filtered] simulation did not end in finished state"),
} }
DeterminismResult::Deterministic SegmentResult{
determinism:DeterminismResult::Deterministic,
ticks:instruction_count as u64+non_idle_count,
nanoseconds,
}
} }
type ThreadResult=Result<Option<DeterminismResult>,ReplayError>; type ThreadResult=Result<Option<SegmentResult>,ReplayError>;
fn read_and_run(file_path:std::path::PathBuf,physics_data:&PhysicsData)->ThreadResult{ fn read_and_run(file_path:std::path::PathBuf,physics_data:&PhysicsData)->ThreadResult{
let data=read_entire_file(file_path.as_path())?; let data=read_entire_file(file_path.as_path())?;
let bot=strafesnet_snf::read_bot(data)?.read_all()?; let bot=strafesnet_snf::read_bot(data)?.read_all()?;
@ -198,10 +217,18 @@ fn test_determinism()->Result<(),ReplayError>{
invalid:u32, invalid:u32,
error:u32, error:u32,
} }
let mut nanoseconds=0;
let mut ticks=0;
let Totals{deterministic,nondeterministic,invalid,error}=thread_results.into_iter().fold(Totals::default(),|mut totals,result|{ let Totals{deterministic,nondeterministic,invalid,error}=thread_results.into_iter().fold(Totals::default(),|mut totals,result|{
match result{ match result{
Ok(Some(DeterminismResult::Deterministic))=>totals.deterministic+=1, Ok(Some(segment_result))=>{
Ok(Some(DeterminismResult::NonDeterministic))=>totals.nondeterministic+=1, ticks+=segment_result.ticks;
nanoseconds+=segment_result.nanoseconds;
match segment_result.determinism{
DeterminismResult::Deterministic=>totals.deterministic+=1,
DeterminismResult::NonDeterministic=>totals.nondeterministic+=1,
}
},
Ok(None)=>totals.invalid+=1, Ok(None)=>totals.invalid+=1,
Err(_)=>totals.error+=1, Err(_)=>totals.error+=1,
} }
@ -212,6 +239,7 @@ fn test_determinism()->Result<(),ReplayError>{
println!("nondeterministic={nondeterministic}"); println!("nondeterministic={nondeterministic}");
println!("invalid={invalid}"); println!("invalid={invalid}");
println!("error={error}"); println!("error={error}");
println!("average ticks/s per core: {}",ticks*1_000_000_000/nanoseconds);
assert!(nondeterministic==0); assert!(nondeterministic==0);
assert!(invalid==0); assert!(invalid==0);

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

@ -75,13 +75,9 @@ fn planes_to_faces(face_list:std::collections::HashSet<Face>)->Result<Faces,Plan
let mut intersection=solve3(face0,face1,face2).ok_or(PlanesToFacesError::InitIntersection)?; let mut intersection=solve3(face0,face1,face2).ok_or(PlanesToFacesError::InitIntersection)?;
// repeatedly update face0, face1 until all faces form part of the convex solid // repeatedly update face1, face2 until all faces form part of the convex solid
'find: loop{ 'find: loop{
if let Some(a)=detect_loop.checked_sub(1){ detect_loop=detect_loop.checked_sub(1).ok_or(PlanesToFacesError::InfiniteLoop1)?;
detect_loop=a;
}else{
return Err(PlanesToFacesError::InfiniteLoop1);
}
// test if any *other* faces occlude the intersection // test if any *other* faces occlude the intersection
for new_face in &face_list{ for new_face in &face_list{
// new face occludes intersection point // new face occludes intersection point
@ -122,12 +118,10 @@ fn planes_to_faces(face_list:std::collections::HashSet<Face>)->Result<Faces,Plan
if core::ptr::eq(face2,new_face){ if core::ptr::eq(face2,new_face){
continue; continue;
} }
if let Some(new_intersection)=solve3(new_face,face1,face2){ // new_face occludes intersection meaning intersection is not on convex solid and face0 is degenrate
// face0 does not occlude (or intersect) the new intersection if (new_face.dot.fix_2()/Planar64::ONE).lt_ratio(new_face.normal.dot(intersection.num)/intersection.den){
if (face0.dot.fix_2()/Planar64::ONE).lt_ratio(face0.normal.dot(new_intersection.num)/new_intersection.den){ // abort! reject face0 entirely
// abort! reject face0 entirely continue 'face;
continue 'face;
}
} }
} }
@ -177,11 +171,7 @@ fn planes_to_faces(face_list:std::collections::HashSet<Face>)->Result<Faces,Plan
face2=new_face; face2=new_face;
intersection=new_intersection; intersection=new_intersection;
if let Some(a)=detect_loop.checked_sub(1){ detect_loop=detect_loop.checked_sub(1).ok_or(PlanesToFacesError::InfiniteLoop2)?;
detect_loop=a;
}else{
return Err(PlanesToFacesError::InfiniteLoop2);
}
} }
faces.push(face); faces.push(face);
@ -196,6 +186,7 @@ fn planes_to_faces(face_list:std::collections::HashSet<Face>)->Result<Faces,Plan
} }
} }
#[allow(dead_code)]
#[derive(Debug)] #[derive(Debug)]
pub enum BrushToMeshError{ pub enum BrushToMeshError{
SliceBrushSides, SliceBrushSides,
@ -212,24 +203,7 @@ impl std::fmt::Display for BrushToMeshError{
} }
impl core::error::Error for BrushToMeshError{} impl core::error::Error for BrushToMeshError{}
pub fn brush_to_mesh(bsp:&vbsp::Bsp,brush:&vbsp::Brush)->Result<model::Mesh,BrushToMeshError>{ pub fn faces_to_mesh(faces:Vec<Vec<integer::Planar64Vec3>>)->model::Mesh{
let brush_start_idx=brush.brush_side as usize;
let sides_range=brush_start_idx..brush_start_idx+brush.num_brush_sides as usize;
let sides=bsp.brush_sides.get(sides_range).ok_or(BrushToMeshError::SliceBrushSides)?;
let face_list=sides.iter().map(|side|{
let plane=bsp.plane(side.plane as usize)?;
Some(Face{
normal:valve_transform_normal(plane.normal.into()),
dot:valve_transform_dist(plane.dist.into()),
})
}).collect::<Option<std::collections::HashSet<_>>>().ok_or(BrushToMeshError::MissingPlane)?;
if face_list.len()<4{
return Err(BrushToMeshError::InvalidFaceCount{count:face_list.len()});
}
let faces=planes_to_faces(face_list).map_err(BrushToMeshError::InvalidPlanes)?;
// generate the mesh // generate the mesh
let mut mb=model::MeshBuilder::new(); let mut mb=model::MeshBuilder::new();
let color=mb.acquire_color_id(glam::Vec4::ONE); let color=mb.acquire_color_id(glam::Vec4::ONE);
@ -237,7 +211,7 @@ pub fn brush_to_mesh(bsp:&vbsp::Bsp,brush:&vbsp::Brush)->Result<model::Mesh,Brus
// normals are ignored by physics // normals are ignored by physics
let normal=mb.acquire_normal_id(integer::vec3::ZERO); let normal=mb.acquire_normal_id(integer::vec3::ZERO);
let polygon_list=faces.faces.into_iter().map(|face|{ let polygon_list=faces.into_iter().map(|face|{
face.into_iter().map(|pos|{ face.into_iter().map(|pos|{
let pos=mb.acquire_pos_id(pos); let pos=mb.acquire_pos_id(pos);
mb.acquire_vertex_id(model::IndexedVertex{ mb.acquire_vertex_id(model::IndexedVertex{
@ -253,12 +227,54 @@ pub fn brush_to_mesh(bsp:&vbsp::Bsp,brush:&vbsp::Brush)->Result<model::Mesh,Brus
let physics_groups=vec![model::IndexedPhysicsGroup{ let physics_groups=vec![model::IndexedPhysicsGroup{
groups:vec![model::PolygonGroupId::new(0)], groups:vec![model::PolygonGroupId::new(0)],
}]; }];
let graphics_groups=vec![model::IndexedGraphicsGroup{ let graphics_groups=vec![];
render:model::RenderConfigId::new(0),
groups:vec![model::PolygonGroupId::new(0)],
}];
Ok(mb.build(polygon_groups,graphics_groups,physics_groups)) mb.build(polygon_groups,graphics_groups,physics_groups)
}
pub fn brush_to_mesh(bsp:&vbsp::Bsp,brush:&vbsp::Brush)->Result<model::Mesh,BrushToMeshError>{
let brush_start_idx=brush.brush_side as usize;
let sides_range=brush_start_idx..brush_start_idx+brush.num_brush_sides as usize;
let sides=bsp.brush_sides.get(sides_range).ok_or(BrushToMeshError::SliceBrushSides)?;
let face_list=sides.iter().map(|side|{
// The so-called tumor brushes have TRIGGER bit set
// but also ignore visleaf hint and skip sides
const TUMOR:vbsp::TextureFlags=vbsp::TextureFlags::HINT.union(vbsp::TextureFlags::SKIP).union(vbsp::TextureFlags::TRIGGER);
if let Some(texture_info)=bsp.texture_info(side.texture_info as usize){
if texture_info.flags.intersects(TUMOR){
return None;
}
}
let plane=bsp.plane(side.plane as usize)?;
Some(Face{
normal:valve_transform_normal(plane.normal.into()),
dot:valve_transform_dist(plane.dist.into()),
})
}).collect::<Option<std::collections::HashSet<_>>>().ok_or(BrushToMeshError::MissingPlane)?;
if face_list.len()<4{
return Err(BrushToMeshError::InvalidFaceCount{count:face_list.len()});
}
let faces=planes_to_faces(face_list).map_err(BrushToMeshError::InvalidPlanes)?;
let mesh=faces_to_mesh(faces.faces);
Ok(mesh)
}
pub fn unit_cube()->model::Mesh{
let face_list=[
Face{normal:integer::vec3::X,dot:Planar64::ONE},
Face{normal:integer::vec3::Y,dot:Planar64::ONE},
Face{normal:integer::vec3::Z,dot:Planar64::ONE},
Face{normal:integer::vec3::NEG_X,dot:Planar64::ONE},
Face{normal:integer::vec3::NEG_Y,dot:Planar64::ONE},
Face{normal:integer::vec3::NEG_Z,dot:Planar64::ONE},
].into_iter().collect();
let faces=planes_to_faces(face_list).unwrap();
let mesh=faces_to_mesh(faces.faces);
mesh
} }
#[cfg(test)] #[cfg(test)]
@ -275,6 +291,7 @@ mod test{
Face{normal:integer::vec3::NEG_Z,dot:Planar64::ONE}, Face{normal:integer::vec3::NEG_Z,dot:Planar64::ONE},
].into_iter().collect(); ].into_iter().collect();
let faces=planes_to_faces(face_list).unwrap(); let faces=planes_to_faces(face_list).unwrap();
assert_eq!(faces.faces.len(),6);
dbg!(faces); dbg!(faces);
} }
#[test] #[test]
@ -289,6 +306,37 @@ mod test{
Face{normal:integer::vec3::NEG_Z,dot:Planar64::EPSILON}, Face{normal:integer::vec3::NEG_Z,dot:Planar64::EPSILON},
].into_iter().collect(); ].into_iter().collect();
let faces=planes_to_faces(face_list).unwrap(); let faces=planes_to_faces(face_list).unwrap();
assert_eq!(faces.faces.len(),6);
dbg!(faces);
}
#[test]
fn test_cube_with_degernate_face2(){
let face_list=[
Face{normal:integer::vec3::X,dot:Planar64::ONE},
Face{normal:integer::vec3::Y,dot:Planar64::ONE},
Face{normal:integer::vec3::Z,dot:Planar64::ONE},
Face{normal:integer::vec3::NEG_X,dot:Planar64::ONE},
Face{normal:integer::vec3::NEG_Y,dot:Planar64::ONE},
Face{normal:integer::vec3::NEG_Z,dot:Planar64::ONE},
Face{normal:integer::vec3::NEG_X+integer::vec3::NEG_Z,dot:-Planar64::EPSILON},
].into_iter().collect();
let faces=planes_to_faces(face_list).unwrap();
assert_eq!(faces.faces.len(),5);
dbg!(faces);
}
#[test]
fn test_cube_with_degernate_face3(){
let face_list=[
Face{normal:integer::vec3::X,dot:Planar64::ONE},
Face{normal:integer::vec3::Y,dot:Planar64::ONE},
Face{normal:integer::vec3::Z,dot:Planar64::ONE},
Face{normal:integer::vec3::NEG_X,dot:Planar64::ONE},
Face{normal:integer::vec3::NEG_Y,dot:Planar64::ONE},
Face{normal:integer::vec3::NEG_Z,dot:Planar64::ONE},
Face{normal:integer::vec3::NEG_X+integer::vec3::NEG_Z,dot:Planar64::EPSILON},
].into_iter().collect();
let faces=planes_to_faces(face_list).unwrap();
assert_eq!(faces.faces.len(),7);
dbg!(faces); dbg!(faces);
} }
} }

@ -1,9 +1,12 @@
use std::borrow::Cow; use std::borrow::Cow;
use strafesnet_common::{map,model,integer,gameplay_attributes}; use vbsp_entities_css::Entity;
use strafesnet_common::{map,model,integer,gameplay_attributes as attr};
use strafesnet_deferred_loader::deferred_loader::{MeshDeferredLoader,RenderConfigDeferredLoader}; use strafesnet_deferred_loader::deferred_loader::{MeshDeferredLoader,RenderConfigDeferredLoader};
use strafesnet_deferred_loader::mesh::Meshes; use strafesnet_deferred_loader::mesh::Meshes;
use strafesnet_deferred_loader::texture::{RenderConfigs,Texture}; use strafesnet_deferred_loader::texture::{RenderConfigs,Texture};
use strafesnet_common::gameplay_modes::{NormalizedMode,NormalizedModes,Mode,Stage};
use crate::valve_transform; use crate::valve_transform;
@ -32,6 +35,47 @@ fn ingest_vertex(
}) })
} }
fn add_brush<'a>(
mesh_deferred_loader:&mut MeshDeferredLoader<&'a str>,
world_models:&mut Vec<model::Model>,
prop_models:&mut Vec<model::Model>,
model:&'a str,
origin:vbsp::Vector,
rendercolor:vbsp::Color,
attributes:attr::CollisionAttributesId,
){
let transform=integer::Planar64Affine3::from_translation(
valve_transform(origin.into())
);
let color=(glam::Vec3::from_array([
rendercolor.r as f32,
rendercolor.g as f32,
rendercolor.b as f32
])/255.0).extend(1.0);
match model.split_at(1){
// The first character of brush.model is '*'
("*",id_str)=>match id_str.parse(){
Ok(mesh_id)=>{
let mesh=model::MeshId::new(mesh_id);
world_models.push(
model::Model{mesh,attributes,transform,color}
);
},
Err(e)=>{
println!("Brush model int parse error: {e} model={model}");
return;
},
},
_=>{
let mesh=mesh_deferred_loader.acquire_mesh_id(model);
prop_models.push(
model::Model{mesh,attributes,transform,color}
);
}
}
}
pub fn convert<'a>( pub fn convert<'a>(
bsp:&'a crate::Bsp, bsp:&'a crate::Bsp,
render_config_deferred_loader:&mut RenderConfigDeferredLoader<Cow<'a,str>>, render_config_deferred_loader:&mut RenderConfigDeferredLoader<Cow<'a,str>>,
@ -39,32 +83,64 @@ pub fn convert<'a>(
)->PartialMap1{ )->PartialMap1{
let bsp=bsp.as_ref(); let bsp=bsp.as_ref();
//figure out real attributes later //figure out real attributes later
let mut unique_attributes=Vec::new(); let unique_attributes=vec![
unique_attributes.push(gameplay_attributes::CollisionAttributes::Decoration); attr::CollisionAttributes::Decoration,
unique_attributes.push(gameplay_attributes::CollisionAttributes::contact_default()); attr::CollisionAttributes::contact_default(),
const ATTRIBUTE_DECORATION:gameplay_attributes::CollisionAttributesId=gameplay_attributes::CollisionAttributesId::new(0); attr::CollisionAttributes::intersect_default(),
const ATTRIBUTE_CONTACT_DEFAULT:gameplay_attributes::CollisionAttributesId=gameplay_attributes::CollisionAttributesId::new(1); // ladder
attr::CollisionAttributes::Contact(
attr::ContactAttributes{
contacting:attr::ContactingAttributes{
contact_behaviour:Some(attr::ContactingBehaviour::Ladder(
attr::ContactingLadder{sticky:true}
))
},
general:attr::GeneralAttributes::default(),
}
),
// water
attr::CollisionAttributes::Intersect(
attr::IntersectAttributes{
intersecting:attr::IntersectingAttributes{
water:Some(attr::IntersectingWater{
viscosity:integer::Planar64::ONE,
density:integer::Planar64::ONE,
velocity:integer::vec3::ZERO,
}),
},
general:attr::GeneralAttributes::default(),
}
),
];
const ATTRIBUTE_DECORATION:attr::CollisionAttributesId=attr::CollisionAttributesId::new(0);
const ATTRIBUTE_CONTACT_DEFAULT:attr::CollisionAttributesId=attr::CollisionAttributesId::new(1);
const ATTRIBUTE_INTERSECT_DEFAULT:attr::CollisionAttributesId=attr::CollisionAttributesId::new(2);
const ATTRIBUTE_LADDER_DEFAULT:attr::CollisionAttributesId=attr::CollisionAttributesId::new(3);
const ATTRIBUTE_WATER_DEFAULT:attr::CollisionAttributesId=attr::CollisionAttributesId::new(4);
//declare all prop models to Loader //declare all prop models to Loader
let prop_models=Vec::new(); let mut prop_models=bsp.static_props().map(|prop|{
// let prop_models=bsp.static_props().map(|prop|{ const DEG_TO_RAD:f32=std::f32::consts::TAU/360.0;
// //get or create mesh_id //get or create mesh_id
// let mesh_id=mesh_deferred_loader.acquire_mesh_id(prop.model()); let mesh_id=mesh_deferred_loader.acquire_mesh_id(prop.model());
// let placement=prop.as_prop_placement(); model::Model{
// model::Model{ mesh:mesh_id,
// mesh:mesh_id, attributes:ATTRIBUTE_DECORATION,
// attributes:ATTRIBUTE_DECORATION, transform:integer::Planar64Affine3::new(
// transform:integer::Planar64Affine3::new( integer::mat3::try_from_f32_array_2d(
// integer::mat3::try_from_f32_array_2d(( //TODO: figure this out
// glam::Mat3A::from_diagonal(glam::Vec3::splat(placement.scale)) glam::Mat3A::from_euler(
// //TODO: figure this out glam::EulerRot::XYZ,
// *glam::Mat3A::from_quat(glam::Quat::from_array(placement.rotation.into())) prop.angles.pitch*DEG_TO_RAD,
// ).to_cols_array_2d()).unwrap(), prop.angles.yaw*DEG_TO_RAD,
// valve_transform(placement.origin.into()), prop.angles.roll*DEG_TO_RAD
// ), ).to_cols_array_2d()
// color:glam::Vec4::ONE, ).unwrap(),
// } valve_transform(prop.origin.into()),
// }).collect(); ),
color:glam::Vec4::ONE,
}
}).collect();
//TODO: make the main map one single mesh with a bunch of different physics groups and graphics groups //TODO: make the main map one single mesh with a bunch of different physics groups and graphics groups
@ -75,7 +151,7 @@ pub fn convert<'a>(
let color=mb.acquire_color_id(glam::Vec4::ONE); let color=mb.acquire_color_id(glam::Vec4::ONE);
let mut graphics_groups=Vec::new(); let mut graphics_groups=Vec::new();
//let mut render_id_to_graphics_group_id=std::collections::HashMap::new(); let mut render_id_to_graphics_group_id=std::collections::HashMap::new();
let polygon_groups=world_model.faces().enumerate().map(|(polygon_group_id,face)|{ let polygon_groups=world_model.faces().enumerate().map(|(polygon_group_id,face)|{
let polygon_group_id=model::PolygonGroupId::new(polygon_group_id as u32); let polygon_group_id=model::PolygonGroupId::new(polygon_group_id as u32);
let face_texture=face.texture(); let face_texture=face.texture();
@ -105,15 +181,15 @@ pub fn convert<'a>(
//a render config for it, and then returns the id to that render config //a render config for it, and then returns the id to that render config
let render_id=render_config_deferred_loader.acquire_render_config_id(Some(Cow::Borrowed(face_texture_data.name()))); let render_id=render_config_deferred_loader.acquire_render_config_id(Some(Cow::Borrowed(face_texture_data.name())));
//deduplicate graphics groups by render id //deduplicate graphics groups by render id
// let graphics_group_id=*render_id_to_graphics_group_id.entry(render_id).or_insert_with(||{ let graphics_group_id=*render_id_to_graphics_group_id.entry(render_id).or_insert_with(||{
// let graphics_group_id=graphics_groups.len(); let graphics_group_id=graphics_groups.len();
// graphics_groups.push(model::IndexedGraphicsGroup{ graphics_groups.push(model::IndexedGraphicsGroup{
// render:render_id, render:render_id,
// groups:vec![], groups:vec![],
// }); });
// graphics_group_id graphics_group_id
// }); });
// graphics_groups[graphics_group_id].groups.push(polygon_group_id); graphics_groups[graphics_group_id].groups.push(polygon_group_id);
} }
model::PolygonGroup::PolygonList(model::PolygonList::new(polygon_list)) model::PolygonGroup::PolygonList(model::PolygonList::new(polygon_list))
}).collect(); }).collect();
@ -121,79 +197,154 @@ pub fn convert<'a>(
mb.build(polygon_groups,graphics_groups,vec![]) mb.build(polygon_groups,graphics_groups,vec![])
}).collect(); }).collect();
let brush_mesh_start_idx=world_meshes.len(); let mut found_spawn=None;
let mut world_models=Vec::new();
// the one and only world model 0
world_models.push(model::Model{
mesh:model::MeshId::new(0),
attributes:ATTRIBUTE_DECORATION,
transform:integer::Planar64Affine3::IDENTITY,
color:glam::Vec4::W,
});
const WHITE:vbsp::Color=vbsp::Color{r:255,g:255,b:255};
for raw_ent in &bsp.entities{
match raw_ent.parse(){
Ok(Entity::Cycler(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,brush.rendercolor,ATTRIBUTE_DECORATION),
Ok(Entity::EnvSprite(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,brush.rendercolor,ATTRIBUTE_DECORATION),
Ok(Entity::FuncBreakable(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,brush.rendercolor,ATTRIBUTE_DECORATION),
Ok(Entity::FuncBrush(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,brush.rendercolor,ATTRIBUTE_DECORATION),
Ok(Entity::FuncButton(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,brush.rendercolor,ATTRIBUTE_DECORATION),
Ok(Entity::FuncDoor(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,brush.rendercolor,ATTRIBUTE_DECORATION),
Ok(Entity::FuncDoorRotating(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,brush.rendercolor,ATTRIBUTE_DECORATION),
Ok(Entity::FuncIllusionary(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,brush.rendercolor,ATTRIBUTE_DECORATION),
Ok(Entity::FuncMonitor(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,brush.rendercolor,ATTRIBUTE_DECORATION),
Ok(Entity::FuncMovelinear(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,brush.rendercolor,ATTRIBUTE_DECORATION),
Ok(Entity::FuncPhysbox(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,brush.rendercolor,ATTRIBUTE_DECORATION),
Ok(Entity::FuncPhysboxMultiplayer(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,brush.rendercolor,ATTRIBUTE_DECORATION),
Ok(Entity::FuncRotButton(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,WHITE,ATTRIBUTE_DECORATION),
Ok(Entity::FuncRotating(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,brush.rendercolor,ATTRIBUTE_DECORATION),
Ok(Entity::FuncTracktrain(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,brush.rendercolor,ATTRIBUTE_DECORATION),
Ok(Entity::FuncTrain(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,brush.rendercolor,ATTRIBUTE_DECORATION),
Ok(Entity::FuncWall(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin.unwrap_or_default(),brush.rendercolor,ATTRIBUTE_DECORATION),
Ok(Entity::FuncWallToggle(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin.unwrap_or_default(),brush.rendercolor,ATTRIBUTE_DECORATION),
Ok(Entity::FuncWaterAnalog(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,brush.rendercolor.unwrap_or(WHITE),ATTRIBUTE_DECORATION),
Ok(Entity::PropDoorRotating(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,WHITE,ATTRIBUTE_DECORATION),
Ok(Entity::PropDynamic(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,WHITE,ATTRIBUTE_DECORATION),
Ok(Entity::PropDynamicOverride(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,WHITE,ATTRIBUTE_DECORATION),
Ok(Entity::PropPhysics(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,WHITE,ATTRIBUTE_DECORATION),
Ok(Entity::PropPhysicsMultiplayer(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,WHITE,ATTRIBUTE_DECORATION),
Ok(Entity::PropPhysicsOverride(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,WHITE,ATTRIBUTE_DECORATION),
Ok(Entity::PropRagdoll(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,WHITE,ATTRIBUTE_DECORATION),
Ok(Entity::TriggerGravity(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,WHITE,ATTRIBUTE_DECORATION),
Ok(Entity::TriggerHurt(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,WHITE,ATTRIBUTE_DECORATION),
Ok(Entity::TriggerLook(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,WHITE,ATTRIBUTE_DECORATION),
Ok(Entity::TriggerMultiple(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model.unwrap_or_default(),brush.origin,WHITE,ATTRIBUTE_DECORATION),
Ok(Entity::TriggerOnce(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,WHITE,ATTRIBUTE_DECORATION),
Ok(Entity::TriggerProximity(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,WHITE,ATTRIBUTE_DECORATION),
Ok(Entity::TriggerPush(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,WHITE,ATTRIBUTE_DECORATION),
Ok(Entity::TriggerSoundscape(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,WHITE,ATTRIBUTE_DECORATION),
Ok(Entity::TriggerTeleport(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model.unwrap_or_default(),brush.origin,WHITE,ATTRIBUTE_DECORATION),
Ok(Entity::TriggerVphysicsMotion(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,WHITE,ATTRIBUTE_DECORATION),
Ok(Entity::TriggerWind(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,WHITE,ATTRIBUTE_DECORATION),
Ok(Entity::InfoPlayerCounterterrorist(spawn))=>{
found_spawn=Some(valve_transform(spawn.origin.into()));
},
Ok(Entity::InfoPlayerTerrorist(spawn))=>{
found_spawn=Some(valve_transform(spawn.origin.into()));
},
Err(e)=>{
println!("Bsp Entity parse error: {e}");
},
_=>(),
}
}
// physics models
for brush in &bsp.brushes{ for brush in &bsp.brushes{
const RELEVANT:vbsp::BrushFlags=
vbsp::BrushFlags::SOLID
.union(vbsp::BrushFlags::PLAYERCLIP)
.union(vbsp::BrushFlags::WATER)
.union(vbsp::BrushFlags::MOVEABLE)
.union(vbsp::BrushFlags::LADDER);
if !brush.flags.intersects(RELEVANT){
continue;
}
let is_ladder=brush.flags.contains(vbsp::BrushFlags::LADDER);
let is_water=brush.flags.contains(vbsp::BrushFlags::WATER);
let attributes=match (is_ladder,is_water){
(true,false)=>ATTRIBUTE_LADDER_DEFAULT,
(false,true)=>ATTRIBUTE_WATER_DEFAULT,
(false,false)=>ATTRIBUTE_CONTACT_DEFAULT,
(true,true)=>{
// water ladder? wtf
println!("brush is a water ladder o_o defaulting to ladder");
ATTRIBUTE_LADDER_DEFAULT
}
};
let mesh_result=crate::brush::brush_to_mesh(bsp,brush); let mesh_result=crate::brush::brush_to_mesh(bsp,brush);
match mesh_result{ match mesh_result{
Ok(mesh)=>world_meshes.push(mesh), Ok(mesh)=>{
let mesh_id=model::MeshId::new(world_meshes.len() as u32);
world_meshes.push(mesh);
world_models.push(model::Model{
mesh:mesh_id,
attributes,
transform:integer::Planar64Affine3::new(
integer::mat3::identity(),
integer::vec3::ZERO,
),
color:glam::Vec4::ONE,
});
},
Err(e)=>println!("Brush mesh error: {e}"), Err(e)=>println!("Brush mesh error: {e}"),
} }
} }
println!("How many brush: {}",bsp.brushes.len());
println!("Generated brush models: {}",world_meshes.len()-brush_mesh_start_idx);
let world_models:Vec<model::Model>= let mut modes_list=Vec::new();
//one instance of the main world mesh if let Some(spawn_point)=found_spawn{
std::iter::once(( // create a new mesh
//world_model let mesh_id=model::MeshId::new(world_meshes.len() as u32);
model::MeshId::new(0), world_meshes.push(crate::brush::unit_cube());
//model_origin // create a new model
vbsp::Vector::from([0.0,0.0,0.0]), let model_id=model::ModelId::new(world_models.len() as u32);
//model_color world_models.push(model::Model{
vbsp::Color{r:255,g:255,b:255}, mesh:mesh_id,
)).chain( attributes:ATTRIBUTE_INTERSECT_DEFAULT,
//entities sprinkle instances of the other meshes around transform:integer::Planar64Affine3::from_translation(spawn_point),
bsp.entities.iter() color:glam::Vec4::W,
.flat_map(|ent|ent.parse())//ignore entity parsing errors });
.filter_map(|ent|match ent{
vbsp::Entity::Brush(brush)=>Some(brush), let first_stage=Stage::empty(model_id);
vbsp::Entity::BrushIllusionary(brush)=>Some(brush), let main_mode=Mode::new(
vbsp::Entity::BrushWall(brush)=>Some(brush), strafesnet_common::gameplay_style::StyleModifiers::source_bhop(),
vbsp::Entity::BrushWallToggle(brush)=>Some(brush), model_id,
_=>None, std::collections::HashMap::new(),
}).flat_map(|brush| vec![first_stage],
//The first character of brush.model is '*' std::collections::HashMap::new(),
brush.model[1..].parse().map(|mesh_id|//ignore parse int errors );
(model::MeshId::new(mesh_id),brush.origin,brush.color) modes_list.push(NormalizedMode::new(main_mode));
) }
)
).map(|(mesh_id,model_origin,vbsp::Color{r,g,b})|model::Model{
mesh:mesh_id,
attributes:ATTRIBUTE_DECORATION,
transform:integer::Planar64Affine3::new(
integer::mat3::identity(),
valve_transform(model_origin.into())
),
color:(glam::Vec3::from_array([r as f32,g as f32,b as f32])/255.0).extend(1.0),
}).chain(
// physics models
(brush_mesh_start_idx..world_meshes.len()).map(|mesh_id|model::Model{
mesh:model::MeshId::new(mesh_id as u32),
attributes:ATTRIBUTE_CONTACT_DEFAULT,
transform:integer::Planar64Affine3::new(
integer::mat3::identity(),
integer::vec3::ZERO,
),
color:glam::Vec4::ONE,
})
).collect();
PartialMap1{ PartialMap1{
attributes:unique_attributes, attributes:unique_attributes,
world_meshes, world_meshes,
prop_models, prop_models,
world_models, world_models,
modes:strafesnet_common::gameplay_modes::Modes::new(Vec::new()), modes:NormalizedModes::new(modes_list),
} }
} }
//partially constructed map types //partially constructed map types
pub struct PartialMap1{ pub struct PartialMap1{
attributes:Vec<strafesnet_common::gameplay_attributes::CollisionAttributes>, attributes:Vec<attr::CollisionAttributes>,
prop_models:Vec<model::Model>, prop_models:Vec<model::Model>,
world_meshes:Vec<model::Mesh>, world_meshes:Vec<model::Mesh>,
world_models:Vec<model::Model>, world_models:Vec<model::Model>,
modes:strafesnet_common::gameplay_modes::Modes, modes:NormalizedModes,
} }
impl PartialMap1{ impl PartialMap1{
pub fn add_prop_meshes<'a>( pub fn add_prop_meshes<'a>(
@ -211,12 +362,12 @@ impl PartialMap1{
} }
} }
pub struct PartialMap2{ pub struct PartialMap2{
attributes:Vec<strafesnet_common::gameplay_attributes::CollisionAttributes>, attributes:Vec<attr::CollisionAttributes>,
prop_meshes:Vec<(model::MeshId,model::Mesh)>, prop_meshes:Vec<(model::MeshId,model::Mesh)>,
prop_models:Vec<model::Model>, prop_models:Vec<model::Model>,
world_meshes:Vec<model::Mesh>, world_meshes:Vec<model::Mesh>,
world_models:Vec<model::Model>, world_models:Vec<model::Model>,
modes:strafesnet_common::gameplay_modes::Modes, modes:NormalizedModes,
} }
impl PartialMap2{ impl PartialMap2{
pub fn add_render_configs_and_textures( pub fn add_render_configs_and_textures(

@ -47,7 +47,7 @@ pub enum MeshError{
Io(std::io::Error), Io(std::io::Error),
VMDL(vmdl::ModelError), VMDL(vmdl::ModelError),
VBSP(vbsp::BspError), VBSP(vbsp::BspError),
MissingMdl, MissingMdl(String),
MissingVtx, MissingVtx,
MissingVvd, MissingVvd,
} }
@ -132,7 +132,7 @@ impl<'bsp,'vpk,'a> Loader for ModelLoader<'bsp,'vpk,'a>
vvd_path.set_extension("vvd"); vvd_path.set_extension("vvd");
vtx_path.set_extension("dx90.vtx"); vtx_path.set_extension("dx90.vtx");
// TODO: search more packs, possibly using an index of multiple packs // TODO: search more packs, possibly using an index of multiple packs
let mdl=self.finder.find(mdl_path_lower.as_str())?.ok_or(MeshError::MissingMdl)?; let mdl=self.finder.find(mdl_path_lower.as_str())?.ok_or(MeshError::MissingMdl(mdl_path_lower))?;
let vtx=self.finder.find(vtx_path.as_os_str().to_str().unwrap())?.ok_or(MeshError::MissingVtx)?; let vtx=self.finder.find(vtx_path.as_os_str().to_str().unwrap())?.ok_or(MeshError::MissingVtx)?;
let vvd=self.finder.find(vvd_path.as_os_str().to_str().unwrap())?.ok_or(MeshError::MissingVvd)?; let vvd=self.finder.find(vvd_path.as_os_str().to_str().unwrap())?.ok_or(MeshError::MissingVvd)?;
Ok(vmdl::Model::from_parts( Ok(vmdl::Model::from_parts(

@ -1,7 +1,7 @@
[package] [package]
name = "strafesnet_common" name = "strafesnet_common"
version = "0.6.0" version = "0.6.0"
edition = "2021" edition = "2024"
repository = "https://git.itzana.me/StrafesNET/strafe-project" repository = "https://git.itzana.me/StrafesNET/strafe-project"
license = "MIT OR Apache-2.0" license = "MIT OR Apache-2.0"
description = "Common types and helpers for Strafe Client associated projects." description = "Common types and helpers for Strafe Client associated projects."
@ -15,5 +15,5 @@ bitflags = "2.6.0"
fixed_wide = { version = "0.1.2", path = "../fixed_wide", registry = "strafesnet", features = ["deferred-division","zeroes","wide-mul"] } fixed_wide = { version = "0.1.2", path = "../fixed_wide", registry = "strafesnet", features = ["deferred-division","zeroes","wide-mul"] }
linear_ops = { version = "0.1.0", path = "../linear_ops", registry = "strafesnet", features = ["deferred-division","named-fields"] } linear_ops = { version = "0.1.0", path = "../linear_ops", registry = "strafesnet", features = ["deferred-division","named-fields"] }
ratio_ops = { version = "0.1.0", path = "../ratio_ops", registry = "strafesnet" } ratio_ops = { version = "0.1.0", path = "../ratio_ops", registry = "strafesnet" }
glam = "0.29.0" glam = "0.30.0"
id = { version = "0.1.0", registry = "strafesnet" } id = { version = "0.1.0", registry = "strafesnet" }

@ -7,42 +7,57 @@ pub struct Aabb{
} }
impl Default for Aabb{ impl Default for Aabb{
#[inline]
fn default()->Self{ fn default()->Self{
Self{min:vec3::MAX,max:vec3::MIN} Self{min:vec3::MAX,max:vec3::MIN}
} }
} }
impl Aabb{ impl Aabb{
#[inline]
pub const fn new(min:Planar64Vec3,max:Planar64Vec3)->Self{ pub const fn new(min:Planar64Vec3,max:Planar64Vec3)->Self{
Self{min,max} Self{min,max}
} }
#[inline]
pub const fn max(&self)->Planar64Vec3{ pub const fn max(&self)->Planar64Vec3{
self.max self.max
} }
#[inline]
pub const fn min(&self)->Planar64Vec3{ pub const fn min(&self)->Planar64Vec3{
self.min self.min
} }
#[inline]
pub fn grow(&mut self,point:Planar64Vec3){ pub fn grow(&mut self,point:Planar64Vec3){
self.min=self.min.min(point); self.min=self.min.min(point);
self.max=self.max.max(point); self.max=self.max.max(point);
} }
#[inline]
pub fn join(&mut self,aabb:&Aabb){ pub fn join(&mut self,aabb:&Aabb){
self.min=self.min.min(aabb.min); self.min=self.min.min(aabb.min);
self.max=self.max.max(aabb.max); self.max=self.max.max(aabb.max);
} }
#[inline]
pub fn inflate(&mut self,hs:Planar64Vec3){ pub fn inflate(&mut self,hs:Planar64Vec3){
self.min-=hs; self.min-=hs;
self.max+=hs; self.max+=hs;
} }
#[inline]
pub fn contains(&self,point:Planar64Vec3)->bool{
let bvec=self.min.lt(point)&point.lt(self.max);
bvec.all()
}
#[inline]
pub fn intersects(&self,aabb:&Aabb)->bool{ pub fn intersects(&self,aabb:&Aabb)->bool{
let bvec=self.min.lt(aabb.max)&aabb.min.lt(self.max); let bvec=self.min.lt(aabb.max)&aabb.min.lt(self.max);
bvec.all() bvec.all()
} }
#[inline]
pub fn size(&self)->Planar64Vec3{ pub fn size(&self)->Planar64Vec3{
self.max-self.min self.max-self.min
} }
#[inline]
pub fn center(&self)->Planar64Vec3{ pub fn center(&self)->Planar64Vec3{
self.min+(self.max-self.min)>>1 self.min.map_zip(self.max,|(min,max)|min.midpoint(max))
} }
//probably use floats for area & volume because we don't care about precision //probably use floats for area & volume because we don't care about precision
// pub fn area_weight(&self)->f32{ // pub fn area_weight(&self)->f32{

@ -1,4 +1,10 @@
use std::cmp::Ordering;
use std::collections::BTreeMap;
use crate::aabb::Aabb; use crate::aabb::Aabb;
use crate::ray::Ray;
use crate::integer::{Ratio,Planar64};
use crate::instruction::{InstructionCollector,TimedInstruction};
//da algaritum //da algaritum
//lista boxens //lista boxens
@ -10,35 +16,117 @@ use crate::aabb::Aabb;
//sort the centerpoints on each axis (3 lists) //sort the centerpoints on each axis (3 lists)
//bv is put into octant based on whether it is upper or lower in each list //bv is put into octant based on whether it is upper or lower in each list
pub enum RecursiveContent<R,T>{
Branch(Vec<R>), pub fn intersect_aabb(ray:&Ray,aabb:&Aabb)->Option<Ratio<Planar64,Planar64>>{
Leaf(T), // n.(o+d*t)==n.p
// n.o + n.d * t == n.p
// t == (n.p - n.o)/n.d
let mut hit=None;
match ray.direction.x.cmp(&Planar64::ZERO){
Ordering::Less=>{
let rel_min=aabb.min()-ray.origin;
let rel_max=aabb.max()-ray.origin;
let dy=rel_max.x*ray.direction.y;
let dz=rel_max.x*ray.direction.z;
// x is negative, so inequalities are flipped
if rel_min.y*ray.direction.x>dy&&dy>rel_max.y*ray.direction.x
&&rel_min.z*ray.direction.x>dz&&dz>rel_max.z*ray.direction.x{
let t=rel_max.x/ray.direction.x;
hit=Some(hit.map_or(t,|best_t|t.min(best_t)));
}
},
Ordering::Equal=>(),
Ordering::Greater=>{
let rel_min=aabb.min()-ray.origin;
let rel_max=aabb.max()-ray.origin;
let dy=rel_min.x*ray.direction.y;
let dz=rel_min.x*ray.direction.z;
// x is positive, so inequalities are normal
if rel_min.y*ray.direction.x<dy&&dy<rel_max.y*ray.direction.x
&&rel_min.z*ray.direction.x<dz&&dz<rel_max.z*ray.direction.x{
let t=rel_min.x/ray.direction.x;
hit=Some(hit.map_or(t,|best_t|t.min(best_t)));
}
},
}
match ray.direction.z.cmp(&Planar64::ZERO){
Ordering::Less=>{
let rel_min=aabb.min()-ray.origin;
let rel_max=aabb.max()-ray.origin;
let dx=rel_max.z*ray.direction.x;
let dy=rel_max.z*ray.direction.y;
// z is negative, so inequalities are flipped
if rel_min.x*ray.direction.z>dx&&dx>rel_max.x*ray.direction.z
&&rel_min.y*ray.direction.z>dy&&dy>rel_max.y*ray.direction.z{
let t=rel_max.z/ray.direction.z;
hit=Some(hit.map_or(t,|best_t|t.min(best_t)));
}
},
Ordering::Equal=>(),
Ordering::Greater=>{
let rel_min=aabb.min()-ray.origin;
let rel_max=aabb.max()-ray.origin;
let dx=rel_min.z*ray.direction.x;
let dy=rel_min.z*ray.direction.y;
// z is positive, so inequalities are normal
if rel_min.x*ray.direction.z<dx&&dx<rel_max.x*ray.direction.z
&&rel_min.y*ray.direction.z<dy&&dy<rel_max.y*ray.direction.z{
let t=rel_min.z/ray.direction.z;
hit=Some(hit.map_or(t,|best_t|t.min(best_t)));
}
},
}
match ray.direction.y.cmp(&Planar64::ZERO){
Ordering::Less=>{
let rel_min=aabb.min()-ray.origin;
let rel_max=aabb.max()-ray.origin;
let dz=rel_max.y*ray.direction.z;
let dx=rel_max.y*ray.direction.x;
// y is negative, so inequalities are flipped
if rel_min.z*ray.direction.y>dz&&dz>rel_max.z*ray.direction.y
&&rel_min.x*ray.direction.y>dx&&dx>rel_max.x*ray.direction.y{
let t=rel_max.y/ray.direction.y;
hit=Some(hit.map_or(t,|best_t|t.min(best_t)));
}
},
Ordering::Equal=>(),
Ordering::Greater=>{
let rel_min=aabb.min()-ray.origin;
let rel_max=aabb.max()-ray.origin;
let dz=rel_min.y*ray.direction.z;
let dx=rel_min.y*ray.direction.x;
// y is positive, so inequalities are normal
if rel_min.z*ray.direction.y<dz&&dz<rel_max.z*ray.direction.y
&&rel_min.x*ray.direction.y<dx&&dx<rel_max.x*ray.direction.y{
let t=rel_min.y/ray.direction.y;
hit=Some(hit.map_or(t,|best_t|t.min(best_t)));
}
},
}
hit
} }
impl<R,T> Default for RecursiveContent<R,T>{
fn default()->Self{ pub enum RecursiveContent<N,L>{
Branch(Vec<N>),
Leaf(L),
}
impl<N,L> RecursiveContent<N,L>{
pub fn empty()->Self{
Self::Branch(Vec::new()) Self::Branch(Vec::new())
} }
} }
pub struct BvhNode<T>{ pub struct BvhNode<L>{
content:RecursiveContent<BvhNode<T>,T>, content:RecursiveContent<BvhNode<L>,L>,
aabb:Aabb, aabb:Aabb,
} }
impl<T> Default for BvhNode<T>{ impl<L> BvhNode<L>{
fn default()->Self{ pub fn empty()->Self{
Self{ Self{
content:Default::default(), content:RecursiveContent::empty(),
aabb:Aabb::default(), aabb:Aabb::default(),
} }
} }
} pub fn sample_aabb<F:FnMut(&L)>(&self,aabb:&Aabb,f:&mut F){
pub struct BvhWeightNode<W,T>{
content:RecursiveContent<BvhWeightNode<W,T>,T>,
weight:W,
aabb:Aabb,
}
impl<T> BvhNode<T>{
pub fn the_tester<F:FnMut(&T)>(&self,aabb:&Aabb,f:&mut F){
match &self.content{ match &self.content{
RecursiveContent::Leaf(model)=>f(model), RecursiveContent::Leaf(model)=>f(model),
RecursiveContent::Branch(children)=>for child in children{ RecursiveContent::Branch(children)=>for child in children{
@ -47,51 +135,101 @@ impl<T> BvhNode<T>{
//you're probably not going to spend a lot of time outside the map, //you're probably not going to spend a lot of time outside the map,
//so the test is extra work for nothing //so the test is extra work for nothing
if aabb.intersects(&child.aabb){ if aabb.intersects(&child.aabb){
child.the_tester(aabb,f); child.sample_aabb(aabb,f);
} }
}, },
} }
} }
pub fn into_visitor<F:FnMut(T)>(self,f:&mut F){ fn populate_nodes<'a,T,F>(
match self.content{ &'a self,
RecursiveContent::Leaf(model)=>f(model), collector:&mut InstructionCollector<&'a L,Ratio<Planar64,Planar64>>,
nodes:&mut BTreeMap<Ratio<Planar64,Planar64>,&'a BvhNode<L>>,
ray:&Ray,
start_time:Ratio<Planar64,Planar64>,
f:&F,
)
where
T:Ord+Copy,
Ratio<Planar64,Planar64>:From<T>,
F:Fn(&L,&Ray)->Option<T>,
{
match &self.content{
RecursiveContent::Leaf(leaf)=>if let Some(time)=f(leaf,ray){
let ins=TimedInstruction{time:time.into(),instruction:leaf};
if start_time.lt_ratio(ins.time)&&ins.time.lt_ratio(collector.time()){
collector.collect(Some(ins));
}
},
RecursiveContent::Branch(children)=>for child in children{ RecursiveContent::Branch(children)=>for child in children{
child.into_visitor(f) if child.aabb.contains(ray.origin){
}, child.populate_nodes(collector,nodes,ray,start_time,f);
} }else{
} // Am I an upcoming superstar?
pub fn weigh_contents<W:Copy+std::iter::Sum<W>,F:Fn(&T)->W>(self,f:&F)->BvhWeightNode<W,T>{ if let Some(t)=intersect_aabb(ray,&child.aabb){
match self.content{ if start_time.lt_ratio(t)&&t.lt_ratio(collector.time()){
RecursiveContent::Leaf(model)=>BvhWeightNode{ nodes.insert(t,child);
weight:f(&model), }
content:RecursiveContent::Leaf(model), }
aabb:self.aabb,
},
RecursiveContent::Branch(children)=>{
let branch:Vec<BvhWeightNode<W,T>>=children.into_iter().map(|child|
child.weigh_contents(f)
).collect();
BvhWeightNode{
weight:branch.iter().map(|node|node.weight).sum(),
content:RecursiveContent::Branch(branch),
aabb:self.aabb,
} }
}, },
} }
} }
} pub fn sample_ray<T,F>(
&self,
ray:&Ray,
start_time:T,
time_limit:T,
f:F,
)->Option<(T,&L)>
where
T:Ord+Copy,
T:From<Ratio<Planar64,Planar64>>,
Ratio<Planar64,Planar64>:From<T>,
F:Fn(&L,&Ray)->Option<T>,
{
// source of nondeterminism when Aabb boundaries are coplanar
let mut nodes=BTreeMap::new();
impl <W,T> BvhWeightNode<W,T>{ let start_time=start_time.into();
pub const fn weight(&self)->&W{ let time_limit=time_limit.into();
&self.weight let mut collector=InstructionCollector::new(time_limit);
// break open all nodes that contain ray.origin and populate nodes with future intersection times
self.populate_nodes(&mut collector,&mut nodes,ray,start_time,&f);
// swim through nodes one at a time
while let Some((t,node))=nodes.pop_first(){
if collector.time()<t{
break;
}
match &node.content{
RecursiveContent::Leaf(leaf)=>if let Some(time)=f(leaf,ray){
let ins=TimedInstruction{time:time.into(),instruction:leaf};
// this lower bound can also be omitted
// but it causes type inference errors lol
if start_time.lt_ratio(ins.time)&&ins.time.lt_ratio(collector.time()){
collector.collect(Some(ins));
}
},
// break open the node and predict collisions with the child nodes
RecursiveContent::Branch(children)=>for child in children{
// Am I an upcoming superstar?
if let Some(t)=intersect_aabb(ray,&child.aabb){
// we don't need to check the lower bound
// because child aabbs are guaranteed to be within the parent bounds.
if t<collector.time(){
nodes.insert(t,child);
}
}
},
}
}
collector.take().map(|TimedInstruction{time,instruction:leaf}|(time.into(),leaf))
} }
pub const fn aabb(&self)->&Aabb{ pub fn into_inner(self)->(RecursiveContent<BvhNode<L>,L>,Aabb){
&self.aabb (self.content,self.aabb)
} }
pub fn into_content(self)->RecursiveContent<BvhWeightNode<W,T>,T>{ pub fn into_visitor<F:FnMut(L)>(self,f:&mut F){
self.content
}
pub fn into_visitor<F:FnMut(T)>(self,f:&mut F){
match self.content{ match self.content{
RecursiveContent::Leaf(model)=>f(model), RecursiveContent::Leaf(model)=>f(model),
RecursiveContent::Branch(children)=>for child in children{ RecursiveContent::Branch(children)=>for child in children{
@ -130,9 +268,9 @@ fn generate_bvh_node<T>(boxen:Vec<(T,Aabb)>,force:bool)->BvhNode<T>{
sort_y.push((i,center.y)); sort_y.push((i,center.y));
sort_z.push((i,center.z)); sort_z.push((i,center.z));
} }
sort_x.sort_by(|tup0,tup1|tup0.1.cmp(&tup1.1)); sort_x.sort_by_key(|&(_,c)|c);
sort_y.sort_by(|tup0,tup1|tup0.1.cmp(&tup1.1)); sort_y.sort_by_key(|&(_,c)|c);
sort_z.sort_by(|tup0,tup1|tup0.1.cmp(&tup1.1)); sort_z.sort_by_key(|&(_,c)|c);
let h=n/2; let h=n/2;
let median_x=sort_x[h].1; let median_x=sort_x[h].1;
let median_y=sort_y[h].1; let median_y=sort_y[h].1;

@ -171,4 +171,7 @@ impl CollisionAttributes{
pub fn contact_default()->Self{ pub fn contact_default()->Self{
Self::Contact(ContactAttributes::default()) Self::Contact(ContactAttributes::default())
} }
pub fn intersect_default()->Self{
Self::Intersect(IntersectAttributes::default())
}
} }

@ -1,7 +1,6 @@
use std::collections::{HashSet,HashMap}; use std::collections::{HashSet,HashMap};
use crate::model::ModelId; use crate::model::ModelId;
use crate::gameplay_style; use crate::gameplay_style;
use crate::updatable::Updatable;
#[derive(Clone)] #[derive(Clone)]
pub struct StageElement{ pub struct StageElement{
@ -128,18 +127,6 @@ impl Stage{
self.unordered_checkpoints.contains(&model_id) self.unordered_checkpoints.contains(&model_id)
} }
} }
#[derive(Default)]
pub struct StageUpdate{
//other behaviour models of this stage can have
ordered_checkpoints:HashMap<CheckpointId,ModelId>,
unordered_checkpoints:HashSet<ModelId>,
}
impl Updatable<StageUpdate> for Stage{
fn update(&mut self,update:StageUpdate){
self.ordered_checkpoints.extend(update.ordered_checkpoints);
self.unordered_checkpoints.extend(update.unordered_checkpoints);
}
}
#[derive(Clone,Copy,Hash,Eq,PartialEq)] #[derive(Clone,Copy,Hash,Eq,PartialEq)]
pub enum Zone{ pub enum Zone{
@ -211,34 +198,47 @@ impl Mode{
pub fn push_stage(&mut self,stage:Stage){ pub fn push_stage(&mut self,stage:Stage){
self.stages.push(stage) self.stages.push(stage)
} }
pub fn get_stage_mut(&mut self,stage:StageId)->Option<&mut Stage>{ pub fn get_stage_mut(&mut self,StageId(stage_id):StageId)->Option<&mut Stage>{
self.stages.get_mut(stage.0 as usize) self.stages.get_mut(stage_id as usize)
} }
pub fn get_spawn_model_id(&self,stage:StageId)->Option<ModelId>{ pub fn get_spawn_model_id(&self,StageId(stage_id):StageId)->Option<ModelId>{
self.stages.get(stage.0 as usize).map(|s|s.spawn) self.stages.get(stage_id as usize).map(|s|s.spawn)
} }
pub fn get_zone(&self,model_id:ModelId)->Option<&Zone>{ pub fn get_zone(&self,model_id:ModelId)->Option<&Zone>{
self.zones.get(&model_id) self.zones.get(&model_id)
} }
pub fn get_stage(&self,stage_id:StageId)->Option<&Stage>{ pub fn get_stage(&self,StageId(stage_id):StageId)->Option<&Stage>{
self.stages.get(stage_id.0 as usize) self.stages.get(stage_id as usize)
} }
pub fn get_element(&self,model_id:ModelId)->Option<&StageElement>{ pub fn get_element(&self,model_id:ModelId)->Option<&StageElement>{
self.elements.get(&model_id) self.elements.get(&model_id)
} }
}
#[derive(Clone)]
pub struct NormalizedMode(Mode);
impl NormalizedMode{
pub fn new(mode:Mode)->Self{
Self(mode)
}
pub fn into_inner(self)->Mode{
let Self(mode)=self;
mode
}
//TODO: put this in the SNF //TODO: put this in the SNF
pub fn denormalize_data(&mut self){ pub fn denormalize(self)->Mode{
let NormalizedMode(mut mode)=self;
//expand and index normalized data //expand and index normalized data
self.zones.insert(self.start,Zone::Start); mode.zones.insert(mode.start,Zone::Start);
for (stage_id,stage) in self.stages.iter().enumerate(){ for (stage_id,stage) in mode.stages.iter().enumerate(){
self.elements.insert(stage.spawn,StageElement{ mode.elements.insert(stage.spawn,StageElement{
stage_id:StageId(stage_id as u32), stage_id:StageId(stage_id as u32),
force:false, force:false,
behaviour:StageElementBehaviour::SpawnAt, behaviour:StageElementBehaviour::SpawnAt,
jump_limit:None, jump_limit:None,
}); });
for (_,&model) in &stage.ordered_checkpoints{ for (_,&model) in &stage.ordered_checkpoints{
self.elements.insert(model,StageElement{ mode.elements.insert(model,StageElement{
stage_id:StageId(stage_id as u32), stage_id:StageId(stage_id as u32),
force:false, force:false,
behaviour:StageElementBehaviour::Checkpoint, behaviour:StageElementBehaviour::Checkpoint,
@ -246,7 +246,7 @@ impl Mode{
}); });
} }
for &model in &stage.unordered_checkpoints{ for &model in &stage.unordered_checkpoints{
self.elements.insert(model,StageElement{ mode.elements.insert(model,StageElement{
stage_id:StageId(stage_id as u32), stage_id:StageId(stage_id as u32),
force:false, force:false,
behaviour:StageElementBehaviour::Checkpoint, behaviour:StageElementBehaviour::Checkpoint,
@ -254,53 +254,13 @@ impl Mode{
}); });
} }
} }
} mode
}
//this would be nice as a macro
#[derive(Default)]
pub struct ModeUpdate{
zones:HashMap<ModelId,Zone>,
stages:HashMap<StageId,StageUpdate>,
//mutually exlusive stage element behaviour
elements:HashMap<ModelId,StageElement>,
}
impl Updatable<ModeUpdate> for Mode{
fn update(&mut self,update:ModeUpdate){
self.zones.extend(update.zones);
for (stage,stage_update) in update.stages{
if let Some(stage)=self.stages.get_mut(stage.0 as usize){
stage.update(stage_update);
}
}
self.elements.extend(update.elements);
}
}
impl ModeUpdate{
pub fn zone(model_id:ModelId,zone:Zone)->Self{
let mut mu=Self::default();
mu.zones.insert(model_id,zone);
mu
}
pub fn stage(stage_id:StageId,stage_update:StageUpdate)->Self{
let mut mu=Self::default();
mu.stages.insert(stage_id,stage_update);
mu
}
pub fn element(model_id:ModelId,element:StageElement)->Self{
let mut mu=Self::default();
mu.elements.insert(model_id,element);
mu
}
pub fn map_stage_element_ids<F:Fn(StageId)->StageId>(&mut self,f:F){
for (_,stage_element) in self.elements.iter_mut(){
stage_element.stage_id=f(stage_element.stage_id);
}
} }
} }
#[derive(Default,Clone)] #[derive(Default,Clone)]
pub struct Modes{ pub struct Modes{
pub modes:Vec<Mode>, modes:Vec<Mode>,
} }
impl Modes{ impl Modes{
pub const fn new(modes:Vec<Mode>)->Self{ pub const fn new(modes:Vec<Mode>)->Self{
@ -314,19 +274,182 @@ impl Modes{
pub fn push_mode(&mut self,mode:Mode){ pub fn push_mode(&mut self,mode:Mode){
self.modes.push(mode) self.modes.push(mode)
} }
pub fn get_mode(&self,mode:ModeId)->Option<&Mode>{ pub fn get_mode(&self,ModeId(mode_id):ModeId)->Option<&Mode>{
self.modes.get(mode.0 as usize) self.modes.get(mode_id as usize)
} }
} }
pub struct ModesUpdate{
modes:HashMap<ModeId,ModeUpdate>, #[derive(Clone)]
pub struct NormalizedModes{
modes:Vec<NormalizedMode>,
} }
impl Updatable<ModesUpdate> for Modes{ impl NormalizedModes{
fn update(&mut self,update:ModesUpdate){ pub fn new(modes:Vec<NormalizedMode>)->Self{
for (mode,mode_update) in update.modes{ Self{modes}
if let Some(mode)=self.modes.get_mut(mode.0 as usize){ }
mode.update(mode_update); pub fn len(&self)->usize{
} self.modes.len()
}
pub fn denormalize(self)->Modes{
Modes{
modes:self.modes.into_iter().map(NormalizedMode::denormalize).collect(),
} }
} }
} }
impl IntoIterator for NormalizedModes{
type Item=<Vec<NormalizedMode> as IntoIterator>::Item;
type IntoIter=<Vec<NormalizedMode> as IntoIterator>::IntoIter;
fn into_iter(self)->Self::IntoIter{
self.modes.into_iter()
}
}
#[derive(Default)]
pub struct StageUpdate{
//other behaviour models of this stage can have
ordered_checkpoints:HashMap<CheckpointId,ModelId>,
unordered_checkpoints:HashSet<ModelId>,
}
impl StageUpdate{
fn apply_to(self,stage:&mut Stage){
stage.ordered_checkpoints.extend(self.ordered_checkpoints);
stage.unordered_checkpoints.extend(self.unordered_checkpoints);
}
}
//this would be nice as a macro
#[derive(Default)]
pub struct ModeUpdate{
zones:Option<(ModelId,Zone)>,
stages:Option<(StageId,StageUpdate)>,
//mutually exlusive stage element behaviour
elements:Option<(ModelId,StageElement)>,
}
impl ModeUpdate{
fn apply_to(self,mode:&mut Mode){
mode.zones.extend(self.zones);
if let Some((StageId(stage_id),stage_update))=self.stages{
if let Some(stage)=mode.stages.get_mut(stage_id as usize){
stage_update.apply_to(stage);
}
}
mode.elements.extend(self.elements);
}
}
impl ModeUpdate{
pub fn zone(model_id:ModelId,zone:Zone)->Self{
Self{
zones:Some((model_id,zone)),
stages:None,
elements:None,
}
}
pub fn stage(stage_id:StageId,stage_update:StageUpdate)->Self{
Self{
zones:None,
stages:Some((stage_id,stage_update)),
elements:None,
}
}
pub fn element(model_id:ModelId,element:StageElement)->Self{
Self{
zones:None,
stages:None,
elements:Some((model_id,element)),
}
}
pub fn map_stage_element_ids<F:Fn(StageId)->StageId>(&mut self,f:F){
for (_,stage_element) in self.elements.iter_mut(){
stage_element.stage_id=f(stage_element.stage_id);
}
}
}
struct ModeBuilder{
mode:Mode,
stage_id_map:HashMap<StageId,StageId>,
}
#[derive(Default)]
pub struct ModesBuilder{
modes:HashMap<ModeId,Mode>,
stages:HashMap<ModeId,HashMap<StageId,Stage>>,
mode_updates:Vec<(ModeId,ModeUpdate)>,
stage_updates:Vec<(ModeId,StageId,StageUpdate)>,
}
impl ModesBuilder{
pub fn build_normalized(mut self)->NormalizedModes{
//collect modes and stages into contiguous arrays
let mut unique_modes:Vec<(ModeId,Mode)>
=self.modes.into_iter().collect();
unique_modes.sort_by_key(|&(mode_id,_)|mode_id);
let (mut modes,mode_id_map):(Vec<ModeBuilder>,HashMap<ModeId,ModeId>)
=unique_modes.into_iter().enumerate()
.map(|(final_mode_id,(builder_mode_id,mut mode))|{
(
ModeBuilder{
stage_id_map:self.stages.remove(&builder_mode_id).map_or_else(||HashMap::new(),|stages|{
let mut unique_stages:Vec<(StageId,Stage)>
=stages.into_iter().collect();
unique_stages.sort_by_key(|&(StageId(stage_id),_)|stage_id);
unique_stages.into_iter().enumerate()
.map(|(final_stage_id,(builder_stage_id,stage))|{
mode.push_stage(stage);
(builder_stage_id,StageId::new(final_stage_id as u32))
}).collect()
}),
mode,
},
(
builder_mode_id,
ModeId::new(final_mode_id as u32)
)
)
}).unzip();
//TODO: failure messages or errors or something
//push stage updates
for (builder_mode_id,builder_stage_id,stage_update) in self.stage_updates{
if let Some(final_mode_id)=mode_id_map.get(&builder_mode_id){
if let Some(mode)=modes.get_mut(final_mode_id.get() as usize){
if let Some(&final_stage_id)=mode.stage_id_map.get(&builder_stage_id){
if let Some(stage)=mode.mode.get_stage_mut(final_stage_id){
stage_update.apply_to(stage);
}
}
}
}
}
//push mode updates
for (builder_mode_id,mut mode_update) in self.mode_updates{
if let Some(final_mode_id)=mode_id_map.get(&builder_mode_id){
if let Some(mode)=modes.get_mut(final_mode_id.get() as usize){
//map stage id on stage elements
mode_update.map_stage_element_ids(|stage_id|
//walk down one stage id at a time until a stage is found
//TODO use better logic like BTreeMap::upper_bound instead of walking
// final_stage_id_from_builder_stage_id.upper_bound(Bound::Included(&stage_id))
// .value().copied().unwrap_or(StageId::FIRST)
(0..=stage_id.get()).rev().find_map(|builder_stage_id|
//map the stage element to that stage
mode.stage_id_map.get(&StageId::new(builder_stage_id)).copied()
).unwrap_or(StageId::FIRST)
);
mode_update.apply_to(&mut mode.mode);
}
}
}
NormalizedModes::new(modes.into_iter().map(|mode_builder|NormalizedMode(mode_builder.mode)).collect())
}
pub fn insert_mode(&mut self,mode_id:ModeId,mode:Mode){
assert!(self.modes.insert(mode_id,mode).is_none(),"Cannot replace existing mode");
}
pub fn insert_stage(&mut self,mode_id:ModeId,stage_id:StageId,stage:Stage){
assert!(self.stages.entry(mode_id).or_insert(HashMap::new()).insert(stage_id,stage).is_none(),"Cannot replace existing stage");
}
pub fn push_mode_update(&mut self,mode_id:ModeId,mode_update:ModeUpdate){
self.mode_updates.push((mode_id,mode_update));
}
// fn push_stage_update(&mut self,mode_id:ModeId,stage_id:StageId,stage_update:StageUpdate){
// self.stage_updates.push((mode_id,stage_id,stage_update));
// }
}

@ -63,7 +63,7 @@ impl JumpImpulse{
velocity:Planar64Vec3, velocity:Planar64Vec3,
jump_dir:Planar64Vec3, jump_dir:Planar64Vec3,
gravity:&Planar64Vec3, gravity:&Planar64Vec3,
mass:Planar64, _mass:Planar64,
)->Planar64Vec3{ )->Planar64Vec3{
match self{ match self{
&JumpImpulse::Time(time)=>velocity-(*gravity*time).map(|t|t.divide().fix_1()), &JumpImpulse::Time(time)=>velocity-(*gravity*time).map(|t|t.divide().fix_1()),
@ -78,7 +78,7 @@ impl JumpImpulse{
velocity-(*gravity*(radicand.sqrt().fix_2()+v_g)/gg).divide().fix_1() velocity-(*gravity*(radicand.sqrt().fix_2()+v_g)/gg).divide().fix_1()
}, },
&JumpImpulse::Linear(jump_speed)=>velocity+(jump_dir*jump_speed/jump_dir.length()).divide().fix_1(), &JumpImpulse::Linear(jump_speed)=>velocity+(jump_dir*jump_speed/jump_dir.length()).divide().fix_1(),
&JumpImpulse::Energy(energy)=>{ &JumpImpulse::Energy(_energy)=>{
//calculate energy //calculate energy
//let e=gravity.dot(velocity); //let e=gravity.dot(velocity);
//add //add
@ -355,7 +355,7 @@ pub struct LadderSettings{
pub dot:Planar64, pub dot:Planar64,
} }
impl LadderSettings{ impl LadderSettings{
pub const fn accel(&self,target_diff:Planar64Vec3,gravity:Planar64Vec3)->Planar64{ pub const fn accel(&self,_target_diff:Planar64Vec3,_gravity:Planar64Vec3)->Planar64{
//TODO: fallible ladder accel //TODO: fallible ladder accel
self.accelerate.accel self.accelerate.accel
} }
@ -529,12 +529,12 @@ impl StyleModifiers{
pub fn source_bhop()->Self{ pub fn source_bhop()->Self{
Self{ Self{
controls_mask:Controls::all()-Controls::MoveUp-Controls::MoveDown, controls_mask:Controls::all(),
controls_mask_state:Controls::all(), controls_mask_state:Controls::all(),
strafe:Some(StrafeSettings{ strafe:Some(StrafeSettings{
enable:ControlsActivation::full_2d(), enable:ControlsActivation::full_2d(),
air_accel_limit:Some(Planar64::raw(150<<28)*100), air_accel_limit:Some(Planar64::raw((150<<28)*100)),
mv:(Planar64::raw(30)*VALVE_SCALE).fix_1(), mv:Planar64::raw(30<<28),
tick_rate:Ratio64::new(100,AbsoluteTime::ONE_SECOND.get() as u64).unwrap(), tick_rate:Ratio64::new(100,AbsoluteTime::ONE_SECOND.get() as u64).unwrap(),
}), }),
jump:Some(JumpSettings{ jump:Some(JumpSettings{
@ -570,12 +570,12 @@ impl StyleModifiers{
} }
pub fn source_surf()->Self{ pub fn source_surf()->Self{
Self{ Self{
controls_mask:Controls::all()-Controls::MoveUp-Controls::MoveDown, controls_mask:Controls::all(),
controls_mask_state:Controls::all(), controls_mask_state:Controls::all(),
strafe:Some(StrafeSettings{ strafe:Some(StrafeSettings{
enable:ControlsActivation::full_2d(), enable:ControlsActivation::full_2d(),
air_accel_limit:Some((int(150)*66*VALVE_SCALE).fix_1()), air_accel_limit:Some((int(150)*66*VALVE_SCALE).fix_1()),
mv:(int(30)*VALVE_SCALE).fix_1(), mv:Planar64::raw(30<<28),
tick_rate:Ratio64::new(66,AbsoluteTime::ONE_SECOND.get() as u64).unwrap(), tick_rate:Ratio64::new(66,AbsoluteTime::ONE_SECOND.get() as u64).unwrap(),
}), }),
jump:Some(JumpSettings{ jump:Some(JumpSettings{

@ -1,13 +1,11 @@
use crate::integer::Time;
#[derive(Clone,Debug)] #[derive(Clone,Debug)]
pub struct TimedInstruction<I,T>{ pub struct TimedInstruction<I,T>{
pub time:Time<T>, pub time:T,
pub instruction:I, pub instruction:I,
} }
impl<I,T> TimedInstruction<I,T>{ impl<I,T> TimedInstruction<I,T>{
#[inline] #[inline]
pub fn set_time<TimeInner>(self,new_time:Time<TimeInner>)->TimedInstruction<I,TimeInner>{ pub fn set_time<T2>(self,new_time:T2)->TimedInstruction<I,T2>{
TimedInstruction{ TimedInstruction{
time:new_time, time:new_time,
instruction:self.instruction, instruction:self.instruction,
@ -17,21 +15,21 @@ impl<I,T> TimedInstruction<I,T>{
/// Ensure all emitted instructions are processed before consuming external instructions /// Ensure all emitted instructions are processed before consuming external instructions
pub trait InstructionEmitter<I>{ pub trait InstructionEmitter<I>{
type TimeInner; type Time;
fn next_instruction(&self,time_limit:Time<Self::TimeInner>)->Option<TimedInstruction<I,Self::TimeInner>>; fn next_instruction(&self,time_limit:Self::Time)->Option<TimedInstruction<I,Self::Time>>;
} }
/// Apply an atomic state update /// Apply an atomic state update
pub trait InstructionConsumer<I>{ pub trait InstructionConsumer<I>{
type TimeInner; type Time;
fn process_instruction(&mut self,instruction:TimedInstruction<I,Self::TimeInner>); fn process_instruction(&mut self,instruction:TimedInstruction<I,Self::Time>);
} }
/// If the object produces its own instructions, allow exhaustively feeding them back in /// If the object produces its own instructions, allow exhaustively feeding them back in
pub trait InstructionFeedback<I,T>:InstructionEmitter<I,TimeInner=T>+InstructionConsumer<I,TimeInner=T> pub trait InstructionFeedback<I,T>:InstructionEmitter<I,Time=T>+InstructionConsumer<I,Time=T>
where where
Time<T>:Copy, T:Copy,
{ {
#[inline] #[inline]
fn process_exhaustive(&mut self,time_limit:Time<T>){ fn process_exhaustive(&mut self,time_limit:T){
while let Some(instruction)=self.next_instruction(time_limit){ while let Some(instruction)=self.next_instruction(time_limit){
self.process_instruction(instruction); self.process_instruction(instruction);
} }
@ -39,39 +37,24 @@ pub trait InstructionFeedback<I,T>:InstructionEmitter<I,TimeInner=T>+Instruction
} }
impl<I,T,X> InstructionFeedback<I,T> for X impl<I,T,X> InstructionFeedback<I,T> for X
where where
Time<T>:Copy, T:Copy,
X:InstructionEmitter<I,TimeInner=T>+InstructionConsumer<I,TimeInner=T>, X:InstructionEmitter<I,Time=T>+InstructionConsumer<I,Time=T>,
{} {}
//PROPER PRIVATE FIELDS!!! //PROPER PRIVATE FIELDS!!!
pub struct InstructionCollector<I,T>{ pub struct InstructionCollector<I,T>{
time:Time<T>, time:T,
instruction:Option<I>, instruction:Option<I>,
} }
impl<I,T> InstructionCollector<I,T> impl<I,T> InstructionCollector<I,T>{
where Time<T>:Copy+PartialOrd,
{
#[inline] #[inline]
pub const fn new(time:Time<T>)->Self{ pub const fn new(time:T)->Self{
Self{ Self{
time, time,
instruction:None instruction:None
} }
} }
#[inline] #[inline]
pub const fn time(&self)->Time<T>{
self.time
}
#[inline]
pub fn collect(&mut self,instruction:Option<TimedInstruction<I,T>>){
if let Some(ins)=instruction{
if ins.time<self.time{
self.time=ins.time;
self.instruction=Some(ins.instruction);
}
}
}
#[inline]
pub fn take(self)->Option<TimedInstruction<I,T>>{ pub fn take(self)->Option<TimedInstruction<I,T>>{
//STEAL INSTRUCTION AND DESTROY INSTRUCTIONCOLLECTOR //STEAL INSTRUCTION AND DESTROY INSTRUCTIONCOLLECTOR
self.instruction.map(|instruction|TimedInstruction{ self.instruction.map(|instruction|TimedInstruction{
@ -80,3 +63,20 @@ impl<I,T> InstructionCollector<I,T>
}) })
} }
} }
impl<I,T:Copy> InstructionCollector<I,T>{
#[inline]
pub const fn time(&self)->T{
self.time
}
}
impl<I,T:PartialOrd> InstructionCollector<I,T>{
#[inline]
pub fn collect(&mut self,instruction:Option<TimedInstruction<I,T>>){
if let Some(ins)=instruction{
if ins.time<self.time{
self.time=ins.time;
self.instruction=Some(ins.instruction);
}
}
}
}

@ -4,11 +4,11 @@ pub use ratio_ops::ratio::{Ratio,Divide};
//integer units //integer units
/// specific example of a "default" time type /// specific example of a "default" time type
#[derive(Clone,Copy,Hash,Eq,PartialEq,PartialOrd,Debug)] #[derive(Clone,Copy,Hash,Eq,PartialEq,Ord,PartialOrd,Debug)]
pub enum TimeInner{} pub enum TimeInner{}
pub type AbsoluteTime=Time<TimeInner>; pub type AbsoluteTime=Time<TimeInner>;
#[derive(Clone,Copy,Hash,Eq,PartialEq,PartialOrd,Debug)] #[derive(Clone,Copy,Hash,Eq,PartialEq,Ord,PartialOrd,Debug)]
pub struct Time<T>(i64,core::marker::PhantomData<T>); pub struct Time<T>(i64,core::marker::PhantomData<T>);
impl<T> Time<T>{ impl<T> Time<T>{
pub const MIN:Self=Self::raw(i64::MIN); pub const MIN:Self=Self::raw(i64::MIN);
@ -63,6 +63,12 @@ impl<T> From<Planar64> for Time<T>{
Self::raw((value*Planar64::raw(1_000_000_000)).fix_1().to_raw()) Self::raw((value*Planar64::raw(1_000_000_000)).fix_1().to_raw())
} }
} }
impl<T> From<Time<T>> for Ratio<Planar64,Planar64>{
#[inline]
fn from(value:Time<T>)->Self{
value.to_ratio()
}
}
impl<T,Num,Den,N1,T1> From<Ratio<Num,Den>> for Time<T> impl<T,Num,Den,N1,T1> From<Ratio<Num,Den>> for Time<T>
where where
Num:core::ops::Mul<Planar64,Output=N1>, Num:core::ops::Mul<Planar64,Output=N1>,
@ -648,11 +654,19 @@ pub struct Planar64Affine3{
pub translation:Planar64Vec3, pub translation:Planar64Vec3,
} }
impl Planar64Affine3{ impl Planar64Affine3{
pub const IDENTITY:Self=Self::new(mat3::identity(),vec3::ZERO);
#[inline] #[inline]
pub const fn new(matrix3:Planar64Mat3,translation:Planar64Vec3)->Self{ pub const fn new(matrix3:Planar64Mat3,translation:Planar64Vec3)->Self{
Self{matrix3,translation} Self{matrix3,translation}
} }
#[inline] #[inline]
pub const fn from_translation(translation:Planar64Vec3)->Self{
Self{
matrix3:mat3::identity(),
translation,
}
}
#[inline]
pub fn transform_point3(&self,point:Planar64Vec3)->vec3::Vector3<Fixed<2,64>>{ pub fn transform_point3(&self,point:Planar64Vec3)->vec3::Vector3<Fixed<2,64>>{
self.translation.fix_2()+self.matrix3*point self.translation.fix_2()+self.matrix3*point
} }

@ -1,5 +1,6 @@
pub mod bvh; pub mod bvh;
pub mod map; pub mod map;
pub mod ray;
pub mod run; pub mod run;
pub mod aabb; pub mod aabb;
pub mod model; pub mod model;
@ -8,7 +9,6 @@ pub mod timer;
pub mod integer; pub mod integer;
pub mod physics; pub mod physics;
pub mod session; pub mod session;
pub mod updatable;
pub mod instruction; pub mod instruction;
pub mod gameplay_attributes; pub mod gameplay_attributes;
pub mod gameplay_modes; pub mod gameplay_modes;

@ -4,7 +4,7 @@ use crate::gameplay_attributes;
//this is a temporary struct to try to get the code running again //this is a temporary struct to try to get the code running again
//TODO: use snf::map::Region to update the data in physics and graphics instead of this //TODO: use snf::map::Region to update the data in physics and graphics instead of this
pub struct CompleteMap{ pub struct CompleteMap{
pub modes:gameplay_modes::Modes, pub modes:gameplay_modes::NormalizedModes,
pub attributes:Vec<gameplay_attributes::CollisionAttributes>, pub attributes:Vec<gameplay_attributes::CollisionAttributes>,
pub meshes:Vec<model::Mesh>, pub meshes:Vec<model::Mesh>,
pub models:Vec<model::Model>, pub models:Vec<model::Model>,

@ -1,7 +1,7 @@
use crate::mouse::MouseState; use crate::mouse::MouseState;
use crate::gameplay_modes::{ModeId,StageId}; use crate::gameplay_modes::{ModeId,StageId};
#[derive(Clone,Copy,Hash,Eq,PartialEq,PartialOrd,Debug)] #[derive(Clone,Copy,Hash,Eq,PartialEq,Ord,PartialOrd,Debug)]
pub enum TimeInner{} pub enum TimeInner{}
pub type Time=crate::integer::Time<TimeInner>; pub type Time=crate::integer::Time<TimeInner>;

20
lib/common/src/ray.rs Normal file

@ -0,0 +1,20 @@
use ratio_ops::ratio::Ratio;
use crate::integer::{self,Planar64,Planar64Vec3};
pub struct Ray{
pub origin:Planar64Vec3,
pub direction:Planar64Vec3,
}
impl Ray{
pub fn extrapolate<Num,Den,N1,T1>(&self,t:Ratio<Num,Den>)->Planar64Vec3
where
Num:Copy,
Den:Copy,
Num:core::ops::Mul<Planar64,Output=N1>,
Planar64:core::ops::Mul<Den,Output=N1>,
N1:integer::Divide<Den,Output=T1>,
T1:integer::Fix<Planar64>,
{
self.origin+self.direction.map(|elem|(t*elem).divide().fix())
}
}

@ -2,7 +2,7 @@ use crate::timer::{TimerFixed,Realtime,Paused,Unpaused};
use crate::physics::{TimeInner as PhysicsTimeInner,Time as PhysicsTime}; use crate::physics::{TimeInner as PhysicsTimeInner,Time as PhysicsTime};
#[derive(Clone,Copy,Hash,Eq,PartialEq,PartialOrd,Debug)] #[derive(Clone,Copy,Hash,Eq,PartialEq,Ord,PartialOrd,Debug)]
pub enum TimeInner{} pub enum TimeInner{}
pub type Time=crate::integer::Time<TimeInner>; pub type Time=crate::integer::Time<TimeInner>;

@ -1,3 +1,3 @@
#[derive(Clone,Copy,Hash,Eq,PartialEq,PartialOrd,Debug)] #[derive(Clone,Copy,Hash,Eq,PartialEq,Ord,PartialOrd,Debug)]
pub enum TimeInner{} pub enum TimeInner{}
pub type Time=crate::integer::Time<TimeInner>; pub type Time=crate::integer::Time<TimeInner>;

@ -1,57 +0,0 @@
// This whole thing should be a drive macro
pub trait Updatable<Updater>{
fn update(&mut self,update:Updater);
}
#[derive(Clone,Copy,Hash,Eq,PartialEq)]
struct InnerId(u32);
#[derive(Clone)]
struct Inner{
id:InnerId,
enabled:bool,
}
#[derive(Clone,Copy,Hash,Eq,PartialEq)]
struct OuterId(u32);
struct Outer{
id:OuterId,
inners:std::collections::HashMap<InnerId,Inner>,
}
enum Update<I,U>{
Insert(I),
Update(U),
Remove
}
struct InnerUpdate{
//#[updatable(Update)]
enabled:Option<bool>,
}
struct OuterUpdate{
//#[updatable(Insert,Update,Remove)]
inners:std::collections::HashMap<InnerId,Update<Inner,InnerUpdate>>,
//#[updatable(Update)]
//inners:std::collections::HashMap<InnerId,InnerUpdate>,
}
impl Updatable<InnerUpdate> for Inner{
fn update(&mut self,update:InnerUpdate){
if let Some(enabled)=update.enabled{
self.enabled=enabled;
}
}
}
impl Updatable<OuterUpdate> for Outer{
fn update(&mut self,update:OuterUpdate){
for (id,up) in update.inners{
match up{
Update::Insert(new_inner)=>self.inners.insert(id,new_inner),
Update::Update(inner_update)=>self.inners.get_mut(&id).map(|inner|{
let old=inner.clone();
inner.update(inner_update);
old
}),
Update::Remove=>self.inners.remove(&id),
};
}
}
}

@ -1,7 +1,7 @@
[package] [package]
name = "strafesnet_deferred_loader" name = "strafesnet_deferred_loader"
version = "0.5.0" version = "0.5.0"
edition = "2021" edition = "2024"
repository = "https://git.itzana.me/StrafesNET/strafe-project" repository = "https://git.itzana.me/StrafesNET/strafe-project"
license = "MIT OR Apache-2.0" license = "MIT OR Apache-2.0"
description = "Acquire IDs for objects before loading them in bulk." description = "Acquire IDs for objects before loading them in bulk."

@ -1,7 +1,7 @@
[package] [package]
name = "fixed_wide" name = "fixed_wide"
version = "0.1.2" version = "0.1.2"
edition = "2021" edition = "2024"
repository = "https://git.itzana.me/StrafesNET/strafe-project" repository = "https://git.itzana.me/StrafesNET/strafe-project"
license = "MIT OR Apache-2.0" license = "MIT OR Apache-2.0"
description = "Fixed point numbers with optional widening Mul operator." description = "Fixed point numbers with optional widening Mul operator."
@ -14,7 +14,7 @@ wide-mul=[]
zeroes=["dep:arrayvec"] zeroes=["dep:arrayvec"]
[dependencies] [dependencies]
bnum = "0.12.0" bnum = "0.13.0"
arrayvec = { version = "0.7.6", optional = true } arrayvec = { version = "0.7.6", optional = true }
paste = "1.0.15" paste = "1.0.15"
ratio_ops = { version = "0.1.0", path = "../ratio_ops", registry = "strafesnet", optional = true } ratio_ops = { version = "0.1.0", path = "../ratio_ops", registry = "strafesnet", optional = true }

@ -33,6 +33,14 @@ impl<const N:usize,const F:usize> Fixed<N,F>{
self.bits self.bits
} }
#[inline] #[inline]
pub const fn as_bits(&self)->&BInt<N>{
&self.bits
}
#[inline]
pub const fn as_bits_mut(&mut self)->&mut BInt<N>{
&mut self.bits
}
#[inline]
pub const fn raw_digit(value:i64)->Self{ pub const fn raw_digit(value:i64)->Self{
let mut digits=[0u64;N]; let mut digits=[0u64;N];
digits[0]=value.abs() as u64; digits[0]=value.abs() as u64;
@ -56,6 +64,10 @@ impl<const N:usize,const F:usize> Fixed<N,F>{
pub const fn abs(self)->Self{ pub const fn abs(self)->Self{
Self::from_bits(self.bits.abs()) Self::from_bits(self.bits.abs())
} }
#[inline]
pub const fn midpoint(self,other:Self)->Self{
Self::from_bits(self.bits.midpoint(other.bits))
}
} }
impl<const F:usize> Fixed<1,F>{ impl<const F:usize> Fixed<1,F>{
/// My old code called this function everywhere so let's provide it /// My old code called this function everywhere so let's provide it
@ -788,13 +800,10 @@ macro_rules! impl_not_const_generic{
let wide_self=self.[<fix_ $_2n>](); let wide_self=self.[<fix_ $_2n>]();
//descend down the bits and check if flipping each bit would push the square over the input value //descend down the bits and check if flipping each bit would push the square over the input value
for shift in (0..=max_shift).rev(){ for shift in (0..=max_shift).rev(){
let new_result={ result.as_bits_mut().as_bits_mut().set_bit(shift,true);
let mut bits=result.to_bits().to_bits(); if wide_self<result.[<wide_mul_ $n _ $n>](result){
bits.set_bit(shift,true); // put it back lol
Self::from_bits(BInt::from_bits(bits)) result.as_bits_mut().as_bits_mut().set_bit(shift,false);
};
if new_result.[<wide_mul_ $n _ $n>](new_result)<=wide_self{
result=new_result;
} }
} }
result result

@ -1,7 +1,7 @@
[package] [package]
name = "linear_ops" name = "linear_ops"
version = "0.1.0" version = "0.1.0"
edition = "2021" edition = "2024"
repository = "https://git.itzana.me/StrafesNET/strafe-project" repository = "https://git.itzana.me/StrafesNET/strafe-project"
license = "MIT OR Apache-2.0" license = "MIT OR Apache-2.0"
description = "Vector/Matrix operations using trait bounds." description = "Vector/Matrix operations using trait bounds."

@ -1,7 +1,7 @@
[package] [package]
name = "ratio_ops" name = "ratio_ops"
version = "0.1.0" version = "0.1.1"
edition = "2021" edition = "2024"
repository = "https://git.itzana.me/StrafesNET/strafe-project" repository = "https://git.itzana.me/StrafesNET/strafe-project"
license = "MIT OR Apache-2.0" license = "MIT OR Apache-2.0"
description = "Ratio operations using trait bounds for avoiding division like the plague." description = "Ratio operations using trait bounds for avoiding division like the plague."

@ -268,30 +268,35 @@ impl<LhsNum,LhsDen,RhsNum,RhsDen,T,U> PartialEq<Ratio<RhsNum,RhsDen>> for Ratio<
} }
impl<Num,Den> Eq for Ratio<Num,Den> where Self:PartialEq{} impl<Num,Den> Eq for Ratio<Num,Den> where Self:PartialEq{}
// Wow! These were both completely wrong!
// Idea: use a 'signed' trait instead of parity and float the sign to the numerator.
impl<LhsNum,LhsDen,RhsNum,RhsDen,T,U> PartialOrd<Ratio<RhsNum,RhsDen>> for Ratio<LhsNum,LhsDen> impl<LhsNum,LhsDen,RhsNum,RhsDen,T,U> PartialOrd<Ratio<RhsNum,RhsDen>> for Ratio<LhsNum,LhsDen>
where where
LhsNum:Copy, LhsNum:Copy,
LhsDen:Copy, LhsDen:Copy+Parity,
RhsNum:Copy, RhsNum:Copy,
RhsDen:Copy, RhsDen:Copy+Parity,
LhsNum:core::ops::Mul<RhsDen,Output=T>, LhsNum:core::ops::Mul<RhsDen,Output=T>,
LhsDen:core::ops::Mul<RhsNum,Output=T>,
RhsNum:core::ops::Mul<LhsDen,Output=U>, RhsNum:core::ops::Mul<LhsDen,Output=U>,
T:PartialOrd<U>, RhsDen:core::ops::Mul<LhsNum,Output=U>,
T:PartialOrd<U>+Ord,
{ {
#[inline] #[inline]
fn partial_cmp(&self,other:&Ratio<RhsNum,RhsDen>)->Option<core::cmp::Ordering>{ fn partial_cmp(&self,&other:&Ratio<RhsNum,RhsDen>)->Option<core::cmp::Ordering>{
(self.num*other.den).partial_cmp(&(other.num*self.den)) self.partial_cmp_ratio(other)
} }
} }
impl<Num,Den,T> Ord for Ratio<Num,Den> impl<Num,Den,T> Ord for Ratio<Num,Den>
where where
Num:Copy, Num:Copy,
Den:Copy, Den:Copy+Parity,
Num:core::ops::Mul<Den,Output=T>, Num:core::ops::Mul<Den,Output=T>,
Den:core::ops::Mul<Num,Output=T>,
T:Ord, T:Ord,
{ {
#[inline] #[inline]
fn cmp(&self,other:&Self)->std::cmp::Ordering{ fn cmp(&self,&other:&Self)->std::cmp::Ordering{
(self.num*other.den).cmp(&(other.num*self.den)) self.cmp_ratio(other)
} }
} }

@ -1,7 +1,7 @@
[package] [package]
name = "strafesnet_rbx_loader" name = "strafesnet_rbx_loader"
version = "0.6.0" version = "0.6.0"
edition = "2021" edition = "2024"
repository = "https://git.itzana.me/StrafesNET/strafe-project" repository = "https://git.itzana.me/StrafesNET/strafe-project"
license = "MIT OR Apache-2.0" license = "MIT OR Apache-2.0"
description = "Convert Roblox place and model files to StrafesNET data structures." description = "Convert Roblox place and model files to StrafesNET data structures."
@ -11,7 +11,7 @@ authors = ["Rhys Lloyd <krakow20@gmail.com>"]
[dependencies] [dependencies]
bytemuck = "1.14.3" bytemuck = "1.14.3"
glam = "0.29.0" glam = "0.30.0"
lazy-regex = "3.1.0" lazy-regex = "3.1.0"
rbx_binary = { version = "0.7.4", registry = "strafesnet" } rbx_binary = { version = "0.7.4", registry = "strafesnet" }
rbx_dom_weak = { version = "2.7.0", registry = "strafesnet" } rbx_dom_weak = { version = "2.7.0", registry = "strafesnet" }

@ -84,13 +84,13 @@ where
fn ingest_faces2_lods3( fn ingest_faces2_lods3(
polygon_groups:&mut Vec<PolygonGroup>, polygon_groups:&mut Vec<PolygonGroup>,
vertex_id_map:&HashMap<rbx_mesh::mesh::VertexId2,VertexId>, vertex_id_map:&HashMap<rbx_mesh::mesh::VertexId2,VertexId>,
faces:&Vec<rbx_mesh::mesh::Face2>, faces:&[rbx_mesh::mesh::Face2],
lods:&Vec<rbx_mesh::mesh::Lod3> lods:&[rbx_mesh::mesh::Lod3],
){ ){
//faces have to be split into polygon groups based on lod //faces have to be split into polygon groups based on lod
polygon_groups.extend(lods.windows(2).map(|lod_pair| polygon_groups.extend(lods.windows(2).map(|lod_pair|
PolygonGroup::PolygonList(PolygonList::new(faces[lod_pair[0].0 as usize..lod_pair[1].0 as usize].iter().map(|face| 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[&face.0],vertex_id_map[&face.1],vertex_id_map[&face.2]] vec![vertex_id_map[&v0],vertex_id_map[&v1],vertex_id_map[&v2]]
).collect())) ).collect()))
)) ))
} }

@ -4,12 +4,11 @@ use crate::primitives;
use strafesnet_common::aabb::Aabb; use strafesnet_common::aabb::Aabb;
use strafesnet_common::map; use strafesnet_common::map;
use strafesnet_common::model; use strafesnet_common::model;
use strafesnet_common::gameplay_modes; use strafesnet_common::gameplay_modes::{NormalizedModes,Mode,ModeId,ModeUpdate,ModesBuilder,Stage,StageElement,StageElementBehaviour,StageId,Zone};
use strafesnet_common::gameplay_style; use strafesnet_common::gameplay_style;
use strafesnet_common::gameplay_attributes as attr; use strafesnet_common::gameplay_attributes as attr;
use strafesnet_common::integer::{self,vec3,Planar64,Planar64Vec3,Planar64Mat3,Planar64Affine3}; use strafesnet_common::integer::{self,vec3,Planar64,Planar64Vec3,Planar64Mat3,Planar64Affine3};
use strafesnet_common::model::RenderConfigId; use strafesnet_common::model::RenderConfigId;
use strafesnet_common::updatable::Updatable;
use strafesnet_deferred_loader::deferred_loader::{RenderConfigDeferredLoader,MeshDeferredLoader}; use strafesnet_deferred_loader::deferred_loader::{RenderConfigDeferredLoader,MeshDeferredLoader};
use strafesnet_deferred_loader::mesh::Meshes; use strafesnet_deferred_loader::mesh::Meshes;
use strafesnet_deferred_loader::texture::{RenderConfigs,Texture}; use strafesnet_deferred_loader::texture::{RenderConfigs,Texture};
@ -52,93 +51,7 @@ fn planar64_affine3_from_roblox(cf:&rbx_dom_weak::types::CFrame,size:&rbx_dom_we
vec3::try_from_f32_array([cf.position.x,cf.position.y,cf.position.z]).unwrap() vec3::try_from_f32_array([cf.position.x,cf.position.y,cf.position.z]).unwrap()
) )
} }
struct ModeBuilder{
mode:gameplay_modes::Mode,
final_stage_id_from_builder_stage_id:HashMap<gameplay_modes::StageId,gameplay_modes::StageId>,
}
#[derive(Default)]
struct ModesBuilder{
modes:HashMap<gameplay_modes::ModeId,gameplay_modes::Mode>,
stages:HashMap<gameplay_modes::ModeId,HashMap<gameplay_modes::StageId,gameplay_modes::Stage>>,
mode_updates:Vec<(gameplay_modes::ModeId,gameplay_modes::ModeUpdate)>,
stage_updates:Vec<(gameplay_modes::ModeId,gameplay_modes::StageId,gameplay_modes::StageUpdate)>,
}
impl ModesBuilder{
fn build(mut self)->gameplay_modes::Modes{
//collect modes and stages into contiguous arrays
let mut unique_modes:Vec<(gameplay_modes::ModeId,gameplay_modes::Mode)>
=self.modes.into_iter().collect();
unique_modes.sort_by_key(|&(mode_id,_)|mode_id);
let (mut modes,final_mode_id_from_builder_mode_id):(Vec<ModeBuilder>,HashMap<gameplay_modes::ModeId,gameplay_modes::ModeId>)
=unique_modes.into_iter().enumerate()
.map(|(final_mode_id,(builder_mode_id,mut mode))|{
(
ModeBuilder{
final_stage_id_from_builder_stage_id:self.stages.remove(&builder_mode_id).map_or_else(||HashMap::new(),|stages|{
let mut unique_stages:Vec<(gameplay_modes::StageId,gameplay_modes::Stage)>
=stages.into_iter().collect();
unique_stages.sort_by(|a,b|a.0.cmp(&b.0));
unique_stages.into_iter().enumerate()
.map(|(final_stage_id,(builder_stage_id,stage))|{
mode.push_stage(stage);
(builder_stage_id,gameplay_modes::StageId::new(final_stage_id as u32))
}).collect()
}),
mode,
},
(
builder_mode_id,
gameplay_modes::ModeId::new(final_mode_id as u32)
)
)
}).unzip();
//TODO: failure messages or errors or something
//push stage updates
for (builder_mode_id,builder_stage_id,stage_update) in self.stage_updates{
if let Some(final_mode_id)=final_mode_id_from_builder_mode_id.get(&builder_mode_id){
if let Some(mode)=modes.get_mut(final_mode_id.get() as usize){
if let Some(&final_stage_id)=mode.final_stage_id_from_builder_stage_id.get(&builder_stage_id){
if let Some(stage)=mode.mode.get_stage_mut(final_stage_id){
stage.update(stage_update);
}
}
}
}
}
//push mode updates
for (builder_mode_id,mut mode_update) in self.mode_updates{
if let Some(final_mode_id)=final_mode_id_from_builder_mode_id.get(&builder_mode_id){
if let Some(mode)=modes.get_mut(final_mode_id.get() as usize){
//map stage id on stage elements
mode_update.map_stage_element_ids(|stage_id|
//walk down one stage id at a time until a stage is found
//TODO use better logic like BTreeMap::upper_bound instead of walking
// final_stage_id_from_builder_stage_id.upper_bound(Bound::Included(&stage_id))
// .value().copied().unwrap_or(gameplay_modes::StageId::FIRST)
(0..=stage_id.get()).rev().find_map(|builder_stage_id|
//map the stage element to that stage
mode.final_stage_id_from_builder_stage_id.get(&gameplay_modes::StageId::new(builder_stage_id)).copied()
).unwrap_or(gameplay_modes::StageId::FIRST)
);
mode.mode.update(mode_update);
}
}
}
gameplay_modes::Modes::new(modes.into_iter().map(|mode_builder|mode_builder.mode).collect())
}
fn insert_mode(&mut self,mode_id:gameplay_modes::ModeId,mode:gameplay_modes::Mode){
assert!(self.modes.insert(mode_id,mode).is_none(),"Cannot replace existing mode");
}
fn insert_stage(&mut self,mode_id:gameplay_modes::ModeId,stage_id:gameplay_modes::StageId,stage:gameplay_modes::Stage){
assert!(self.stages.entry(mode_id).or_insert(HashMap::new()).insert(stage_id,stage).is_none(),"Cannot replace existing stage");
}
fn push_mode_update(&mut self,mode_id:gameplay_modes::ModeId,mode_update:gameplay_modes::ModeUpdate){
self.mode_updates.push((mode_id,mode_update));
}
// fn push_stage_update(&mut self,mode_id:gameplay_modes::ModeId,stage_id:gameplay_modes::StageId,stage_update:gameplay_modes::StageUpdate){
// self.stage_updates.push((mode_id,stage_id,stage_update));
// }
}
fn get_attributes(name:&str,can_collide:bool,velocity:Planar64Vec3,model_id:model::ModelId,modes_builder:&mut ModesBuilder,wormhole_in_model_to_id:&mut HashMap<model::ModelId,u32>,wormhole_id_to_out_model:&mut HashMap<u32,model::ModelId>)->attr::CollisionAttributes{ fn get_attributes(name:&str,can_collide:bool,velocity:Planar64Vec3,model_id:model::ModelId,modes_builder:&mut ModesBuilder,wormhole_in_model_to_id:&mut HashMap<model::ModelId,u32>,wormhole_id_to_out_model:&mut HashMap<u32,model::ModelId>)->attr::CollisionAttributes{
let mut general=attr::GeneralAttributes::default(); let mut general=attr::GeneralAttributes::default();
let mut intersecting=attr::IntersectingAttributes::default(); let mut intersecting=attr::IntersectingAttributes::default();
@ -167,8 +80,8 @@ fn get_attributes(name:&str,can_collide:bool,velocity:Planar64Vec3,model_id:mode
force_can_collide=false; force_can_collide=false;
force_intersecting=true; force_intersecting=true;
modes_builder.insert_mode( modes_builder.insert_mode(
gameplay_modes::ModeId::MAIN, ModeId::MAIN,
gameplay_modes::Mode::empty( Mode::empty(
gameplay_style::StyleModifiers::roblox_bhop(), gameplay_style::StyleModifiers::roblox_bhop(),
model_id model_id
) )
@ -178,10 +91,10 @@ fn get_attributes(name:&str,can_collide:bool,velocity:Planar64Vec3,model_id:mode
force_can_collide=false; force_can_collide=false;
force_intersecting=true; force_intersecting=true;
modes_builder.push_mode_update( modes_builder.push_mode_update(
gameplay_modes::ModeId::MAIN, ModeId::MAIN,
gameplay_modes::ModeUpdate::zone( ModeUpdate::zone(
model_id, model_id,
gameplay_modes::Zone::Finish, Zone::Finish,
), ),
); );
}, },
@ -189,19 +102,19 @@ fn get_attributes(name:&str,can_collide:bool,velocity:Planar64Vec3,model_id:mode
force_can_collide=false; force_can_collide=false;
force_intersecting=true; force_intersecting=true;
modes_builder.push_mode_update( modes_builder.push_mode_update(
gameplay_modes::ModeId::MAIN, ModeId::MAIN,
gameplay_modes::ModeUpdate::zone( ModeUpdate::zone(
model_id, model_id,
gameplay_modes::Zone::Anticheat, Zone::Anticheat,
), ),
); );
}, },
"Platform"=>{ "Platform"=>{
modes_builder.push_mode_update( modes_builder.push_mode_update(
gameplay_modes::ModeId::MAIN, ModeId::MAIN,
gameplay_modes::ModeUpdate::element( ModeUpdate::element(
model_id, model_id,
gameplay_modes::StageElement::new(gameplay_modes::StageId::FIRST,false,gameplay_modes::StageElementBehaviour::Platform,None),//roblox does not know which stage the platform belongs to StageElement::new(StageId::FIRST,false,StageElementBehaviour::Platform,None),//roblox does not know which stage the platform belongs to
), ),
); );
}, },
@ -213,8 +126,8 @@ fn get_attributes(name:&str,can_collide:bool,velocity:Planar64Vec3,model_id:mode
force_can_collide=false; force_can_collide=false;
force_intersecting=true; force_intersecting=true;
modes_builder.insert_mode( modes_builder.insert_mode(
gameplay_modes::ModeId::new(captures[2].parse::<u32>().unwrap()), ModeId::new(captures[2].parse::<u32>().unwrap()),
gameplay_modes::Mode::empty( Mode::empty(
gameplay_style::StyleModifiers::roblox_bhop(), gameplay_style::StyleModifiers::roblox_bhop(),
model_id model_id
) )
@ -231,8 +144,8 @@ 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::regex!(r"^(Force)?(Spawn|SpawnAt|Trigger|Teleport|Platform)(\d+)$")
.captures(other){ .captures(other){
force_intersecting=true; force_intersecting=true;
let stage_id=gameplay_modes::StageId::new(captures[3].parse::<u32>().unwrap()); let stage_id=StageId::new(captures[3].parse::<u32>().unwrap());
let stage_element=gameplay_modes::StageElement::new( let stage_element=StageElement::new(
//stage_id: //stage_id:
stage_id, stage_id,
//force: //force:
@ -244,26 +157,26 @@ fn get_attributes(name:&str,can_collide:bool,velocity:Planar64Vec3,model_id:mode
match &captures[2]{ match &captures[2]{
"Spawn"=>{ "Spawn"=>{
modes_builder.insert_stage( modes_builder.insert_stage(
gameplay_modes::ModeId::MAIN, ModeId::MAIN,
stage_id, stage_id,
gameplay_modes::Stage::empty(model_id), Stage::empty(model_id),
); );
//TODO: let denormalize handle this //TODO: let denormalize handle this
gameplay_modes::StageElementBehaviour::SpawnAt StageElementBehaviour::SpawnAt
}, },
"SpawnAt"=>gameplay_modes::StageElementBehaviour::SpawnAt, "SpawnAt"=>StageElementBehaviour::SpawnAt,
//cancollide false so you don't hit the side //cancollide false so you don't hit the side
//NOT a decoration //NOT a decoration
"Trigger"=>{force_can_collide=false;gameplay_modes::StageElementBehaviour::Trigger}, "Trigger"=>{force_can_collide=false;StageElementBehaviour::Trigger},
"Teleport"=>{force_can_collide=false;gameplay_modes::StageElementBehaviour::Teleport}, "Teleport"=>{force_can_collide=false;StageElementBehaviour::Teleport},
"Platform"=>gameplay_modes::StageElementBehaviour::Platform, "Platform"=>StageElementBehaviour::Platform,
_=>panic!("regex1[2] messed up bad"), _=>panic!("regex1[2] messed up bad"),
}, },
None None
); );
modes_builder.push_mode_update( modes_builder.push_mode_update(
gameplay_modes::ModeId::MAIN, ModeId::MAIN,
gameplay_modes::ModeUpdate::element( ModeUpdate::element(
model_id, model_id,
stage_element, stage_element,
), ),
@ -272,14 +185,14 @@ fn get_attributes(name:&str,can_collide:bool,velocity:Planar64Vec3,model_id:mode
.captures(other){ .captures(other){
match &captures[1]{ match &captures[1]{
"Jump"=>modes_builder.push_mode_update( "Jump"=>modes_builder.push_mode_update(
gameplay_modes::ModeId::MAIN, ModeId::MAIN,
gameplay_modes::ModeUpdate::element( ModeUpdate::element(
model_id, model_id,
//jump_limit: //jump_limit:
gameplay_modes::StageElement::new( StageElement::new(
gameplay_modes::StageId::FIRST, StageId::FIRST,
false, false,
gameplay_modes::StageElementBehaviour::Check, StageElementBehaviour::Check,
Some(captures[2].parse::<u8>().unwrap()) Some(captures[2].parse::<u8>().unwrap())
) )
), ),
@ -296,13 +209,13 @@ fn get_attributes(name:&str,can_collide:bool,velocity:Planar64Vec3,model_id:mode
force_can_collide=false; force_can_collide=false;
force_intersecting=true; force_intersecting=true;
modes_builder.push_mode_update( modes_builder.push_mode_update(
gameplay_modes::ModeId::new(captures[2].parse::<u32>().unwrap()), ModeId::new(captures[2].parse::<u32>().unwrap()),
gameplay_modes::ModeUpdate::zone( ModeUpdate::zone(
model_id, model_id,
//zone: //zone:
match &captures[1]{ match &captures[1]{
"Finish"=>gameplay_modes::Zone::Finish, "Finish"=>Zone::Finish,
"Anticheat"=>gameplay_modes::Zone::Anticheat, "Anticheat"=>Zone::Anticheat,
_=>panic!("regex3[1] messed up bad"), _=>panic!("regex3[1] messed up bad"),
}, },
), ),
@ -312,9 +225,9 @@ fn get_attributes(name:&str,can_collide:bool,velocity:Planar64Vec3,model_id:mode
// .captures(other){ // .captures(other){
// match &captures[1]{ // match &captures[1]{
// "OrderedCheckpoint"=>modes_builder.push_stage_update( // "OrderedCheckpoint"=>modes_builder.push_stage_update(
// gameplay_modes::ModeId::MAIN, // ModeId::MAIN,
// gameplay_modes::StageId::new(0), // StageId::new(0),
// gameplay_modes::StageUpdate::ordered_checkpoint(captures[2].parse::<u32>().unwrap()), // StageUpdate::ordered_checkpoint(captures[2].parse::<u32>().unwrap()),
// ), // ),
// _=>panic!("regex3[1] messed up bad"), // _=>panic!("regex3[1] messed up bad"),
// } // }
@ -1009,7 +922,7 @@ impl PartialMap1<'_>{
PartialMap2{ PartialMap2{
meshes:self.primitive_meshes, meshes:self.primitive_meshes,
models, models,
modes:modes_builder.build(), modes:modes_builder.build_normalized(),
attributes:unique_attributes, attributes:unique_attributes,
} }
} }
@ -1018,7 +931,7 @@ impl PartialMap1<'_>{
pub struct PartialMap2{ pub struct PartialMap2{
meshes:Vec<model::Mesh>, meshes:Vec<model::Mesh>,
models:Vec<model::Model>, models:Vec<model::Model>,
modes:gameplay_modes::Modes, modes:NormalizedModes,
attributes:Vec<strafesnet_common::gameplay_attributes::CollisionAttributes>, attributes:Vec<strafesnet_common::gameplay_attributes::CollisionAttributes>,
} }
impl PartialMap2{ impl PartialMap2{

@ -1,7 +1,7 @@
[package] [package]
name = "rbxassetid" name = "rbxassetid"
version = "0.1.0" version = "0.1.0"
edition = "2021" edition = "2024"
repository = "https://git.itzana.me/StrafesNET/strafe-project" repository = "https://git.itzana.me/StrafesNET/strafe-project"
license = "MIT OR Apache-2.0" license = "MIT OR Apache-2.0"
description = "Parse Roblox asset id from 'Content' urls." description = "Parse Roblox asset id from 'Content' urls."

@ -1,7 +1,7 @@
[package] [package]
name = "roblox_emulator" name = "roblox_emulator"
version = "0.4.7" version = "0.4.7"
edition = "2021" edition = "2024"
repository = "https://git.itzana.me/StrafesNET/strafe-project" repository = "https://git.itzana.me/StrafesNET/strafe-project"
license = "MIT OR Apache-2.0" license = "MIT OR Apache-2.0"
description = "Run embedded Luau scripts which manipulate the DOM." description = "Run embedded Luau scripts which manipulate the DOM."
@ -12,7 +12,7 @@ default=["run-service"]
run-service=[] run-service=[]
[dependencies] [dependencies]
glam = "0.29.0" glam = "0.30.0"
mlua = { version = "0.10.1", features = ["luau"] } mlua = { version = "0.10.1", features = ["luau"] }
phf = { version = "0.11.2", features = ["macros"] } phf = { version = "0.11.2", features = ["macros"] }
rbx_dom_weak = { version = "2.7.0", registry = "strafesnet" } rbx_dom_weak = { version = "2.7.0", registry = "strafesnet" }

@ -1,7 +1,7 @@
[package] [package]
name = "strafesnet_snf" name = "strafesnet_snf"
version = "0.3.0" version = "0.3.0"
edition = "2021" edition = "2024"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

@ -6,7 +6,7 @@ use strafesnet_common::physics::Time;
const VERSION:u32=0; const VERSION:u32=0;
type TimedPhysicsInstruction=strafesnet_common::instruction::TimedInstruction<strafesnet_common::physics::Instruction,strafesnet_common::physics::TimeInner>; type TimedPhysicsInstruction=strafesnet_common::instruction::TimedInstruction<strafesnet_common::physics::Instruction,strafesnet_common::physics::Time>;
#[derive(Debug)] #[derive(Debug)]
pub enum Error{ pub enum Error{

@ -6,7 +6,7 @@ use crate::file::BlockId;
use binrw::{binrw,BinReaderExt,BinWriterExt}; use binrw::{binrw,BinReaderExt,BinWriterExt};
use strafesnet_common::model; use strafesnet_common::model;
use strafesnet_common::aabb::Aabb; use strafesnet_common::aabb::Aabb;
use strafesnet_common::bvh::BvhNode; use strafesnet_common::bvh::{BvhNode,RecursiveContent};
use strafesnet_common::gameplay_modes; use strafesnet_common::gameplay_modes;
#[derive(Debug)] #[derive(Debug)]
@ -138,7 +138,7 @@ struct MapHeader{
//#[br(count=num_resources_external)] //#[br(count=num_resources_external)]
//external_resources:Vec<ResourceExternalHeader>, //external_resources:Vec<ResourceExternalHeader>,
#[br(count=num_modes)] #[br(count=num_modes)]
modes:Vec<newtypes::gameplay_modes::Mode>, modes:Vec<newtypes::gameplay_modes::NormalizedMode>,
#[br(count=num_attributes)] #[br(count=num_attributes)]
attributes:Vec<newtypes::gameplay_attributes::CollisionAttributes>, attributes:Vec<newtypes::gameplay_attributes::CollisionAttributes>,
#[br(count=num_render_configs)] #[br(count=num_render_configs)]
@ -181,7 +181,7 @@ fn read_texture<R:BinReaderExt>(file:&mut crate::file::File<R>,block_id:BlockId)
pub struct StreamableMap<R:BinReaderExt>{ pub struct StreamableMap<R:BinReaderExt>{
file:crate::file::File<R>, file:crate::file::File<R>,
//this includes every platform... move the unconstrained datas to their appropriate data block? //this includes every platform... move the unconstrained datas to their appropriate data block?
modes:gameplay_modes::Modes, modes:gameplay_modes::NormalizedModes,
//this is every possible attribute... need some sort of streaming system //this is every possible attribute... need some sort of streaming system
attributes:Vec<strafesnet_common::gameplay_attributes::CollisionAttributes>, attributes:Vec<strafesnet_common::gameplay_attributes::CollisionAttributes>,
//this is every possible render configuration... shaders and such... need streaming //this is every possible render configuration... shaders and such... need streaming
@ -223,7 +223,7 @@ impl<R:BinReaderExt> StreamableMap<R>{
} }
Ok(Self{ Ok(Self{
file, file,
modes:strafesnet_common::gameplay_modes::Modes::new(modes), modes:strafesnet_common::gameplay_modes::NormalizedModes::new(modes),
attributes, attributes,
render_configs, render_configs,
bvh:strafesnet_common::bvh::generate_bvh(bvh), bvh:strafesnet_common::bvh::generate_bvh(bvh),
@ -233,7 +233,7 @@ impl<R:BinReaderExt> StreamableMap<R>{
} }
pub fn get_intersecting_region_block_ids(&self,aabb:&Aabb)->Vec<BlockId>{ pub fn get_intersecting_region_block_ids(&self,aabb:&Aabb)->Vec<BlockId>{
let mut block_ids=Vec::new(); let mut block_ids=Vec::new();
self.bvh.the_tester(aabb,&mut |&block_id|block_ids.push(block_id)); self.bvh.sample_aabb(aabb,&mut |&block_id|block_ids.push(block_id));
block_ids block_ids
} }
pub fn load_region(&mut self,block_id:BlockId)->Result<Vec<(model::ModelId,model::Model)>,Error>{ pub fn load_region(&mut self,block_id:BlockId)->Result<Vec<(model::ModelId,model::Model)>,Error>{
@ -287,12 +287,60 @@ impl<R:BinReaderExt> StreamableMap<R>{
} }
} }
// silly redefinition of Bvh for determining the size of subnodes
// without duplicating work by running weight calculation recursion top down on every node
pub struct BvhWeightNode<W,T>{
content:RecursiveContent<BvhWeightNode<W,T>,T>,
weight:W,
aabb:Aabb,
}
impl <W,T> BvhWeightNode<W,T>{
pub const fn weight(&self)->&W{
&self.weight
}
pub const fn aabb(&self)->&Aabb{
&self.aabb
}
pub fn into_content(self)->RecursiveContent<BvhWeightNode<W,T>,T>{
self.content
}
pub fn into_visitor<F:FnMut(T)>(self,f:&mut F){
match self.content{
RecursiveContent::Leaf(model)=>f(model),
RecursiveContent::Branch(children)=>for child in children{
child.into_visitor(f)
},
}
}
}
pub fn weigh_contents<T,W:Copy+std::iter::Sum<W>,F:Fn(&T)->W>(node:BvhNode<T>,f:&F)->BvhWeightNode<W,T>{
let (content,aabb)=node.into_inner();
match content{
RecursiveContent::Leaf(model)=>BvhWeightNode{
weight:f(&model),
content:RecursiveContent::Leaf(model),
aabb,
},
RecursiveContent::Branch(children)=>{
let branch:Vec<BvhWeightNode<W,T>>=children.into_iter().map(|child|
weigh_contents(child,f)
).collect();
BvhWeightNode{
weight:branch.iter().map(|node|node.weight).sum(),
content:RecursiveContent::Branch(branch),
aabb,
}
},
}
}
const BVH_NODE_MAX_WEIGHT:usize=64*1024;//64 kB const BVH_NODE_MAX_WEIGHT:usize=64*1024;//64 kB
fn collect_spacial_blocks( fn collect_spacial_blocks(
block_location:&mut Vec<u64>, block_location:&mut Vec<u64>,
block_headers:&mut Vec<SpacialBlockHeader>, block_headers:&mut Vec<SpacialBlockHeader>,
sequential_block_data:&mut std::io::Cursor<&mut Vec<u8>>, sequential_block_data:&mut std::io::Cursor<&mut Vec<u8>>,
bvh_node:strafesnet_common::bvh::BvhWeightNode<usize,(model::ModelId,newtypes::model::Model)> bvh_node:BvhWeightNode<usize,(model::ModelId,newtypes::model::Model)>
)->Result<(),Error>{ )->Result<(),Error>{
//inspect the node weights top-down. //inspect the node weights top-down.
//When a node weighs less than the limit, //When a node weighs less than the limit,
@ -342,7 +390,7 @@ pub fn write_map<W:BinWriterExt>(mut writer:W,map:strafesnet_common::map::Comple
} }
Ok(((model::ModelId::new(model_id as u32),model.into()),aabb)) Ok(((model::ModelId::new(model_id as u32),model.into()),aabb))
}).collect::<Result<Vec<_>,_>>()?; }).collect::<Result<Vec<_>,_>>()?;
let bvh=strafesnet_common::bvh::generate_bvh(boxen).weigh_contents(&|_|std::mem::size_of::<newtypes::model::Model>()); let bvh=weigh_contents(strafesnet_common::bvh::generate_bvh(boxen),&|_|std::mem::size_of::<newtypes::model::Model>());
//build blocks //build blocks
//block location is initialized with two values //block location is initialized with two values
//the first value represents the location of the first byte after the file header //the first value represents the location of the first byte after the file header
@ -382,13 +430,13 @@ pub fn write_map<W:BinWriterExt>(mut writer:W,map:strafesnet_common::map::Comple
num_spacial_blocks:spacial_blocks.len() as u32, num_spacial_blocks:spacial_blocks.len() as u32,
num_resource_blocks:resource_blocks.len() as u32, num_resource_blocks:resource_blocks.len() as u32,
//num_resources_external:0, //num_resources_external:0,
num_modes:map.modes.modes.len() as u32, num_modes:map.modes.len() as u32,
num_attributes:map.attributes.len() as u32, num_attributes:map.attributes.len() as u32,
num_render_configs:map.render_configs.len() as u32, num_render_configs:map.render_configs.len() as u32,
spacial_blocks, spacial_blocks,
resource_blocks, resource_blocks,
//external_resources:Vec::new(), //external_resources:Vec::new(),
modes:map.modes.modes.into_iter().map(Into::into).collect(), modes:map.modes.into_iter().map(Into::into).collect(),
attributes:map.attributes.into_iter().map(Into::into).collect(), attributes:map.attributes.into_iter().map(Into::into).collect(),
render_configs:map.render_configs.into_iter().map(Into::into).collect(), render_configs:map.render_configs.into_iter().map(Into::into).collect(),
}; };

@ -157,7 +157,7 @@ pub struct ModeHeader{
} }
#[binrw::binrw] #[binrw::binrw]
#[brw(little)] #[brw(little)]
pub struct Mode{ pub struct NormalizedMode{
pub header:ModeHeader, pub header:ModeHeader,
pub style:super::gameplay_style::StyleModifiers, pub style:super::gameplay_style::StyleModifiers,
pub start:u32, pub start:u32,
@ -179,10 +179,10 @@ impl std::fmt::Display for ModeError{
} }
} }
impl std::error::Error for ModeError{} impl std::error::Error for ModeError{}
impl TryInto<strafesnet_common::gameplay_modes::Mode> for Mode{ impl TryInto<strafesnet_common::gameplay_modes::NormalizedMode> for NormalizedMode{
type Error=ModeError; type Error=ModeError;
fn try_into(self)->Result<strafesnet_common::gameplay_modes::Mode,Self::Error>{ fn try_into(self)->Result<strafesnet_common::gameplay_modes::NormalizedMode,Self::Error>{
Ok(strafesnet_common::gameplay_modes::Mode::new( Ok(strafesnet_common::gameplay_modes::NormalizedMode::new(strafesnet_common::gameplay_modes::Mode::new(
self.style.try_into().map_err(ModeError::StyleModifier)?, self.style.try_into().map_err(ModeError::StyleModifier)?,
strafesnet_common::model::ModelId::new(self.start), strafesnet_common::model::ModelId::new(self.start),
self.zones.into_iter().map(|(model_id,zone)| self.zones.into_iter().map(|(model_id,zone)|
@ -192,12 +192,12 @@ impl TryInto<strafesnet_common::gameplay_modes::Mode> for Mode{
self.elements.into_iter().map(|(model_id,stage_element)| self.elements.into_iter().map(|(model_id,stage_element)|
Ok((strafesnet_common::model::ModelId::new(model_id),stage_element.try_into()?)) Ok((strafesnet_common::model::ModelId::new(model_id),stage_element.try_into()?))
).collect::<Result<_,_>>().map_err(ModeError::StageElement)?, ).collect::<Result<_,_>>().map_err(ModeError::StageElement)?,
)) )))
} }
} }
impl From<strafesnet_common::gameplay_modes::Mode> for Mode{ impl From<strafesnet_common::gameplay_modes::NormalizedMode> for NormalizedMode{
fn from(value:strafesnet_common::gameplay_modes::Mode)->Self{ fn from(value:strafesnet_common::gameplay_modes::NormalizedMode)->Self{
let (style,start,zones,stages,elements)=value.into_inner(); let (style,start,zones,stages,elements)=value.into_inner().into_inner();
Self{ Self{
header:ModeHeader{ header:ModeHeader{
zones:zones.len() as u32, zones:zones.len() as u32,

@ -1,7 +1,7 @@
use super::integer::Time; use super::integer::Time;
use super::common::{bool_from_u8,bool_into_u8}; use super::common::{bool_from_u8,bool_into_u8};
type TimedPhysicsInstruction=strafesnet_common::instruction::TimedInstruction<strafesnet_common::physics::Instruction,strafesnet_common::physics::TimeInner>; type TimedPhysicsInstruction=strafesnet_common::instruction::TimedInstruction<strafesnet_common::physics::Instruction,strafesnet_common::physics::Time>;
#[binrw::binrw] #[binrw::binrw]
#[brw(little)] #[brw(little)]

@ -1,7 +1,7 @@
[package] [package]
name = "map-tool" name = "map-tool"
version = "1.7.0" version = "1.7.0"
edition = "2021" edition = "2024"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
@ -25,10 +25,11 @@ strafesnet_rbx_loader = { version = "0.6.0", path = "../lib/rbx_loader", registr
strafesnet_snf = { version = "0.3.0", path = "../lib/snf", registry = "strafesnet" } strafesnet_snf = { version = "0.3.0", path = "../lib/snf", registry = "strafesnet" }
thiserror = "2.0.11" thiserror = "2.0.11"
tokio = { version = "1.43.0", features = ["macros", "rt-multi-thread", "fs"] } tokio = { version = "1.43.0", features = ["macros", "rt-multi-thread", "fs"] }
vbsp = "0.6.0" vbsp = "0.8.0"
vbsp-entities-css = "0.6.0"
vmdl = "0.2.0" vmdl = "0.2.0"
vmt-parser = "0.2.0" vmt-parser = "0.2.0"
vpk = "0.2.0" vpk = "0.3.0"
vtf = "0.3.0" vtf = "0.3.0"
#[profile.release] #[profile.release]

@ -6,6 +6,7 @@ use futures::StreamExt;
use strafesnet_bsp_loader::loader::BspFinder; use strafesnet_bsp_loader::loader::BspFinder;
use strafesnet_deferred_loader::loader::Loader; use strafesnet_deferred_loader::loader::Loader;
use strafesnet_deferred_loader::deferred_loader::{LoadFailureMode,MeshDeferredLoader,RenderConfigDeferredLoader}; use strafesnet_deferred_loader::deferred_loader::{LoadFailureMode,MeshDeferredLoader,RenderConfigDeferredLoader};
use vbsp_entities_css::Entity;
#[derive(Subcommand)] #[derive(Subcommand)]
pub enum Commands{ pub enum Commands{
@ -244,8 +245,57 @@ async fn gimme_them_textures(path:&Path,vpk_list:&[strafesnet_bsp_loader::Vpk],s
} }
let mut mesh_deferred_loader=MeshDeferredLoader::new(); let mut mesh_deferred_loader=MeshDeferredLoader::new();
for prop in bsp.static_props(){ for name in &bsp.static_props.dict.name{
mesh_deferred_loader.acquire_mesh_id(prop.model()); mesh_deferred_loader.acquire_mesh_id(name.as_str());
}
for raw_ent in &bsp.entities{
let model=match raw_ent.parse(){
Ok(Entity::Cycler(brush))=>brush.model,
Ok(Entity::EnvSprite(brush))=>brush.model,
Ok(Entity::FuncBreakable(brush))=>brush.model,
Ok(Entity::FuncBrush(brush))=>brush.model,
Ok(Entity::FuncButton(brush))=>brush.model,
Ok(Entity::FuncDoor(brush))=>brush.model,
Ok(Entity::FuncDoorRotating(brush))=>brush.model,
Ok(Entity::FuncIllusionary(brush))=>brush.model,
Ok(Entity::FuncMonitor(brush))=>brush.model,
Ok(Entity::FuncMovelinear(brush))=>brush.model,
Ok(Entity::FuncPhysbox(brush))=>brush.model,
Ok(Entity::FuncPhysboxMultiplayer(brush))=>brush.model,
Ok(Entity::FuncRotButton(brush))=>brush.model,
Ok(Entity::FuncRotating(brush))=>brush.model,
Ok(Entity::FuncTracktrain(brush))=>brush.model,
Ok(Entity::FuncTrain(brush))=>brush.model,
Ok(Entity::FuncWall(brush))=>brush.model,
Ok(Entity::FuncWallToggle(brush))=>brush.model,
Ok(Entity::FuncWaterAnalog(brush))=>brush.model,
Ok(Entity::PropDoorRotating(brush))=>brush.model,
Ok(Entity::PropDynamic(brush))=>brush.model,
Ok(Entity::PropDynamicOverride(brush))=>brush.model,
Ok(Entity::PropPhysics(brush))=>brush.model,
Ok(Entity::PropPhysicsMultiplayer(brush))=>brush.model,
Ok(Entity::PropPhysicsOverride(brush))=>brush.model,
Ok(Entity::PropRagdoll(brush))=>brush.model,
Ok(Entity::TriggerGravity(brush))=>brush.model,
Ok(Entity::TriggerHurt(brush))=>brush.model,
Ok(Entity::TriggerLook(brush))=>brush.model,
Ok(Entity::TriggerMultiple(brush))=>brush.model.unwrap_or_default(),
Ok(Entity::TriggerOnce(brush))=>brush.model,
Ok(Entity::TriggerProximity(brush))=>brush.model,
Ok(Entity::TriggerPush(brush))=>brush.model,
Ok(Entity::TriggerSoundscape(brush))=>brush.model,
Ok(Entity::TriggerTeleport(brush))=>brush.model.unwrap_or_default(),
Ok(Entity::TriggerVphysicsMotion(brush))=>brush.model,
Ok(Entity::TriggerWind(brush))=>brush.model,
_=>continue,
};
match model.chars().next(){
Some('*')=>(),
_=>{
mesh_deferred_loader.acquire_mesh_id(model);
},
}
} }
let finder=BspFinder{ let finder=BspFinder{

@ -1,7 +1,7 @@
[package] [package]
name = "strafe-client" name = "strafe-client"
version = "0.11.0" version = "0.11.0"
edition = "2021" edition = "2024"
repository = "https://git.itzana.me/StrafesNET/strafe-project" repository = "https://git.itzana.me/StrafesNET/strafe-project"
license = "Custom" license = "Custom"
description = "StrafesNET game client for bhop and surf." description = "StrafesNET game client for bhop and surf."
@ -16,7 +16,7 @@ source = ["dep:strafesnet_deferred_loader", "dep:strafesnet_bsp_loader"]
roblox = ["dep:strafesnet_deferred_loader", "dep:strafesnet_rbx_loader"] roblox = ["dep:strafesnet_deferred_loader", "dep:strafesnet_rbx_loader"]
[dependencies] [dependencies]
glam = "0.29.0" glam = "0.30.0"
parking_lot = "0.12.1" parking_lot = "0.12.1"
pollster = "0.4.0" pollster = "0.4.0"
strafesnet_bsp_loader = { path = "../lib/bsp_loader", registry = "strafesnet", optional = true } strafesnet_bsp_loader = { path = "../lib/bsp_loader", registry = "strafesnet", optional = true }

@ -1,16 +1,16 @@
use crate::window::Instruction; use crate::window::Instruction;
use strafesnet_common::integer; use strafesnet_common::integer;
use strafesnet_common::instruction::TimedInstruction; use strafesnet_common::instruction::TimedInstruction;
use strafesnet_common::session::TimeInner as SessionTimeInner; use strafesnet_common::session::Time as SessionTime;
pub struct App<'a>{ pub struct App<'a>{
root_time:std::time::Instant, root_time:std::time::Instant,
window_thread:crate::compat_worker::QNWorker<'a,TimedInstruction<Instruction,SessionTimeInner>>, window_thread:crate::compat_worker::QNWorker<'a,TimedInstruction<Instruction,SessionTime>>,
} }
impl<'a> App<'a>{ impl<'a> App<'a>{
pub fn new( pub fn new(
root_time:std::time::Instant, root_time:std::time::Instant,
window_thread:crate::compat_worker::QNWorker<'a,TimedInstruction<Instruction,SessionTimeInner>>, window_thread:crate::compat_worker::QNWorker<'a,TimedInstruction<Instruction,SessionTime>>,
)->App<'a>{ )->App<'a>{
Self{ Self{
root_time, root_time,

@ -2,7 +2,6 @@ mod app;
mod file; mod file;
mod setup; mod setup;
mod window; mod window;
mod worker;
mod compat_worker; mod compat_worker;
mod physics_worker; mod physics_worker;
mod graphics_worker; mod graphics_worker;

@ -6,7 +6,7 @@ use strafesnet_session::session::{
}; };
use strafesnet_common::instruction::{TimedInstruction,InstructionConsumer}; use strafesnet_common::instruction::{TimedInstruction,InstructionConsumer};
use strafesnet_common::physics::Time as PhysicsTime; use strafesnet_common::physics::Time as PhysicsTime;
use strafesnet_common::session::{Time as SessionTime,TimeInner as SessionTimeInner}; use strafesnet_common::session::Time as SessionTime;
use strafesnet_common::timer::Timer; use strafesnet_common::timer::Timer;
pub enum Instruction{ pub enum Instruction{
@ -23,7 +23,7 @@ pub fn new<'a>(
mut graphics_worker:crate::compat_worker::INWorker<'a,crate::graphics_worker::Instruction>, mut graphics_worker:crate::compat_worker::INWorker<'a,crate::graphics_worker::Instruction>,
directories:Directories, directories:Directories,
user_settings:settings::UserSettings, user_settings:settings::UserSettings,
)->crate::compat_worker::QNWorker<'a,TimedInstruction<Instruction,SessionTimeInner>>{ )->crate::compat_worker::QNWorker<'a,TimedInstruction<Instruction,SessionTime>>{
let physics=strafesnet_physics::physics::PhysicsState::default(); let physics=strafesnet_physics::physics::PhysicsState::default();
let timer=Timer::unpaused(SessionTime::ZERO,PhysicsTime::ZERO); let timer=Timer::unpaused(SessionTime::ZERO,PhysicsTime::ZERO);
let simulation=Simulation::new(timer,physics); let simulation=Simulation::new(timer,physics);
@ -32,7 +32,7 @@ pub fn new<'a>(
directories, directories,
simulation, simulation,
); );
crate::compat_worker::QNWorker::new(move |ins:TimedInstruction<Instruction,SessionTimeInner>|{ crate::compat_worker::QNWorker::new(move |ins:TimedInstruction<Instruction,SessionTime>|{
// excruciating pain // excruciating pain
macro_rules! run_session_instruction{ macro_rules! run_session_instruction{
($time:expr,$instruction:expr)=>{ ($time:expr,$instruction:expr)=>{
@ -77,5 +77,8 @@ pub fn new<'a>(
run_session_instruction!(ins.time,SessionInstruction::LoadReplay(bot)); run_session_instruction!(ins.time,SessionInstruction::LoadReplay(bot));
} }
} }
//whatever just do it
session.debug_raycast_print_model_id_if_changed(ins.time);
}) })
} }

@ -1,5 +1,5 @@
use strafesnet_common::instruction::TimedInstruction; use strafesnet_common::instruction::TimedInstruction;
use strafesnet_common::session::{Time as SessionTime,TimeInner as SessionTimeInner}; use strafesnet_common::session::Time as SessionTime;
use strafesnet_common::physics::{MiscInstruction,SetControlInstruction}; use strafesnet_common::physics::{MiscInstruction,SetControlInstruction};
use crate::file::LoadFormat; use crate::file::LoadFormat;
use crate::physics_worker::Instruction as PhysicsWorkerInstruction; use crate::physics_worker::Instruction as PhysicsWorkerInstruction;
@ -17,7 +17,7 @@ struct WindowContext<'a>{
mouse_pos:glam::DVec2, mouse_pos:glam::DVec2,
screen_size:glam::UVec2, screen_size:glam::UVec2,
window:&'a winit::window::Window, window:&'a winit::window::Window,
physics_thread:crate::compat_worker::QNWorker<'a,TimedInstruction<PhysicsWorkerInstruction,SessionTimeInner>>, physics_thread:crate::compat_worker::QNWorker<'a,TimedInstruction<PhysicsWorkerInstruction,SessionTime>>,
} }
impl WindowContext<'_>{ impl WindowContext<'_>{
@ -223,7 +223,7 @@ impl WindowContext<'_>{
pub fn worker<'a>( pub fn worker<'a>(
window:&'a winit::window::Window, window:&'a winit::window::Window,
setup_context:crate::setup::SetupContext<'a>, setup_context:crate::setup::SetupContext<'a>,
)->crate::compat_worker::QNWorker<'a,TimedInstruction<Instruction,SessionTimeInner>>{ )->crate::compat_worker::QNWorker<'a,TimedInstruction<Instruction,SessionTime>>{
// WindowContextSetup::new // WindowContextSetup::new
#[cfg(feature="user-install")] #[cfg(feature="user-install")]
let directories=Directories::user().unwrap(); let directories=Directories::user().unwrap();
@ -252,7 +252,7 @@ pub fn worker<'a>(
}; };
//WindowContextSetup::into_worker //WindowContextSetup::into_worker
crate::compat_worker::QNWorker::new(move |ins:TimedInstruction<Instruction,SessionTimeInner>|{ crate::compat_worker::QNWorker::new(move |ins:TimedInstruction<Instruction,SessionTime>|{
match ins.instruction{ match ins.instruction{
Instruction::WindowEvent(window_event)=>{ Instruction::WindowEvent(window_event)=>{
window_context.window_event(ins.time,window_event); window_context.window_event(ins.time,window_event);

@ -1 +1 @@
/run/media/quat/Files/Documents/map-files/verify-scripts/maps/bhop_snfm /run/media/quat/Files/Documents/map-files/strafesnet/maps/bhop_snfm

@ -1 +1 @@
/run/media/quat/Files/Documents/map-files/verify-scripts/meshes /run/media/quat/Files/Documents/map-files/strafesnet/meshes

@ -1 +1 @@
/run/media/quat/Files/Documents/map-files/verify-scripts/replays /run/media/quat/Files/Documents/map-files/strafesnet/replays

@ -1 +1 @@
/run/media/quat/Files/Documents/map-files/verify-scripts/maps/surf_snfm /run/media/quat/Files/Documents/map-files/strafesnet/maps/surf_snfm

@ -1 +1 @@
/run/media/quat/Files/Documents/map-files/verify-scripts/textures /run/media/quat/Files/Documents/map-files/strafesnet/textures

@ -1 +1 @@
/run/media/quat/Files/Documents/map-files/verify-scripts/unions /run/media/quat/Files/Documents/map-files/strafesnet/unions