Compare commits
50 Commits
sussy-tips
...
entity
Author | SHA1 | Date | |
---|---|---|---|
a56195c887 | |||
70fb970582 | |||
31184d21da | |||
6b4b17f49d | |||
d0c17873f6 | |||
d39705d5e7 | |||
a3a50580f8 | |||
d5bf4549a9 | |||
b5da85fae1 | |||
78c53ae429 | |||
e794b2649c | |||
50f6fe5bd8 | |||
fc3681f41f | |||
a2369b4211 | |||
bfea49ffae | |||
8c7063d340 | |||
651150760c | |||
3d203e2da9 | |||
3798f438cf | |||
4ace8317fe | |||
ab4a9bb922 | |||
a9ef07ce78 | |||
561e41c760 | |||
9896a09576 | |||
82840fa6cb | |||
2c87cb71df | |||
709f622547 | |||
b20264b382 | |||
17cce2d702 | |||
a2b2ddc9ce | |||
cb57d893e0 | |||
9e8d66cec1 | |||
39fe833198 | |||
e7688a95fd | |||
d2cc98b04d | |||
9e887580af | |||
92feac572e | |||
fd02a40783 | |||
7c787a0e0f | |||
6a7c076203 | |||
af3abbcb4d | |||
4859c37780 | |||
19e65802f6 | |||
2cf1547423 | |||
63305f91c7 | |||
e875826250 | |||
0a44c1630f | |||
5cffc03ef6 | |||
d638e633ba | |||
61e44f2aba |
Cargo.lock
engine
graphics
physics
session
settings
integration-testing
lib
bsp_loader
common
Cargo.toml
src
deferred_loader
fixed_wide
linear_ops
ratio_ops
rbx_loader
rbxassetid
roblox_emulator
snf
map-tool
strafe-client
650
Cargo.lock
generated
650
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@ -1,12 +1,12 @@
|
||||
[package]
|
||||
name = "strafesnet_graphics"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
bytemuck = { version = "1.13.1", features = ["derive"] }
|
||||
ddsfile = "0.5.1"
|
||||
glam = "0.29.0"
|
||||
glam = "0.30.0"
|
||||
id = { version = "0.1.0", registry = "strafesnet" }
|
||||
strafesnet_common = { path = "../../lib/common", registry = "strafesnet" }
|
||||
strafesnet_session = { path = "../session", registry = "strafesnet" }
|
||||
|
@ -1,10 +1,10 @@
|
||||
[package]
|
||||
name = "strafesnet_physics"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
arrayvec = "0.7.6"
|
||||
glam = "0.29.0"
|
||||
glam = "0.30.0"
|
||||
id = { version = "0.1.0", 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{
|
||||
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{
|
||||
//this happens to be well-defined. there are no virtual virtices
|
||||
SubmeshVertId::new(
|
||||
self.view.topology.verts.iter()
|
||||
.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))
|
||||
)
|
||||
//assume there is more than zero vertices.
|
||||
|
@ -982,10 +982,7 @@ impl PhysicsContext<'_>{
|
||||
impl PhysicsData{
|
||||
/// use with caution, this is the only non-instruction way to mess with physics
|
||||
pub fn generate_models(&mut self,map:&map::CompleteMap){
|
||||
let mut modes=map.modes.clone();
|
||||
for mode in &mut modes.modes{
|
||||
mode.denormalize_data();
|
||||
}
|
||||
let mut modes=map.modes.clone().denormalize();
|
||||
let mut used_contact_attributes=Vec::new();
|
||||
let mut used_intersect_attributes=Vec::new();
|
||||
|
||||
@ -1259,7 +1256,7 @@ fn set_velocity_cull(body:&mut Body,touching:&mut TouchingState,models:&PhysicsM
|
||||
culled
|
||||
}
|
||||
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{
|
||||
//This is not correct but is better than what I have
|
||||
@ -1814,14 +1811,18 @@ fn atomic_input_instruction(state:&mut PhysicsState,data:&PhysicsData,ins:TimedI
|
||||
},
|
||||
Instruction::Mode(ModeInstruction::Restart(mode_id))=>{
|
||||
//teleport to mode start zone
|
||||
let mut spawn_point=vec3::ZERO;
|
||||
let 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: set camera andles to face the same way as the start zone
|
||||
data.models.get_model_transform(mode.get_start().into()).map(|transform|
|
||||
transform.vertex.translation
|
||||
)
|
||||
).unwrap_or(vec3::ZERO);
|
||||
//TODO: set camera angles to face the same way as the start zone
|
||||
if let Some(transform)=data.models.get_model_transform(mode.get_start()){
|
||||
// NOTE: this value may be 0,0,0 on source maps
|
||||
spawn_point=transform.vertex.translation;
|
||||
}
|
||||
}
|
||||
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);
|
||||
state.set_move_state(data,MoveState::Air);
|
||||
@ -1831,6 +1832,9 @@ fn atomic_input_instruction(state:&mut PhysicsState,data:&PhysicsData,ins:TimedI
|
||||
Instruction::Mode(ModeInstruction::Spawn(mode_id,stage_id))=>{
|
||||
//spawn at a particular stage
|
||||
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){
|
||||
let _=teleport_to_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
|
||||
// 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
|
||||
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
|
||||
pub struct Contact{
|
||||
pub position:Planar64Vec3,
|
||||
|
@ -1,10 +1,10 @@
|
||||
[package]
|
||||
name = "strafesnet_session"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
glam = "0.29.0"
|
||||
glam = "0.30.0"
|
||||
replace_with = "0.1.7"
|
||||
strafesnet_common = { path = "../../lib/common", registry = "strafesnet" }
|
||||
strafesnet_physics = { path = "../physics", registry = "strafesnet" }
|
||||
|
@ -1,10 +1,10 @@
|
||||
[package]
|
||||
name = "strafesnet_settings"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
configparser = "3.0.2"
|
||||
directories = "6.0.0"
|
||||
glam = "0.29.0"
|
||||
glam = "0.30.0"
|
||||
strafesnet_common = { path = "../../lib/common", registry = "strafesnet" }
|
||||
|
@ -1,7 +1,7 @@
|
||||
[package]
|
||||
name = "integration-testing"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
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};
|
||||
|
||||
@ -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>{
|
||||
let mut file=std::fs::File::open(path)?;
|
||||
let mut data=Vec::new();
|
||||
file.read_to_end(&mut data)?;
|
||||
let data=std::fs::read(path)?;
|
||||
Ok(Cursor::new(data))
|
||||
}
|
||||
|
||||
@ -72,7 +72,12 @@ enum DeterminismResult{
|
||||
Deterministic,
|
||||
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
|
||||
let mut physics_deterministic=PhysicsState::default();
|
||||
// create a second physics state
|
||||
@ -82,6 +87,9 @@ fn segment_determinism(bot:strafesnet_snf::bot::Segment,physics_data:&PhysicsDat
|
||||
println!("simulating...");
|
||||
|
||||
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(){
|
||||
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 b1=physics_filtered.camera_body();
|
||||
if b0.position!=b1.position{
|
||||
let nanoseconds=start.elapsed().as_nanos() as u64;
|
||||
println!("desync at instruction #{}",i);
|
||||
println!("non idle instructions completed={non_idle_count}");
|
||||
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!("deterministic state1:\n{:?}",physics_deterministic);
|
||||
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(){
|
||||
Some(time)=>println!("[with idle] finish time:{}",time),
|
||||
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),
|
||||
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{
|
||||
let data=read_entire_file(file_path.as_path())?;
|
||||
let bot=strafesnet_snf::read_bot(data)?.read_all()?;
|
||||
@ -198,10 +217,18 @@ fn test_determinism()->Result<(),ReplayError>{
|
||||
invalid: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|{
|
||||
match result{
|
||||
Ok(Some(DeterminismResult::Deterministic))=>totals.deterministic+=1,
|
||||
Ok(Some(DeterminismResult::NonDeterministic))=>totals.nondeterministic+=1,
|
||||
Ok(Some(segment_result))=>{
|
||||
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,
|
||||
Err(_)=>totals.error+=1,
|
||||
}
|
||||
@ -212,6 +239,7 @@ fn test_determinism()->Result<(),ReplayError>{
|
||||
println!("nondeterministic={nondeterministic}");
|
||||
println!("invalid={invalid}");
|
||||
println!("error={error}");
|
||||
println!("average ticks/s per core: {}",ticks*1_000_000_000/nanoseconds);
|
||||
|
||||
assert!(nondeterministic==0);
|
||||
assert!(invalid==0);
|
||||
|
@ -1,7 +1,7 @@
|
||||
[package]
|
||||
name = "strafesnet_bsp_loader"
|
||||
version = "0.3.0"
|
||||
edition = "2021"
|
||||
edition = "2024"
|
||||
repository = "https://git.itzana.me/StrafesNET/strafe-project"
|
||||
license = "MIT OR Apache-2.0"
|
||||
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
|
||||
|
||||
[dependencies]
|
||||
glam = "0.29.0"
|
||||
glam = "0.30.0"
|
||||
strafesnet_common = { version = "0.6.0", path = "../common", registry = "strafesnet" }
|
||||
strafesnet_deferred_loader = { version = "0.5.0", path = "../deferred_loader", registry = "strafesnet" }
|
||||
vbsp = { version = "0.7.0-codegen1", registry = "strafesnet" }
|
||||
vbsp = "0.8.0"
|
||||
vbsp-entities-css = "0.6.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)?;
|
||||
|
||||
// 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{
|
||||
if let Some(a)=detect_loop.checked_sub(1){
|
||||
detect_loop=a;
|
||||
}else{
|
||||
return Err(PlanesToFacesError::InfiniteLoop1);
|
||||
}
|
||||
detect_loop=detect_loop.checked_sub(1).ok_or(PlanesToFacesError::InfiniteLoop1)?;
|
||||
// test if any *other* faces occlude the intersection
|
||||
for new_face in &face_list{
|
||||
// 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){
|
||||
continue;
|
||||
}
|
||||
if let Some(new_intersection)=solve3(new_face,face1,face2){
|
||||
// face0 does not occlude (or intersect) the new intersection
|
||||
if (face0.dot.fix_2()/Planar64::ONE).lt_ratio(face0.normal.dot(new_intersection.num)/new_intersection.den){
|
||||
// abort! reject face0 entirely
|
||||
continue 'face;
|
||||
}
|
||||
// new_face occludes intersection meaning intersection is not on convex solid and face0 is degenrate
|
||||
if (new_face.dot.fix_2()/Planar64::ONE).lt_ratio(new_face.normal.dot(intersection.num)/intersection.den){
|
||||
// abort! reject face0 entirely
|
||||
continue 'face;
|
||||
}
|
||||
}
|
||||
|
||||
@ -177,11 +171,7 @@ fn planes_to_faces(face_list:std::collections::HashSet<Face>)->Result<Faces,Plan
|
||||
face2=new_face;
|
||||
intersection=new_intersection;
|
||||
|
||||
if let Some(a)=detect_loop.checked_sub(1){
|
||||
detect_loop=a;
|
||||
}else{
|
||||
return Err(PlanesToFacesError::InfiniteLoop2);
|
||||
}
|
||||
detect_loop=detect_loop.checked_sub(1).ok_or(PlanesToFacesError::InfiniteLoop2)?;
|
||||
}
|
||||
|
||||
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)]
|
||||
pub enum BrushToMeshError{
|
||||
SliceBrushSides,
|
||||
@ -204,7 +195,6 @@ pub enum BrushToMeshError{
|
||||
count:usize,
|
||||
},
|
||||
InvalidPlanes(PlanesToFacesError),
|
||||
SkipBecauseTexture,
|
||||
}
|
||||
impl std::fmt::Display for BrushToMeshError{
|
||||
fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{
|
||||
@ -246,16 +236,15 @@ pub fn brush_to_mesh(bsp:&vbsp::Bsp,brush:&vbsp::Brush)->Result<model::Mesh,Brus
|
||||
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)?;
|
||||
for side in sides{
|
||||
if let Some(texture_info)=bsp.textures_info.get(side.texture_info as usize){
|
||||
let texture_info=vbsp::Handle::new(bsp,texture_info);
|
||||
let s=texture_info.name();
|
||||
if s.starts_with("tools/")||s.starts_with("TOOLS/"){
|
||||
return Err(BrushToMeshError::SkipBecauseTexture);
|
||||
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 face_list=sides.iter().filter(|side|side.bevel==0).map(|side|{
|
||||
let plane=bsp.plane(side.plane as usize)?;
|
||||
Some(Face{
|
||||
normal:valve_transform_normal(plane.normal.into()),
|
||||
@ -302,6 +291,7 @@ mod test{
|
||||
Face{normal:integer::vec3::NEG_Z,dot:Planar64::ONE},
|
||||
].into_iter().collect();
|
||||
let faces=planes_to_faces(face_list).unwrap();
|
||||
assert_eq!(faces.faces.len(),6);
|
||||
dbg!(faces);
|
||||
}
|
||||
#[test]
|
||||
@ -316,6 +306,37 @@ mod test{
|
||||
Face{normal:integer::vec3::NEG_Z,dot:Planar64::EPSILON},
|
||||
].into_iter().collect();
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
@ -1,9 +1,12 @@
|
||||
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::mesh::Meshes;
|
||||
use strafesnet_deferred_loader::texture::{RenderConfigs,Texture};
|
||||
use strafesnet_common::gameplay_modes::{NormalizedMode,NormalizedModes,Mode,Stage};
|
||||
|
||||
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>(
|
||||
bsp:&'a crate::Bsp,
|
||||
render_config_deferred_loader:&mut RenderConfigDeferredLoader<Cow<'a,str>>,
|
||||
@ -39,29 +83,60 @@ pub fn convert<'a>(
|
||||
)->PartialMap1{
|
||||
let bsp=bsp.as_ref();
|
||||
//figure out real attributes later
|
||||
let mut unique_attributes=Vec::new();
|
||||
unique_attributes.push(gameplay_attributes::CollisionAttributes::Decoration);
|
||||
unique_attributes.push(gameplay_attributes::CollisionAttributes::contact_default());
|
||||
unique_attributes.push(gameplay_attributes::CollisionAttributes::intersect_default());
|
||||
const ATTRIBUTE_DECORATION:gameplay_attributes::CollisionAttributesId=gameplay_attributes::CollisionAttributesId::new(0);
|
||||
const ATTRIBUTE_CONTACT_DEFAULT:gameplay_attributes::CollisionAttributesId=gameplay_attributes::CollisionAttributesId::new(1);
|
||||
const ATTRIBUTE_INTERSECT_DEFAULT:gameplay_attributes::CollisionAttributesId=gameplay_attributes::CollisionAttributesId::new(2);
|
||||
let unique_attributes=vec![
|
||||
attr::CollisionAttributes::Decoration,
|
||||
attr::CollisionAttributes::contact_default(),
|
||||
attr::CollisionAttributes::intersect_default(),
|
||||
// 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
|
||||
let prop_models=bsp.static_props().map(|prop|{
|
||||
let mut prop_models=bsp.static_props().map(|prop|{
|
||||
const DEG_TO_RAD:f32=std::f32::consts::TAU/360.0;
|
||||
//get or create mesh_id
|
||||
let mesh_id=mesh_deferred_loader.acquire_mesh_id(prop.model());
|
||||
let placement=prop.as_prop_placement();
|
||||
model::Model{
|
||||
mesh:mesh_id,
|
||||
attributes:ATTRIBUTE_DECORATION,
|
||||
transform:integer::Planar64Affine3::new(
|
||||
integer::mat3::try_from_f32_array_2d((
|
||||
glam::Mat3A::from_diagonal(glam::Vec3::splat(placement.scale))
|
||||
integer::mat3::try_from_f32_array_2d(
|
||||
//TODO: figure this out
|
||||
*glam::Mat3A::from_quat(glam::Quat::from_array(placement.rotation.into()))
|
||||
).to_cols_array_2d()).unwrap(),
|
||||
valve_transform(placement.origin.into()),
|
||||
glam::Mat3A::from_euler(
|
||||
glam::EulerRot::XYZ,
|
||||
prop.angles.pitch*DEG_TO_RAD,
|
||||
prop.angles.yaw*DEG_TO_RAD,
|
||||
prop.angles.roll*DEG_TO_RAD
|
||||
).to_cols_array_2d()
|
||||
).unwrap(),
|
||||
valve_transform(prop.origin.into()),
|
||||
),
|
||||
color:glam::Vec4::ONE,
|
||||
}
|
||||
@ -134,40 +209,204 @@ pub fn convert<'a>(
|
||||
color:glam::Vec4::W,
|
||||
});
|
||||
|
||||
for raw_ent in bsp.entities.iter(){
|
||||
match raw_ent.parse(){
|
||||
Ok(vbsp::basic::Entity::Brush(brush))
|
||||
|Ok(vbsp::basic::Entity::BrushIllusionary(brush))
|
||||
|Ok(vbsp::basic::Entity::BrushWall(brush))
|
||||
|Ok(vbsp::basic::Entity::BrushWallToggle(brush))=>{
|
||||
//The first character of brush.model is '*'
|
||||
match brush.model[1..].parse(){
|
||||
Ok(mesh_id)=>{
|
||||
world_models.push(model::Model{
|
||||
mesh:model::MeshId::new(mesh_id),
|
||||
attributes:ATTRIBUTE_DECORATION,
|
||||
transform:integer::Planar64Affine3::from_translation(
|
||||
valve_transform(brush.origin.into())
|
||||
),
|
||||
color:(glam::Vec3::from_array([
|
||||
brush.color.r as f32,
|
||||
brush.color.g as f32,
|
||||
brush.color.b as f32
|
||||
])/255.0).extend(1.0),
|
||||
});
|
||||
},
|
||||
Err(e)=>{
|
||||
println!("Brush model int parse error: {e}");
|
||||
},
|
||||
}
|
||||
},
|
||||
_=>(),
|
||||
}
|
||||
// THE CUBE OF DESTINY
|
||||
let destination_mesh_id=model::MeshId::new(world_meshes.len() as u32);
|
||||
world_meshes.push(crate::brush::unit_cube());
|
||||
|
||||
const WHITE:vbsp::Color=vbsp::Color{r:255,g:255,b:255};
|
||||
const ENTITY_ATTRIBUTE:attr::CollisionAttributesId=ATTRIBUTE_DECORATION;
|
||||
const ENTITY_TRIGGER_ATTRIBUTE:attr::CollisionAttributesId=ATTRIBUTE_INTERSECT_DEFAULT;
|
||||
for raw_ent in &bsp.entities{
|
||||
macro_rules! ent_brush_default{
|
||||
($entity:ident)=>{
|
||||
add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,$entity.model,$entity.origin,$entity.rendercolor,ENTITY_ATTRIBUTE)
|
||||
};
|
||||
} macro_rules! ent_brush_prop{
|
||||
($entity:ident)=>{
|
||||
add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,$entity.model,$entity.origin,WHITE,ENTITY_ATTRIBUTE)
|
||||
};
|
||||
}
|
||||
macro_rules! ent_brush_trigger{
|
||||
($entity:ident)=>{
|
||||
add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,$entity.model,$entity.origin,WHITE,ENTITY_TRIGGER_ATTRIBUTE)
|
||||
};
|
||||
}
|
||||
match raw_ent.parse(){
|
||||
Ok(vbsp::css::Entity::InfoPlayerCounterterrorist(spawn))=>{
|
||||
found_spawn=Some(valve_transform(spawn.origin.into()));
|
||||
},
|
||||
Ok(Entity::AmbientGeneric(_ambient_generic))=>(),
|
||||
Ok(Entity::Cycler(brush))=>ent_brush_default!(brush),
|
||||
Ok(Entity::EnvBeam(_env_beam))=>(),
|
||||
Ok(Entity::EnvBubbles(_env_bubbles))=>(),
|
||||
Ok(Entity::EnvDetailController(_env_detail_controller))=>(),
|
||||
Ok(Entity::EnvEmbers(_env_embers))=>(),
|
||||
Ok(Entity::EnvEntityMaker(_env_entity_maker))=>(),
|
||||
Ok(Entity::EnvExplosion(_env_explosion))=>(),
|
||||
Ok(Entity::EnvFade(_env_fade))=>(),
|
||||
Ok(Entity::EnvFire(_env_fire))=>(),
|
||||
Ok(Entity::EnvFireTrail(_env_fire_trail))=>(),
|
||||
Ok(Entity::EnvFiresource(_env_firesource))=>(),
|
||||
Ok(Entity::EnvFogController(_env_fog_controller))=>(),
|
||||
Ok(Entity::EnvHudhint(_env_hudhint))=>(),
|
||||
Ok(Entity::EnvLaser(_env_laser))=>(),
|
||||
Ok(Entity::EnvLightglow(_env_lightglow))=>(),
|
||||
Ok(Entity::EnvPhysexplosion(_env_physexplosion))=>(),
|
||||
Ok(Entity::EnvProjectedtexture(_env_projectedtexture))=>(),
|
||||
Ok(Entity::EnvScreenoverlay(_env_screenoverlay))=>(),
|
||||
Ok(Entity::EnvShake(_env_shake))=>(),
|
||||
Ok(Entity::EnvShooter(_env_shooter))=>(),
|
||||
Ok(Entity::EnvSmokestack(_env_smokestack))=>(),
|
||||
Ok(Entity::EnvSoundscape(_env_soundscape))=>(),
|
||||
Ok(Entity::EnvSoundscapeProxy(_env_soundscape_proxy))=>(),
|
||||
Ok(Entity::EnvSoundscapeTriggerable(_env_soundscape_triggerable))=>(),
|
||||
Ok(Entity::EnvSpark(_env_spark))=>(),
|
||||
Ok(Entity::EnvSprite(brush))=>ent_brush_default!(brush),
|
||||
Ok(Entity::EnvSpritetrail(_env_spritetrail))=>(),
|
||||
Ok(Entity::EnvSteam(_env_steam))=>(),
|
||||
Ok(Entity::EnvSun(_env_sun))=>(),
|
||||
Ok(Entity::EnvTonemapController(_env_tonemap_controller))=>(),
|
||||
Ok(Entity::EnvWind(_env_wind))=>(),
|
||||
// trigger_teleport.filtername probably has to do with one of these
|
||||
Ok(Entity::FilterActivatorClass(_filter_activator_class))=>(),
|
||||
Ok(Entity::FilterActivatorName(_filter_activator_name))=>(),
|
||||
Ok(Entity::FilterDamageType(_filter_damage_type))=>(),
|
||||
Ok(Entity::FilterMulti(_filter_multi))=>(),
|
||||
Ok(Entity::FuncAreaportal(_func_areaportal))=>(),
|
||||
Ok(Entity::FuncAreaportalwindow(_func_areaportalwindow))=>(),
|
||||
Ok(Entity::FuncBombTarget(_func_bomb_target))=>(),
|
||||
Ok(Entity::FuncBreakable(brush))=>ent_brush_default!(brush),
|
||||
Ok(Entity::FuncBreakableSurf(_func_breakable_surf))=>(),
|
||||
Ok(Entity::FuncBrush(brush))=>ent_brush_default!(brush),
|
||||
Ok(Entity::FuncButton(brush))=>ent_brush_default!(brush),
|
||||
Ok(Entity::FuncBuyzone(_func_buyzone))=>(),
|
||||
Ok(Entity::FuncClipVphysics(_func_clip_vphysics))=>(),
|
||||
Ok(Entity::FuncConveyor(_func_conveyor))=>(),
|
||||
// FuncDoor is Platform
|
||||
Ok(Entity::FuncDoor(brush))=>ent_brush_default!(brush),
|
||||
Ok(Entity::FuncDoorRotating(brush))=>ent_brush_default!(brush),
|
||||
Ok(Entity::FuncDustcloud(_func_dustcloud))=>(),
|
||||
Ok(Entity::FuncDustmotes(_func_dustmotes))=>(),
|
||||
Ok(Entity::FuncFishPool(_func_fish_pool))=>(),
|
||||
Ok(Entity::FuncFootstepControl(_func_footstep_control))=>(),
|
||||
Ok(Entity::FuncHostageRescue(_func_hostage_rescue))=>(),
|
||||
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::FuncLod(_func_lod))=>(),
|
||||
Ok(Entity::FuncMonitor(brush))=>ent_brush_default!(brush),
|
||||
Ok(Entity::FuncMovelinear(brush))=>ent_brush_default!(brush),
|
||||
Ok(Entity::FuncOccluder(_func_occluder))=>(),
|
||||
Ok(Entity::FuncPhysbox(brush))=>ent_brush_default!(brush),
|
||||
Ok(Entity::FuncPhysboxMultiplayer(brush))=>ent_brush_default!(brush),
|
||||
Ok(Entity::FuncPrecipitation(_func_precipitation))=>(),
|
||||
Ok(Entity::FuncRotButton(brush))=>ent_brush_prop!(brush),
|
||||
Ok(Entity::FuncRotating(brush))=>ent_brush_default!(brush),
|
||||
Ok(Entity::FuncSmokevolume(_func_smokevolume))=>(),
|
||||
Ok(Entity::FuncTracktrain(brush))=>ent_brush_default!(brush),
|
||||
Ok(Entity::FuncTrain(brush))=>ent_brush_default!(brush),
|
||||
Ok(Entity::FuncWall(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin.unwrap_or_default(),brush.rendercolor,ENTITY_ATTRIBUTE),
|
||||
Ok(Entity::FuncWallToggle(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin.unwrap_or_default(),brush.rendercolor,ENTITY_ATTRIBUTE),
|
||||
Ok(Entity::FuncWaterAnalog(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,brush.rendercolor.unwrap_or(WHITE),ENTITY_ATTRIBUTE),
|
||||
Ok(Entity::GamePlayerEquip(_game_player_equip))=>(),
|
||||
Ok(Entity::GameText(_game_text))=>(),
|
||||
Ok(Entity::GameUi(_game_ui))=>(),
|
||||
Ok(Entity::GameWeaponManager(_game_weapon_manager))=>(),
|
||||
Ok(Entity::HostageEntity(_hostage_entity))=>(),
|
||||
Ok(Entity::InfoCameraLink(_info_camera_link))=>(),
|
||||
Ok(Entity::InfoLadder(_info_ladder))=>(),
|
||||
Ok(Entity::InfoLightingRelative(_info_lighting_relative))=>(),
|
||||
Ok(Entity::InfoMapParameters(_info_map_parameters))=>(),
|
||||
Ok(Entity::InfoNode(_info_node))=>(),
|
||||
Ok(Entity::InfoNodeHint(_info_node_hint))=>(),
|
||||
Ok(Entity::InfoParticleSystem(_info_particle_system))=>(),
|
||||
Ok(Entity::InfoPlayerCounterterrorist(spawn))=>found_spawn=Some(spawn.origin),
|
||||
Ok(Entity::InfoPlayerLogo(_info_player_logo))=>(),
|
||||
Ok(Entity::InfoPlayerStart(_info_player_start))=>(),
|
||||
Ok(Entity::InfoPlayerTerrorist(spawn))=>found_spawn=Some(spawn.origin),
|
||||
Ok(Entity::InfoTarget(_info_target))=>(),
|
||||
// InfoTeleportDestination is Spawn#
|
||||
Ok(Entity::InfoTeleportDestination(_info_teleport_destination))=>(),
|
||||
Ok(Entity::Infodecal(_infodecal))=>(),
|
||||
Ok(Entity::KeyframeRope(_keyframe_rope))=>(),
|
||||
Ok(Entity::Light(_light))=>(),
|
||||
Ok(Entity::LightEnvironment(_light_environment))=>(),
|
||||
Ok(Entity::LightSpot(_light_spot))=>(),
|
||||
Ok(Entity::LogicAuto(_logic_auto))=>(),
|
||||
Ok(Entity::LogicBranch(_logic_branch))=>(),
|
||||
Ok(Entity::LogicCase(_logic_case))=>(),
|
||||
Ok(Entity::LogicCompare(_logic_compare))=>(),
|
||||
Ok(Entity::LogicMeasureMovement(_logic_measure_movement))=>(),
|
||||
Ok(Entity::LogicRelay(_logic_relay))=>(),
|
||||
Ok(Entity::LogicTimer(_logic_timer))=>(),
|
||||
Ok(Entity::MathCounter(_math_counter))=>(),
|
||||
Ok(Entity::MoveRope(_move_rope))=>(),
|
||||
Ok(Entity::PathTrack(_path_track))=>(),
|
||||
Ok(Entity::PhysBallsocket(_phys_ballsocket))=>(),
|
||||
Ok(Entity::PhysConstraint(_phys_constraint))=>(),
|
||||
Ok(Entity::PhysConstraintsystem(_phys_constraintsystem))=>(),
|
||||
Ok(Entity::PhysHinge(_phys_hinge))=>(),
|
||||
Ok(Entity::PhysKeepupright(_phys_keepupright))=>(),
|
||||
Ok(Entity::PhysLengthconstraint(_phys_lengthconstraint))=>(),
|
||||
Ok(Entity::PhysPulleyconstraint(_phys_pulleyconstraint))=>(),
|
||||
Ok(Entity::PhysRagdollconstraint(_phys_ragdollconstraint))=>(),
|
||||
Ok(Entity::PhysRagdollmagnet(_phys_ragdollmagnet))=>(),
|
||||
Ok(Entity::PhysThruster(_phys_thruster))=>(),
|
||||
Ok(Entity::PhysTorque(_phys_torque))=>(),
|
||||
Ok(Entity::PlayerSpeedmod(_player_speedmod))=>(),
|
||||
Ok(Entity::PlayerWeaponstrip(_player_weaponstrip))=>(),
|
||||
Ok(Entity::PointCamera(_point_camera))=>(),
|
||||
Ok(Entity::PointClientcommand(_point_clientcommand))=>(),
|
||||
Ok(Entity::PointDevshotCamera(_point_devshot_camera))=>(),
|
||||
Ok(Entity::PointServercommand(_point_servercommand))=>(),
|
||||
Ok(Entity::PointSpotlight(_point_spotlight))=>(),
|
||||
Ok(Entity::PointSurroundtest(_point_surroundtest))=>(),
|
||||
Ok(Entity::PointTemplate(_point_template))=>(),
|
||||
Ok(Entity::PointTesla(_point_tesla))=>(),
|
||||
Ok(Entity::PointViewcontrol(_point_viewcontrol))=>(),
|
||||
Ok(Entity::PropDoorRotating(brush))=>ent_brush_prop!(brush),
|
||||
Ok(Entity::PropDynamic(brush))=>ent_brush_prop!(brush),
|
||||
Ok(Entity::PropDynamicOverride(brush))=>ent_brush_prop!(brush),
|
||||
Ok(Entity::PropPhysics(brush))=>ent_brush_prop!(brush),
|
||||
Ok(Entity::PropPhysicsMultiplayer(brush))=>ent_brush_prop!(brush),
|
||||
Ok(Entity::PropPhysicsOverride(brush))=>ent_brush_prop!(brush),
|
||||
Ok(Entity::PropRagdoll(brush))=>ent_brush_prop!(brush),
|
||||
Ok(Entity::ShadowControl(_shadow_control))=>(),
|
||||
Ok(Entity::SkyCamera(_sky_camera))=>(),
|
||||
Ok(Entity::TriggerGravity(brush))=>ent_brush_trigger!(brush),
|
||||
Ok(Entity::TriggerHurt(brush))=>ent_brush_trigger!(brush),
|
||||
Ok(Entity::TriggerLook(brush))=>ent_brush_trigger!(brush),
|
||||
Ok(Entity::TriggerMultiple(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model.unwrap_or_default(),brush.origin,WHITE,ENTITY_TRIGGER_ATTRIBUTE),
|
||||
Ok(Entity::TriggerOnce(brush))=>ent_brush_trigger!(brush),
|
||||
Ok(Entity::TriggerProximity(brush))=>ent_brush_trigger!(brush),
|
||||
// TriggerPush is booster
|
||||
Ok(Entity::TriggerPush(brush))=>ent_brush_trigger!(brush),
|
||||
Ok(Entity::TriggerSoundscape(brush))=>ent_brush_trigger!(brush),
|
||||
// TriggerTeleport is Trigger#
|
||||
Ok(Entity::TriggerTeleport(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model.unwrap_or_default(),brush.origin,WHITE,ENTITY_TRIGGER_ATTRIBUTE),
|
||||
Ok(Entity::TriggerVphysicsMotion(brush))=>ent_brush_trigger!(brush),
|
||||
Ok(Entity::TriggerWind(brush))=>ent_brush_trigger!(brush),
|
||||
Ok(Entity::WaterLodControl(_water_lod_control))=>(),
|
||||
Ok(Entity::WeaponAk47(_weapon_ak47))=>(),
|
||||
Ok(Entity::WeaponAwp(_weapon_awp))=>(),
|
||||
Ok(Entity::WeaponDeagle(_weapon_deagle))=>(),
|
||||
Ok(Entity::WeaponElite(_weapon_elite))=>(),
|
||||
Ok(Entity::WeaponFamas(_weapon_famas))=>(),
|
||||
Ok(Entity::WeaponFiveseven(_weapon_fiveseven))=>(),
|
||||
Ok(Entity::WeaponFlashbang(_weapon_flashbang))=>(),
|
||||
Ok(Entity::WeaponG3sg1(_weapon_g3sg1))=>(),
|
||||
Ok(Entity::WeaponGlock(_weapon_glock))=>(),
|
||||
Ok(Entity::WeaponHegrenade(_weapon_hegrenade))=>(),
|
||||
Ok(Entity::WeaponKnife(_weapon_knife))=>(),
|
||||
Ok(Entity::WeaponM249(_weapon_m249))=>(),
|
||||
Ok(Entity::WeaponM3(_weapon_m3))=>(),
|
||||
Ok(Entity::WeaponM4a1(_weapon_m4a1))=>(),
|
||||
Ok(Entity::WeaponMac10(_weapon_mac10))=>(),
|
||||
Ok(Entity::WeaponP228(_weapon_p228))=>(),
|
||||
Ok(Entity::WeaponP90(_weapon_p90))=>(),
|
||||
Ok(Entity::WeaponScout(_weapon_scout))=>(),
|
||||
Ok(Entity::WeaponSg550(_weapon_sg550))=>(),
|
||||
Ok(Entity::WeaponSmokegrenade(_weapon_smokegrenade))=>(),
|
||||
Ok(Entity::WeaponTmp(_weapon_tmp))=>(),
|
||||
Ok(Entity::WeaponUmp45(_weapon_ump45))=>(),
|
||||
Ok(Entity::WeaponUsp(_weapon_usp))=>(),
|
||||
Ok(Entity::WeaponXm1014(_weapon_xm1014))=>(),
|
||||
Ok(Entity::Worldspawn(_worldspawn))=>(),
|
||||
Err(e)=>{
|
||||
println!("Bsp Entity parse error: {e}");
|
||||
},
|
||||
@ -177,9 +416,27 @@ pub fn convert<'a>(
|
||||
|
||||
// physics models
|
||||
for brush in &bsp.brushes{
|
||||
if !brush.flags.contains(vbsp::BrushFlags::SOLID){
|
||||
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);
|
||||
match mesh_result{
|
||||
Ok(mesh)=>{
|
||||
@ -187,7 +444,7 @@ pub fn convert<'a>(
|
||||
world_meshes.push(mesh);
|
||||
world_models.push(model::Model{
|
||||
mesh:mesh_id,
|
||||
attributes:ATTRIBUTE_CONTACT_DEFAULT,
|
||||
attributes,
|
||||
transform:integer::Planar64Affine3::new(
|
||||
integer::mat3::identity(),
|
||||
integer::vec3::ZERO,
|
||||
@ -201,27 +458,24 @@ pub fn convert<'a>(
|
||||
|
||||
let mut modes_list=Vec::new();
|
||||
if let Some(spawn_point)=found_spawn{
|
||||
// create a new mesh
|
||||
let mesh_id=model::MeshId::new(world_meshes.len() as u32);
|
||||
world_meshes.push(crate::brush::unit_cube());
|
||||
// create a new model
|
||||
let model_id=model::ModelId::new(world_models.len() as u32);
|
||||
world_models.push(model::Model{
|
||||
mesh:mesh_id,
|
||||
mesh:destination_mesh_id,
|
||||
attributes:ATTRIBUTE_INTERSECT_DEFAULT,
|
||||
transform:integer::Planar64Affine3::from_translation(spawn_point),
|
||||
transform:integer::Planar64Affine3::from_translation(valve_transform(spawn_point.into())),
|
||||
color:glam::Vec4::W,
|
||||
});
|
||||
|
||||
let first_stage=strafesnet_common::gameplay_modes::Stage::empty(model_id);
|
||||
let main_mode=strafesnet_common::gameplay_modes::Mode::new(
|
||||
let first_stage=Stage::empty(model_id);
|
||||
let main_mode=Mode::new(
|
||||
strafesnet_common::gameplay_style::StyleModifiers::source_bhop(),
|
||||
model_id,
|
||||
std::collections::HashMap::new(),
|
||||
vec![first_stage],
|
||||
std::collections::HashMap::new(),
|
||||
);
|
||||
modes_list.push(main_mode);
|
||||
modes_list.push(NormalizedMode::new(main_mode));
|
||||
}
|
||||
|
||||
PartialMap1{
|
||||
@ -229,17 +483,17 @@ pub fn convert<'a>(
|
||||
world_meshes,
|
||||
prop_models,
|
||||
world_models,
|
||||
modes:strafesnet_common::gameplay_modes::Modes::new(modes_list),
|
||||
modes:NormalizedModes::new(modes_list),
|
||||
}
|
||||
}
|
||||
|
||||
//partially constructed map types
|
||||
pub struct PartialMap1{
|
||||
attributes:Vec<strafesnet_common::gameplay_attributes::CollisionAttributes>,
|
||||
attributes:Vec<attr::CollisionAttributes>,
|
||||
prop_models:Vec<model::Model>,
|
||||
world_meshes:Vec<model::Mesh>,
|
||||
world_models:Vec<model::Model>,
|
||||
modes:strafesnet_common::gameplay_modes::Modes,
|
||||
modes:NormalizedModes,
|
||||
}
|
||||
impl PartialMap1{
|
||||
pub fn add_prop_meshes<'a>(
|
||||
@ -257,12 +511,12 @@ impl PartialMap1{
|
||||
}
|
||||
}
|
||||
pub struct PartialMap2{
|
||||
attributes:Vec<strafesnet_common::gameplay_attributes::CollisionAttributes>,
|
||||
attributes:Vec<attr::CollisionAttributes>,
|
||||
prop_meshes:Vec<(model::MeshId,model::Mesh)>,
|
||||
prop_models:Vec<model::Model>,
|
||||
world_meshes:Vec<model::Mesh>,
|
||||
world_models:Vec<model::Model>,
|
||||
modes:strafesnet_common::gameplay_modes::Modes,
|
||||
modes:NormalizedModes,
|
||||
}
|
||||
impl PartialMap2{
|
||||
pub fn add_render_configs_and_textures(
|
||||
|
@ -47,7 +47,7 @@ pub enum MeshError{
|
||||
Io(std::io::Error),
|
||||
VMDL(vmdl::ModelError),
|
||||
VBSP(vbsp::BspError),
|
||||
MissingMdl,
|
||||
MissingMdl(String),
|
||||
MissingVtx,
|
||||
MissingVvd,
|
||||
}
|
||||
@ -132,7 +132,7 @@ impl<'bsp,'vpk,'a> Loader for ModelLoader<'bsp,'vpk,'a>
|
||||
vvd_path.set_extension("vvd");
|
||||
vtx_path.set_extension("dx90.vtx");
|
||||
// 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 vvd=self.finder.find(vvd_path.as_os_str().to_str().unwrap())?.ok_or(MeshError::MissingVvd)?;
|
||||
Ok(vmdl::Model::from_parts(
|
||||
|
@ -1,7 +1,7 @@
|
||||
[package]
|
||||
name = "strafesnet_common"
|
||||
version = "0.6.0"
|
||||
edition = "2021"
|
||||
edition = "2024"
|
||||
repository = "https://git.itzana.me/StrafesNET/strafe-project"
|
||||
license = "MIT OR Apache-2.0"
|
||||
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"] }
|
||||
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" }
|
||||
glam = "0.29.0"
|
||||
glam = "0.30.0"
|
||||
id = { version = "0.1.0", registry = "strafesnet" }
|
||||
|
@ -7,42 +7,57 @@ pub struct Aabb{
|
||||
}
|
||||
|
||||
impl Default for Aabb{
|
||||
#[inline]
|
||||
fn default()->Self{
|
||||
Self{min:vec3::MAX,max:vec3::MIN}
|
||||
}
|
||||
}
|
||||
|
||||
impl Aabb{
|
||||
#[inline]
|
||||
pub const fn new(min:Planar64Vec3,max:Planar64Vec3)->Self{
|
||||
Self{min,max}
|
||||
}
|
||||
#[inline]
|
||||
pub const fn max(&self)->Planar64Vec3{
|
||||
self.max
|
||||
}
|
||||
#[inline]
|
||||
pub const fn min(&self)->Planar64Vec3{
|
||||
self.min
|
||||
}
|
||||
#[inline]
|
||||
pub fn grow(&mut self,point:Planar64Vec3){
|
||||
self.min=self.min.min(point);
|
||||
self.max=self.max.max(point);
|
||||
}
|
||||
#[inline]
|
||||
pub fn join(&mut self,aabb:&Aabb){
|
||||
self.min=self.min.min(aabb.min);
|
||||
self.max=self.max.max(aabb.max);
|
||||
}
|
||||
#[inline]
|
||||
pub fn inflate(&mut self,hs:Planar64Vec3){
|
||||
self.min-=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{
|
||||
let bvec=self.min.lt(aabb.max)&aabb.min.lt(self.max);
|
||||
bvec.all()
|
||||
}
|
||||
#[inline]
|
||||
pub fn size(&self)->Planar64Vec3{
|
||||
self.max-self.min
|
||||
}
|
||||
#[inline]
|
||||
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
|
||||
// pub fn area_weight(&self)->f32{
|
||||
|
@ -1,4 +1,10 @@
|
||||
use std::cmp::Ordering;
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use crate::aabb::Aabb;
|
||||
use crate::ray::Ray;
|
||||
use crate::integer::{Ratio,Planar64};
|
||||
use crate::instruction::{InstructionCollector,TimedInstruction};
|
||||
|
||||
//da algaritum
|
||||
//lista boxens
|
||||
@ -10,6 +16,96 @@ use crate::aabb::Aabb;
|
||||
//sort the centerpoints on each axis (3 lists)
|
||||
//bv is put into octant based on whether it is upper or lower in each list
|
||||
|
||||
|
||||
pub fn intersect_aabb(ray:&Ray,aabb:&Aabb)->Option<Ratio<Planar64,Planar64>>{
|
||||
// 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
|
||||
}
|
||||
|
||||
pub enum RecursiveContent<N,L>{
|
||||
Branch(Vec<N>),
|
||||
Leaf(L),
|
||||
@ -44,6 +140,92 @@ impl<L> BvhNode<L>{
|
||||
},
|
||||
}
|
||||
}
|
||||
fn populate_nodes<'a,T,F>(
|
||||
&'a self,
|
||||
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{
|
||||
if child.aabb.contains(ray.origin){
|
||||
child.populate_nodes(collector,nodes,ray,start_time,f);
|
||||
}else{
|
||||
// Am I an upcoming superstar?
|
||||
if let Some(t)=intersect_aabb(ray,&child.aabb){
|
||||
if start_time.lt_ratio(t)&&t.lt_ratio(collector.time()){
|
||||
nodes.insert(t,child);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
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();
|
||||
|
||||
let start_time=start_time.into();
|
||||
let time_limit=time_limit.into();
|
||||
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 fn into_inner(self)->(RecursiveContent<BvhNode<L>,L>,Aabb){
|
||||
(self.content,self.aabb)
|
||||
}
|
||||
|
@ -1,7 +1,6 @@
|
||||
use std::collections::{HashSet,HashMap};
|
||||
use crate::model::ModelId;
|
||||
use crate::gameplay_style;
|
||||
use crate::updatable::Updatable;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct StageElement{
|
||||
@ -128,18 +127,6 @@ impl Stage{
|
||||
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)]
|
||||
pub enum Zone{
|
||||
@ -211,34 +198,47 @@ impl Mode{
|
||||
pub fn push_stage(&mut self,stage:Stage){
|
||||
self.stages.push(stage)
|
||||
}
|
||||
pub fn get_stage_mut(&mut self,stage:StageId)->Option<&mut Stage>{
|
||||
self.stages.get_mut(stage.0 as usize)
|
||||
pub fn get_stage_mut(&mut self,StageId(stage_id):StageId)->Option<&mut Stage>{
|
||||
self.stages.get_mut(stage_id as usize)
|
||||
}
|
||||
pub fn get_spawn_model_id(&self,stage:StageId)->Option<ModelId>{
|
||||
self.stages.get(stage.0 as usize).map(|s|s.spawn)
|
||||
pub fn get_spawn_model_id(&self,StageId(stage_id):StageId)->Option<ModelId>{
|
||||
self.stages.get(stage_id as usize).map(|s|s.spawn)
|
||||
}
|
||||
pub fn get_zone(&self,model_id:ModelId)->Option<&Zone>{
|
||||
self.zones.get(&model_id)
|
||||
}
|
||||
pub fn get_stage(&self,stage_id:StageId)->Option<&Stage>{
|
||||
self.stages.get(stage_id.0 as usize)
|
||||
pub fn get_stage(&self,StageId(stage_id):StageId)->Option<&Stage>{
|
||||
self.stages.get(stage_id as usize)
|
||||
}
|
||||
pub fn get_element(&self,model_id:ModelId)->Option<&StageElement>{
|
||||
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
|
||||
pub fn denormalize_data(&mut self){
|
||||
pub fn denormalize(self)->Mode{
|
||||
let NormalizedMode(mut mode)=self;
|
||||
//expand and index normalized data
|
||||
self.zones.insert(self.start,Zone::Start);
|
||||
for (stage_id,stage) in self.stages.iter().enumerate(){
|
||||
self.elements.insert(stage.spawn,StageElement{
|
||||
mode.zones.insert(mode.start,Zone::Start);
|
||||
for (stage_id,stage) in mode.stages.iter().enumerate(){
|
||||
mode.elements.insert(stage.spawn,StageElement{
|
||||
stage_id:StageId(stage_id as u32),
|
||||
force:false,
|
||||
behaviour:StageElementBehaviour::SpawnAt,
|
||||
jump_limit:None,
|
||||
});
|
||||
for (_,&model) in &stage.ordered_checkpoints{
|
||||
self.elements.insert(model,StageElement{
|
||||
mode.elements.insert(model,StageElement{
|
||||
stage_id:StageId(stage_id as u32),
|
||||
force:false,
|
||||
behaviour:StageElementBehaviour::Checkpoint,
|
||||
@ -246,7 +246,7 @@ impl Mode{
|
||||
});
|
||||
}
|
||||
for &model in &stage.unordered_checkpoints{
|
||||
self.elements.insert(model,StageElement{
|
||||
mode.elements.insert(model,StageElement{
|
||||
stage_id:StageId(stage_id as u32),
|
||||
force:false,
|
||||
behaviour:StageElementBehaviour::Checkpoint,
|
||||
@ -254,53 +254,13 @@ impl 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);
|
||||
}
|
||||
mode
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default,Clone)]
|
||||
pub struct Modes{
|
||||
pub modes:Vec<Mode>,
|
||||
modes:Vec<Mode>,
|
||||
}
|
||||
impl Modes{
|
||||
pub const fn new(modes:Vec<Mode>)->Self{
|
||||
@ -314,19 +274,182 @@ impl Modes{
|
||||
pub fn push_mode(&mut self,mode:Mode){
|
||||
self.modes.push(mode)
|
||||
}
|
||||
pub fn get_mode(&self,mode:ModeId)->Option<&Mode>{
|
||||
self.modes.get(mode.0 as usize)
|
||||
pub fn get_mode(&self,ModeId(mode_id):ModeId)->Option<&Mode>{
|
||||
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{
|
||||
fn update(&mut self,update:ModesUpdate){
|
||||
for (mode,mode_update) in update.modes{
|
||||
if let Some(mode)=self.modes.get_mut(mode.0 as usize){
|
||||
mode.update(mode_update);
|
||||
}
|
||||
impl NormalizedModes{
|
||||
pub fn new(modes:Vec<NormalizedMode>)->Self{
|
||||
Self{modes}
|
||||
}
|
||||
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,
|
||||
jump_dir:Planar64Vec3,
|
||||
gravity:&Planar64Vec3,
|
||||
mass:Planar64,
|
||||
_mass:Planar64,
|
||||
)->Planar64Vec3{
|
||||
match self{
|
||||
&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()
|
||||
},
|
||||
&JumpImpulse::Linear(jump_speed)=>velocity+(jump_dir*jump_speed/jump_dir.length()).divide().fix_1(),
|
||||
&JumpImpulse::Energy(energy)=>{
|
||||
&JumpImpulse::Energy(_energy)=>{
|
||||
//calculate energy
|
||||
//let e=gravity.dot(velocity);
|
||||
//add
|
||||
@ -355,7 +355,7 @@ pub struct LadderSettings{
|
||||
pub dot:Planar64,
|
||||
}
|
||||
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
|
||||
self.accelerate.accel
|
||||
}
|
||||
@ -529,12 +529,12 @@ impl StyleModifiers{
|
||||
|
||||
pub fn source_bhop()->Self{
|
||||
Self{
|
||||
controls_mask:Controls::all()-Controls::MoveUp-Controls::MoveDown,
|
||||
controls_mask:Controls::all(),
|
||||
controls_mask_state:Controls::all(),
|
||||
strafe:Some(StrafeSettings{
|
||||
enable:ControlsActivation::full_2d(),
|
||||
air_accel_limit:Some(Planar64::raw(150<<28)*100),
|
||||
mv:(Planar64::raw(30)*VALVE_SCALE).fix_1(),
|
||||
air_accel_limit:Some(Planar64::raw((150<<28)*100)),
|
||||
mv:Planar64::raw(30<<28),
|
||||
tick_rate:Ratio64::new(100,AbsoluteTime::ONE_SECOND.get() as u64).unwrap(),
|
||||
}),
|
||||
jump:Some(JumpSettings{
|
||||
@ -570,12 +570,12 @@ impl StyleModifiers{
|
||||
}
|
||||
pub fn source_surf()->Self{
|
||||
Self{
|
||||
controls_mask:Controls::all()-Controls::MoveUp-Controls::MoveDown,
|
||||
controls_mask:Controls::all(),
|
||||
controls_mask_state:Controls::all(),
|
||||
strafe:Some(StrafeSettings{
|
||||
enable:ControlsActivation::full_2d(),
|
||||
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(),
|
||||
}),
|
||||
jump:Some(JumpSettings{
|
||||
|
@ -4,11 +4,11 @@ pub use ratio_ops::ratio::{Ratio,Divide};
|
||||
//integer units
|
||||
|
||||
/// 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 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>);
|
||||
impl<T> Time<T>{
|
||||
pub const MIN:Self=Self::raw(i64::MIN);
|
||||
|
@ -1,5 +1,6 @@
|
||||
pub mod bvh;
|
||||
pub mod map;
|
||||
pub mod ray;
|
||||
pub mod run;
|
||||
pub mod aabb;
|
||||
pub mod model;
|
||||
@ -8,7 +9,6 @@ pub mod timer;
|
||||
pub mod integer;
|
||||
pub mod physics;
|
||||
pub mod session;
|
||||
pub mod updatable;
|
||||
pub mod instruction;
|
||||
pub mod gameplay_attributes;
|
||||
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
|
||||
//TODO: use snf::map::Region to update the data in physics and graphics instead of this
|
||||
pub struct CompleteMap{
|
||||
pub modes:gameplay_modes::Modes,
|
||||
pub modes:gameplay_modes::NormalizedModes,
|
||||
pub attributes:Vec<gameplay_attributes::CollisionAttributes>,
|
||||
pub meshes:Vec<model::Mesh>,
|
||||
pub models:Vec<model::Model>,
|
||||
|
@ -1,7 +1,7 @@
|
||||
use crate::mouse::MouseState;
|
||||
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 type Time=crate::integer::Time<TimeInner>;
|
||||
|
||||
|
20
lib/common/src/ray.rs
Normal file
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};
|
||||
|
||||
#[derive(Clone,Copy,Hash,Eq,PartialEq,PartialOrd,Debug)]
|
||||
#[derive(Clone,Copy,Hash,Eq,PartialEq,Ord,PartialOrd,Debug)]
|
||||
pub enum 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 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]
|
||||
name = "strafesnet_deferred_loader"
|
||||
version = "0.5.0"
|
||||
edition = "2021"
|
||||
edition = "2024"
|
||||
repository = "https://git.itzana.me/StrafesNET/strafe-project"
|
||||
license = "MIT OR Apache-2.0"
|
||||
description = "Acquire IDs for objects before loading them in bulk."
|
||||
|
@ -1,7 +1,7 @@
|
||||
[package]
|
||||
name = "fixed_wide"
|
||||
version = "0.1.2"
|
||||
edition = "2021"
|
||||
edition = "2024"
|
||||
repository = "https://git.itzana.me/StrafesNET/strafe-project"
|
||||
license = "MIT OR Apache-2.0"
|
||||
description = "Fixed point numbers with optional widening Mul operator."
|
||||
@ -14,7 +14,7 @@ wide-mul=[]
|
||||
zeroes=["dep:arrayvec"]
|
||||
|
||||
[dependencies]
|
||||
bnum = "0.12.0"
|
||||
bnum = "0.13.0"
|
||||
arrayvec = { version = "0.7.6", optional = true }
|
||||
paste = "1.0.15"
|
||||
ratio_ops = { version = "0.1.0", path = "../ratio_ops", registry = "strafesnet", optional = true }
|
||||
|
@ -33,6 +33,14 @@ impl<const N:usize,const F:usize> Fixed<N,F>{
|
||||
self.bits
|
||||
}
|
||||
#[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{
|
||||
let mut digits=[0u64;N];
|
||||
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{
|
||||
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>{
|
||||
/// 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>]();
|
||||
//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(){
|
||||
let new_result={
|
||||
let mut bits=result.to_bits().to_bits();
|
||||
bits.set_bit(shift,true);
|
||||
Self::from_bits(BInt::from_bits(bits))
|
||||
};
|
||||
if new_result.[<wide_mul_ $n _ $n>](new_result)<=wide_self{
|
||||
result=new_result;
|
||||
result.as_bits_mut().as_bits_mut().set_bit(shift,true);
|
||||
if wide_self<result.[<wide_mul_ $n _ $n>](result){
|
||||
// put it back lol
|
||||
result.as_bits_mut().as_bits_mut().set_bit(shift,false);
|
||||
}
|
||||
}
|
||||
result
|
||||
|
@ -1,7 +1,7 @@
|
||||
[package]
|
||||
name = "linear_ops"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
edition = "2024"
|
||||
repository = "https://git.itzana.me/StrafesNET/strafe-project"
|
||||
license = "MIT OR Apache-2.0"
|
||||
description = "Vector/Matrix operations using trait bounds."
|
||||
|
@ -1,7 +1,7 @@
|
||||
[package]
|
||||
name = "ratio_ops"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
version = "0.1.1"
|
||||
edition = "2024"
|
||||
repository = "https://git.itzana.me/StrafesNET/strafe-project"
|
||||
license = "MIT OR Apache-2.0"
|
||||
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{}
|
||||
|
||||
// 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>
|
||||
where
|
||||
LhsNum:Copy,
|
||||
LhsDen:Copy,
|
||||
LhsDen:Copy+Parity,
|
||||
RhsNum:Copy,
|
||||
RhsDen:Copy,
|
||||
RhsDen:Copy+Parity,
|
||||
LhsNum:core::ops::Mul<RhsDen,Output=T>,
|
||||
LhsDen:core::ops::Mul<RhsNum,Output=T>,
|
||||
RhsNum:core::ops::Mul<LhsDen,Output=U>,
|
||||
T:PartialOrd<U>,
|
||||
RhsDen:core::ops::Mul<LhsNum,Output=U>,
|
||||
T:PartialOrd<U>+Ord,
|
||||
{
|
||||
#[inline]
|
||||
fn partial_cmp(&self,other:&Ratio<RhsNum,RhsDen>)->Option<core::cmp::Ordering>{
|
||||
(self.num*other.den).partial_cmp(&(other.num*self.den))
|
||||
fn partial_cmp(&self,&other:&Ratio<RhsNum,RhsDen>)->Option<core::cmp::Ordering>{
|
||||
self.partial_cmp_ratio(other)
|
||||
}
|
||||
}
|
||||
impl<Num,Den,T> Ord for Ratio<Num,Den>
|
||||
where
|
||||
Num:Copy,
|
||||
Den:Copy,
|
||||
Den:Copy+Parity,
|
||||
Num:core::ops::Mul<Den,Output=T>,
|
||||
Den:core::ops::Mul<Num,Output=T>,
|
||||
T:Ord,
|
||||
{
|
||||
#[inline]
|
||||
fn cmp(&self,other:&Self)->std::cmp::Ordering{
|
||||
(self.num*other.den).cmp(&(other.num*self.den))
|
||||
fn cmp(&self,&other:&Self)->std::cmp::Ordering{
|
||||
self.cmp_ratio(other)
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
[package]
|
||||
name = "strafesnet_rbx_loader"
|
||||
version = "0.6.0"
|
||||
edition = "2021"
|
||||
edition = "2024"
|
||||
repository = "https://git.itzana.me/StrafesNET/strafe-project"
|
||||
license = "MIT OR Apache-2.0"
|
||||
description = "Convert Roblox place and model files to StrafesNET data structures."
|
||||
@ -11,7 +11,7 @@ authors = ["Rhys Lloyd <krakow20@gmail.com>"]
|
||||
|
||||
[dependencies]
|
||||
bytemuck = "1.14.3"
|
||||
glam = "0.29.0"
|
||||
glam = "0.30.0"
|
||||
lazy-regex = "3.1.0"
|
||||
rbx_binary = { version = "0.7.4", registry = "strafesnet" }
|
||||
rbx_dom_weak = { version = "2.7.0", registry = "strafesnet" }
|
||||
|
@ -84,13 +84,13 @@ where
|
||||
fn ingest_faces2_lods3(
|
||||
polygon_groups:&mut Vec<PolygonGroup>,
|
||||
vertex_id_map:&HashMap<rbx_mesh::mesh::VertexId2,VertexId>,
|
||||
faces:&Vec<rbx_mesh::mesh::Face2>,
|
||||
lods:&Vec<rbx_mesh::mesh::Lod3>
|
||||
faces:&[rbx_mesh::mesh::Face2],
|
||||
lods:&[rbx_mesh::mesh::Lod3],
|
||||
){
|
||||
//faces have to be split into polygon groups based on lod
|
||||
polygon_groups.extend(lods.windows(2).map(|lod_pair|
|
||||
PolygonGroup::PolygonList(PolygonList::new(faces[lod_pair[0].0 as usize..lod_pair[1].0 as usize].iter().map(|face|
|
||||
vec![vertex_id_map[&face.0],vertex_id_map[&face.1],vertex_id_map[&face.2]]
|
||||
PolygonGroup::PolygonList(PolygonList::new(faces[lod_pair[0].0 as usize..lod_pair[1].0 as usize].iter().map(|rbx_mesh::mesh::Face2(v0,v1,v2)|
|
||||
vec![vertex_id_map[&v0],vertex_id_map[&v1],vertex_id_map[&v2]]
|
||||
).collect()))
|
||||
))
|
||||
}
|
||||
|
@ -4,12 +4,11 @@ use crate::primitives;
|
||||
use strafesnet_common::aabb::Aabb;
|
||||
use strafesnet_common::map;
|
||||
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_attributes as attr;
|
||||
use strafesnet_common::integer::{self,vec3,Planar64,Planar64Vec3,Planar64Mat3,Planar64Affine3};
|
||||
use strafesnet_common::model::RenderConfigId;
|
||||
use strafesnet_common::updatable::Updatable;
|
||||
use strafesnet_deferred_loader::deferred_loader::{RenderConfigDeferredLoader,MeshDeferredLoader};
|
||||
use strafesnet_deferred_loader::mesh::Meshes;
|
||||
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()
|
||||
)
|
||||
}
|
||||
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{
|
||||
let mut general=attr::GeneralAttributes::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_intersecting=true;
|
||||
modes_builder.insert_mode(
|
||||
gameplay_modes::ModeId::MAIN,
|
||||
gameplay_modes::Mode::empty(
|
||||
ModeId::MAIN,
|
||||
Mode::empty(
|
||||
gameplay_style::StyleModifiers::roblox_bhop(),
|
||||
model_id
|
||||
)
|
||||
@ -178,10 +91,10 @@ fn get_attributes(name:&str,can_collide:bool,velocity:Planar64Vec3,model_id:mode
|
||||
force_can_collide=false;
|
||||
force_intersecting=true;
|
||||
modes_builder.push_mode_update(
|
||||
gameplay_modes::ModeId::MAIN,
|
||||
gameplay_modes::ModeUpdate::zone(
|
||||
ModeId::MAIN,
|
||||
ModeUpdate::zone(
|
||||
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_intersecting=true;
|
||||
modes_builder.push_mode_update(
|
||||
gameplay_modes::ModeId::MAIN,
|
||||
gameplay_modes::ModeUpdate::zone(
|
||||
ModeId::MAIN,
|
||||
ModeUpdate::zone(
|
||||
model_id,
|
||||
gameplay_modes::Zone::Anticheat,
|
||||
Zone::Anticheat,
|
||||
),
|
||||
);
|
||||
},
|
||||
"Platform"=>{
|
||||
modes_builder.push_mode_update(
|
||||
gameplay_modes::ModeId::MAIN,
|
||||
gameplay_modes::ModeUpdate::element(
|
||||
ModeId::MAIN,
|
||||
ModeUpdate::element(
|
||||
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_intersecting=true;
|
||||
modes_builder.insert_mode(
|
||||
gameplay_modes::ModeId::new(captures[2].parse::<u32>().unwrap()),
|
||||
gameplay_modes::Mode::empty(
|
||||
ModeId::new(captures[2].parse::<u32>().unwrap()),
|
||||
Mode::empty(
|
||||
gameplay_style::StyleModifiers::roblox_bhop(),
|
||||
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+)$")
|
||||
.captures(other){
|
||||
force_intersecting=true;
|
||||
let stage_id=gameplay_modes::StageId::new(captures[3].parse::<u32>().unwrap());
|
||||
let stage_element=gameplay_modes::StageElement::new(
|
||||
let stage_id=StageId::new(captures[3].parse::<u32>().unwrap());
|
||||
let stage_element=StageElement::new(
|
||||
//stage_id:
|
||||
stage_id,
|
||||
//force:
|
||||
@ -244,26 +157,26 @@ fn get_attributes(name:&str,can_collide:bool,velocity:Planar64Vec3,model_id:mode
|
||||
match &captures[2]{
|
||||
"Spawn"=>{
|
||||
modes_builder.insert_stage(
|
||||
gameplay_modes::ModeId::MAIN,
|
||||
ModeId::MAIN,
|
||||
stage_id,
|
||||
gameplay_modes::Stage::empty(model_id),
|
||||
Stage::empty(model_id),
|
||||
);
|
||||
//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
|
||||
//NOT a decoration
|
||||
"Trigger"=>{force_can_collide=false;gameplay_modes::StageElementBehaviour::Trigger},
|
||||
"Teleport"=>{force_can_collide=false;gameplay_modes::StageElementBehaviour::Teleport},
|
||||
"Platform"=>gameplay_modes::StageElementBehaviour::Platform,
|
||||
"Trigger"=>{force_can_collide=false;StageElementBehaviour::Trigger},
|
||||
"Teleport"=>{force_can_collide=false;StageElementBehaviour::Teleport},
|
||||
"Platform"=>StageElementBehaviour::Platform,
|
||||
_=>panic!("regex1[2] messed up bad"),
|
||||
},
|
||||
None
|
||||
);
|
||||
modes_builder.push_mode_update(
|
||||
gameplay_modes::ModeId::MAIN,
|
||||
gameplay_modes::ModeUpdate::element(
|
||||
ModeId::MAIN,
|
||||
ModeUpdate::element(
|
||||
model_id,
|
||||
stage_element,
|
||||
),
|
||||
@ -272,14 +185,14 @@ fn get_attributes(name:&str,can_collide:bool,velocity:Planar64Vec3,model_id:mode
|
||||
.captures(other){
|
||||
match &captures[1]{
|
||||
"Jump"=>modes_builder.push_mode_update(
|
||||
gameplay_modes::ModeId::MAIN,
|
||||
gameplay_modes::ModeUpdate::element(
|
||||
ModeId::MAIN,
|
||||
ModeUpdate::element(
|
||||
model_id,
|
||||
//jump_limit:
|
||||
gameplay_modes::StageElement::new(
|
||||
gameplay_modes::StageId::FIRST,
|
||||
StageElement::new(
|
||||
StageId::FIRST,
|
||||
false,
|
||||
gameplay_modes::StageElementBehaviour::Check,
|
||||
StageElementBehaviour::Check,
|
||||
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_intersecting=true;
|
||||
modes_builder.push_mode_update(
|
||||
gameplay_modes::ModeId::new(captures[2].parse::<u32>().unwrap()),
|
||||
gameplay_modes::ModeUpdate::zone(
|
||||
ModeId::new(captures[2].parse::<u32>().unwrap()),
|
||||
ModeUpdate::zone(
|
||||
model_id,
|
||||
//zone:
|
||||
match &captures[1]{
|
||||
"Finish"=>gameplay_modes::Zone::Finish,
|
||||
"Anticheat"=>gameplay_modes::Zone::Anticheat,
|
||||
"Finish"=>Zone::Finish,
|
||||
"Anticheat"=>Zone::Anticheat,
|
||||
_=>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){
|
||||
// match &captures[1]{
|
||||
// "OrderedCheckpoint"=>modes_builder.push_stage_update(
|
||||
// gameplay_modes::ModeId::MAIN,
|
||||
// gameplay_modes::StageId::new(0),
|
||||
// gameplay_modes::StageUpdate::ordered_checkpoint(captures[2].parse::<u32>().unwrap()),
|
||||
// ModeId::MAIN,
|
||||
// StageId::new(0),
|
||||
// StageUpdate::ordered_checkpoint(captures[2].parse::<u32>().unwrap()),
|
||||
// ),
|
||||
// _=>panic!("regex3[1] messed up bad"),
|
||||
// }
|
||||
@ -1009,7 +922,7 @@ impl PartialMap1<'_>{
|
||||
PartialMap2{
|
||||
meshes:self.primitive_meshes,
|
||||
models,
|
||||
modes:modes_builder.build(),
|
||||
modes:modes_builder.build_normalized(),
|
||||
attributes:unique_attributes,
|
||||
}
|
||||
}
|
||||
@ -1018,7 +931,7 @@ impl PartialMap1<'_>{
|
||||
pub struct PartialMap2{
|
||||
meshes:Vec<model::Mesh>,
|
||||
models:Vec<model::Model>,
|
||||
modes:gameplay_modes::Modes,
|
||||
modes:NormalizedModes,
|
||||
attributes:Vec<strafesnet_common::gameplay_attributes::CollisionAttributes>,
|
||||
}
|
||||
impl PartialMap2{
|
||||
|
@ -1,7 +1,7 @@
|
||||
[package]
|
||||
name = "rbxassetid"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
edition = "2024"
|
||||
repository = "https://git.itzana.me/StrafesNET/strafe-project"
|
||||
license = "MIT OR Apache-2.0"
|
||||
description = "Parse Roblox asset id from 'Content' urls."
|
||||
|
@ -1,7 +1,7 @@
|
||||
[package]
|
||||
name = "roblox_emulator"
|
||||
version = "0.4.7"
|
||||
edition = "2021"
|
||||
edition = "2024"
|
||||
repository = "https://git.itzana.me/StrafesNET/strafe-project"
|
||||
license = "MIT OR Apache-2.0"
|
||||
description = "Run embedded Luau scripts which manipulate the DOM."
|
||||
@ -12,7 +12,7 @@ default=["run-service"]
|
||||
run-service=[]
|
||||
|
||||
[dependencies]
|
||||
glam = "0.29.0"
|
||||
glam = "0.30.0"
|
||||
mlua = { version = "0.10.1", features = ["luau"] }
|
||||
phf = { version = "0.11.2", features = ["macros"] }
|
||||
rbx_dom_weak = { version = "2.7.0", registry = "strafesnet" }
|
||||
|
@ -1,7 +1,7 @@
|
||||
[package]
|
||||
name = "strafesnet_snf"
|
||||
version = "0.3.0"
|
||||
edition = "2021"
|
||||
edition = "2024"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
|
@ -138,7 +138,7 @@ struct MapHeader{
|
||||
//#[br(count=num_resources_external)]
|
||||
//external_resources:Vec<ResourceExternalHeader>,
|
||||
#[br(count=num_modes)]
|
||||
modes:Vec<newtypes::gameplay_modes::Mode>,
|
||||
modes:Vec<newtypes::gameplay_modes::NormalizedMode>,
|
||||
#[br(count=num_attributes)]
|
||||
attributes:Vec<newtypes::gameplay_attributes::CollisionAttributes>,
|
||||
#[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>{
|
||||
file:crate::file::File<R>,
|
||||
//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
|
||||
attributes:Vec<strafesnet_common::gameplay_attributes::CollisionAttributes>,
|
||||
//this is every possible render configuration... shaders and such... need streaming
|
||||
@ -223,7 +223,7 @@ impl<R:BinReaderExt> StreamableMap<R>{
|
||||
}
|
||||
Ok(Self{
|
||||
file,
|
||||
modes:strafesnet_common::gameplay_modes::Modes::new(modes),
|
||||
modes:strafesnet_common::gameplay_modes::NormalizedModes::new(modes),
|
||||
attributes,
|
||||
render_configs,
|
||||
bvh:strafesnet_common::bvh::generate_bvh(bvh),
|
||||
@ -430,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_resource_blocks:resource_blocks.len() as u32,
|
||||
//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_render_configs:map.render_configs.len() as u32,
|
||||
spacial_blocks,
|
||||
resource_blocks,
|
||||
//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(),
|
||||
render_configs:map.render_configs.into_iter().map(Into::into).collect(),
|
||||
};
|
||||
|
@ -157,7 +157,7 @@ pub struct ModeHeader{
|
||||
}
|
||||
#[binrw::binrw]
|
||||
#[brw(little)]
|
||||
pub struct Mode{
|
||||
pub struct NormalizedMode{
|
||||
pub header:ModeHeader,
|
||||
pub style:super::gameplay_style::StyleModifiers,
|
||||
pub start:u32,
|
||||
@ -179,10 +179,10 @@ impl std::fmt::Display 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;
|
||||
fn try_into(self)->Result<strafesnet_common::gameplay_modes::Mode,Self::Error>{
|
||||
Ok(strafesnet_common::gameplay_modes::Mode::new(
|
||||
fn try_into(self)->Result<strafesnet_common::gameplay_modes::NormalizedMode,Self::Error>{
|
||||
Ok(strafesnet_common::gameplay_modes::NormalizedMode::new(strafesnet_common::gameplay_modes::Mode::new(
|
||||
self.style.try_into().map_err(ModeError::StyleModifier)?,
|
||||
strafesnet_common::model::ModelId::new(self.start),
|
||||
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)|
|
||||
Ok((strafesnet_common::model::ModelId::new(model_id),stage_element.try_into()?))
|
||||
).collect::<Result<_,_>>().map_err(ModeError::StageElement)?,
|
||||
))
|
||||
)))
|
||||
}
|
||||
}
|
||||
impl From<strafesnet_common::gameplay_modes::Mode> for Mode{
|
||||
fn from(value:strafesnet_common::gameplay_modes::Mode)->Self{
|
||||
let (style,start,zones,stages,elements)=value.into_inner();
|
||||
impl From<strafesnet_common::gameplay_modes::NormalizedMode> for NormalizedMode{
|
||||
fn from(value:strafesnet_common::gameplay_modes::NormalizedMode)->Self{
|
||||
let (style,start,zones,stages,elements)=value.into_inner().into_inner();
|
||||
Self{
|
||||
header:ModeHeader{
|
||||
zones:zones.len() as u32,
|
||||
|
@ -1,7 +1,7 @@
|
||||
[package]
|
||||
name = "map-tool"
|
||||
version = "1.7.0"
|
||||
edition = "2021"
|
||||
edition = "2024"
|
||||
|
||||
# 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" }
|
||||
thiserror = "2.0.11"
|
||||
tokio = { version = "1.43.0", features = ["macros", "rt-multi-thread", "fs"] }
|
||||
vbsp = { version = "0.7.0-codegen1", registry = "strafesnet" }
|
||||
vbsp = "0.8.0"
|
||||
vbsp-entities-css = "0.6.0"
|
||||
vmdl = "0.2.0"
|
||||
vmt-parser = "0.2.0"
|
||||
vpk = "0.2.0"
|
||||
vpk = "0.3.0"
|
||||
vtf = "0.3.0"
|
||||
|
||||
#[profile.release]
|
||||
|
@ -6,6 +6,7 @@ use futures::StreamExt;
|
||||
use strafesnet_bsp_loader::loader::BspFinder;
|
||||
use strafesnet_deferred_loader::loader::Loader;
|
||||
use strafesnet_deferred_loader::deferred_loader::{LoadFailureMode,MeshDeferredLoader,RenderConfigDeferredLoader};
|
||||
use vbsp_entities_css::Entity;
|
||||
|
||||
#[derive(Subcommand)]
|
||||
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();
|
||||
for prop in bsp.static_props(){
|
||||
mesh_deferred_loader.acquire_mesh_id(prop.model());
|
||||
for name in &bsp.static_props.dict.name{
|
||||
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{
|
||||
|
@ -1,7 +1,7 @@
|
||||
[package]
|
||||
name = "strafe-client"
|
||||
version = "0.11.0"
|
||||
edition = "2021"
|
||||
edition = "2024"
|
||||
repository = "https://git.itzana.me/StrafesNET/strafe-project"
|
||||
license = "Custom"
|
||||
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"]
|
||||
|
||||
[dependencies]
|
||||
glam = "0.29.0"
|
||||
glam = "0.30.0"
|
||||
parking_lot = "0.12.1"
|
||||
pollster = "0.4.0"
|
||||
strafesnet_bsp_loader = { path = "../lib/bsp_loader", registry = "strafesnet", optional = true }
|
||||
|
Reference in New Issue
Block a user