Compare commits

..

76 Commits

Author SHA1 Message Date
9a50ecf12a rbx_loader: disable roblox_emulator run-service feature 2025-04-17 17:08:21 -07:00
04d7694da4 rbx_loader: use extended instance 2025-04-17 17:08:21 -07:00
0a3825ca2d roblox_emulator: use extended instances 2025-04-17 17:08:21 -07:00
bef16f65ff use forked rbx-dom for extended instances 2025-04-17 15:42:18 -07:00
a3d39a81d0 update rbx-dom 2025-04-17 13:35:04 -07:00
cb31c847d5 map-tool: code clean ups 2025-04-14 21:23:23 -07:00
f579370841 update deps 2025-04-14 21:23:23 -07:00
2a542e0026 remove pointless PhantomData 2025-04-13 21:23:21 -07:00
ca51256a16 deferred_loader: remove PhantomData using GATs 2025-04-13 20:11:47 -07:00
65e17da386 rbx_loader: deconstruct instead of index tuple (with vengeance) 2025-03-21 13:03:55 -07:00
1cf21be185 rbx_loader: rename primitive functions to reflect what it does 2025-03-21 12:55:04 -07:00
413e176741 todo 2025-03-21 12:53:29 -07:00
39b2397d5e rbx_loader: implement roblox cylinder 2025-03-21 12:51:53 -07:00
2ed01dffbd physics: TODO 2025-03-20 18:28:15 -07:00
d0ee597416 rbx_loader: refactor primitive types 2025-03-20 18:28:15 -07:00
be85530000 rbx_loader: primitives: code cleanup 2025-03-20 18:28:15 -07:00
55ff199269 physics: [BUG 6] update move state on collision end with non-walk contact while walking 2025-03-20 13:32:52 -07:00
02615af1fd common: integer: angle constructor 2025-03-19 16:34:42 -07:00
6594ffa87e common: integer: more zero constants 2025-03-19 16:34:38 -07:00
6295dc68ef physics: fix lint 2025-03-19 12:52:54 -07:00
8fb0d30e1d graphics: move get_instances_buffer_data away from the cool render code 2025-03-14 15:38:35 -07:00
0fb0230cb1 tools: create dev profile 2025-03-14 15:24:47 -07:00
bdb1090664 physics: implement precise width conversion 2025-03-13 17:06:23 -07:00
3ce7746489 snf: implement precise width conversion 2025-03-13 17:06:23 -07:00
7978050f8a rbx_loader: implement precise width conversion 2025-03-13 17:06:23 -07:00
77ee36fa72 bsp_loader: implement precise width conversion 2025-03-13 17:06:23 -07:00
0fc1ec3086 common: implement precise width conversion 2025-03-13 16:57:36 -07:00
f60c03ac56 common: update deps 2025-03-13 16:57:36 -07:00
b570d8809d linear_ops: v0.1.1 unwrap vector of results 2025-03-13 16:57:36 -07:00
3f268ec034 linear_ops: allow unwraping vector of results 2025-03-13 16:57:36 -07:00
ba161ef6d1 fixed_wide: v0.2.0 precise width conversion 2025-03-13 16:57:36 -07:00
0755d4413c fixed_wide: expand upon width conversion 2025-03-13 16:13:03 -07:00
459cb13957 physics: monomorphise MeshQuery over AsRef 2025-03-12 19:34:45 -07:00
a5ad7d4d6e it: cli 2025-03-12 17:23:21 -07:00
3d5e76f078 bsp_loader: fix utf8 brush model 2025-03-11 18:13:39 -07:00
78c53ae429 physics: set style on reset and spawn 2025-03-11 17:09:29 -07:00
e794b2649c bsp_loader: implement ladders and water 2025-03-11 16:41:46 -07:00
50f6fe5bd8 common: fix source styles 2025-03-11 15:26:45 -07:00
fc3681f41f it: read_entire_file use built in 2025-03-11 14:07:24 -07:00
a2369b4211 rbx_loader: never do &Vec + deconstruct Face2 (style) 2025-03-09 23:47:23 -07:00
bfea49ffae it: measure simulate speed 2025-03-08 17:39:47 -08:00
8c7063d340 common: aabb: make function inline-able 2025-03-08 14:34:54 -08:00
651150760c common: aabb: use midpoint for center 2025-03-08 14:34:54 -08:00
3d203e2da9 fixed_wide: add midpoint 2025-03-08 14:34:54 -08:00
3798f438cf use as_bits_mut to optimize sqrt 2025-03-08 13:58:56 -08:00
4ace8317fe update deps 2025-03-08 13:55:14 -08:00
ab4a9bb922 bsp_loader: generate prop matrix with euler angles 2025-03-02 18:23:47 -08:00
a9ef07ce78 update vbsp 2025-03-02 18:23:47 -08:00
561e41c760 common: delete unused ModesUpdate 2025-02-28 13:15:55 -08:00
9896a09576 common: delete updatable trait 2025-02-28 13:15:55 -08:00
82840fa6cb refactor type safety for modes data normalization 2025-02-28 13:15:55 -08:00
2c87cb71df common: gameplay_modes: rename internal field on ModeBuilder 2025-02-28 12:37:49 -08:00
709f622547 common: don't use .0 for newtypes 2025-02-28 12:10:16 -08:00
b20264b382 common: move ModesBuilder to common 2025-02-28 11:08:38 -08:00
17cce2d702 silence lints 2025-02-28 10:50:53 -08:00
a2b2ddc9ce bsp_loader: use texture_info function 2025-02-26 15:29:23 -08:00
cb57d893e0 update vbsp fork 2025-02-26 15:26:56 -08:00
9e8d66cec1 map-tool: gimme_them_textures iterate over a shorter list of prop model paths 2025-02-26 12:00:51 -08:00
39fe833198 map-tool: grab brush models 2025-02-26 12:00:51 -08:00
e7688a95fd bsp_loader: generate physics from brushes 2025-02-26 12:00:51 -08:00
d2cc98b04d deps: fork vbsp 2025-02-26 11:59:59 -08:00
9e887580af bsp_loader: valve_transform_{dist|normal} 2025-02-21 13:18:20 -08:00
92feac572e bsp_loader: include missing model path in error 2025-02-21 13:18:20 -08:00
fd02a40783 common: CollisionAttributes::intersect_default 2025-02-21 13:18:20 -08:00
7c787a0e0f linear_ops: repr(transparent) to ward off UB 2025-02-21 13:18:20 -08:00
6a7c076203 Planar64Affine3::IDENTITY 2025-02-21 13:18:20 -08:00
af3abbcb4d Planar64Affine3::from_translation 2025-02-21 13:14:57 -08:00
4859c37780 common: BvhNode::sample_ray 2025-02-21 12:54:13 -08:00
19e65802f6 common: Aabb::contains(point) 2025-02-21 12:54:13 -08:00
2cf1547423 common: Time is Ord 2025-02-21 12:54:13 -08:00
63305f91c7 common: Ray module 2025-02-21 12:54:13 -08:00
e875826250 TransformedMesh::faces 2025-02-21 12:54:13 -08:00
0a44c1630f ratio_ops v0.1.1 fix Ord 2025-02-21 12:54:13 -08:00
5cffc03ef6 ratio_ops: fix Ord for Ratio 2025-02-21 12:53:14 -08:00
d638e633ba update deps 2025-02-20 19:02:03 -08:00
61e44f2aba upgrade rust edition to 2024 2025-02-20 18:58:01 -08:00
69 changed files with 2696 additions and 1893 deletions

1097
Cargo.lock generated

File diff suppressed because it is too large Load Diff

@ -24,3 +24,7 @@ resolver = "2"
#lto = true #lto = true
strip = true strip = true
codegen-units = 1 codegen-units = 1
[profile.dev]
strip = false
opt-level = 3

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

@ -103,6 +103,26 @@ impl std::default::Default for GraphicsCamera{
} }
} }
const MODEL_BUFFER_SIZE:usize=4*4 + 12 + 4;//let size=std::mem::size_of::<ModelInstance>();
const MODEL_BUFFER_SIZE_BYTES:usize=MODEL_BUFFER_SIZE*4;
fn get_instances_buffer_data(instances:&[GraphicsModelOwned])->Vec<f32>{
let mut raw=Vec::with_capacity(MODEL_BUFFER_SIZE*instances.len());
for mi in instances{
//model transform
raw.extend_from_slice(&AsRef::<[f32; 4*4]>::as_ref(&mi.transform)[..]);
//normal transform
raw.extend_from_slice(AsRef::<[f32; 3]>::as_ref(&mi.normal_transform.x_axis));
raw.extend_from_slice(&[0.0]);
raw.extend_from_slice(AsRef::<[f32; 3]>::as_ref(&mi.normal_transform.y_axis));
raw.extend_from_slice(&[0.0]);
raw.extend_from_slice(AsRef::<[f32; 3]>::as_ref(&mi.normal_transform.z_axis));
raw.extend_from_slice(&[0.0]);
//color
raw.extend_from_slice(AsRef::<[f32; 4]>::as_ref(&mi.color.get()));
}
raw
}
pub struct GraphicsState{ pub struct GraphicsState{
pipelines:GraphicsPipelines, pipelines:GraphicsPipelines,
bind_groups:GraphicsBindGroups, bind_groups:GraphicsBindGroups,
@ -967,22 +987,3 @@ impl GraphicsState{
self.staging_belt.recall(); self.staging_belt.recall();
} }
} }
const MODEL_BUFFER_SIZE:usize=4*4 + 12 + 4;//let size=std::mem::size_of::<ModelInstance>();
const MODEL_BUFFER_SIZE_BYTES:usize=MODEL_BUFFER_SIZE*4;
fn get_instances_buffer_data(instances:&[GraphicsModelOwned])->Vec<f32>{
let mut raw=Vec::with_capacity(MODEL_BUFFER_SIZE*instances.len());
for mi in instances{
//model transform
raw.extend_from_slice(&AsRef::<[f32; 4*4]>::as_ref(&mi.transform)[..]);
//normal transform
raw.extend_from_slice(AsRef::<[f32; 3]>::as_ref(&mi.normal_transform.x_axis));
raw.extend_from_slice(&[0.0]);
raw.extend_from_slice(AsRef::<[f32; 3]>::as_ref(&mi.normal_transform.y_axis));
raw.extend_from_slice(&[0.0]);
raw.extend_from_slice(AsRef::<[f32; 3]>::as_ref(&mi.normal_transform.z_axis));
raw.extend_from_slice(&[0.0]);
//color
raw.extend_from_slice(AsRef::<[f32; 4]>::as_ref(&mi.color.get()));
}
raw
}

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

@ -42,12 +42,12 @@ impl<T> Body<T>
pub fn extrapolated_position(&self,time:Time<T>)->Planar64Vec3{ pub fn extrapolated_position(&self,time:Time<T>)->Planar64Vec3{
let dt=time-self.time; let dt=time-self.time;
self.position self.position
+(self.velocity*dt).map(|elem|elem.divide().fix_1()) +(self.velocity*dt).map(|elem|elem.divide().clamp_1())
+self.acceleration.map(|elem|(dt*dt*elem/2).divide().fix_1()) +self.acceleration.map(|elem|(dt*dt*elem/2).divide().clamp_1())
} }
pub fn extrapolated_velocity(&self,time:Time<T>)->Planar64Vec3{ pub fn extrapolated_velocity(&self,time:Time<T>)->Planar64Vec3{
let dt=time-self.time; let dt=time-self.time;
self.velocity+(self.acceleration*dt).map(|elem|elem.divide().fix_1()) self.velocity+(self.acceleration*dt).map(|elem|elem.divide().clamp_1())
} }
pub fn advance_time(&mut self,time:Time<T>){ pub fn advance_time(&mut self,time:Time<T>){
self.position=self.extrapolated_position(time); self.position=self.extrapolated_position(time);
@ -71,12 +71,12 @@ impl<T> Body<T>
D2:Copy, D2:Copy,
Planar64:core::ops::Mul<D2,Output=N4>, Planar64:core::ops::Mul<D2,Output=N4>,
N4:integer::Divide<D2,Output=T1>, N4:integer::Divide<D2,Output=T1>,
T1:integer::Fix<Planar64>, T1:integer::Clamp<Planar64>,
{ {
// a*dt^2/2 + v*dt + p // a*dt^2/2 + v*dt + p
// (a*dt/2+v)*dt+p // (a*dt/2+v)*dt+p
(self.acceleration.map(|elem|dt*elem/2)+self.velocity).map(|elem|dt.mul_ratio(elem)) (self.acceleration.map(|elem|dt*elem/2)+self.velocity).map(|elem|dt.mul_ratio(elem))
.map(|elem|elem.divide().fix())+self.position .map(|elem|elem.divide().clamp())+self.position
} }
pub fn extrapolated_velocity_ratio_dt<Num,Den,N1,T1>(&self,dt:integer::Ratio<Num,Den>)->Planar64Vec3 pub fn extrapolated_velocity_ratio_dt<Num,Den,N1,T1>(&self,dt:integer::Ratio<Num,Den>)->Planar64Vec3
where where
@ -85,10 +85,10 @@ impl<T> Body<T>
Num:core::ops::Mul<Planar64,Output=N1>, Num:core::ops::Mul<Planar64,Output=N1>,
Planar64:core::ops::Mul<Den,Output=N1>, Planar64:core::ops::Mul<Den,Output=N1>,
N1:integer::Divide<Den,Output=T1>, N1:integer::Divide<Den,Output=T1>,
T1:integer::Fix<Planar64>, T1:integer::Clamp<Planar64>,
{ {
// a*dt + v // a*dt + v
self.acceleration.map(|elem|(dt*elem).divide().fix())+self.velocity self.acceleration.map(|elem|(dt*elem).divide().clamp())+self.velocity
} }
pub fn advance_time_ratio_dt(&mut self,dt:crate::model::GigaTime){ pub fn advance_time_ratio_dt(&mut self,dt:crate::model::GigaTime){
self.position=self.extrapolated_position_ratio_dt(dt); self.position=self.extrapolated_position_ratio_dt(dt);

@ -57,13 +57,14 @@ impl<F:Copy,M:MeshQuery<Normal=Vector3<F>,Offset=Fixed<4,128>>> FEV<M>
} }
} }
//test each edge collision time, ignoring roots with zero or conflicting derivative //test each edge collision time, ignoring roots with zero or conflicting derivative
for &directed_edge_id in mesh.face_edges(face_id).iter(){ for &directed_edge_id in mesh.face_edges(face_id).as_ref(){
let edge_n=mesh.directed_edge_n(directed_edge_id); let edge_n=mesh.directed_edge_n(directed_edge_id);
let n=n.cross(edge_n); let n=n.cross(edge_n);
let verts=mesh.edge_verts(directed_edge_id.as_undirected()); let &[v0,v1]=mesh.edge_verts(directed_edge_id.as_undirected()).as_ref();
//WARNING: d is moved out of the *2 block because of adding two vertices! //WARNING: d is moved out of the *2 block because of adding two vertices!
//WARNING: precision is swept under the rug! //WARNING: precision is swept under the rug!
for dt in Fixed::<4,128>::zeroes2(n.dot(body.position*2-(mesh.vert(verts[0])+mesh.vert(verts[1]))).fix_4(),n.dot(body.velocity).fix_4()*2,n.dot(body.acceleration).fix_4()){ //wrap for speed
for dt in Fixed::<4,128>::zeroes2(n.dot(body.position*2-(mesh.vert(v0)+mesh.vert(v1))).wrap_4(),n.dot(body.velocity).wrap_4()*2,n.dot(body.acceleration).wrap_4()){
if body_time.le_ratio(dt)&&dt.lt_ratio(best_time)&&n.dot(body.extrapolated_velocity_ratio_dt(dt)).is_negative(){ if body_time.le_ratio(dt)&&dt.lt_ratio(best_time)&&n.dot(body.extrapolated_velocity_ratio_dt(dt)).is_negative(){
best_time=dt; best_time=dt;
best_transition=Transition::Next(FEV::Edge(directed_edge_id.as_undirected()),dt); best_transition=Transition::Next(FEV::Edge(directed_edge_id.as_undirected()),dt);
@ -77,13 +78,15 @@ impl<F:Copy,M:MeshQuery<Normal=Vector3<F>,Offset=Fixed<4,128>>> FEV<M>
//test each face collision time, ignoring roots with zero or conflicting derivative //test each face collision time, ignoring roots with zero or conflicting derivative
let edge_n=mesh.edge_n(edge_id); let edge_n=mesh.edge_n(edge_id);
let edge_verts=mesh.edge_verts(edge_id); let edge_verts=mesh.edge_verts(edge_id);
let delta_pos=body.position*2-(mesh.vert(edge_verts[0])+mesh.vert(edge_verts[1])); let &[ev0,ev1]=edge_verts.as_ref();
for (i,&edge_face_id) in mesh.edge_faces(edge_id).iter().enumerate(){ let delta_pos=body.position*2-(mesh.vert(ev0)+mesh.vert(ev1));
for (i,&edge_face_id) in mesh.edge_faces(edge_id).as_ref().iter().enumerate(){
let face_n=mesh.face_nd(edge_face_id).0; let face_n=mesh.face_nd(edge_face_id).0;
//edge_n gets parity from the order of edge_faces //edge_n gets parity from the order of edge_faces
let n=face_n.cross(edge_n)*((i as i64)*2-1); let n=face_n.cross(edge_n)*((i as i64)*2-1);
//WARNING yada yada d *2 //WARNING yada yada d *2
for dt in Fixed::<4,128>::zeroes2(n.dot(delta_pos).fix_4(),n.dot(body.velocity).fix_4()*2,n.dot(body.acceleration).fix_4()){ //wrap for speed
for dt in Fixed::<4,128>::zeroes2(n.dot(delta_pos).wrap_4(),n.dot(body.velocity).wrap_4()*2,n.dot(body.acceleration).wrap_4()){
if body_time.le_ratio(dt)&&dt.lt_ratio(best_time)&&n.dot(body.extrapolated_velocity_ratio_dt(dt)).is_negative(){ if body_time.le_ratio(dt)&&dt.lt_ratio(best_time)&&n.dot(body.extrapolated_velocity_ratio_dt(dt)).is_negative(){
best_time=dt; best_time=dt;
best_transition=Transition::Next(FEV::Face(edge_face_id),dt); best_transition=Transition::Next(FEV::Face(edge_face_id),dt);
@ -92,12 +95,12 @@ impl<F:Copy,M:MeshQuery<Normal=Vector3<F>,Offset=Fixed<4,128>>> FEV<M>
} }
} }
//test each vertex collision time, ignoring roots with zero or conflicting derivative //test each vertex collision time, ignoring roots with zero or conflicting derivative
for (i,&vert_id) in edge_verts.iter().enumerate(){ for (i,&vert_id) in edge_verts.as_ref().iter().enumerate(){
//vertex normal gets parity from vert index //vertex normal gets parity from vert index
let n=edge_n*(1-2*(i as i64)); let n=edge_n*(1-2*(i as i64));
for dt in Fixed::<2,64>::zeroes2((n.dot(body.position-mesh.vert(vert_id)))*2,n.dot(body.velocity)*2,n.dot(body.acceleration)){ for dt in Fixed::<2,64>::zeroes2((n.dot(body.position-mesh.vert(vert_id)))*2,n.dot(body.velocity)*2,n.dot(body.acceleration)){
if body_time.le_ratio(dt)&&dt.lt_ratio(best_time)&&n.dot(body.extrapolated_velocity_ratio_dt(dt)).is_negative(){ if body_time.le_ratio(dt)&&dt.lt_ratio(best_time)&&n.dot(body.extrapolated_velocity_ratio_dt(dt)).is_negative(){
let dt=Ratio::new(dt.num.fix_4(),dt.den.fix_4()); let dt=Ratio::new(dt.num.widen_4(),dt.den.widen_4());
best_time=dt; best_time=dt;
best_transition=Transition::Next(FEV::Vert(vert_id),dt); best_transition=Transition::Next(FEV::Vert(vert_id),dt);
break; break;
@ -108,12 +111,12 @@ impl<F:Copy,M:MeshQuery<Normal=Vector3<F>,Offset=Fixed<4,128>>> FEV<M>
}, },
&FEV::Vert(vert_id)=>{ &FEV::Vert(vert_id)=>{
//test each edge collision time, ignoring roots with zero or conflicting derivative //test each edge collision time, ignoring roots with zero or conflicting derivative
for &directed_edge_id in mesh.vert_edges(vert_id).iter(){ for &directed_edge_id in mesh.vert_edges(vert_id).as_ref(){
//edge is directed away from vertex, but we want the dot product to turn out negative //edge is directed away from vertex, but we want the dot product to turn out negative
let n=-mesh.directed_edge_n(directed_edge_id); let n=-mesh.directed_edge_n(directed_edge_id);
for dt in Fixed::<2,64>::zeroes2((n.dot(body.position-mesh.vert(vert_id)))*2,n.dot(body.velocity)*2,n.dot(body.acceleration)){ for dt in Fixed::<2,64>::zeroes2((n.dot(body.position-mesh.vert(vert_id)))*2,n.dot(body.velocity)*2,n.dot(body.acceleration)){
if body_time.le_ratio(dt)&&dt.lt_ratio(best_time)&&n.dot(body.extrapolated_velocity_ratio_dt(dt)).is_negative(){ if body_time.le_ratio(dt)&&dt.lt_ratio(best_time)&&n.dot(body.extrapolated_velocity_ratio_dt(dt)).is_negative(){
let dt=Ratio::new(dt.num.fix_4(),dt.den.fix_4()); let dt=Ratio::new(dt.num.widen_4(),dt.den.widen_4());
best_time=dt; best_time=dt;
best_transition=Transition::Next(FEV::Edge(directed_edge_id.as_undirected()),dt); best_transition=Transition::Next(FEV::Edge(directed_edge_id.as_undirected()),dt);
break; break;
@ -128,11 +131,11 @@ impl<F:Copy,M:MeshQuery<Normal=Vector3<F>,Offset=Fixed<4,128>>> FEV<M>
pub fn crawl(mut self,mesh:&M,relative_body:&Body,start_time:Time,time_limit:Time)->CrawlResult<M>{ pub fn crawl(mut self,mesh:&M,relative_body:&Body,start_time:Time,time_limit:Time)->CrawlResult<M>{
let mut body_time={ let mut body_time={
let r=(start_time-relative_body.time).to_ratio(); let r=(start_time-relative_body.time).to_ratio();
Ratio::new(r.num.fix_4(),r.den.fix_4()) Ratio::new(r.num.widen_4(),r.den.widen_4())
}; };
let time_limit={ let time_limit={
let r=(time_limit-relative_body.time).to_ratio(); let r=(time_limit-relative_body.time).to_ratio();
Ratio::new(r.num.fix_4(),r.den.fix_4()) Ratio::new(r.num.widen_4(),r.den.widen_4())
}; };
for _ in 0..20{ for _ in 0..20{
match self.next_transition(body_time,mesh,relative_body,time_limit){ match self.next_transition(body_time,mesh,relative_body,time_limit){

@ -1,4 +1,3 @@
use std::borrow::{Borrow,Cow};
use std::collections::{HashSet,HashMap}; use std::collections::{HashSet,HashMap};
use core::ops::Range; use core::ops::Range;
use strafesnet_common::integer::vec3::Vector3; use strafesnet_common::integer::vec3::Vector3;
@ -8,6 +7,13 @@ use strafesnet_common::physics::Time;
type Body=crate::body::Body<strafesnet_common::physics::TimeInner>; type Body=crate::body::Body<strafesnet_common::physics::TimeInner>;
struct AsRefHelper<T>(T);
impl<T> AsRef<T> for AsRefHelper<T>{
fn as_ref(&self)->&T{
&self.0
}
}
pub trait UndirectedEdge{ pub trait UndirectedEdge{
type DirectedEdge:Copy+DirectedEdge; type DirectedEdge:Copy+DirectedEdge;
fn as_directed(&self,parity:bool)->Self::DirectedEdge; fn as_directed(&self,parity:bool)->Self::DirectedEdge;
@ -69,27 +75,27 @@ struct Face{
} }
struct Vert(Planar64Vec3); struct Vert(Planar64Vec3);
pub trait MeshQuery{ pub trait MeshQuery{
type Face:Clone; type Face:Copy;
type Edge:Clone+DirectedEdge; type Edge:Copy+DirectedEdge;
type Vert:Clone; type Vert:Copy;
// Vertex must be Planar64Vec3 because it represents an actual position // Vertex must be Planar64Vec3 because it represents an actual position
type Normal; type Normal;
type Offset; type Offset;
fn edge_n(&self,edge_id:<Self::Edge as DirectedEdge>::UndirectedEdge)->Planar64Vec3{ fn edge_n(&self,edge_id:<Self::Edge as DirectedEdge>::UndirectedEdge)->Planar64Vec3{
let verts=self.edge_verts(edge_id); let &[v0,v1]=self.edge_verts(edge_id).as_ref();
self.vert(verts[1].clone())-self.vert(verts[0].clone()) self.vert(v1)-self.vert(v0)
} }
fn directed_edge_n(&self,directed_edge_id:Self::Edge)->Planar64Vec3{ fn directed_edge_n(&self,directed_edge_id:Self::Edge)->Planar64Vec3{
let verts=self.edge_verts(directed_edge_id.as_undirected()); let &[v0,v1]=self.edge_verts(directed_edge_id.as_undirected()).as_ref();
(self.vert(verts[1].clone())-self.vert(verts[0].clone()))*((directed_edge_id.parity() as i64)*2-1) (self.vert(v1)-self.vert(v0))*((directed_edge_id.parity() as i64)*2-1)
} }
fn vert(&self,vert_id:Self::Vert)->Planar64Vec3; fn vert(&self,vert_id:Self::Vert)->Planar64Vec3;
fn face_nd(&self,face_id:Self::Face)->(Self::Normal,Self::Offset); fn face_nd(&self,face_id:Self::Face)->(Self::Normal,Self::Offset);
fn face_edges(&self,face_id:Self::Face)->Cow<[Self::Edge]>; fn face_edges(&self,face_id:Self::Face)->impl AsRef<[Self::Edge]>;
fn edge_faces(&self,edge_id:<Self::Edge as DirectedEdge>::UndirectedEdge)->Cow<[Self::Face;2]>; fn edge_faces(&self,edge_id:<Self::Edge as DirectedEdge>::UndirectedEdge)->impl AsRef<[Self::Face;2]>;
fn edge_verts(&self,edge_id:<Self::Edge as DirectedEdge>::UndirectedEdge)->Cow<[Self::Vert;2]>; fn edge_verts(&self,edge_id:<Self::Edge as DirectedEdge>::UndirectedEdge)->impl AsRef<[Self::Vert;2]>;
fn vert_edges(&self,vert_id:Self::Vert)->Cow<[Self::Edge]>; fn vert_edges(&self,vert_id:Self::Vert)->impl AsRef<[Self::Edge]>;
fn vert_faces(&self,vert_id:Self::Vert)->Cow<[Self::Face]>; fn vert_faces(&self,vert_id:Self::Vert)->impl AsRef<[Self::Face]>;
} }
struct FaceRefs{ struct FaceRefs{
edges:Vec<SubmeshDirectedEdgeId>, edges:Vec<SubmeshDirectedEdgeId>,
@ -372,8 +378,8 @@ impl TryFrom<&model::Mesh> for PhysicsMesh{
} }
//assume face hash is stable, and there are no flush faces... //assume face hash is stable, and there are no flush faces...
let face=Face{ let face=Face{
normal:(normal/len as i64).divide().fix_1(), normal:(normal/len as i64).divide().narrow_1().unwrap(),
dot:(dot/(len*len) as i64).fix_1(), dot:(dot/(len*len) as i64).narrow_1().unwrap(),
}; };
let face_id=match face_id_from_face.get(&face){ let face_id=match face_id_from_face.get(&face){
Some(&face_id)=>face_id, Some(&face_id)=>face_id,
@ -435,20 +441,20 @@ impl MeshQuery for PhysicsMeshView<'_>{
let vert_idx=self.topology.verts[vert_id.get() as usize].get() as usize; let vert_idx=self.topology.verts[vert_id.get() as usize].get() as usize;
self.data.verts[vert_idx].0 self.data.verts[vert_idx].0
} }
fn face_edges(&self,face_id:SubmeshFaceId)->Cow<[SubmeshDirectedEdgeId]>{ fn face_edges(&self,face_id:SubmeshFaceId)->impl AsRef<[SubmeshDirectedEdgeId]>{
Cow::Borrowed(&self.topology.face_topology[face_id.get() as usize].edges) self.topology.face_topology[face_id.get() as usize].edges.as_slice()
} }
fn edge_faces(&self,edge_id:SubmeshEdgeId)->Cow<[SubmeshFaceId;2]>{ fn edge_faces(&self,edge_id:SubmeshEdgeId)->impl AsRef<[SubmeshFaceId;2]>{
Cow::Borrowed(&self.topology.edge_topology[edge_id.get() as usize].faces) AsRefHelper(self.topology.edge_topology[edge_id.get() as usize].faces)
} }
fn edge_verts(&self,edge_id:SubmeshEdgeId)->Cow<[SubmeshVertId;2]>{ fn edge_verts(&self,edge_id:SubmeshEdgeId)->impl AsRef<[SubmeshVertId;2]>{
Cow::Borrowed(&self.topology.edge_topology[edge_id.get() as usize].verts) AsRefHelper(self.topology.edge_topology[edge_id.get() as usize].verts)
} }
fn vert_edges(&self,vert_id:SubmeshVertId)->Cow<[SubmeshDirectedEdgeId]>{ fn vert_edges(&self,vert_id:SubmeshVertId)->impl AsRef<[SubmeshDirectedEdgeId]>{
Cow::Borrowed(&self.topology.vert_topology[vert_id.get() as usize].edges) self.topology.vert_topology[vert_id.get() as usize].edges.as_slice()
} }
fn vert_faces(&self,vert_id:SubmeshVertId)->Cow<[SubmeshFaceId]>{ fn vert_faces(&self,vert_id:SubmeshVertId)->impl AsRef<[SubmeshFaceId]>{
Cow::Borrowed(&self.topology.vert_topology[vert_id.get() as usize].faces) self.topology.vert_topology[vert_id.get() as usize].faces.as_slice()
} }
} }
@ -484,12 +490,15 @@ impl TransformedMesh<'_>{
pub fn verts<'a>(&'a self)->impl Iterator<Item=vec3::Vector3<Fixed<2,64>>>+'a{ pub fn verts<'a>(&'a self)->impl Iterator<Item=vec3::Vector3<Fixed<2,64>>>+'a{
self.view.data.verts.iter().map(|&Vert(pos)|self.transform.vertex.transform_point3(pos)) self.view.data.verts.iter().map(|&Vert(pos)|self.transform.vertex.transform_point3(pos))
} }
pub fn faces(&self)->impl Iterator<Item=SubmeshFaceId>{
(0..self.view.topology.faces.len() as u32).map(SubmeshFaceId::new)
}
fn farthest_vert(&self,dir:Planar64Vec3)->SubmeshVertId{ fn farthest_vert(&self,dir:Planar64Vec3)->SubmeshVertId{
//this happens to be well-defined. there are no virtual virtices //this happens to be well-defined. there are no virtual virtices
SubmeshVertId::new( SubmeshVertId::new(
self.view.topology.verts.iter() self.view.topology.verts.iter()
.enumerate() .enumerate()
.max_by_key(|(_,&vert_id)| .max_by_key(|&(_,&vert_id)|
dir.dot(self.transform.vertex.transform_point3(self.view.data.verts[vert_id.get() as usize].0)) dir.dot(self.transform.vertex.transform_point3(self.view.data.verts[vert_id.get() as usize].0))
) )
//assume there is more than zero vertices. //assume there is more than zero vertices.
@ -510,26 +519,27 @@ impl MeshQuery for TransformedMesh<'_>{
(transformed_n,transformed_d) (transformed_n,transformed_d)
} }
fn vert(&self,vert_id:SubmeshVertId)->Planar64Vec3{ fn vert(&self,vert_id:SubmeshVertId)->Planar64Vec3{
self.transform.vertex.transform_point3(self.view.vert(vert_id)).fix_1() // wrap for speed
self.transform.vertex.transform_point3(self.view.vert(vert_id)).wrap_1()
} }
#[inline] #[inline]
fn face_edges(&self,face_id:SubmeshFaceId)->Cow<[SubmeshDirectedEdgeId]>{ fn face_edges(&self,face_id:SubmeshFaceId)->impl AsRef<[SubmeshDirectedEdgeId]>{
self.view.face_edges(face_id) self.view.face_edges(face_id)
} }
#[inline] #[inline]
fn edge_faces(&self,edge_id:SubmeshEdgeId)->Cow<[SubmeshFaceId;2]>{ fn edge_faces(&self,edge_id:SubmeshEdgeId)->impl AsRef<[SubmeshFaceId;2]>{
self.view.edge_faces(edge_id) self.view.edge_faces(edge_id)
} }
#[inline] #[inline]
fn edge_verts(&self,edge_id:SubmeshEdgeId)->Cow<[SubmeshVertId;2]>{ fn edge_verts(&self,edge_id:SubmeshEdgeId)->impl AsRef<[SubmeshVertId;2]>{
self.view.edge_verts(edge_id) self.view.edge_verts(edge_id)
} }
#[inline] #[inline]
fn vert_edges(&self,vert_id:SubmeshVertId)->Cow<[SubmeshDirectedEdgeId]>{ fn vert_edges(&self,vert_id:SubmeshVertId)->impl AsRef<[SubmeshDirectedEdgeId]>{
self.view.vert_edges(vert_id) self.view.vert_edges(vert_id)
} }
#[inline] #[inline]
fn vert_faces(&self,vert_id:SubmeshVertId)->Cow<[SubmeshFaceId]>{ fn vert_faces(&self,vert_id:SubmeshVertId)->impl AsRef<[SubmeshFaceId]>{
self.view.vert_faces(vert_id) self.view.vert_faces(vert_id)
} }
} }
@ -618,12 +628,12 @@ impl MinkowskiMesh<'_>{
} }
fn next_transition_vert(&self,vert_id:MinkowskiVert,best_distance_squared:&mut Fixed<2,64>,infinity_dir:Planar64Vec3,point:Planar64Vec3)->Transition{ fn next_transition_vert(&self,vert_id:MinkowskiVert,best_distance_squared:&mut Fixed<2,64>,infinity_dir:Planar64Vec3,point:Planar64Vec3)->Transition{
let mut best_transition=Transition::Done; let mut best_transition=Transition::Done;
for &directed_edge_id in self.vert_edges(vert_id).iter(){ for &directed_edge_id in self.vert_edges(vert_id).as_ref(){
let edge_n=self.directed_edge_n(directed_edge_id); let edge_n=self.directed_edge_n(directed_edge_id);
//is boundary uncrossable by a crawl from infinity //is boundary uncrossable by a crawl from infinity
let edge_verts=self.edge_verts(directed_edge_id.as_undirected()); let edge_verts=self.edge_verts(directed_edge_id.as_undirected());
//select opposite vertex //select opposite vertex
let test_vert_id=edge_verts[directed_edge_id.parity() as usize]; let test_vert_id=edge_verts.as_ref()[directed_edge_id.parity() as usize];
//test if it's closer //test if it's closer
let diff=point-self.vert(test_vert_id); let diff=point-self.vert(test_vert_id);
if edge_n.dot(infinity_dir).is_zero(){ if edge_n.dot(infinity_dir).is_zero(){
@ -639,7 +649,7 @@ impl MinkowskiMesh<'_>{
fn final_ev(&self,vert_id:MinkowskiVert,best_distance_squared:&mut Fixed<2,64>,infinity_dir:Planar64Vec3,point:Planar64Vec3)->EV{ fn final_ev(&self,vert_id:MinkowskiVert,best_distance_squared:&mut Fixed<2,64>,infinity_dir:Planar64Vec3,point:Planar64Vec3)->EV{
let mut best_transition=EV::Vert(vert_id); let mut best_transition=EV::Vert(vert_id);
let diff=point-self.vert(vert_id); let diff=point-self.vert(vert_id);
for &directed_edge_id in self.vert_edges(vert_id).iter(){ for &directed_edge_id in self.vert_edges(vert_id).as_ref(){
let edge_n=self.directed_edge_n(directed_edge_id); let edge_n=self.directed_edge_n(directed_edge_id);
//is boundary uncrossable by a crawl from infinity //is boundary uncrossable by a crawl from infinity
//check if time of collision is outside Time::MIN..Time::MAX //check if time of collision is outside Time::MIN..Time::MAX
@ -650,7 +660,8 @@ impl MinkowskiMesh<'_>{
if !d.is_negative()&&d<=edge_nn{ if !d.is_negative()&&d<=edge_nn{
let distance_squared={ let distance_squared={
let c=diff.cross(edge_n); let c=diff.cross(edge_n);
(c.dot(c)/edge_nn).divide().fix_2() //wrap for speed
(c.dot(c)/edge_nn).divide().wrap_2()
}; };
if distance_squared<=*best_distance_squared{ if distance_squared<=*best_distance_squared{
best_transition=EV::Edge(directed_edge_id.as_undirected()); best_transition=EV::Edge(directed_edge_id.as_undirected());
@ -686,10 +697,10 @@ impl MinkowskiMesh<'_>{
let edge_n=self.edge_n(edge_id); let edge_n=self.edge_n(edge_id);
// point is multiplied by two because vert_sum sums two vertices. // point is multiplied by two because vert_sum sums two vertices.
let delta_pos=point*2-{ let delta_pos=point*2-{
let &[v0,v1]=self.edge_verts(edge_id).borrow(); let &[v0,v1]=self.edge_verts(edge_id).as_ref();
self.vert(v0)+self.vert(v1) self.vert(v0)+self.vert(v1)
}; };
for (i,&face_id) in self.edge_faces(edge_id).iter().enumerate(){ for (i,&face_id) in self.edge_faces(edge_id).as_ref().iter().enumerate(){
let face_n=self.face_nd(face_id).0; let face_n=self.face_nd(face_id).0;
//edge-face boundary nd, n facing out of the face towards the edge //edge-face boundary nd, n facing out of the face towards the edge
let boundary_n=face_n.cross(edge_n)*(i as i64*2-1); let boundary_n=face_n.cross(edge_n)*(i as i64*2-1);
@ -755,19 +766,20 @@ impl MinkowskiMesh<'_>{
}; };
let mut best_time={ let mut best_time={
let r=(time_limit-relative_body.time).to_ratio(); let r=(time_limit-relative_body.time).to_ratio();
Ratio::new(r.num.fix_4(),r.den.fix_4()) Ratio::new(r.num.widen_4(),r.den.widen_4())
}; };
let mut best_edge=None; let mut best_edge=None;
let face_n=self.face_nd(contact_face_id).0; let face_n=self.face_nd(contact_face_id).0;
for &directed_edge_id in self.face_edges(contact_face_id).iter(){ for &directed_edge_id in self.face_edges(contact_face_id).as_ref(){
let edge_n=self.directed_edge_n(directed_edge_id); let edge_n=self.directed_edge_n(directed_edge_id);
//f x e points in //f x e points in
let n=face_n.cross(edge_n); let n=face_n.cross(edge_n);
let verts=self.edge_verts(directed_edge_id.as_undirected()); let &[v0,v1]=self.edge_verts(directed_edge_id.as_undirected()).as_ref();
let d=n.dot(self.vert(verts[0])+self.vert(verts[1])); let d=n.dot(self.vert(v0)+self.vert(v1));
//WARNING! d outside of *2 //WARNING! d outside of *2
//WARNING: truncated precision //WARNING: truncated precision
for dt in Fixed::<4,128>::zeroes2(((n.dot(relative_body.position))*2-d).fix_4(),n.dot(relative_body.velocity).fix_4()*2,n.dot(relative_body.acceleration).fix_4()){ //wrap for speed
for dt in Fixed::<4,128>::zeroes2(((n.dot(relative_body.position))*2-d).wrap_4(),n.dot(relative_body.velocity).wrap_4()*2,n.dot(relative_body.acceleration).wrap_4()){
if start_time.le_ratio(dt)&&dt.lt_ratio(best_time)&&n.dot(relative_body.extrapolated_velocity_ratio_dt(dt)).is_negative(){ if start_time.le_ratio(dt)&&dt.lt_ratio(best_time)&&n.dot(relative_body.extrapolated_velocity_ratio_dt(dt)).is_negative(){
best_time=dt; best_time=dt;
best_edge=Some(directed_edge_id); best_edge=Some(directed_edge_id);
@ -808,12 +820,12 @@ impl MeshQuery for MinkowskiMesh<'_>{
MinkowskiFace::EdgeEdge(e0,e1,parity)=>{ MinkowskiFace::EdgeEdge(e0,e1,parity)=>{
let edge0_n=self.mesh0.edge_n(e0); let edge0_n=self.mesh0.edge_n(e0);
let edge1_n=self.mesh1.edge_n(e1); let edge1_n=self.mesh1.edge_n(e1);
let &[e0v0,e0v1]=self.mesh0.edge_verts(e0).borrow(); let &[e0v0,e0v1]=self.mesh0.edge_verts(e0).as_ref();
let &[e1v0,e1v1]=self.mesh1.edge_verts(e1).borrow(); let &[e1v0,e1v1]=self.mesh1.edge_verts(e1).as_ref();
let n=edge0_n.cross(edge1_n); let n=edge0_n.cross(edge1_n);
let e0d=n.dot(self.mesh0.vert(e0v0)+self.mesh0.vert(e0v1)); let e0d=n.dot(self.mesh0.vert(e0v0)+self.mesh0.vert(e0v1));
let e1d=n.dot(self.mesh1.vert(e1v0)+self.mesh1.vert(e1v1)); let e1d=n.dot(self.mesh1.vert(e1v0)+self.mesh1.vert(e1v1));
((n*(parity as i64*4-2)).fix_3(),((e0d-e1d)*(parity as i64*2-1)).fix_4()) ((n*(parity as i64*4-2)).widen_3(),((e0d-e1d)*(parity as i64*2-1)).widen_4())
}, },
MinkowskiFace::FaceVert(f0,v1)=>{ MinkowskiFace::FaceVert(f0,v1)=>{
let (n,d)=self.mesh0.face_nd(f0); let (n,d)=self.mesh0.face_nd(f0);
@ -828,44 +840,44 @@ impl MeshQuery for MinkowskiMesh<'_>{
}, },
} }
} }
fn face_edges(&self,face_id:MinkowskiFace)->Cow<[MinkowskiDirectedEdge]>{ fn face_edges(&self,face_id:MinkowskiFace)->impl AsRef<[MinkowskiDirectedEdge]>{
match face_id{ match face_id{
MinkowskiFace::VertFace(v0,f1)=>{ MinkowskiFace::VertFace(v0,f1)=>{
Cow::Owned(self.mesh1.face_edges(f1).iter().map(|&edge_id1|{ self.mesh1.face_edges(f1).as_ref().iter().map(|&edge_id1|
MinkowskiDirectedEdge::VertEdge(v0,edge_id1.reverse()) MinkowskiDirectedEdge::VertEdge(v0,edge_id1.reverse())
}).collect()) ).collect()
}, },
MinkowskiFace::EdgeEdge(e0,e1,parity)=>{ MinkowskiFace::EdgeEdge(e0,e1,parity)=>{
let e0v=self.mesh0.edge_verts(e0); let &[e0v0,e0v1]=self.mesh0.edge_verts(e0).as_ref();
let e1v=self.mesh1.edge_verts(e1); let &[e1v0,e1v1]=self.mesh1.edge_verts(e1).as_ref();
//could sort this if ordered edges are needed //could sort this if ordered edges are needed
//probably just need to reverse this list according to parity //probably just need to reverse this list according to parity
Cow::Owned(vec![ vec![
MinkowskiDirectedEdge::VertEdge(e0v[0],e1.as_directed(parity)), MinkowskiDirectedEdge::VertEdge(e0v0,e1.as_directed(parity)),
MinkowskiDirectedEdge::EdgeVert(e0.as_directed(!parity),e1v[0]), MinkowskiDirectedEdge::EdgeVert(e0.as_directed(!parity),e1v0),
MinkowskiDirectedEdge::VertEdge(e0v[1],e1.as_directed(!parity)), MinkowskiDirectedEdge::VertEdge(e0v1,e1.as_directed(!parity)),
MinkowskiDirectedEdge::EdgeVert(e0.as_directed(parity),e1v[1]), MinkowskiDirectedEdge::EdgeVert(e0.as_directed(parity),e1v1),
]) ]
}, },
MinkowskiFace::FaceVert(f0,v1)=>{ MinkowskiFace::FaceVert(f0,v1)=>{
Cow::Owned(self.mesh0.face_edges(f0).iter().map(|&edge_id0|{ self.mesh0.face_edges(f0).as_ref().iter().map(|&edge_id0|
MinkowskiDirectedEdge::EdgeVert(edge_id0,v1) MinkowskiDirectedEdge::EdgeVert(edge_id0,v1)
}).collect()) ).collect()
}, },
} }
} }
fn edge_faces(&self,edge_id:MinkowskiEdge)->Cow<[MinkowskiFace;2]>{ fn edge_faces(&self,edge_id:MinkowskiEdge)->impl AsRef<[MinkowskiFace;2]>{
match edge_id{ match edge_id{
MinkowskiEdge::VertEdge(v0,e1)=>{ MinkowskiEdge::VertEdge(v0,e1)=>{
//faces are listed backwards from the minkowski mesh //faces are listed backwards from the minkowski mesh
let v0e=self.mesh0.vert_edges(v0); let v0e=self.mesh0.vert_edges(v0);
let &[e1f0,e1f1]=self.mesh1.edge_faces(e1).borrow(); let &[e1f0,e1f1]=self.mesh1.edge_faces(e1).as_ref();
Cow::Owned([(e1f1,false),(e1f0,true)].map(|(edge_face_id1,face_parity)|{ AsRefHelper([(e1f1,false),(e1f0,true)].map(|(edge_face_id1,face_parity)|{
let mut best_edge=None; let mut best_edge=None;
let mut best_d:Ratio<Fixed<8,256>,Fixed<8,256>>=Ratio::new(Fixed::ZERO,Fixed::ONE); let mut best_d:Ratio<Fixed<8,256>,Fixed<8,256>>=Ratio::new(Fixed::ZERO,Fixed::ONE);
let edge_face1_n=self.mesh1.face_nd(edge_face_id1).0; let edge_face1_n=self.mesh1.face_nd(edge_face_id1).0;
let edge_face1_nn=edge_face1_n.dot(edge_face1_n); let edge_face1_nn=edge_face1_n.dot(edge_face1_n);
for &directed_edge_id0 in v0e.iter(){ for &directed_edge_id0 in v0e.as_ref(){
let edge0_n=self.mesh0.directed_edge_n(directed_edge_id0); let edge0_n=self.mesh0.directed_edge_n(directed_edge_id0);
//must be behind other face. //must be behind other face.
let d=edge_face1_n.dot(edge0_n); let d=edge_face1_n.dot(edge0_n);
@ -889,13 +901,13 @@ impl MeshQuery for MinkowskiMesh<'_>{
MinkowskiEdge::EdgeVert(e0,v1)=>{ MinkowskiEdge::EdgeVert(e0,v1)=>{
//tracking index with an external variable because .enumerate() is not available //tracking index with an external variable because .enumerate() is not available
let v1e=self.mesh1.vert_edges(v1); let v1e=self.mesh1.vert_edges(v1);
let &[e0f0,e0f1]=self.mesh0.edge_faces(e0).borrow(); let &[e0f0,e0f1]=self.mesh0.edge_faces(e0).as_ref();
Cow::Owned([(e0f0,true),(e0f1,false)].map(|(edge_face_id0,face_parity)|{ AsRefHelper([(e0f0,true),(e0f1,false)].map(|(edge_face_id0,face_parity)|{
let mut best_edge=None; let mut best_edge=None;
let mut best_d:Ratio<Fixed<8,256>,Fixed<8,256>>=Ratio::new(Fixed::ZERO,Fixed::ONE); let mut best_d:Ratio<Fixed<8,256>,Fixed<8,256>>=Ratio::new(Fixed::ZERO,Fixed::ONE);
let edge_face0_n=self.mesh0.face_nd(edge_face_id0).0; let edge_face0_n=self.mesh0.face_nd(edge_face_id0).0;
let edge_face0_nn=edge_face0_n.dot(edge_face0_n); let edge_face0_nn=edge_face0_n.dot(edge_face0_n);
for &directed_edge_id1 in v1e.iter(){ for &directed_edge_id1 in v1e.as_ref(){
let edge1_n=self.mesh1.directed_edge_n(directed_edge_id1); let edge1_n=self.mesh1.directed_edge_n(directed_edge_id1);
let d=edge_face0_n.dot(edge1_n); let d=edge_face0_n.dot(edge1_n);
if d.is_negative(){ if d.is_negative(){
@ -915,31 +927,27 @@ impl MeshQuery for MinkowskiMesh<'_>{
}, },
} }
} }
fn edge_verts(&self,edge_id:MinkowskiEdge)->Cow<[MinkowskiVert;2]>{ fn edge_verts(&self,edge_id:MinkowskiEdge)->impl AsRef<[MinkowskiVert;2]>{
match edge_id{ AsRefHelper(match edge_id{
MinkowskiEdge::VertEdge(v0,e1)=>{ MinkowskiEdge::VertEdge(v0,e1)=>(*self.mesh1.edge_verts(e1).as_ref()).map(|vert_id1|
Cow::Owned(self.mesh1.edge_verts(e1).map(|vert_id1|{ MinkowskiVert::VertVert(v0,vert_id1)
MinkowskiVert::VertVert(v0,vert_id1) ),
})) MinkowskiEdge::EdgeVert(e0,v1)=>(*self.mesh0.edge_verts(e0).as_ref()).map(|vert_id0|
}, MinkowskiVert::VertVert(vert_id0,v1)
MinkowskiEdge::EdgeVert(e0,v1)=>{ ),
Cow::Owned(self.mesh0.edge_verts(e0).map(|vert_id0|{ })
MinkowskiVert::VertVert(vert_id0,v1)
}))
},
}
} }
fn vert_edges(&self,vert_id:MinkowskiVert)->Cow<[MinkowskiDirectedEdge]>{ fn vert_edges(&self,vert_id:MinkowskiVert)->impl AsRef<[MinkowskiDirectedEdge]>{
match vert_id{ match vert_id{
MinkowskiVert::VertVert(v0,v1)=>{ MinkowskiVert::VertVert(v0,v1)=>{
let mut edges=Vec::new(); let mut edges=Vec::new();
//detect shared volume when the other mesh is mirrored along a test edge dir //detect shared volume when the other mesh is mirrored along a test edge dir
let v0f=self.mesh0.vert_faces(v0); let v0f=self.mesh0.vert_faces(v0);
let v1f=self.mesh1.vert_faces(v1); let v1f=self.mesh1.vert_faces(v1);
let v0f_n:Vec<_>=v0f.iter().map(|&face_id|self.mesh0.face_nd(face_id).0).collect(); let v0f_n:Vec<_>=v0f.as_ref().iter().map(|&face_id|self.mesh0.face_nd(face_id).0).collect();
let v1f_n:Vec<_>=v1f.iter().map(|&face_id|self.mesh1.face_nd(face_id).0).collect(); let v1f_n:Vec<_>=v1f.as_ref().iter().map(|&face_id|self.mesh1.face_nd(face_id).0).collect();
let the_len=v0f.len()+v1f.len(); let the_len=v0f.as_ref().len()+v1f.as_ref().len();
for &directed_edge_id in self.mesh0.vert_edges(v0).iter(){ for &directed_edge_id in self.mesh0.vert_edges(v0).as_ref(){
let n=self.mesh0.directed_edge_n(directed_edge_id); let n=self.mesh0.directed_edge_n(directed_edge_id);
let nn=n.dot(n); let nn=n.dot(n);
// TODO: there's gotta be a better way to do this // TODO: there's gotta be a better way to do this
@ -949,30 +957,33 @@ impl MeshQuery for MinkowskiMesh<'_>{
face_normals.clone_from(&v0f_n); face_normals.clone_from(&v0f_n);
for face_n in &v1f_n{ for face_n in &v1f_n{
//add reflected mesh1 faces //add reflected mesh1 faces
face_normals.push(*face_n-(n*face_n.dot(n)*2/nn).divide().fix_3()); //wrap for speed
face_normals.push(*face_n-(n*face_n.dot(n)*2/nn).divide().wrap_3());
} }
if is_empty_volume(face_normals){ if is_empty_volume(face_normals){
edges.push(MinkowskiDirectedEdge::EdgeVert(directed_edge_id,v1)); edges.push(MinkowskiDirectedEdge::EdgeVert(directed_edge_id,v1));
} }
} }
for &directed_edge_id in self.mesh1.vert_edges(v1).iter(){ for &directed_edge_id in self.mesh1.vert_edges(v1).as_ref(){
let n=self.mesh1.directed_edge_n(directed_edge_id); let n=self.mesh1.directed_edge_n(directed_edge_id);
let nn=n.dot(n); let nn=n.dot(n);
let mut face_normals=Vec::with_capacity(the_len); let mut face_normals=Vec::with_capacity(the_len);
face_normals.clone_from(&v1f_n); face_normals.clone_from(&v1f_n);
for face_n in &v0f_n{ for face_n in &v0f_n{
face_normals.push(*face_n-(n*face_n.dot(n)*2/nn).divide().fix_3()); //wrap for speed
face_normals.push(*face_n-(n*face_n.dot(n)*2/nn).divide().wrap_3());
} }
if is_empty_volume(face_normals){ if is_empty_volume(face_normals){
edges.push(MinkowskiDirectedEdge::VertEdge(v0,directed_edge_id)); edges.push(MinkowskiDirectedEdge::VertEdge(v0,directed_edge_id));
} }
} }
Cow::Owned(edges) edges
}, },
} }
} }
fn vert_faces(&self,_vert_id:MinkowskiVert)->Cow<[MinkowskiFace]>{ fn vert_faces(&self,_vert_id:MinkowskiVert)->impl AsRef<[MinkowskiFace]>{
unimplemented!() unimplemented!();
vec![]
} }
} }
@ -1002,6 +1013,6 @@ fn is_empty_volume(normals:Vec<Vector3<Fixed<3,96>>>)->bool{
#[test] #[test]
fn test_is_empty_volume(){ fn test_is_empty_volume(){
assert!(!is_empty_volume([vec3::X.fix_3(),vec3::Y.fix_3(),vec3::Z.fix_3()].to_vec())); assert!(!is_empty_volume([vec3::X.widen_3(),vec3::Y.widen_3(),vec3::Z.widen_3()].to_vec()));
assert!(is_empty_volume([vec3::X.fix_3(),vec3::Y.fix_3(),vec3::Z.fix_3(),vec3::NEG_X.fix_3()].to_vec())); assert!(is_empty_volume([vec3::X.widen_3(),vec3::Y.widen_3(),vec3::Z.widen_3(),vec3::NEG_X.widen_3()].to_vec()));
} }

@ -107,6 +107,7 @@ enum TransientAcceleration{
#[derive(Clone,Debug)] #[derive(Clone,Debug)]
struct ContactMoveState{ struct ContactMoveState{
jump_direction:JumpDirection, jump_direction:JumpDirection,
// TODO: this is bad data normalization, remove this
contact:ContactCollision, contact:ContactCollision,
target:TransientAcceleration, target:TransientAcceleration,
} }
@ -117,8 +118,8 @@ impl TransientAcceleration{
}else{ }else{
//normal friction acceleration is clippedAcceleration.dot(normal)*friction //normal friction acceleration is clippedAcceleration.dot(normal)*friction
TransientAcceleration::Reachable{ TransientAcceleration::Reachable{
acceleration:target_diff.with_length(accel).divide().fix_1(), acceleration:target_diff.with_length(accel).divide().wrap_1(),
time:time+Time::from((target_diff.length()/accel).divide().fix_1()) time:time+Time::from((target_diff.length()/accel).divide().clamp_1())
} }
} }
} }
@ -407,7 +408,7 @@ impl HitboxMesh{
let transform=PhysicsMeshTransform::new(transform); let transform=PhysicsMeshTransform::new(transform);
let transformed_mesh=TransformedMesh::new(mesh.complete_mesh_view(),&transform); let transformed_mesh=TransformedMesh::new(mesh.complete_mesh_view(),&transform);
for vert in transformed_mesh.verts(){ for vert in transformed_mesh.verts(){
aabb.grow(vert.fix_1()); aabb.grow(vert.narrow_1().unwrap());
} }
Self{ Self{
halfsize:aabb.size()>>1, halfsize:aabb.size()>>1,
@ -460,12 +461,12 @@ impl StyleHelper for StyleModifiers{
} }
fn get_y_control_dir(&self,camera:&PhysicsCamera,controls:Controls)->Planar64Vec3{ fn get_y_control_dir(&self,camera:&PhysicsCamera,controls:Controls)->Planar64Vec3{
(camera.rotation_y()*self.get_control_dir(controls)).fix_1() (camera.rotation_y()*self.get_control_dir(controls)).wrap_1()
} }
fn get_propulsion_control_dir(&self,camera:&PhysicsCamera,controls:Controls)->Planar64Vec3{ fn get_propulsion_control_dir(&self,camera:&PhysicsCamera,controls:Controls)->Planar64Vec3{
//don't interpolate this! discrete mouse movement, constant acceleration //don't interpolate this! discrete mouse movement, constant acceleration
(camera.rotation()*self.get_control_dir(controls)).fix_1() (camera.rotation()*self.get_control_dir(controls)).wrap_1()
} }
fn calculate_mesh(&self)->HitboxMesh{ fn calculate_mesh(&self)->HitboxMesh{
let mesh=match self.hitbox.mesh{ let mesh=match self.hitbox.mesh{
@ -601,12 +602,17 @@ impl MoveState{
fn cull_velocity(&mut self,velocity:Planar64Vec3,body:&mut Body,touching:&mut TouchingState,models:&PhysicsModels,hitbox_mesh:&HitboxMesh,style:&StyleModifiers,camera:&PhysicsCamera,input_state:&InputState){ fn cull_velocity(&mut self,velocity:Planar64Vec3,body:&mut Body,touching:&mut TouchingState,models:&PhysicsModels,hitbox_mesh:&HitboxMesh,style:&StyleModifiers,camera:&PhysicsCamera,input_state:&InputState){
//TODO: be more precise about contacts //TODO: be more precise about contacts
if set_velocity_cull(body,touching,models,hitbox_mesh,velocity){ if set_velocity_cull(body,touching,models,hitbox_mesh,velocity){
//TODO do better // TODO do better
// TODO: unduplicate this code
match self.get_walk_state(){ match self.get_walk_state(){
//did you stop touching the thing you were walking on? // did you stop touching the thing you were walking on?
Some(walk_state)=>if !touching.contacts.contains(&walk_state.contact){ Some(walk_state)=>if !touching.contacts.contains(&walk_state.contact){
self.set_move_state(MoveState::Air,body,touching,models,hitbox_mesh,style,camera,input_state); self.set_move_state(MoveState::Air,body,touching,models,hitbox_mesh,style,camera,input_state);
}else{
// stopped touching something else while walking
self.apply_enum_and_input_and_body(body,touching,models,hitbox_mesh,style,camera,input_state);
}, },
// not walking, but stopped touching something
None=>self.apply_enum_and_body(body,touching,models,hitbox_mesh,style,camera,input_state), None=>self.apply_enum_and_body(body,touching,models,hitbox_mesh,style,camera,input_state),
} }
} }
@ -982,10 +988,7 @@ impl PhysicsContext<'_>{
impl PhysicsData{ impl PhysicsData{
/// use with caution, this is the only non-instruction way to mess with physics /// use with caution, this is the only non-instruction way to mess with physics
pub fn generate_models(&mut self,map:&map::CompleteMap){ pub fn generate_models(&mut self,map:&map::CompleteMap){
let mut modes=map.modes.clone(); let modes=map.modes.clone().denormalize();
for mode in &mut modes.modes{
mode.denormalize_data();
}
let mut used_contact_attributes=Vec::new(); let mut used_contact_attributes=Vec::new();
let mut used_intersect_attributes=Vec::new(); let mut used_intersect_attributes=Vec::new();
@ -1087,7 +1090,7 @@ impl PhysicsData{
let mut aabb=aabb::Aabb::default(); let mut aabb=aabb::Aabb::default();
let transformed_mesh=TransformedMesh::new(view,transform); let transformed_mesh=TransformedMesh::new(view,transform);
for v in transformed_mesh.verts(){ for v in transformed_mesh.verts(){
aabb.grow(v.fix_1()); aabb.grow(v.narrow_1().unwrap());
} }
(ConvexMeshId{ (ConvexMeshId{
model_id, model_id,
@ -1165,7 +1168,8 @@ fn contact_normal(models:&PhysicsModels,hitbox_mesh:&HitboxMesh,contact:&Contact
let model_mesh=models.contact_mesh(contact); let model_mesh=models.contact_mesh(contact);
let minkowski=model_physics::MinkowskiMesh::minkowski_sum(model_mesh,hitbox_mesh.transformed_mesh()); let minkowski=model_physics::MinkowskiMesh::minkowski_sum(model_mesh,hitbox_mesh.transformed_mesh());
// TODO: normalize to i64::MAX>>1 // TODO: normalize to i64::MAX>>1
minkowski.face_nd(contact.face_id).0.fix_1() // wrap for speed
minkowski.face_nd(contact.face_id).0.wrap_1()
} }
fn recalculate_touching( fn recalculate_touching(
@ -1259,7 +1263,7 @@ fn set_velocity_cull(body:&mut Body,touching:&mut TouchingState,models:&PhysicsM
culled culled
} }
fn set_velocity(body:&mut Body,touching:&TouchingState,models:&PhysicsModels,hitbox_mesh:&HitboxMesh,v:Planar64Vec3){ fn set_velocity(body:&mut Body,touching:&TouchingState,models:&PhysicsModels,hitbox_mesh:&HitboxMesh,v:Planar64Vec3){
body.velocity=touching.constrain_velocity(models,hitbox_mesh,v);; body.velocity=touching.constrain_velocity(models,hitbox_mesh,v);
} }
fn set_acceleration_cull(body:&mut Body,touching:&mut TouchingState,models:&PhysicsModels,hitbox_mesh:&HitboxMesh,a:Planar64Vec3)->bool{ fn set_acceleration_cull(body:&mut Body,touching:&mut TouchingState,models:&PhysicsModels,hitbox_mesh:&HitboxMesh,a:Planar64Vec3)->bool{
//This is not correct but is better than what I have //This is not correct but is better than what I have
@ -1324,7 +1328,7 @@ fn teleport_to_spawn(
const EPSILON:Planar64=Planar64::raw((1<<32)/16); const EPSILON:Planar64=Planar64::raw((1<<32)/16);
let transform=models.get_model_transform(spawn_model_id).ok_or(TeleportToSpawnError::NoModel)?; let transform=models.get_model_transform(spawn_model_id).ok_or(TeleportToSpawnError::NoModel)?;
//TODO: transform.vertex.matrix3.col(1)+transform.vertex.translation //TODO: transform.vertex.matrix3.col(1)+transform.vertex.translation
let point=transform.vertex.transform_point3(vec3::Y).fix_1()+Planar64Vec3::new([Planar64::ZERO,style.hitbox.halfsize.y+EPSILON,Planar64::ZERO]); let point=transform.vertex.transform_point3(vec3::Y).clamp_1()+Planar64Vec3::new([Planar64::ZERO,style.hitbox.halfsize.y+EPSILON,Planar64::ZERO]);
teleport(point,move_state,body,touching,run,mode_state,Some(mode),models,hitbox_mesh,bvh,style,camera,input_state,time); teleport(point,move_state,body,touching,run,mode_state,Some(mode),models,hitbox_mesh,bvh,style,camera,input_state,time);
Ok(()) Ok(())
} }
@ -1488,7 +1492,7 @@ fn collision_start_contact(
Some(gameplay_attributes::ContactingBehaviour::Surf)=>(), Some(gameplay_attributes::ContactingBehaviour::Surf)=>(),
Some(gameplay_attributes::ContactingBehaviour::Cling)=>println!("Unimplemented!"), Some(gameplay_attributes::ContactingBehaviour::Cling)=>println!("Unimplemented!"),
&Some(gameplay_attributes::ContactingBehaviour::Elastic(elasticity))=>{ &Some(gameplay_attributes::ContactingBehaviour::Elastic(elasticity))=>{
let reflected_velocity=body.velocity+((body.velocity-incident_velocity)*Planar64::raw(elasticity as i64+1)).fix_1(); let reflected_velocity=body.velocity+((body.velocity-incident_velocity)*Planar64::raw(elasticity as i64+1)).wrap_1();
set_velocity(body,touching,models,hitbox_mesh,reflected_velocity); set_velocity(body,touching,models,hitbox_mesh,reflected_velocity);
}, },
Some(gameplay_attributes::ContactingBehaviour::Ladder(contacting_ladder))=> Some(gameplay_attributes::ContactingBehaviour::Ladder(contacting_ladder))=>
@ -1622,10 +1626,14 @@ fn collision_end_contact(
//TODO do better //TODO do better
//this is inner code from move_state.cull_velocity //this is inner code from move_state.cull_velocity
match move_state.get_walk_state(){ match move_state.get_walk_state(){
//did you stop touching the thing you were walking on? // did you stop touching the thing you were walking on?
Some(walk_state)=>if walk_state.contact==contact{ Some(walk_state)=>if walk_state.contact==contact{
move_state.set_move_state(MoveState::Air,body,touching,models,hitbox_mesh,style,camera,input_state); move_state.set_move_state(MoveState::Air,body,touching,models,hitbox_mesh,style,camera,input_state);
}else{
// stopped touching something else while walking
move_state.apply_enum_and_input_and_body(body,touching,models,hitbox_mesh,style,camera,input_state);
}, },
// not walking, but stopped touching something
None=>move_state.apply_enum_and_body(body,touching,models,hitbox_mesh,style,camera,input_state), None=>move_state.apply_enum_and_body(body,touching,models,hitbox_mesh,style,camera,input_state),
} }
} }
@ -1711,7 +1719,7 @@ fn atomic_internal_instruction(state:&mut PhysicsState,data:&PhysicsData,ins:Tim
let control_dir=state.style.get_control_dir(masked_controls); let control_dir=state.style.get_control_dir(masked_controls);
if control_dir!=vec3::ZERO{ if control_dir!=vec3::ZERO{
let camera_mat=state.camera.simulate_move_rotation_y(state.input_state.lerp_delta(state.time).x); let camera_mat=state.camera.simulate_move_rotation_y(state.input_state.lerp_delta(state.time).x);
if let Some(ticked_velocity)=strafe_settings.tick_velocity(state.body.velocity,(camera_mat*control_dir).with_length(Planar64::ONE).divide().fix_1()){ if let Some(ticked_velocity)=strafe_settings.tick_velocity(state.body.velocity,(camera_mat*control_dir).with_length(Planar64::ONE).divide().wrap_1()){
//this is wrong but will work ig //this is wrong but will work ig
//need to note which push planes activate in push solve and keep those //need to note which push planes activate in push solve and keep those
state.cull_velocity(data,ticked_velocity); state.cull_velocity(data,ticked_velocity);
@ -1814,14 +1822,18 @@ fn atomic_input_instruction(state:&mut PhysicsState,data:&PhysicsData,ins:TimedI
}, },
Instruction::Mode(ModeInstruction::Restart(mode_id))=>{ Instruction::Mode(ModeInstruction::Restart(mode_id))=>{
//teleport to mode start zone //teleport to mode start zone
let mut spawn_point=vec3::ZERO;
let mode=data.modes.get_mode(mode_id); let mode=data.modes.get_mode(mode_id);
let spawn_point=mode.and_then(|mode| if let Some(mode)=mode{
// set style
state.style=mode.get_style().clone();
//TODO: spawn at the bottom of the start zone plus the hitbox size //TODO: spawn at the bottom of the start zone plus the hitbox size
//TODO: set camera andles to face the same way as the start zone //TODO: set camera angles to face the same way as the start zone
data.models.get_model_transform(mode.get_start().into()).map(|transform| if let Some(transform)=data.models.get_model_transform(mode.get_start()){
transform.vertex.translation // NOTE: this value may be 0,0,0 on source maps
) spawn_point=transform.vertex.translation;
).unwrap_or(vec3::ZERO); }
}
set_position(spawn_point,&mut state.move_state,&mut state.body,&mut state.touching,&mut state.run,&mut state.mode_state,mode,&data.models,&data.hitbox_mesh,&data.bvh,&state.style,&state.camera,&state.input_state,state.time); set_position(spawn_point,&mut state.move_state,&mut state.body,&mut state.touching,&mut state.run,&mut state.mode_state,mode,&data.models,&data.hitbox_mesh,&data.bvh,&state.style,&state.camera,&state.input_state,state.time);
set_velocity(&mut state.body,&state.touching,&data.models,&data.hitbox_mesh,vec3::ZERO); set_velocity(&mut state.body,&state.touching,&data.models,&data.hitbox_mesh,vec3::ZERO);
state.set_move_state(data,MoveState::Air); state.set_move_state(data,MoveState::Air);
@ -1831,6 +1843,9 @@ fn atomic_input_instruction(state:&mut PhysicsState,data:&PhysicsData,ins:TimedI
Instruction::Mode(ModeInstruction::Spawn(mode_id,stage_id))=>{ Instruction::Mode(ModeInstruction::Spawn(mode_id,stage_id))=>{
//spawn at a particular stage //spawn at a particular stage
if let Some(mode)=data.modes.get_mode(mode_id){ if let Some(mode)=data.modes.get_mode(mode_id){
// set style
state.style=mode.get_style().clone();
// teleport to stage in mode
if let Some(stage)=mode.get_stage(stage_id){ if let Some(stage)=mode.get_stage(stage_id){
let _=teleport_to_spawn( let _=teleport_to_spawn(
stage.spawn(), stage.spawn(),

@ -1,4 +1,6 @@
use strafesnet_common::integer::{self,vec3::{self,Vector3},Fixed,Planar64,Planar64Vec3,Ratio}; use strafesnet_common::integer::vec3::{self,Vector3};
use strafesnet_common::integer::{Fixed,Planar64Vec3,Ratio};
use strafesnet_common::ray::Ray;
// This algorithm is based on Lua code // This algorithm is based on Lua code
// written by Trey Reynolds in 2021 // written by Trey Reynolds in 2021
@ -12,24 +14,6 @@ type Conts<'a>=arrayvec::ArrayVec<&'a Contact,4>;
// hack to allow comparing ratios to zero // hack to allow comparing ratios to zero
const RATIO_ZERO:Ratio<Fixed<1,32>,Fixed<1,32>>=Ratio::new(Fixed::ZERO,Fixed::EPSILON); const RATIO_ZERO:Ratio<Fixed<1,32>,Fixed<1,32>>=Ratio::new(Fixed::ZERO,Fixed::EPSILON);
struct Ray{
origin:Planar64Vec3,
direction:Planar64Vec3,
}
impl Ray{
fn extrapolate<Num,Den,N1,T1>(&self,t:Ratio<Num,Den>)->Planar64Vec3
where
Num:Copy,
Den:Copy,
Num:core::ops::Mul<Planar64,Output=N1>,
Planar64:core::ops::Mul<Den,Output=N1>,
N1:integer::Divide<Den,Output=T1>,
T1:integer::Fix<Planar64>,
{
self.origin+self.direction.map(|elem|(t*elem).divide().fix())
}
}
/// Information about a contact restriction /// Information about a contact restriction
pub struct Contact{ pub struct Contact{
pub position:Planar64Vec3, pub position:Planar64Vec3,
@ -168,18 +152,19 @@ const fn get_push_ray_0(point:Planar64Vec3)->Ray{
Ray{origin:point,direction:vec3::ZERO} Ray{origin:point,direction:vec3::ZERO}
} }
fn get_push_ray_1(point:Planar64Vec3,c0:&Contact)->Option<Ray>{ fn get_push_ray_1(point:Planar64Vec3,c0:&Contact)->Option<Ray>{
let direction=solve1(c0)?.divide().fix_1(); //wrap for speed
let direction=solve1(c0)?.divide().wrap_1();
let [s0]=decompose1(direction,c0.velocity)?; let [s0]=decompose1(direction,c0.velocity)?;
if s0.lt_ratio(RATIO_ZERO){ if s0.lt_ratio(RATIO_ZERO){
return None; return None;
} }
let origin=point+solve1( let origin=point+solve1(
&c0.relative_to(point), &c0.relative_to(point),
)?.divide().fix_1(); )?.divide().wrap_1();
Some(Ray{origin,direction}) Some(Ray{origin,direction})
} }
fn get_push_ray_2(point:Planar64Vec3,c0:&Contact,c1:&Contact)->Option<Ray>{ fn get_push_ray_2(point:Planar64Vec3,c0:&Contact,c1:&Contact)->Option<Ray>{
let direction=solve2(c0,c1)?.divide().fix_1(); let direction=solve2(c0,c1)?.divide().wrap_1();
let [s0,s1]=decompose2(direction,c0.velocity,c1.velocity)?; let [s0,s1]=decompose2(direction,c0.velocity,c1.velocity)?;
if s0.lt_ratio(RATIO_ZERO)||s1.lt_ratio(RATIO_ZERO){ if s0.lt_ratio(RATIO_ZERO)||s1.lt_ratio(RATIO_ZERO){
return None; return None;
@ -187,11 +172,11 @@ fn get_push_ray_2(point:Planar64Vec3,c0:&Contact,c1:&Contact)->Option<Ray>{
let origin=point+solve2( let origin=point+solve2(
&c0.relative_to(point), &c0.relative_to(point),
&c1.relative_to(point), &c1.relative_to(point),
)?.divide().fix_1(); )?.divide().wrap_1();
Some(Ray{origin,direction}) Some(Ray{origin,direction})
} }
fn get_push_ray_3(point:Planar64Vec3,c0:&Contact,c1:&Contact,c2:&Contact)->Option<Ray>{ fn get_push_ray_3(point:Planar64Vec3,c0:&Contact,c1:&Contact,c2:&Contact)->Option<Ray>{
let direction=solve3(c0,c1,c2)?.divide().fix_1(); let direction=solve3(c0,c1,c2)?.divide().wrap_1();
let [s0,s1,s2]=decompose3(direction,c0.velocity,c1.velocity,c2.velocity)?; let [s0,s1,s2]=decompose3(direction,c0.velocity,c1.velocity,c2.velocity)?;
if s0.lt_ratio(RATIO_ZERO)||s1.lt_ratio(RATIO_ZERO)||s2.lt_ratio(RATIO_ZERO){ if s0.lt_ratio(RATIO_ZERO)||s1.lt_ratio(RATIO_ZERO)||s2.lt_ratio(RATIO_ZERO){
return None; return None;
@ -200,7 +185,7 @@ fn get_push_ray_3(point:Planar64Vec3,c0:&Contact,c1:&Contact,c2:&Contact)->Optio
&c0.relative_to(point), &c0.relative_to(point),
&c1.relative_to(point), &c1.relative_to(point),
&c2.relative_to(point), &c2.relative_to(point),
)?.divide().fix_1(); )?.divide().wrap_1();
Some(Ray{origin,direction}) Some(Ray{origin,direction})
} }

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

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

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

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

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

@ -34,6 +34,7 @@ pub enum PlanesToFacesError{
InitFace2, InitFace2,
InitIntersection, InitIntersection,
FindNewIntersection, FindNewIntersection,
// Narrow(strafesnet_common::integer::NarrowError),
EmptyFaces, EmptyFaces,
InfiniteLoop1, InfiniteLoop1,
InfiniteLoop2, InfiniteLoop2,
@ -75,22 +76,18 @@ fn planes_to_faces(face_list:std::collections::HashSet<Face>)->Result<Faces,Plan
let mut intersection=solve3(face0,face1,face2).ok_or(PlanesToFacesError::InitIntersection)?; let mut intersection=solve3(face0,face1,face2).ok_or(PlanesToFacesError::InitIntersection)?;
// repeatedly update face0, face1 until all faces form part of the convex solid // repeatedly update face1, face2 until all faces form part of the convex solid
'find: loop{ 'find: loop{
if let Some(a)=detect_loop.checked_sub(1){ detect_loop=detect_loop.checked_sub(1).ok_or(PlanesToFacesError::InfiniteLoop1)?;
detect_loop=a;
}else{
return Err(PlanesToFacesError::InfiniteLoop1);
}
// test if any *other* faces occlude the intersection // test if any *other* faces occlude the intersection
for new_face in &face_list{ for new_face in &face_list{
// new face occludes intersection point // new face occludes intersection point
if (new_face.dot.fix_2()/Planar64::ONE).lt_ratio(new_face.normal.dot(intersection.num)/intersection.den){ if (new_face.dot.widen_2()/Planar64::ONE).lt_ratio(new_face.normal.dot(intersection.num)/intersection.den){
// replace one of the faces with the new face // replace one of the faces with the new face
// dont' try to replace face0 because we are exploring that face in particular // dont' try to replace face0 because we are exploring that face in particular
if let Some(new_intersection)=solve3(face0,new_face,face2){ if let Some(new_intersection)=solve3(face0,new_face,face2){
// face1 does not occlude (or intersect) the new intersection // face1 does not occlude (or intersect) the new intersection
if (face1.dot.fix_2()/Planar64::ONE).gt_ratio(face1.normal.dot(new_intersection.num)/new_intersection.den){ if (face1.dot.widen_2()/Planar64::ONE).gt_ratio(face1.normal.dot(new_intersection.num)/new_intersection.den){
face1=new_face; face1=new_face;
intersection=new_intersection; intersection=new_intersection;
continue 'find; continue 'find;
@ -98,7 +95,7 @@ fn planes_to_faces(face_list:std::collections::HashSet<Face>)->Result<Faces,Plan
} }
if let Some(new_intersection)=solve3(face0,face1,new_face){ if let Some(new_intersection)=solve3(face0,face1,new_face){
// face2 does not occlude (or intersect) the new intersection // face2 does not occlude (or intersect) the new intersection
if (face2.dot.fix_2()/Planar64::ONE).gt_ratio(face2.normal.dot(new_intersection.num)/new_intersection.den){ if (face2.dot.widen_2()/Planar64::ONE).gt_ratio(face2.normal.dot(new_intersection.num)/new_intersection.den){
face2=new_face; face2=new_face;
intersection=new_intersection; intersection=new_intersection;
continue 'find; continue 'find;
@ -122,12 +119,10 @@ fn planes_to_faces(face_list:std::collections::HashSet<Face>)->Result<Faces,Plan
if core::ptr::eq(face2,new_face){ if core::ptr::eq(face2,new_face){
continue; continue;
} }
if let Some(new_intersection)=solve3(new_face,face1,face2){ // new_face occludes intersection meaning intersection is not on convex solid and face0 is degenrate
// face0 does not occlude (or intersect) the new intersection if (new_face.dot.widen_2()/Planar64::ONE).lt_ratio(new_face.normal.dot(intersection.num)/intersection.den){
if (face0.dot.fix_2()/Planar64::ONE).lt_ratio(face0.normal.dot(new_intersection.num)/new_intersection.den){ // abort! reject face0 entirely
// abort! reject face0 entirely continue 'face;
continue 'face;
}
} }
} }
@ -143,7 +138,7 @@ fn planes_to_faces(face_list:std::collections::HashSet<Face>)->Result<Faces,Plan
loop{ loop{
// push point onto vertices // push point onto vertices
// problem: this may push a vertex that does not fit in the fixed point range and is thus meaningless // problem: this may push a vertex that does not fit in the fixed point range and is thus meaningless
face.push(intersection.divide().fix_1()); face.push(intersection.divide().narrow_1().unwrap());
// we looped back around to face1, we're done! // we looped back around to face1, we're done!
if core::ptr::eq(face1,face2){ if core::ptr::eq(face1,face2){
@ -177,11 +172,7 @@ fn planes_to_faces(face_list:std::collections::HashSet<Face>)->Result<Faces,Plan
face2=new_face; face2=new_face;
intersection=new_intersection; intersection=new_intersection;
if let Some(a)=detect_loop.checked_sub(1){ detect_loop=detect_loop.checked_sub(1).ok_or(PlanesToFacesError::InfiniteLoop2)?;
detect_loop=a;
}else{
return Err(PlanesToFacesError::InfiniteLoop2);
}
} }
faces.push(face); faces.push(face);
@ -196,6 +187,7 @@ fn planes_to_faces(face_list:std::collections::HashSet<Face>)->Result<Faces,Plan
} }
} }
#[allow(dead_code)]
#[derive(Debug)] #[derive(Debug)]
pub enum BrushToMeshError{ pub enum BrushToMeshError{
SliceBrushSides, SliceBrushSides,
@ -204,7 +196,6 @@ pub enum BrushToMeshError{
count:usize, count:usize,
}, },
InvalidPlanes(PlanesToFacesError), InvalidPlanes(PlanesToFacesError),
SkipBecauseTexture,
} }
impl std::fmt::Display for BrushToMeshError{ impl std::fmt::Display for BrushToMeshError{
fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{ fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{
@ -246,16 +237,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 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_range=brush_start_idx..brush_start_idx+brush.num_brush_sides as usize;
let sides=bsp.brush_sides.get(sides_range).ok_or(BrushToMeshError::SliceBrushSides)?; let sides=bsp.brush_sides.get(sides_range).ok_or(BrushToMeshError::SliceBrushSides)?;
for side in sides{ let face_list=sides.iter().map(|side|{
if let Some(texture_info)=bsp.textures_info.get(side.texture_info as usize){ // The so-called tumor brushes have TRIGGER bit set
let texture_info=vbsp::Handle::new(bsp,texture_info); // but also ignore visleaf hint and skip sides
let s=texture_info.name(); const TUMOR:vbsp::TextureFlags=vbsp::TextureFlags::HINT.union(vbsp::TextureFlags::SKIP).union(vbsp::TextureFlags::TRIGGER);
if s.starts_with("tools/")||s.starts_with("TOOLS/"){ if let Some(texture_info)=bsp.texture_info(side.texture_info as usize){
return Err(BrushToMeshError::SkipBecauseTexture); 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)?; let plane=bsp.plane(side.plane as usize)?;
Some(Face{ Some(Face{
normal:valve_transform_normal(plane.normal.into()), normal:valve_transform_normal(plane.normal.into()),
@ -302,6 +292,7 @@ mod test{
Face{normal:integer::vec3::NEG_Z,dot:Planar64::ONE}, Face{normal:integer::vec3::NEG_Z,dot:Planar64::ONE},
].into_iter().collect(); ].into_iter().collect();
let faces=planes_to_faces(face_list).unwrap(); let faces=planes_to_faces(face_list).unwrap();
assert_eq!(faces.faces.len(),6);
dbg!(faces); dbg!(faces);
} }
#[test] #[test]
@ -316,6 +307,37 @@ mod test{
Face{normal:integer::vec3::NEG_Z,dot:Planar64::EPSILON}, Face{normal:integer::vec3::NEG_Z,dot:Planar64::EPSILON},
].into_iter().collect(); ].into_iter().collect();
let faces=planes_to_faces(face_list).unwrap(); let faces=planes_to_faces(face_list).unwrap();
assert_eq!(faces.faces.len(),6);
dbg!(faces);
}
#[test]
fn test_cube_with_degernate_face2(){
let face_list=[
Face{normal:integer::vec3::X,dot:Planar64::ONE},
Face{normal:integer::vec3::Y,dot:Planar64::ONE},
Face{normal:integer::vec3::Z,dot:Planar64::ONE},
Face{normal:integer::vec3::NEG_X,dot:Planar64::ONE},
Face{normal:integer::vec3::NEG_Y,dot:Planar64::ONE},
Face{normal:integer::vec3::NEG_Z,dot:Planar64::ONE},
Face{normal:integer::vec3::NEG_X+integer::vec3::NEG_Z,dot:-Planar64::EPSILON},
].into_iter().collect();
let faces=planes_to_faces(face_list).unwrap();
assert_eq!(faces.faces.len(),5);
dbg!(faces);
}
#[test]
fn test_cube_with_degernate_face3(){
let face_list=[
Face{normal:integer::vec3::X,dot:Planar64::ONE},
Face{normal:integer::vec3::Y,dot:Planar64::ONE},
Face{normal:integer::vec3::Z,dot:Planar64::ONE},
Face{normal:integer::vec3::NEG_X,dot:Planar64::ONE},
Face{normal:integer::vec3::NEG_Y,dot:Planar64::ONE},
Face{normal:integer::vec3::NEG_Z,dot:Planar64::ONE},
Face{normal:integer::vec3::NEG_X+integer::vec3::NEG_Z,dot:Planar64::EPSILON},
].into_iter().collect();
let faces=planes_to_faces(face_list).unwrap();
assert_eq!(faces.faces.len(),7);
dbg!(faces); dbg!(faces);
} }
} }

@ -1,9 +1,12 @@
use std::borrow::Cow; use std::borrow::Cow;
use strafesnet_common::{map,model,integer,gameplay_attributes}; use vbsp_entities_css::Entity;
use strafesnet_common::{map,model,integer,gameplay_attributes as attr};
use strafesnet_deferred_loader::deferred_loader::{MeshDeferredLoader,RenderConfigDeferredLoader}; use strafesnet_deferred_loader::deferred_loader::{MeshDeferredLoader,RenderConfigDeferredLoader};
use strafesnet_deferred_loader::mesh::Meshes; use strafesnet_deferred_loader::mesh::Meshes;
use strafesnet_deferred_loader::texture::{RenderConfigs,Texture}; use strafesnet_deferred_loader::texture::{RenderConfigs,Texture};
use strafesnet_common::gameplay_modes::{NormalizedMode,NormalizedModes,Mode,Stage};
use crate::valve_transform; use crate::valve_transform;
@ -32,6 +35,47 @@ fn ingest_vertex(
}) })
} }
fn add_brush<'a>(
mesh_deferred_loader:&mut MeshDeferredLoader<&'a str>,
world_models:&mut Vec<model::Model>,
prop_models:&mut Vec<model::Model>,
model:&'a str,
origin:vbsp::Vector,
rendercolor:vbsp::Color,
attributes:attr::CollisionAttributesId,
){
let transform=integer::Planar64Affine3::from_translation(
valve_transform(origin.into())
);
let color=(glam::Vec3::from_array([
rendercolor.r as f32,
rendercolor.g as f32,
rendercolor.b as f32
])/255.0).extend(1.0);
match model.chars().next(){
// The first character of brush.model is '*'
Some('*')=>match model[1..].parse(){
Ok(mesh_id)=>{
let mesh=model::MeshId::new(mesh_id);
world_models.push(
model::Model{mesh,attributes,transform,color}
);
},
Err(e)=>{
println!("Brush model int parse error: {e} model={model}");
return;
},
},
_=>{
let mesh=mesh_deferred_loader.acquire_mesh_id(model);
prop_models.push(
model::Model{mesh,attributes,transform,color}
);
}
}
}
pub fn convert<'a>( pub fn convert<'a>(
bsp:&'a crate::Bsp, bsp:&'a crate::Bsp,
render_config_deferred_loader:&mut RenderConfigDeferredLoader<Cow<'a,str>>, render_config_deferred_loader:&mut RenderConfigDeferredLoader<Cow<'a,str>>,
@ -39,29 +83,60 @@ pub fn convert<'a>(
)->PartialMap1{ )->PartialMap1{
let bsp=bsp.as_ref(); let bsp=bsp.as_ref();
//figure out real attributes later //figure out real attributes later
let mut unique_attributes=Vec::new(); let unique_attributes=vec![
unique_attributes.push(gameplay_attributes::CollisionAttributes::Decoration); attr::CollisionAttributes::Decoration,
unique_attributes.push(gameplay_attributes::CollisionAttributes::contact_default()); attr::CollisionAttributes::contact_default(),
unique_attributes.push(gameplay_attributes::CollisionAttributes::intersect_default()); attr::CollisionAttributes::intersect_default(),
const ATTRIBUTE_DECORATION:gameplay_attributes::CollisionAttributesId=gameplay_attributes::CollisionAttributesId::new(0); // ladder
const ATTRIBUTE_CONTACT_DEFAULT:gameplay_attributes::CollisionAttributesId=gameplay_attributes::CollisionAttributesId::new(1); attr::CollisionAttributes::Contact(
const ATTRIBUTE_INTERSECT_DEFAULT:gameplay_attributes::CollisionAttributesId=gameplay_attributes::CollisionAttributesId::new(2); attr::ContactAttributes{
contacting:attr::ContactingAttributes{
contact_behaviour:Some(attr::ContactingBehaviour::Ladder(
attr::ContactingLadder{sticky:true}
))
},
general:attr::GeneralAttributes::default(),
}
),
// water
attr::CollisionAttributes::Intersect(
attr::IntersectAttributes{
intersecting:attr::IntersectingAttributes{
water:Some(attr::IntersectingWater{
viscosity:integer::Planar64::ONE,
density:integer::Planar64::ONE,
velocity:integer::vec3::ZERO,
}),
},
general:attr::GeneralAttributes::default(),
}
),
];
const ATTRIBUTE_DECORATION:attr::CollisionAttributesId=attr::CollisionAttributesId::new(0);
const ATTRIBUTE_CONTACT_DEFAULT:attr::CollisionAttributesId=attr::CollisionAttributesId::new(1);
const ATTRIBUTE_INTERSECT_DEFAULT:attr::CollisionAttributesId=attr::CollisionAttributesId::new(2);
const ATTRIBUTE_LADDER_DEFAULT:attr::CollisionAttributesId=attr::CollisionAttributesId::new(3);
const ATTRIBUTE_WATER_DEFAULT:attr::CollisionAttributesId=attr::CollisionAttributesId::new(4);
//declare all prop models to Loader //declare all prop models to Loader
let prop_models=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 //get or create mesh_id
let mesh_id=mesh_deferred_loader.acquire_mesh_id(prop.model()); let mesh_id=mesh_deferred_loader.acquire_mesh_id(prop.model());
let placement=prop.as_prop_placement();
model::Model{ model::Model{
mesh:mesh_id, mesh:mesh_id,
attributes:ATTRIBUTE_DECORATION, attributes:ATTRIBUTE_DECORATION,
transform:integer::Planar64Affine3::new( transform:integer::Planar64Affine3::new(
integer::mat3::try_from_f32_array_2d(( integer::mat3::try_from_f32_array_2d(
glam::Mat3A::from_diagonal(glam::Vec3::splat(placement.scale))
//TODO: figure this out //TODO: figure this out
*glam::Mat3A::from_quat(glam::Quat::from_array(placement.rotation.into())) glam::Mat3A::from_euler(
).to_cols_array_2d()).unwrap(), glam::EulerRot::XYZ,
valve_transform(placement.origin.into()), 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, color:glam::Vec4::ONE,
} }
@ -134,38 +209,50 @@ pub fn convert<'a>(
color:glam::Vec4::W, color:glam::Vec4::W,
}); });
for raw_ent in bsp.entities.iter(){ const WHITE:vbsp::Color=vbsp::Color{r:255,g:255,b:255};
for raw_ent in &bsp.entities{
match raw_ent.parse(){ match raw_ent.parse(){
Ok(vbsp::basic::Entity::Brush(brush)) Ok(Entity::Cycler(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,brush.rendercolor,ATTRIBUTE_DECORATION),
|Ok(vbsp::basic::Entity::BrushIllusionary(brush)) Ok(Entity::EnvSprite(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,brush.rendercolor,ATTRIBUTE_DECORATION),
|Ok(vbsp::basic::Entity::BrushWall(brush)) Ok(Entity::FuncBreakable(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,brush.rendercolor,ATTRIBUTE_DECORATION),
|Ok(vbsp::basic::Entity::BrushWallToggle(brush))=>{ Ok(Entity::FuncBrush(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,brush.rendercolor,ATTRIBUTE_DECORATION),
//The first character of brush.model is '*' Ok(Entity::FuncButton(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,brush.rendercolor,ATTRIBUTE_DECORATION),
match brush.model[1..].parse(){ Ok(Entity::FuncDoor(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,brush.rendercolor,ATTRIBUTE_DECORATION),
Ok(mesh_id)=>{ Ok(Entity::FuncDoorRotating(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,brush.rendercolor,ATTRIBUTE_DECORATION),
world_models.push(model::Model{ Ok(Entity::FuncIllusionary(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,brush.rendercolor,ATTRIBUTE_DECORATION),
mesh:model::MeshId::new(mesh_id), Ok(Entity::FuncMonitor(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,brush.rendercolor,ATTRIBUTE_DECORATION),
attributes:ATTRIBUTE_DECORATION, Ok(Entity::FuncMovelinear(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,brush.rendercolor,ATTRIBUTE_DECORATION),
transform:integer::Planar64Affine3::from_translation( Ok(Entity::FuncPhysbox(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,brush.rendercolor,ATTRIBUTE_DECORATION),
valve_transform(brush.origin.into()) Ok(Entity::FuncPhysboxMultiplayer(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,brush.rendercolor,ATTRIBUTE_DECORATION),
), Ok(Entity::FuncRotButton(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,WHITE,ATTRIBUTE_DECORATION),
color:(glam::Vec3::from_array([ Ok(Entity::FuncRotating(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,brush.rendercolor,ATTRIBUTE_DECORATION),
brush.color.r as f32, Ok(Entity::FuncTracktrain(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,brush.rendercolor,ATTRIBUTE_DECORATION),
brush.color.g as f32, Ok(Entity::FuncTrain(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,brush.rendercolor,ATTRIBUTE_DECORATION),
brush.color.b as f32 Ok(Entity::FuncWall(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin.unwrap_or_default(),brush.rendercolor,ATTRIBUTE_DECORATION),
])/255.0).extend(1.0), Ok(Entity::FuncWallToggle(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin.unwrap_or_default(),brush.rendercolor,ATTRIBUTE_DECORATION),
}); Ok(Entity::FuncWaterAnalog(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,brush.rendercolor.unwrap_or(WHITE),ATTRIBUTE_DECORATION),
}, Ok(Entity::PropDoorRotating(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,WHITE,ATTRIBUTE_DECORATION),
Err(e)=>{ Ok(Entity::PropDynamic(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,WHITE,ATTRIBUTE_DECORATION),
println!("Brush model int parse error: {e}"); Ok(Entity::PropDynamicOverride(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,WHITE,ATTRIBUTE_DECORATION),
}, Ok(Entity::PropPhysics(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,WHITE,ATTRIBUTE_DECORATION),
} Ok(Entity::PropPhysicsMultiplayer(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,WHITE,ATTRIBUTE_DECORATION),
Ok(Entity::PropPhysicsOverride(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,WHITE,ATTRIBUTE_DECORATION),
Ok(Entity::PropRagdoll(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,WHITE,ATTRIBUTE_DECORATION),
Ok(Entity::TriggerGravity(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,WHITE,ATTRIBUTE_DECORATION),
Ok(Entity::TriggerHurt(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,WHITE,ATTRIBUTE_DECORATION),
Ok(Entity::TriggerLook(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,WHITE,ATTRIBUTE_DECORATION),
Ok(Entity::TriggerMultiple(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model.unwrap_or_default(),brush.origin,WHITE,ATTRIBUTE_DECORATION),
Ok(Entity::TriggerOnce(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,WHITE,ATTRIBUTE_DECORATION),
Ok(Entity::TriggerProximity(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,WHITE,ATTRIBUTE_DECORATION),
Ok(Entity::TriggerPush(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,WHITE,ATTRIBUTE_DECORATION),
Ok(Entity::TriggerSoundscape(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,WHITE,ATTRIBUTE_DECORATION),
Ok(Entity::TriggerTeleport(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model.unwrap_or_default(),brush.origin,WHITE,ATTRIBUTE_DECORATION),
Ok(Entity::TriggerVphysicsMotion(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,WHITE,ATTRIBUTE_DECORATION),
Ok(Entity::TriggerWind(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,WHITE,ATTRIBUTE_DECORATION),
Ok(Entity::InfoPlayerCounterterrorist(spawn))=>{
found_spawn=Some(valve_transform(spawn.origin.into()));
}, },
_=>(), Ok(Entity::InfoPlayerTerrorist(spawn))=>{
}
match raw_ent.parse(){
Ok(vbsp::css::Entity::InfoPlayerCounterterrorist(spawn))=>{
found_spawn=Some(valve_transform(spawn.origin.into())); found_spawn=Some(valve_transform(spawn.origin.into()));
}, },
Err(e)=>{ Err(e)=>{
@ -177,9 +264,27 @@ pub fn convert<'a>(
// physics models // physics models
for brush in &bsp.brushes{ 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; continue;
} }
let is_ladder=brush.flags.contains(vbsp::BrushFlags::LADDER);
let is_water=brush.flags.contains(vbsp::BrushFlags::WATER);
let attributes=match (is_ladder,is_water){
(true,false)=>ATTRIBUTE_LADDER_DEFAULT,
(false,true)=>ATTRIBUTE_WATER_DEFAULT,
(false,false)=>ATTRIBUTE_CONTACT_DEFAULT,
(true,true)=>{
// water ladder? wtf
println!("brush is a water ladder o_o defaulting to ladder");
ATTRIBUTE_LADDER_DEFAULT
}
};
let mesh_result=crate::brush::brush_to_mesh(bsp,brush); let mesh_result=crate::brush::brush_to_mesh(bsp,brush);
match mesh_result{ match mesh_result{
Ok(mesh)=>{ Ok(mesh)=>{
@ -187,7 +292,7 @@ pub fn convert<'a>(
world_meshes.push(mesh); world_meshes.push(mesh);
world_models.push(model::Model{ world_models.push(model::Model{
mesh:mesh_id, mesh:mesh_id,
attributes:ATTRIBUTE_CONTACT_DEFAULT, attributes,
transform:integer::Planar64Affine3::new( transform:integer::Planar64Affine3::new(
integer::mat3::identity(), integer::mat3::identity(),
integer::vec3::ZERO, integer::vec3::ZERO,
@ -213,15 +318,15 @@ pub fn convert<'a>(
color:glam::Vec4::W, color:glam::Vec4::W,
}); });
let first_stage=strafesnet_common::gameplay_modes::Stage::empty(model_id); let first_stage=Stage::empty(model_id);
let main_mode=strafesnet_common::gameplay_modes::Mode::new( let main_mode=Mode::new(
strafesnet_common::gameplay_style::StyleModifiers::source_bhop(), strafesnet_common::gameplay_style::StyleModifiers::source_bhop(),
model_id, model_id,
std::collections::HashMap::new(), std::collections::HashMap::new(),
vec![first_stage], vec![first_stage],
std::collections::HashMap::new(), std::collections::HashMap::new(),
); );
modes_list.push(main_mode); modes_list.push(NormalizedMode::new(main_mode));
} }
PartialMap1{ PartialMap1{
@ -229,17 +334,17 @@ pub fn convert<'a>(
world_meshes, world_meshes,
prop_models, prop_models,
world_models, world_models,
modes:strafesnet_common::gameplay_modes::Modes::new(modes_list), modes:NormalizedModes::new(modes_list),
} }
} }
//partially constructed map types //partially constructed map types
pub struct PartialMap1{ pub struct PartialMap1{
attributes:Vec<strafesnet_common::gameplay_attributes::CollisionAttributes>, attributes:Vec<attr::CollisionAttributes>,
prop_models:Vec<model::Model>, prop_models:Vec<model::Model>,
world_meshes:Vec<model::Mesh>, world_meshes:Vec<model::Mesh>,
world_models:Vec<model::Model>, world_models:Vec<model::Model>,
modes:strafesnet_common::gameplay_modes::Modes, modes:NormalizedModes,
} }
impl PartialMap1{ impl PartialMap1{
pub fn add_prop_meshes<'a>( pub fn add_prop_meshes<'a>(
@ -257,12 +362,12 @@ impl PartialMap1{
} }
} }
pub struct PartialMap2{ pub struct PartialMap2{
attributes:Vec<strafesnet_common::gameplay_attributes::CollisionAttributes>, attributes:Vec<attr::CollisionAttributes>,
prop_meshes:Vec<(model::MeshId,model::Mesh)>, prop_meshes:Vec<(model::MeshId,model::Mesh)>,
prop_models:Vec<model::Model>, prop_models:Vec<model::Model>,
world_meshes:Vec<model::Mesh>, world_meshes:Vec<model::Mesh>,
world_models:Vec<model::Model>, world_models:Vec<model::Model>,
modes:strafesnet_common::gameplay_modes::Modes, modes:NormalizedModes,
} }
impl PartialMap2{ impl PartialMap2{
pub fn add_render_configs_and_textures( pub fn add_render_configs_and_textures(

@ -22,17 +22,17 @@ impl From<std::io::Error> for TextureError{
} }
} }
pub struct TextureLoader<'a>(std::marker::PhantomData<&'a ()>); pub struct TextureLoader;
impl TextureLoader<'_>{ impl TextureLoader{
pub fn new()->Self{ pub fn new()->Self{
Self(std::marker::PhantomData) Self
} }
} }
impl<'a> Loader for TextureLoader<'a>{ impl Loader for TextureLoader{
type Error=TextureError; type Error=TextureError;
type Index=Cow<'a,str>; type Index<'a>=Cow<'a,str>;
type Resource=Texture; type Resource=Texture;
fn load(&mut self,index:Self::Index)->Result<Self::Resource,Self::Error>{ fn load<'a>(&mut self,index:Self::Index<'a>)->Result<Self::Resource,Self::Error>{
let file_name=format!("textures/{}.dds",index); let file_name=format!("textures/{}.dds",index);
let mut file=std::fs::File::open(file_name)?; let mut file=std::fs::File::open(file_name)?;
let mut data=Vec::new(); let mut data=Vec::new();
@ -47,7 +47,7 @@ pub enum MeshError{
Io(std::io::Error), Io(std::io::Error),
VMDL(vmdl::ModelError), VMDL(vmdl::ModelError),
VBSP(vbsp::BspError), VBSP(vbsp::BspError),
MissingMdl, MissingMdl(String),
MissingVtx, MissingVtx,
MissingVvd, MissingVvd,
} }
@ -100,30 +100,24 @@ impl<'bsp,'vpk> BspFinder<'bsp,'vpk>{
} }
} }
pub struct ModelLoader<'bsp,'vpk,'a>{ pub struct ModelLoader<'bsp,'vpk>{
finder:BspFinder<'bsp,'vpk>, finder:BspFinder<'bsp,'vpk>,
life:core::marker::PhantomData<&'a ()>,
} }
impl ModelLoader<'_,'_,'_>{ impl ModelLoader<'_,'_>{
#[inline] #[inline]
pub const fn new<'bsp,'vpk,'a>( pub const fn new<'bsp,'vpk>(
finder:BspFinder<'bsp,'vpk>, finder:BspFinder<'bsp,'vpk>,
)->ModelLoader<'bsp,'vpk,'a>{ )->ModelLoader<'bsp,'vpk>{
ModelLoader{ ModelLoader{
finder, finder,
life:core::marker::PhantomData,
} }
} }
} }
impl<'bsp,'vpk,'a> Loader for ModelLoader<'bsp,'vpk,'a> impl<'bsp,'vpk> Loader for ModelLoader<'bsp,'vpk>{
where
'bsp:'a,
'vpk:'a,
{
type Error=MeshError; type Error=MeshError;
type Index=&'a str; type Index<'a>=&'a str where Self:'a;
type Resource=vmdl::Model; type Resource=vmdl::Model;
fn load(&mut self,index:Self::Index)->Result<Self::Resource,Self::Error>{ fn load<'a>(&'a mut self,index:Self::Index<'a>)->Result<Self::Resource,Self::Error>{
let mdl_path_lower=index.to_lowercase(); let mdl_path_lower=index.to_lowercase();
//.mdl, .vvd, .dx90.vtx //.mdl, .vvd, .dx90.vtx
let path=std::path::PathBuf::from(mdl_path_lower.as_str()); let path=std::path::PathBuf::from(mdl_path_lower.as_str());
@ -132,7 +126,7 @@ impl<'bsp,'vpk,'a> Loader for ModelLoader<'bsp,'vpk,'a>
vvd_path.set_extension("vvd"); vvd_path.set_extension("vvd");
vtx_path.set_extension("dx90.vtx"); vtx_path.set_extension("dx90.vtx");
// TODO: search more packs, possibly using an index of multiple packs // TODO: search more packs, possibly using an index of multiple packs
let mdl=self.finder.find(mdl_path_lower.as_str())?.ok_or(MeshError::MissingMdl)?; let mdl=self.finder.find(mdl_path_lower.as_str())?.ok_or(MeshError::MissingMdl(mdl_path_lower))?;
let vtx=self.finder.find(vtx_path.as_os_str().to_str().unwrap())?.ok_or(MeshError::MissingVtx)?; let vtx=self.finder.find(vtx_path.as_os_str().to_str().unwrap())?.ok_or(MeshError::MissingVtx)?;
let vvd=self.finder.find(vvd_path.as_os_str().to_str().unwrap())?.ok_or(MeshError::MissingVvd)?; let vvd=self.finder.find(vvd_path.as_os_str().to_str().unwrap())?.ok_or(MeshError::MissingVvd)?;
Ok(vmdl::Model::from_parts( Ok(vmdl::Model::from_parts(
@ -143,31 +137,27 @@ impl<'bsp,'vpk,'a> Loader for ModelLoader<'bsp,'vpk,'a>
} }
} }
pub struct MeshLoader<'bsp,'vpk,'load,'a>{ pub struct MeshLoader<'bsp,'vpk,'load,'str>{
finder:BspFinder<'bsp,'vpk>, finder:BspFinder<'bsp,'vpk>,
deferred_loader:&'load mut strafesnet_deferred_loader::deferred_loader::RenderConfigDeferredLoader<Cow<'a,str>>, deferred_loader:&'load mut strafesnet_deferred_loader::deferred_loader::RenderConfigDeferredLoader<Cow<'str,str>>,
} }
impl MeshLoader<'_,'_,'_,'_>{ impl MeshLoader<'_,'_,'_,'_>{
#[inline] #[inline]
pub const fn new<'bsp,'vpk,'load,'a>( pub const fn new<'bsp,'vpk,'load,'str>(
finder:BspFinder<'bsp,'vpk>, finder:BspFinder<'bsp,'vpk>,
deferred_loader:&'load mut strafesnet_deferred_loader::deferred_loader::RenderConfigDeferredLoader<Cow<'a,str>>, deferred_loader:&'load mut strafesnet_deferred_loader::deferred_loader::RenderConfigDeferredLoader<Cow<'str,str>>,
)->MeshLoader<'bsp,'vpk,'load,'a>{ )->MeshLoader<'bsp,'vpk,'load,'str>{
MeshLoader{ MeshLoader{
finder, finder,
deferred_loader deferred_loader
} }
} }
} }
impl<'bsp,'vpk,'load,'a> Loader for MeshLoader<'bsp,'vpk,'load,'a> impl<'str,'bsp,'vpk,'load> Loader for MeshLoader<'bsp,'vpk,'load,'str>{
where
'bsp:'a,
'vpk:'a,
{
type Error=MeshError; type Error=MeshError;
type Index=&'a str; type Index<'a>=&'a str where Self:'a;
type Resource=Mesh; type Resource=Mesh;
fn load(&mut self,index:Self::Index)->Result<Self::Resource,Self::Error>{ fn load<'a>(&'a mut self,index:Self::Index<'a>)->Result<Self::Resource,Self::Error>{
let model=ModelLoader::new(self.finder).load(index)?; let model=ModelLoader::new(self.finder).load(index)?;
let mesh=crate::mesh::convert_mesh(model,&mut self.deferred_loader); let mesh=crate::mesh::convert_mesh(model,&mut self.deferred_loader);
Ok(mesh) Ok(mesh)

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

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

@ -1,4 +1,10 @@
use std::cmp::Ordering;
use std::collections::BTreeMap;
use crate::aabb::Aabb; use crate::aabb::Aabb;
use crate::ray::Ray;
use crate::integer::{Ratio,Planar64};
use crate::instruction::{InstructionCollector,TimedInstruction};
//da algaritum //da algaritum
//lista boxens //lista boxens
@ -10,6 +16,96 @@ use crate::aabb::Aabb;
//sort the centerpoints on each axis (3 lists) //sort the centerpoints on each axis (3 lists)
//bv is put into octant based on whether it is upper or lower in each list //bv is put into octant based on whether it is upper or lower in each list
pub 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>{ pub enum RecursiveContent<N,L>{
Branch(Vec<N>), Branch(Vec<N>),
Leaf(L), 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){ pub fn into_inner(self)->(RecursiveContent<BvhNode<L>,L>,Aabb){
(self.content,self.aabb) (self.content,self.aabb)
} }

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

@ -63,22 +63,22 @@ impl JumpImpulse{
velocity:Planar64Vec3, velocity:Planar64Vec3,
jump_dir:Planar64Vec3, jump_dir:Planar64Vec3,
gravity:&Planar64Vec3, gravity:&Planar64Vec3,
mass:Planar64, _mass:Planar64,
)->Planar64Vec3{ )->Planar64Vec3{
match self{ match self{
&JumpImpulse::Time(time)=>velocity-(*gravity*time).map(|t|t.divide().fix_1()), &JumpImpulse::Time(time)=>velocity-(*gravity*time).map(|t|t.divide().clamp_1()),
&JumpImpulse::Height(height)=>{ &JumpImpulse::Height(height)=>{
//height==-v.y*v.y/(2*g.y); //height==-v.y*v.y/(2*g.y);
//use energy to determine max height //use energy to determine max height
let gg=gravity.length_squared(); let gg=gravity.length_squared();
let g=gg.sqrt().fix_1(); let g=gg.sqrt();
let v_g=gravity.dot(velocity); let v_g=gravity.dot(velocity);
//do it backwards //do it backwards
let radicand=v_g*v_g+(g*height*2).fix_4(); let radicand=v_g*v_g+(g*height*2).widen_4();
velocity-(*gravity*(radicand.sqrt().fix_2()+v_g)/gg).divide().fix_1() velocity-(*gravity*(radicand.sqrt().wrap_2()+v_g)/gg).divide().clamp_1()
}, },
&JumpImpulse::Linear(jump_speed)=>velocity+(jump_dir*jump_speed/jump_dir.length()).divide().fix_1(), &JumpImpulse::Linear(jump_speed)=>velocity+(jump_dir*jump_speed/jump_dir.length()).divide().clamp_1(),
&JumpImpulse::Energy(energy)=>{ &JumpImpulse::Energy(_energy)=>{
//calculate energy //calculate energy
//let e=gravity.dot(velocity); //let e=gravity.dot(velocity);
//add //add
@ -91,10 +91,10 @@ impl JumpImpulse{
pub fn get_jump_deltav(&self,gravity:&Planar64Vec3,mass:Planar64)->Planar64{ pub fn get_jump_deltav(&self,gravity:&Planar64Vec3,mass:Planar64)->Planar64{
//gravity.length() is actually the proper calculation because the jump is always opposite the gravity direction //gravity.length() is actually the proper calculation because the jump is always opposite the gravity direction
match self{ match self{
&JumpImpulse::Time(time)=>(gravity.length().fix_1()*time/2).divide().fix_1(), &JumpImpulse::Time(time)=>(gravity.length().wrap_1()*time/2).divide().clamp_1(),
&JumpImpulse::Height(height)=>(gravity.length()*height*2).sqrt().fix_1(), &JumpImpulse::Height(height)=>(gravity.length()*height*2).sqrt().wrap_1(),
&JumpImpulse::Linear(deltav)=>deltav, &JumpImpulse::Linear(deltav)=>deltav,
&JumpImpulse::Energy(energy)=>(energy.sqrt()*2/mass.sqrt()).divide().fix_1(), &JumpImpulse::Energy(energy)=>(energy.sqrt()*2/mass.sqrt()).divide().clamp_1(),
} }
} }
} }
@ -126,10 +126,10 @@ impl JumpSettings{
None=>rel_velocity, None=>rel_velocity,
}; };
let j=boost_vel.dot(jump_dir); let j=boost_vel.dot(jump_dir);
let js=jump_speed.fix_2(); let js=jump_speed.widen_2();
if j<js{ if j<js{
//weak booster: just do a regular jump //weak booster: just do a regular jump
boost_vel+jump_dir.with_length(js-j).divide().fix_1() boost_vel+jump_dir.with_length(js-j).divide().wrap_1()
}else{ }else{
//activate booster normally, jump does nothing //activate booster normally, jump does nothing
boost_vel boost_vel
@ -142,13 +142,13 @@ impl JumpSettings{
None=>rel_velocity, None=>rel_velocity,
}; };
let j=boost_vel.dot(jump_dir); let j=boost_vel.dot(jump_dir);
let js=jump_speed.fix_2(); let js=jump_speed.widen_2();
if j<js{ if j<js{
//speed in direction of jump cannot be lower than amount //speed in direction of jump cannot be lower than amount
boost_vel+jump_dir.with_length(js-j).divide().fix_1() boost_vel+jump_dir.with_length(js-j).divide().wrap_1()
}else{ }else{
//boost and jump add together //boost and jump add together
boost_vel+jump_dir.with_length(js).divide().fix_1() boost_vel+jump_dir.with_length(js).divide().wrap_1()
} }
} }
(false,JumpCalculation::Max)=>{ (false,JumpCalculation::Max)=>{
@ -159,10 +159,10 @@ impl JumpSettings{
None=>rel_velocity, None=>rel_velocity,
}; };
let boost_dot=boost_vel.dot(jump_dir); let boost_dot=boost_vel.dot(jump_dir);
let js=jump_speed.fix_2(); let js=jump_speed.widen_2();
if boost_dot<js{ if boost_dot<js{
//weak boost is extended to jump speed //weak boost is extended to jump speed
boost_vel+jump_dir.with_length(js-boost_dot).divide().fix_1() boost_vel+jump_dir.with_length(js-boost_dot).divide().wrap_1()
}else{ }else{
//activate booster normally, jump does nothing //activate booster normally, jump does nothing
boost_vel boost_vel
@ -174,7 +174,7 @@ impl JumpSettings{
Some(booster)=>booster.boost(rel_velocity), Some(booster)=>booster.boost(rel_velocity),
None=>rel_velocity, None=>rel_velocity,
}; };
boost_vel+jump_dir.with_length(jump_speed).divide().fix_1() boost_vel+jump_dir.with_length(jump_speed).divide().wrap_1()
}, },
} }
} }
@ -267,9 +267,9 @@ pub struct StrafeSettings{
impl StrafeSettings{ impl StrafeSettings{
pub fn tick_velocity(&self,velocity:Planar64Vec3,control_dir:Planar64Vec3)->Option<Planar64Vec3>{ pub fn tick_velocity(&self,velocity:Planar64Vec3,control_dir:Planar64Vec3)->Option<Planar64Vec3>{
let d=velocity.dot(control_dir); let d=velocity.dot(control_dir);
let mv=self.mv.fix_2(); let mv=self.mv.widen_2();
match d<mv{ match d<mv{
true=>Some(velocity+(control_dir*self.air_accel_limit.map_or(mv-d,|limit|limit.fix_2().min(mv-d))).fix_1()), true=>Some(velocity+(control_dir*self.air_accel_limit.map_or(mv-d,|limit|limit.widen_2().min(mv-d))).wrap_1()),
false=>None, false=>None,
} }
} }
@ -290,7 +290,7 @@ pub struct PropulsionSettings{
} }
impl PropulsionSettings{ impl PropulsionSettings{
pub fn acceleration(&self,control_dir:Planar64Vec3)->Planar64Vec3{ pub fn acceleration(&self,control_dir:Planar64Vec3)->Planar64Vec3{
(control_dir*self.magnitude).fix_1() (control_dir*self.magnitude).clamp_1()
} }
} }
@ -310,13 +310,13 @@ pub struct WalkSettings{
impl WalkSettings{ impl WalkSettings{
pub fn accel(&self,target_diff:Planar64Vec3,gravity:Planar64Vec3)->Planar64{ pub fn accel(&self,target_diff:Planar64Vec3,gravity:Planar64Vec3)->Planar64{
//TODO: fallible walk accel //TODO: fallible walk accel
let diff_len=target_diff.length().fix_1(); let diff_len=target_diff.length().wrap_1();
let friction=if diff_len<self.accelerate.topspeed{ let friction=if diff_len<self.accelerate.topspeed{
self.static_friction self.static_friction
}else{ }else{
self.kinetic_friction self.kinetic_friction
}; };
self.accelerate.accel.min((-gravity.y*friction).fix_1()) self.accelerate.accel.min((-gravity.y*friction).clamp_1())
} }
pub fn get_walk_target_velocity(&self,control_dir:Planar64Vec3,normal:Planar64Vec3)->Planar64Vec3{ pub fn get_walk_target_velocity(&self,control_dir:Planar64Vec3,normal:Planar64Vec3)->Planar64Vec3{
if control_dir==crate::integer::vec3::ZERO{ if control_dir==crate::integer::vec3::ZERO{
@ -332,7 +332,7 @@ impl WalkSettings{
if cr==crate::integer::vec3::ZERO_2{ if cr==crate::integer::vec3::ZERO_2{
crate::integer::vec3::ZERO crate::integer::vec3::ZERO
}else{ }else{
(cr.cross(normal)*self.accelerate.topspeed/((nn*(nnmm-dd)).sqrt())).divide().fix_1() (cr.cross(normal)*self.accelerate.topspeed/((nn*(nnmm-dd)).sqrt())).divide().clamp_1()
} }
}else{ }else{
crate::integer::vec3::ZERO crate::integer::vec3::ZERO
@ -341,7 +341,7 @@ impl WalkSettings{
pub fn is_slope_walkable(&self,normal:Planar64Vec3,up:Planar64Vec3)->bool{ pub fn is_slope_walkable(&self,normal:Planar64Vec3,up:Planar64Vec3)->bool{
//normal is not guaranteed to be unit length //normal is not guaranteed to be unit length
let ny=normal.dot(up); let ny=normal.dot(up);
let h=normal.length().fix_1(); let h=normal.length().wrap_1();
//remember this is a normal vector //remember this is a normal vector
ny.is_positive()&&h*self.surf_dot<ny ny.is_positive()&&h*self.surf_dot<ny
} }
@ -355,7 +355,7 @@ pub struct LadderSettings{
pub dot:Planar64, pub dot:Planar64,
} }
impl LadderSettings{ impl LadderSettings{
pub const fn accel(&self,target_diff:Planar64Vec3,gravity:Planar64Vec3)->Planar64{ pub const fn accel(&self,_target_diff:Planar64Vec3,_gravity:Planar64Vec3)->Planar64{
//TODO: fallible ladder accel //TODO: fallible ladder accel
self.accelerate.accel self.accelerate.accel
} }
@ -368,13 +368,13 @@ impl LadderSettings{
let nnmm=nn*mm; let nnmm=nn*mm;
let d=normal.dot(control_dir); let d=normal.dot(control_dir);
let mut dd=d*d; let mut dd=d*d;
if (self.dot*self.dot*nnmm).fix_4()<dd{ if (self.dot*self.dot*nnmm).clamp_4()<dd{
if d.is_negative(){ if d.is_negative(){
control_dir=Planar64Vec3::new([Planar64::ZERO,mm.fix_1(),Planar64::ZERO]); control_dir=Planar64Vec3::new([Planar64::ZERO,mm.clamp_1(),Planar64::ZERO]);
}else{ }else{
control_dir=Planar64Vec3::new([Planar64::ZERO,-mm.fix_1(),Planar64::ZERO]); control_dir=Planar64Vec3::new([Planar64::ZERO,-mm.clamp_1(),Planar64::ZERO]);
} }
dd=(normal.y*normal.y).fix_4(); dd=(normal.y*normal.y).widen_4();
} }
//n=d if you are standing on top of a ladder and press E. //n=d if you are standing on top of a ladder and press E.
//two fixes: //two fixes:
@ -385,7 +385,7 @@ impl LadderSettings{
if cr==crate::integer::vec3::ZERO_2{ if cr==crate::integer::vec3::ZERO_2{
crate::integer::vec3::ZERO crate::integer::vec3::ZERO
}else{ }else{
(cr.cross(normal)*self.accelerate.topspeed/((nn*(nnmm-dd)).sqrt())).divide().fix_1() (cr.cross(normal)*self.accelerate.topspeed/((nn*(nnmm-dd)).sqrt())).divide().clamp_1()
} }
}else{ }else{
crate::integer::vec3::ZERO crate::integer::vec3::ZERO
@ -417,7 +417,7 @@ impl Hitbox{
} }
pub fn source()->Self{ pub fn source()->Self{
Self{ Self{
halfsize:((int3(33,73,33)>>1)*VALVE_SCALE).fix_1(), halfsize:((int3(33,73,33)>>1)*VALVE_SCALE).narrow_1().unwrap(),
mesh:HitboxMesh::Box, mesh:HitboxMesh::Box,
} }
} }
@ -529,20 +529,20 @@ impl StyleModifiers{
pub fn source_bhop()->Self{ pub fn source_bhop()->Self{
Self{ Self{
controls_mask:Controls::all()-Controls::MoveUp-Controls::MoveDown, controls_mask:Controls::all(),
controls_mask_state:Controls::all(), controls_mask_state:Controls::all(),
strafe:Some(StrafeSettings{ strafe:Some(StrafeSettings{
enable:ControlsActivation::full_2d(), enable:ControlsActivation::full_2d(),
air_accel_limit:Some(Planar64::raw(150<<28)*100), air_accel_limit:Some(Planar64::raw((150<<28)*100)),
mv:(Planar64::raw(30)*VALVE_SCALE).fix_1(), mv:Planar64::raw(30<<28),
tick_rate:Ratio64::new(100,AbsoluteTime::ONE_SECOND.get() as u64).unwrap(), tick_rate:Ratio64::new(100,AbsoluteTime::ONE_SECOND.get() as u64).unwrap(),
}), }),
jump:Some(JumpSettings{ jump:Some(JumpSettings{
impulse:JumpImpulse::Height((int(52)*VALVE_SCALE).fix_1()), impulse:JumpImpulse::Height((int(52)*VALVE_SCALE).narrow_1().unwrap()),
calculation:JumpCalculation::JumpThenBoost, calculation:JumpCalculation::JumpThenBoost,
limit_minimum:true, limit_minimum:true,
}), }),
gravity:(int3(0,-800,0)*VALVE_SCALE).fix_1(), gravity:(int3(0,-800,0)*VALVE_SCALE).narrow_1().unwrap(),
mass:int(1), mass:int(1),
rocket:None, rocket:None,
walk:Some(WalkSettings{ walk:Some(WalkSettings{
@ -565,25 +565,25 @@ impl StyleModifiers{
magnitude:int(12),//? magnitude:int(12),//?
}), }),
hitbox:Hitbox::source(), hitbox:Hitbox::source(),
camera_offset:((int3(0,64,0)-(int3(0,73,0)>>1))*VALVE_SCALE).fix_1(), camera_offset:((int3(0,64,0)-(int3(0,73,0)>>1))*VALVE_SCALE).narrow_1().unwrap(),
} }
} }
pub fn source_surf()->Self{ pub fn source_surf()->Self{
Self{ Self{
controls_mask:Controls::all()-Controls::MoveUp-Controls::MoveDown, controls_mask:Controls::all(),
controls_mask_state:Controls::all(), controls_mask_state:Controls::all(),
strafe:Some(StrafeSettings{ strafe:Some(StrafeSettings{
enable:ControlsActivation::full_2d(), enable:ControlsActivation::full_2d(),
air_accel_limit:Some((int(150)*66*VALVE_SCALE).fix_1()), air_accel_limit:Some((int(150)*66*VALVE_SCALE).narrow_1().unwrap()),
mv:(int(30)*VALVE_SCALE).fix_1(), mv:Planar64::raw(30<<28),
tick_rate:Ratio64::new(66,AbsoluteTime::ONE_SECOND.get() as u64).unwrap(), tick_rate:Ratio64::new(66,AbsoluteTime::ONE_SECOND.get() as u64).unwrap(),
}), }),
jump:Some(JumpSettings{ jump:Some(JumpSettings{
impulse:JumpImpulse::Height((int(52)*VALVE_SCALE).fix_1()), impulse:JumpImpulse::Height((int(52)*VALVE_SCALE).narrow_1().unwrap()),
calculation:JumpCalculation::JumpThenBoost, calculation:JumpCalculation::JumpThenBoost,
limit_minimum:true, limit_minimum:true,
}), }),
gravity:(int3(0,-800,0)*VALVE_SCALE).fix_1(), gravity:(int3(0,-800,0)*VALVE_SCALE).narrow_1().unwrap(),
mass:int(1), mass:int(1),
rocket:None, rocket:None,
walk:Some(WalkSettings{ walk:Some(WalkSettings{
@ -606,7 +606,7 @@ impl StyleModifiers{
magnitude:int(12),//? magnitude:int(12),//?
}), }),
hitbox:Hitbox::source(), hitbox:Hitbox::source(),
camera_offset:((int3(0,64,0)-(int3(0,73,0)>>1))*VALVE_SCALE).fix_1(), camera_offset:((int3(0,64,0)-(int3(0,73,0)>>1))*VALVE_SCALE).narrow_1().unwrap(),
} }
} }
} }

@ -1,14 +1,14 @@
pub use fixed_wide::fixed::{Fixed,Fix}; pub use fixed_wide::fixed::*;
pub use ratio_ops::ratio::{Ratio,Divide}; pub use ratio_ops::ratio::{Ratio,Divide};
//integer units //integer units
/// specific example of a "default" time type /// specific example of a "default" time type
#[derive(Clone,Copy,Hash,Eq,PartialEq,PartialOrd,Debug)] #[derive(Clone,Copy,Hash,Eq,PartialEq,Ord,PartialOrd,Debug)]
pub enum TimeInner{} pub enum TimeInner{}
pub type AbsoluteTime=Time<TimeInner>; pub type AbsoluteTime=Time<TimeInner>;
#[derive(Clone,Copy,Hash,Eq,PartialEq,PartialOrd,Debug)] #[derive(Clone,Copy,Hash,Eq,PartialEq,Ord,PartialOrd,Debug)]
pub struct Time<T>(i64,core::marker::PhantomData<T>); pub struct Time<T>(i64,core::marker::PhantomData<T>);
impl<T> Time<T>{ impl<T> Time<T>{
pub const MIN:Self=Self::raw(i64::MIN); pub const MIN:Self=Self::raw(i64::MIN);
@ -60,7 +60,7 @@ impl<T> Time<T>{
impl<T> From<Planar64> for Time<T>{ impl<T> From<Planar64> for Time<T>{
#[inline] #[inline]
fn from(value:Planar64)->Self{ fn from(value:Planar64)->Self{
Self::raw((value*Planar64::raw(1_000_000_000)).fix_1().to_raw()) Self::raw((value*Planar64::raw(1_000_000_000)).clamp_1().to_raw())
} }
} }
impl<T> From<Time<T>> for Ratio<Planar64,Planar64>{ impl<T> From<Time<T>> for Ratio<Planar64,Planar64>{
@ -73,11 +73,11 @@ impl<T,Num,Den,N1,T1> From<Ratio<Num,Den>> for Time<T>
where where
Num:core::ops::Mul<Planar64,Output=N1>, Num:core::ops::Mul<Planar64,Output=N1>,
N1:Divide<Den,Output=T1>, N1:Divide<Den,Output=T1>,
T1:Fix<Planar64>, T1:Clamp<Planar64>,
{ {
#[inline] #[inline]
fn from(value:Ratio<Num,Den>)->Self{ fn from(value:Ratio<Num,Den>)->Self{
Self::raw((value*Planar64::raw(1_000_000_000)).divide().fix().to_raw()) Self::raw((value*Planar64::raw(1_000_000_000)).divide().clamp().to_raw())
} }
} }
impl<T> std::fmt::Display for Time<T>{ impl<T> std::fmt::Display for Time<T>{
@ -401,6 +401,10 @@ impl Angle32{
pub const NEG_FRAC_PI_2:Self=Self(-1<<30); pub const NEG_FRAC_PI_2:Self=Self(-1<<30);
pub const PI:Self=Self(-1<<31); pub const PI:Self=Self(-1<<31);
#[inline] #[inline]
pub const fn raw(num:i32)->Self{
Self(num)
}
#[inline]
pub const fn wrap_from_i64(theta:i64)->Self{ pub const fn wrap_from_i64(theta:i64)->Self{
//take lower bits //take lower bits
//note: this was checked on compiler explorer and compiles to 1 instruction! //note: this was checked on compiler explorer and compiles to 1 instruction!
@ -515,8 +519,8 @@ fn angle_sin_cos(){
println!("cordic s={} c={}",(s/h).divide(),(c/h).divide()); println!("cordic s={} c={}",(s/h).divide(),(c/h).divide());
let (fs,fc)=f.sin_cos(); let (fs,fc)=f.sin_cos();
println!("float s={} c={}",fs,fc); println!("float s={} c={}",fs,fc);
assert!(close_enough((c/h).divide().fix_1(),Planar64::raw((fc*((1u64<<32) as f64)) as i64))); assert!(close_enough((c/h).divide().wrap_1(),Planar64::raw((fc*((1u64<<32) as f64)) as i64)));
assert!(close_enough((s/h).divide().fix_1(),Planar64::raw((fs*((1u64<<32) as f64)) as i64))); assert!(close_enough((s/h).divide().wrap_1(),Planar64::raw((fs*((1u64<<32) as f64)) as i64)));
} }
test_angle(1.0); test_angle(1.0);
test_angle(std::f64::consts::PI/4.0); test_angle(std::f64::consts::PI/4.0);
@ -558,6 +562,10 @@ pub mod vec3{
pub const MAX:Planar64Vec3=Planar64Vec3::new([Planar64::MAX;3]); pub const MAX:Planar64Vec3=Planar64Vec3::new([Planar64::MAX;3]);
pub const ZERO:Planar64Vec3=Planar64Vec3::new([Planar64::ZERO;3]); pub const ZERO:Planar64Vec3=Planar64Vec3::new([Planar64::ZERO;3]);
pub const ZERO_2:linear_ops::types::Vector3<Fixed::<2,64>>=linear_ops::types::Vector3::new([Fixed::<2,64>::ZERO;3]); pub const ZERO_2:linear_ops::types::Vector3<Fixed::<2,64>>=linear_ops::types::Vector3::new([Fixed::<2,64>::ZERO;3]);
pub const ZERO_3:linear_ops::types::Vector3<Fixed::<3,96>>=linear_ops::types::Vector3::new([Fixed::<3,96>::ZERO;3]);
pub const ZERO_4:linear_ops::types::Vector3<Fixed::<4,128>>=linear_ops::types::Vector3::new([Fixed::<4,128>::ZERO;3]);
pub const ZERO_5:linear_ops::types::Vector3<Fixed::<5,160>>=linear_ops::types::Vector3::new([Fixed::<5,160>::ZERO;3]);
pub const ZERO_6:linear_ops::types::Vector3<Fixed::<6,192>>=linear_ops::types::Vector3::new([Fixed::<6,192>::ZERO;3]);
pub const X:Planar64Vec3=Planar64Vec3::new([Planar64::ONE,Planar64::ZERO,Planar64::ZERO]); pub const X:Planar64Vec3=Planar64Vec3::new([Planar64::ONE,Planar64::ZERO,Planar64::ZERO]);
pub const Y:Planar64Vec3=Planar64Vec3::new([Planar64::ZERO,Planar64::ONE,Planar64::ZERO]); pub const Y:Planar64Vec3=Planar64Vec3::new([Planar64::ZERO,Planar64::ONE,Planar64::ZERO]);
pub const Z:Planar64Vec3=Planar64Vec3::new([Planar64::ZERO,Planar64::ZERO,Planar64::ONE]); pub const Z:Planar64Vec3=Planar64Vec3::new([Planar64::ZERO,Planar64::ZERO,Planar64::ONE]);
@ -625,8 +633,8 @@ pub mod mat3{
let (yc,ys)=y.cos_sin(); let (yc,ys)=y.cos_sin();
Planar64Mat3::from_cols([ Planar64Mat3::from_cols([
Planar64Vec3::new([xc,Planar64::ZERO,-xs]), Planar64Vec3::new([xc,Planar64::ZERO,-xs]),
Planar64Vec3::new([(xs*ys).fix_1(),yc,(xc*ys).fix_1()]), Planar64Vec3::new([(xs*ys).wrap_1(),yc,(xc*ys).wrap_1()]),
Planar64Vec3::new([(xs*yc).fix_1(),-ys,(xc*yc).fix_1()]), Planar64Vec3::new([(xs*yc).wrap_1(),-ys,(xc*yc).wrap_1()]),
]) ])
} }
#[inline] #[inline]
@ -668,7 +676,7 @@ impl Planar64Affine3{
} }
#[inline] #[inline]
pub fn transform_point3(&self,point:Planar64Vec3)->vec3::Vector3<Fixed<2,64>>{ pub fn transform_point3(&self,point:Planar64Vec3)->vec3::Vector3<Fixed<2,64>>{
self.translation.fix_2()+self.matrix3*point self.translation.widen_2()+self.matrix3*point
} }
} }
impl Into<glam::Mat4> for Planar64Affine3{ impl Into<glam::Mat4> for Planar64Affine3{

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

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

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

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

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

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

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

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

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

@ -45,7 +45,7 @@ impl<H:core::hash::Hash+Eq> RenderConfigDeferredLoader<H>{
pub fn into_indices(self)->impl Iterator<Item=H>{ pub fn into_indices(self)->impl Iterator<Item=H>{
self.render_config_id_from_asset_id.into_keys().flatten() self.render_config_id_from_asset_id.into_keys().flatten()
} }
pub fn into_render_configs<L:Loader<Resource=Texture,Index=H>>(mut self,loader:&mut L,failure_mode:LoadFailureMode)->Result<RenderConfigs,L::Error>{ pub fn into_render_configs<'a,L:Loader<Resource=Texture,Index<'a>=H>+'a>(mut self,loader:&mut L,failure_mode:LoadFailureMode)->Result<RenderConfigs,L::Error>{
let mut sorted_textures=vec![None;self.texture_count as usize]; let mut sorted_textures=vec![None;self.texture_count as usize];
for (index_option,render_config_id) in self.render_config_id_from_asset_id{ for (index_option,render_config_id) in self.render_config_id_from_asset_id{
let render_config=&mut self.render_configs[render_config_id.get() as usize]; let render_config=&mut self.render_configs[render_config_id.get() as usize];
@ -93,7 +93,7 @@ impl<H:core::hash::Hash+Eq> MeshDeferredLoader<H>{
pub fn into_indices(self)->impl Iterator<Item=H>{ pub fn into_indices(self)->impl Iterator<Item=H>{
self.mesh_id_from_asset_id.into_keys() self.mesh_id_from_asset_id.into_keys()
} }
pub fn into_meshes<L:Loader<Resource=Mesh,Index=H>>(self,loader:&mut L,failure_mode:LoadFailureMode)->Result<Meshes,L::Error>{ pub fn into_meshes<'a,L:Loader<Resource=Mesh,Index<'a>=H>+'a>(self,loader:&mut L,failure_mode:LoadFailureMode)->Result<Meshes,L::Error>{
let mut mesh_list=vec![None;self.mesh_id_from_asset_id.len()]; let mut mesh_list=vec![None;self.mesh_id_from_asset_id.len()];
for (index,mesh_id) in self.mesh_id_from_asset_id{ for (index,mesh_id) in self.mesh_id_from_asset_id{
let resource_result=loader.load(index); let resource_result=loader.load(index);

@ -2,7 +2,7 @@ use std::error::Error;
pub trait Loader{ pub trait Loader{
type Error:Error; type Error:Error;
type Index; type Index<'a> where Self:'a;
type Resource; type Resource;
fn load(&mut self,index:Self::Index)->Result<Self::Resource,Self::Error>; fn load<'a>(&mut self,index:Self::Index<'a>)->Result<Self::Resource,Self::Error>;
} }

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

@ -33,6 +33,14 @@ impl<const N:usize,const F:usize> Fixed<N,F>{
self.bits self.bits
} }
#[inline] #[inline]
pub const fn as_bits(&self)->&BInt<N>{
&self.bits
}
#[inline]
pub const fn as_bits_mut(&mut self)->&mut BInt<N>{
&mut self.bits
}
#[inline]
pub const fn raw_digit(value:i64)->Self{ pub const fn raw_digit(value:i64)->Self{
let mut digits=[0u64;N]; let mut digits=[0u64;N];
digits[0]=value.abs() as u64; digits[0]=value.abs() as u64;
@ -56,6 +64,10 @@ impl<const N:usize,const F:usize> Fixed<N,F>{
pub const fn abs(self)->Self{ pub const fn abs(self)->Self{
Self::from_bits(self.bits.abs()) Self::from_bits(self.bits.abs())
} }
#[inline]
pub const fn midpoint(self,other:Self)->Self{
Self::from_bits(self.bits.midpoint(other.bits))
}
} }
impl<const F:usize> Fixed<1,F>{ impl<const F:usize> Fixed<1,F>{
/// My old code called this function everywhere so let's provide it /// My old code called this function everywhere so let's provide it
@ -651,74 +663,94 @@ macro_repeated!(
1,2,3,4,5,6,7,8 1,2,3,4,5,6,7,8
); );
pub trait Fix<Out>{ #[derive(Debug,Eq,PartialEq)]
fn fix(self)->Out; pub enum NarrowError{
Overflow,
Underflow,
} }
macro_rules! impl_fix_rhs_lt_lhs_not_const_generic{ pub trait Wrap<Output>{
fn wrap(self)->Output;
}
pub trait Clamp<Output>{
fn clamp(self)->Output;
}
impl<const N:usize,const F:usize> Clamp<Fixed<N,F>> for Result<Fixed<N,F>,NarrowError>{
fn clamp(self)->Fixed<N,F>{
match self{
Ok(fixed)=>fixed,
Err(NarrowError::Overflow)=>Fixed::MAX,
Err(NarrowError::Underflow)=>Fixed::MIN,
}
}
}
macro_rules! impl_narrow_not_const_generic{
( (
(), (),
($lhs:expr,$rhs:expr) ($lhs:expr,$rhs:expr)
)=>{ )=>{
impl Fixed<$lhs,{$lhs*32}> paste::item!{
{ impl Fixed<$lhs,{$lhs*32}>
paste::item!{ {
#[inline] #[inline]
pub fn [<fix_ $rhs>](self)->Fixed<$rhs,{$rhs*32}>{ pub fn [<wrap_ $rhs>](self)->Fixed<$rhs,{$rhs*32}>{
Fixed::from_bits(bnum::cast::As::as_::<BInt::<$rhs>>(self.bits.shr(($lhs-$rhs)*32))) Fixed::from_bits(bnum::cast::As::as_::<BInt::<$rhs>>(self.bits.shr(($lhs-$rhs)*32)))
} }
#[inline]
pub fn [<narrow_ $rhs>](self)->Result<Fixed<$rhs,{$rhs*32}>,NarrowError>{
if Fixed::<$rhs,{$rhs*32}>::MAX.[<widen_ $lhs>]().bits<self.bits{
return Err(NarrowError::Overflow);
}
if self.bits<Fixed::<$rhs,{$rhs*32}>::MIN.[<widen_ $lhs>]().bits{
return Err(NarrowError::Underflow);
}
Ok(self.[<wrap_ $rhs>]())
}
#[inline]
pub fn [<clamp_ $rhs>](self)->Fixed<$rhs,{$rhs*32}>{
self.[<narrow_ $rhs>]().clamp()
}
} }
} impl Wrap<Fixed<$rhs,{$rhs*32}>> for Fixed<$lhs,{$lhs*32}>{
impl Fix<Fixed<$rhs,{$rhs*32}>> for Fixed<$lhs,{$lhs*32}>{ #[inline]
fn fix(self)->Fixed<$rhs,{$rhs*32}>{ fn wrap(self)->Fixed<$rhs,{$rhs*32}>{
paste::item!{ self.[<wrap_ $rhs>]()
self.[<fix_ $rhs>]() }
}
impl TryInto<Fixed<$rhs,{$rhs*32}>> for Fixed<$lhs,{$lhs*32}>{
type Error=NarrowError;
#[inline]
fn try_into(self)->Result<Fixed<$rhs,{$rhs*32}>,Self::Error>{
self.[<narrow_ $rhs>]()
}
}
impl Clamp<Fixed<$rhs,{$rhs*32}>> for Fixed<$lhs,{$lhs*32}>{
#[inline]
fn clamp(self)->Fixed<$rhs,{$rhs*32}>{
self.[<clamp_ $rhs>]()
} }
} }
} }
} }
} }
macro_rules! impl_fix_lhs_lt_rhs_not_const_generic{ macro_rules! impl_widen_not_const_generic{
( (
(), (),
($lhs:expr,$rhs:expr) ($lhs:expr,$rhs:expr)
)=>{ )=>{
impl Fixed<$lhs,{$lhs*32}> paste::item!{
{ impl Fixed<$lhs,{$lhs*32}>
paste::item!{ {
#[inline] #[inline]
pub fn [<fix_ $rhs>](self)->Fixed<$rhs,{$rhs*32}>{ pub fn [<widen_ $rhs>](self)->Fixed<$rhs,{$rhs*32}>{
Fixed::from_bits(bnum::cast::As::as_::<BInt::<$rhs>>(self.bits).shl(($rhs-$lhs)*32)) Fixed::from_bits(bnum::cast::As::as_::<BInt::<$rhs>>(self.bits).shl(($rhs-$lhs)*32))
} }
} }
} impl Into<Fixed<$rhs,{$rhs*32}>> for Fixed<$lhs,{$lhs*32}>{
impl Fix<Fixed<$rhs,{$rhs*32}>> for Fixed<$lhs,{$lhs*32}>{
fn fix(self)->Fixed<$rhs,{$rhs*32}>{
paste::item!{
self.[<fix_ $rhs>]()
}
}
}
}
}
macro_rules! impl_fix_lhs_eq_rhs_not_const_generic{
(
(),
($lhs:expr,$rhs:expr)
)=>{
impl Fixed<$lhs,{$lhs*32}>
{
paste::item!{
#[inline] #[inline]
pub fn [<fix_ $rhs>](self)->Fixed<$rhs,{$rhs*32}>{ fn into(self)->Fixed<$rhs,{$rhs*32}>{
self self.[<widen_ $rhs>]()
}
}
}
impl Fix<Fixed<$rhs,{$rhs*32}>> for Fixed<$lhs,{$lhs*32}>{
fn fix(self)->Fixed<$rhs,{$rhs*32}>{
paste::item!{
self.[<fix_ $rhs>]()
} }
} }
} }
@ -728,7 +760,7 @@ macro_rules! impl_fix_lhs_eq_rhs_not_const_generic{
// I LOVE NOT BEING ABLE TO USE CONST GENERICS // I LOVE NOT BEING ABLE TO USE CONST GENERICS
macro_repeated!( macro_repeated!(
impl_fix_rhs_lt_lhs_not_const_generic,(), impl_narrow_not_const_generic,(),
(2,1),(3,1),(4,1),(5,1),(6,1),(7,1),(8,1),(9,1),(10,1),(11,1),(12,1),(13,1),(14,1),(15,1),(16,1),(17,1), (2,1),(3,1),(4,1),(5,1),(6,1),(7,1),(8,1),(9,1),(10,1),(11,1),(12,1),(13,1),(14,1),(15,1),(16,1),(17,1),
(3,2),(4,2),(5,2),(6,2),(7,2),(8,2),(9,2),(10,2),(11,2),(12,2),(13,2),(14,2),(15,2),(16,2), (3,2),(4,2),(5,2),(6,2),(7,2),(8,2),(9,2),(10,2),(11,2),(12,2),(13,2),(14,2),(15,2),(16,2),
(4,3),(5,3),(6,3),(7,3),(8,3),(9,3),(10,3),(11,3),(12,3),(13,3),(14,3),(15,3),(16,3), (4,3),(5,3),(6,3),(7,3),(8,3),(9,3),(10,3),(11,3),(12,3),(13,3),(14,3),(15,3),(16,3),
@ -746,7 +778,7 @@ macro_repeated!(
(16,15) (16,15)
); );
macro_repeated!( macro_repeated!(
impl_fix_lhs_lt_rhs_not_const_generic,(), impl_widen_not_const_generic,(),
(1,2), (1,2),
(1,3),(2,3), (1,3),(2,3),
(1,4),(2,4),(3,4), (1,4),(2,4),(3,4),
@ -761,11 +793,8 @@ macro_repeated!(
(1,13),(2,13),(3,13),(4,13),(5,13),(6,13),(7,13),(8,13),(9,13),(10,13),(11,13),(12,13), (1,13),(2,13),(3,13),(4,13),(5,13),(6,13),(7,13),(8,13),(9,13),(10,13),(11,13),(12,13),
(1,14),(2,14),(3,14),(4,14),(5,14),(6,14),(7,14),(8,14),(9,14),(10,14),(11,14),(12,14),(13,14), (1,14),(2,14),(3,14),(4,14),(5,14),(6,14),(7,14),(8,14),(9,14),(10,14),(11,14),(12,14),(13,14),
(1,15),(2,15),(3,15),(4,15),(5,15),(6,15),(7,15),(8,15),(9,15),(10,15),(11,15),(12,15),(13,15),(14,15), (1,15),(2,15),(3,15),(4,15),(5,15),(6,15),(7,15),(8,15),(9,15),(10,15),(11,15),(12,15),(13,15),(14,15),
(1,16),(2,16),(3,16),(4,16),(5,16),(6,16),(7,16),(8,16),(9,16),(10,16),(11,16),(12,16),(13,16),(14,16),(15,16) (1,16),(2,16),(3,16),(4,16),(5,16),(6,16),(7,16),(8,16),(9,16),(10,16),(11,16),(12,16),(13,16),(14,16),(15,16),
); (1,17)
macro_repeated!(
impl_fix_lhs_eq_rhs_not_const_generic,(),
(1,1),(2,2),(3,3),(4,4),(5,5),(6,6),(7,7),(8,8),(9,9),(10,10),(11,11),(12,12),(13,13),(14,14),(15,15),(16,16)
); );
macro_rules! impl_not_const_generic{ macro_rules! impl_not_const_generic{
@ -785,16 +814,13 @@ macro_rules! impl_not_const_generic{
let mut result=Self::ZERO; let mut result=Self::ZERO;
//resize self to match the wide mul output //resize self to match the wide mul output
let wide_self=self.[<fix_ $_2n>](); let wide_self=self.[<widen_ $_2n>]();
//descend down the bits and check if flipping each bit would push the square over the input value //descend down the bits and check if flipping each bit would push the square over the input value
for shift in (0..=max_shift).rev(){ for shift in (0..=max_shift).rev(){
let new_result={ result.as_bits_mut().as_bits_mut().set_bit(shift,true);
let mut bits=result.to_bits().to_bits(); if wide_self<result.[<wide_mul_ $n _ $n>](result){
bits.set_bit(shift,true); // put it back lol
Self::from_bits(BInt::from_bits(bits)) result.as_bits_mut().as_bits_mut().set_bit(shift,false);
};
if new_result.[<wide_mul_ $n _ $n>](new_result)<=wide_self{
result=new_result;
} }
} }
result result

@ -61,7 +61,7 @@ fn from_f32(){
let b:Result<I32F32,_>=Into::<f32>::into(I32F32::MIN).try_into(); let b:Result<I32F32,_>=Into::<f32>::into(I32F32::MIN).try_into();
assert_eq!(b,Err(crate::fixed::FixedFromFloatError::Overflow)); assert_eq!(b,Err(crate::fixed::FixedFromFloatError::Overflow));
//16 is within the 24 bits of float precision //16 is within the 24 bits of float precision
let b:Result<I32F32,_>=Into::<f32>::into(-I32F32::MIN.fix_2()).try_into(); let b:Result<I32F32,_>=Into::<f32>::into(-I32F32::MIN.widen_2()).try_into();
assert_eq!(b,Err(crate::fixed::FixedFromFloatError::Overflow)); assert_eq!(b,Err(crate::fixed::FixedFromFloatError::Overflow));
let b:Result<I32F32,_>=f32::MIN_POSITIVE.try_into(); let b:Result<I32F32,_>=f32::MIN_POSITIVE.try_into();
assert_eq!(b,Err(crate::fixed::FixedFromFloatError::Underflow)); assert_eq!(b,Err(crate::fixed::FixedFromFloatError::Underflow));
@ -136,11 +136,24 @@ fn test_bint(){
} }
#[test] #[test]
fn test_fix(){ fn test_wrap(){
assert_eq!(I32F32::ONE.fix_8(),I256F256::ONE); assert_eq!(I32F32::ONE,I256F256::ONE.wrap_1());
assert_eq!(I32F32::ONE,I256F256::ONE.fix_1()); assert_eq!(I32F32::NEG_ONE,I256F256::NEG_ONE.wrap_1());
assert_eq!(I32F32::NEG_ONE.fix_8(),I256F256::NEG_ONE); }
assert_eq!(I32F32::NEG_ONE,I256F256::NEG_ONE.fix_1()); #[test]
fn test_narrow(){
assert_eq!(Ok(I32F32::ONE),I256F256::ONE.narrow_1());
assert_eq!(Ok(I32F32::NEG_ONE),I256F256::NEG_ONE.narrow_1());
}
#[test]
fn test_widen(){
assert_eq!(I32F32::ONE.widen_8(),I256F256::ONE);
assert_eq!(I32F32::NEG_ONE.widen_8(),I256F256::NEG_ONE);
}
#[test]
fn test_clamp(){
assert_eq!(I32F32::ONE,I256F256::ONE.clamp_1());
assert_eq!(I32F32::NEG_ONE,I256F256::NEG_ONE.clamp_1());
} }
#[test] #[test]
fn test_sqrt(){ fn test_sqrt(){

@ -15,8 +15,10 @@ macro_rules! impl_zeroes{
let radicand=a1*a1-a2*a0*4; let radicand=a1*a1-a2*a0*4;
match radicand.cmp(&<Self as core::ops::Mul>::Output::ZERO){ match radicand.cmp(&<Self as core::ops::Mul>::Output::ZERO){
Ordering::Greater=>{ Ordering::Greater=>{
// using wrap because sqrt always halves the number of leading digits.
// clamp would be more defensive, but is slower.
paste::item!{ paste::item!{
let planar_radicand=radicand.sqrt().[<fix_ $n>](); let planar_radicand=radicand.sqrt().[<wrap_ $n>]();
} }
//sort roots ascending and avoid taking the difference of large numbers //sort roots ascending and avoid taking the difference of large numbers
let zeroes=match (a2pos,Self::ZERO<a1){ let zeroes=match (a2pos,Self::ZERO<a1){

@ -1,7 +1,7 @@
[package] [package]
name = "linear_ops" name = "linear_ops"
version = "0.1.0" version = "0.1.1"
edition = "2021" edition = "2024"
repository = "https://git.itzana.me/StrafesNET/strafe-project" repository = "https://git.itzana.me/StrafesNET/strafe-project"
license = "MIT OR Apache-2.0" license = "MIT OR Apache-2.0"
description = "Vector/Matrix operations using trait bounds." description = "Vector/Matrix operations using trait bounds."
@ -15,7 +15,7 @@ deferred-division=["dep:ratio_ops"]
[dependencies] [dependencies]
ratio_ops = { version = "0.1.0", path = "../ratio_ops", registry = "strafesnet", optional = true } ratio_ops = { version = "0.1.0", path = "../ratio_ops", registry = "strafesnet", optional = true }
fixed_wide = { version = "0.1.2", path = "../fixed_wide", registry = "strafesnet", optional = true } fixed_wide = { version = "0.2.0", path = "../fixed_wide", registry = "strafesnet", optional = true }
paste = { version = "1.0.15", optional = true } paste = { version = "1.0.15", optional = true }
[dev-dependencies] [dev-dependencies]

@ -38,40 +38,95 @@ macro_rules! impl_fixed_wide_vector {
$crate::macro_4!(impl_fixed_wide_vector_not_const_generic,()); $crate::macro_4!(impl_fixed_wide_vector_not_const_generic,());
// I LOVE NOT BEING ABLE TO USE CONST GENERICS // I LOVE NOT BEING ABLE TO USE CONST GENERICS
$crate::macro_repeated!( $crate::macro_repeated!(
impl_fix_not_const_generic,(), impl_narrow_not_const_generic,(),
(1,1),(2,1),(3,1),(4,1),(5,1),(6,1),(7,1),(8,1),(9,1),(10,1),(11,1),(12,1),(13,1),(14,1),(15,1),(16,1), (2,1),(3,1),(4,1),(5,1),(6,1),(7,1),(8,1),(9,1),(10,1),(11,1),(12,1),(13,1),(14,1),(15,1),(16,1),(17,1),
(1,2),(2,2),(3,2),(4,2),(5,2),(6,2),(7,2),(8,2),(9,2),(10,2),(11,2),(12,2),(13,2),(14,2),(15,2),(16,2), (3,2),(4,2),(5,2),(6,2),(7,2),(8,2),(9,2),(10,2),(11,2),(12,2),(13,2),(14,2),(15,2),(16,2),
(1,3),(2,3),(3,3),(4,3),(5,3),(6,3),(7,3),(8,3),(9,3),(10,3),(11,3),(12,3),(13,3),(14,3),(15,3),(16,3), (4,3),(5,3),(6,3),(7,3),(8,3),(9,3),(10,3),(11,3),(12,3),(13,3),(14,3),(15,3),(16,3),
(1,4),(2,4),(3,4),(4,4),(5,4),(6,4),(7,4),(8,4),(9,4),(10,4),(11,4),(12,4),(13,4),(14,4),(15,4),(16,4), (5,4),(6,4),(7,4),(8,4),(9,4),(10,4),(11,4),(12,4),(13,4),(14,4),(15,4),(16,4),
(1,5),(2,5),(3,5),(4,5),(5,5),(6,5),(7,5),(8,5),(9,5),(10,5),(11,5),(12,5),(13,5),(14,5),(15,5),(16,5), (6,5),(7,5),(8,5),(9,5),(10,5),(11,5),(12,5),(13,5),(14,5),(15,5),(16,5),
(1,6),(2,6),(3,6),(4,6),(5,6),(6,6),(7,6),(8,6),(9,6),(10,6),(11,6),(12,6),(13,6),(14,6),(15,6),(16,6), (7,6),(8,6),(9,6),(10,6),(11,6),(12,6),(13,6),(14,6),(15,6),(16,6),
(1,7),(2,7),(3,7),(4,7),(5,7),(6,7),(7,7),(8,7),(9,7),(10,7),(11,7),(12,7),(13,7),(14,7),(15,7),(16,7), (8,7),(9,7),(10,7),(11,7),(12,7),(13,7),(14,7),(15,7),(16,7),
(1,8),(2,8),(3,8),(4,8),(5,8),(6,8),(7,8),(8,8),(9,8),(10,8),(11,8),(12,8),(13,8),(14,8),(15,8),(16,8), (9,8),(10,8),(11,8),(12,8),(13,8),(14,8),(15,8),(16,8),
(1,9),(2,9),(3,9),(4,9),(5,9),(6,9),(7,9),(8,9),(9,9),(10,9),(11,9),(12,9),(13,9),(14,9),(15,9),(16,9), (10,9),(11,9),(12,9),(13,9),(14,9),(15,9),(16,9),
(1,10),(2,10),(3,10),(4,10),(5,10),(6,10),(7,10),(8,10),(9,10),(10,10),(11,10),(12,10),(13,10),(14,10),(15,10),(16,10), (11,10),(12,10),(13,10),(14,10),(15,10),(16,10),
(1,11),(2,11),(3,11),(4,11),(5,11),(6,11),(7,11),(8,11),(9,11),(10,11),(11,11),(12,11),(13,11),(14,11),(15,11),(16,11), (12,11),(13,11),(14,11),(15,11),(16,11),
(1,12),(2,12),(3,12),(4,12),(5,12),(6,12),(7,12),(8,12),(9,12),(10,12),(11,12),(12,12),(13,12),(14,12),(15,12),(16,12), (13,12),(14,12),(15,12),(16,12),
(1,13),(2,13),(3,13),(4,13),(5,13),(6,13),(7,13),(8,13),(9,13),(10,13),(11,13),(12,13),(13,13),(14,13),(15,13),(16,13), (14,13),(15,13),(16,13),
(1,14),(2,14),(3,14),(4,14),(5,14),(6,14),(7,14),(8,14),(9,14),(10,14),(11,14),(12,14),(13,14),(14,14),(15,14),(16,14), (15,14),(16,14),
(1,15),(2,15),(3,15),(4,15),(5,15),(6,15),(7,15),(8,15),(9,15),(10,15),(11,15),(12,15),(13,15),(14,15),(15,15),(16,15), (16,15)
(1,16),(2,16),(3,16),(4,16),(5,16),(6,16),(7,16),(8,16),(9,16),(10,16),(11,16),(12,16),(13,16),(14,16),(15,16),(16,16)
); );
$crate::macro_repeated!(
impl_widen_not_const_generic,(),
(1,2),
(1,3),(2,3),
(1,4),(2,4),(3,4),
(1,5),(2,5),(3,5),(4,5),
(1,6),(2,6),(3,6),(4,6),(5,6),
(1,7),(2,7),(3,7),(4,7),(5,7),(6,7),
(1,8),(2,8),(3,8),(4,8),(5,8),(6,8),(7,8),
(1,9),(2,9),(3,9),(4,9),(5,9),(6,9),(7,9),(8,9),
(1,10),(2,10),(3,10),(4,10),(5,10),(6,10),(7,10),(8,10),(9,10),
(1,11),(2,11),(3,11),(4,11),(5,11),(6,11),(7,11),(8,11),(9,11),(10,11),
(1,12),(2,12),(3,12),(4,12),(5,12),(6,12),(7,12),(8,12),(9,12),(10,12),(11,12),
(1,13),(2,13),(3,13),(4,13),(5,13),(6,13),(7,13),(8,13),(9,13),(10,13),(11,13),(12,13),
(1,14),(2,14),(3,14),(4,14),(5,14),(6,14),(7,14),(8,14),(9,14),(10,14),(11,14),(12,14),(13,14),
(1,15),(2,15),(3,15),(4,15),(5,15),(6,15),(7,15),(8,15),(9,15),(10,15),(11,15),(12,15),(13,15),(14,15),
(1,16),(2,16),(3,16),(4,16),(5,16),(6,16),(7,16),(8,16),(9,16),(10,16),(11,16),(12,16),(13,16),(14,16),(15,16),
(1,17)
);
impl<const N:usize,T:fixed_wide::fixed::Wrap<U>,U> fixed_wide::fixed::Wrap<Vector<N,U>> for Vector<N,T>
{
#[inline]
fn wrap(self)->Vector<N,U>{
self.map(|t|t.wrap())
}
}
impl<const N:usize,T:fixed_wide::fixed::Clamp<U>,U> fixed_wide::fixed::Clamp<Vector<N,U>> for Vector<N,T>
{
#[inline]
fn clamp(self)->Vector<N,U>{
self.map(|t|t.clamp())
}
}
}; };
} }
#[doc(hidden)] #[doc(hidden)]
#[macro_export(local_inner_macros)] #[macro_export(local_inner_macros)]
macro_rules! impl_fix_not_const_generic{ macro_rules! impl_narrow_not_const_generic{
( (
(), (),
($lhs:expr,$rhs:expr) ($lhs:expr,$rhs:expr)
)=>{ )=>{
impl<const N:usize> Vector<N,fixed_wide::fixed::Fixed<$lhs,{$lhs*32}>> paste::item!{
{ impl<const N:usize> Vector<N,fixed_wide::fixed::Fixed<$lhs,{$lhs*32}>>{
paste::item!{
#[inline] #[inline]
pub fn [<fix_ $rhs>](self)->Vector<N,fixed_wide::fixed::Fixed<$rhs,{$rhs*32}>>{ pub fn [<wrap_ $rhs>](self)->Vector<N,fixed_wide::fixed::Fixed<$rhs,{$rhs*32}>>{
self.map(|t|t.[<fix_ $rhs>]()) self.map(|t|t.[<wrap_ $rhs>]())
}
#[inline]
pub fn [<narrow_ $rhs>](self)->Vector<N,Result<fixed_wide::fixed::Fixed<$rhs,{$rhs*32}>,fixed_wide::fixed::NarrowError>>{
self.map(|t|t.[<narrow_ $rhs>]())
}
#[inline]
pub fn [<clamp_ $rhs>](self)->Vector<N,fixed_wide::fixed::Fixed<$rhs,{$rhs*32}>>{
self.map(|t|t.[<clamp_ $rhs>]())
}
}
}
}
}
#[doc(hidden)]
#[macro_export(local_inner_macros)]
macro_rules! impl_widen_not_const_generic{
(
(),
($lhs:expr,$rhs:expr)
)=>{
paste::item!{
impl<const N:usize> Vector<N,fixed_wide::fixed::Fixed<$lhs,{$lhs*32}>>{
#[inline]
pub fn [<widen_ $rhs>](self)->Vector<N,fixed_wide::fixed::Fixed<$rhs,{$rhs*32}>>{
self.map(|t|t.[<widen_ $rhs>]())
} }
} }
} }

@ -58,6 +58,15 @@ macro_rules! impl_vector {
} }
} }
impl<const N:usize,T,E:std::fmt::Debug> Vector<N,Result<T,E>>{
#[inline]
pub fn unwrap(self)->Vector<N,T>{
Vector{
array:self.array.map(Result::unwrap)
}
}
}
impl<const N:usize,T:Ord> Vector<N,T>{ impl<const N:usize,T:Ord> Vector<N,T>{
#[inline] #[inline]
pub fn min(self,rhs:Self)->Self{ pub fn min(self,rhs:Self)->Self{

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

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

@ -1,7 +1,7 @@
[package] [package]
name = "strafesnet_rbx_loader" name = "strafesnet_rbx_loader"
version = "0.6.0" version = "0.6.0"
edition = "2021" edition = "2024"
repository = "https://git.itzana.me/StrafesNET/strafe-project" repository = "https://git.itzana.me/StrafesNET/strafe-project"
license = "MIT OR Apache-2.0" license = "MIT OR Apache-2.0"
description = "Convert Roblox place and model files to StrafesNET data structures." description = "Convert Roblox place and model files to StrafesNET data structures."
@ -11,14 +11,14 @@ authors = ["Rhys Lloyd <krakow20@gmail.com>"]
[dependencies] [dependencies]
bytemuck = "1.14.3" bytemuck = "1.14.3"
glam = "0.29.0" glam = "0.30.0"
lazy-regex = "3.1.0" lazy-regex = "3.1.0"
rbx_binary = { version = "0.7.4", registry = "strafesnet" } rbx_binary = { version = "1.1.0-sn3", registry = "strafesnet"}
rbx_dom_weak = { version = "2.7.0", registry = "strafesnet" } rbx_dom_weak = { version = "3.1.0-sn1", registry = "strafesnet"}
rbx_mesh = "0.3.1" rbx_mesh = "0.3.1"
rbx_reflection_database = { version = "0.2.10", registry = "strafesnet" } rbx_reflection_database = "1.0.0"
rbx_xml = { version = "0.13.3", registry = "strafesnet" } rbx_xml = { version = "1.1.0-sn3", registry = "strafesnet"}
rbxassetid = { version = "0.1.0", path = "../rbxassetid", registry = "strafesnet" } rbxassetid = { version = "0.1.0", path = "../rbxassetid", registry = "strafesnet" }
roblox_emulator = { version = "0.4.7", path = "../roblox_emulator", registry = "strafesnet" } roblox_emulator = { version = "0.4.7", path = "../roblox_emulator", default-features = false, registry = "strafesnet" }
strafesnet_common = { version = "0.6.0", path = "../common", registry = "strafesnet" } strafesnet_common = { version = "0.6.0", path = "../common", registry = "strafesnet" }
strafesnet_deferred_loader = { version = "0.5.0", path = "../deferred_loader", registry = "strafesnet" } strafesnet_deferred_loader = { version = "0.5.0", path = "../deferred_loader", registry = "strafesnet" }

@ -1,5 +1,5 @@
use std::io::Read; use std::io::Read;
use rbx_dom_weak::WeakDom; use roblox_emulator::types::WeakDom;
use strafesnet_deferred_loader::deferred_loader::{LoadFailureMode,MeshDeferredLoader,RenderConfigDeferredLoader}; use strafesnet_deferred_loader::deferred_loader::{LoadFailureMode,MeshDeferredLoader,RenderConfigDeferredLoader};
mod rbx; mod rbx;
@ -95,8 +95,8 @@ pub fn read<R:Read>(input:R)->Result<Model,ReadError>{
let mut buf=std::io::BufReader::new(input); let mut buf=std::io::BufReader::new(input);
let peek=std::io::BufRead::fill_buf(&mut buf).map_err(ReadError::Io)?; let peek=std::io::BufRead::fill_buf(&mut buf).map_err(ReadError::Io)?;
match &peek[0..8]{ match &peek[0..8]{
b"<roblox!"=>rbx_binary::from_reader(buf).map(Model::new).map_err(ReadError::RbxBinary), b"<roblox!"=>rbx_binary::from_reader_generic(buf).map(Model::new).map_err(ReadError::RbxBinary),
b"<roblox "=>rbx_xml::from_reader_default(buf).map(Model::new).map_err(ReadError::RbxXml), b"<roblox "=>rbx_xml::from_reader_generic(buf,Default::default()).map(Model::new).map_err(ReadError::RbxXml),
_=>Err(ReadError::UnknownFileFormat), _=>Err(ReadError::UnknownFileFormat),
} }
} }

@ -1,10 +1,11 @@
use std::io::Read; use std::io::Read;
use rbx_dom_weak::ustr;
use rbxassetid::{RobloxAssetId,RobloxAssetIdParseErr}; use rbxassetid::{RobloxAssetId,RobloxAssetIdParseErr};
use strafesnet_common::model::Mesh; use strafesnet_common::model::Mesh;
use strafesnet_deferred_loader::{loader::Loader,texture::Texture}; use strafesnet_deferred_loader::{loader::Loader,texture::Texture};
use crate::data::RobloxMeshBytes; use crate::data::RobloxMeshBytes;
use crate::rbx::RobloxFaceTextureDescription; use crate::rbx::RobloxPartDescription;
fn read_entire_file(path:impl AsRef<std::path::Path>)->Result<Vec<u8>,std::io::Error>{ fn read_entire_file(path:impl AsRef<std::path::Path>)->Result<Vec<u8>,std::io::Error>{
let mut file=std::fs::File::open(path)?; let mut file=std::fs::File::open(path)?;
@ -36,17 +37,17 @@ impl From<RobloxAssetIdParseErr> for TextureError{
} }
} }
pub struct TextureLoader<'a>(std::marker::PhantomData<&'a ()>); pub struct TextureLoader;
impl TextureLoader<'_>{ impl TextureLoader{
pub fn new()->Self{ pub fn new()->Self{
Self(std::marker::PhantomData) Self
} }
} }
impl<'a> Loader for TextureLoader<'a>{ impl Loader for TextureLoader{
type Error=TextureError; type Error=TextureError;
type Index=&'a str; type Index<'a>=&'a str;
type Resource=Texture; type Resource=Texture;
fn load(&mut self,index:Self::Index)->Result<Self::Resource,Self::Error>{ fn load<'a>(&mut self,index:Self::Index<'a>)->Result<Self::Resource,Self::Error>{
let RobloxAssetId(asset_id)=index.parse()?; let RobloxAssetId(asset_id)=index.parse()?;
let file_name=format!("textures/{}.dds",asset_id); let file_name=format!("textures/{}.dds",asset_id);
let data=read_entire_file(file_name)?; let data=read_entire_file(file_name)?;
@ -104,7 +105,7 @@ pub enum MeshType<'a>{
mesh_data:&'a [u8], mesh_data:&'a [u8],
physics_data:&'a [u8], physics_data:&'a [u8],
size_float_bits:[u32;3], size_float_bits:[u32;3],
part_texture_description:[Option<RobloxFaceTextureDescription>;6], part_texture_description:RobloxPartDescription,
}, },
} }
#[derive(Hash,Eq,PartialEq)] #[derive(Hash,Eq,PartialEq)]
@ -124,7 +125,7 @@ impl MeshIndex<'_>{
mesh_data:&'a [u8], mesh_data:&'a [u8],
physics_data:&'a [u8], physics_data:&'a [u8],
size:&rbx_dom_weak::types::Vector3, size:&rbx_dom_weak::types::Vector3,
part_texture_description:crate::rbx::RobloxPartDescription, part_texture_description:RobloxPartDescription,
)->MeshIndex<'a>{ )->MeshIndex<'a>{
MeshIndex{ MeshIndex{
mesh_type:MeshType::Union{ mesh_type:MeshType::Union{
@ -138,17 +139,17 @@ impl MeshIndex<'_>{
} }
} }
pub struct MeshLoader<'a>(std::marker::PhantomData<&'a ()>); pub struct MeshLoader;
impl MeshLoader<'_>{ impl MeshLoader{
pub fn new()->Self{ pub fn new()->Self{
Self(std::marker::PhantomData) Self
} }
} }
impl<'a> Loader for MeshLoader<'a>{ impl Loader for MeshLoader{
type Error=MeshError; type Error=MeshError;
type Index=MeshIndex<'a>; type Index<'a>=MeshIndex<'a>;
type Resource=Mesh; type Resource=Mesh;
fn load(&mut self,index:Self::Index)->Result<Self::Resource,Self::Error>{ fn load<'a>(&mut self,index:Self::Index<'a>)->Result<Self::Resource,Self::Error>{
let mesh=match index.mesh_type{ let mesh=match index.mesh_type{
MeshType::FileMesh=>{ MeshType::FileMesh=>{
let RobloxAssetId(asset_id)=index.content.parse()?; let RobloxAssetId(asset_id)=index.content.parse()?;
@ -171,12 +172,12 @@ impl<'a> Loader for MeshLoader<'a>{
return Err(MeshError::MissingInstance); return Err(MeshError::MissingInstance);
}; };
if physics_data.is_empty(){ if physics_data.is_empty(){
if let Some(rbx_dom_weak::types::Variant::BinaryString(data))=instance.properties.get("PhysicsData"){ if let Some(rbx_dom_weak::types::Variant::BinaryString(data))=instance.properties.get(&ustr("PhysicsData")){
physics_data=data.as_ref(); physics_data=data.as_ref();
} }
} }
if mesh_data.is_empty(){ if mesh_data.is_empty(){
if let Some(rbx_dom_weak::types::Variant::BinaryString(data))=instance.properties.get("MeshData"){ if let Some(rbx_dom_weak::types::Variant::BinaryString(data))=instance.properties.get(&ustr("MeshData")){
mesh_data=data.as_ref(); mesh_data=data.as_ref();
} }
} }

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

@ -1,5 +1,6 @@
use strafesnet_common::model::{Color4,TextureCoordinate,Mesh,IndexedGraphicsGroup,IndexedPhysicsGroup,IndexedVertex,PolygonGroupId,PolygonGroup,PolygonList,PositionId,TextureCoordinateId,NormalId,ColorId,VertexId,RenderConfigId}; use crate::rbx::{RobloxPartDescription,RobloxWedgeDescription,RobloxCornerWedgeDescription};
use strafesnet_common::integer::{vec3,Planar64Vec3}; use strafesnet_common::model::{Color4,TextureCoordinate,Mesh,MeshBuilder,IndexedGraphicsGroup,IndexedPhysicsGroup,IndexedVertex,PolygonGroupId,PolygonGroup,PolygonList,PositionId,TextureCoordinateId,NormalId,ColorId,VertexId,RenderConfigId};
use strafesnet_common::integer::{vec3,Planar64,Planar64Vec3};
#[derive(Debug)] #[derive(Debug)]
pub enum Primitives{ pub enum Primitives{
@ -9,7 +10,22 @@ pub enum Primitives{
Wedge, Wedge,
CornerWedge, CornerWedge,
} }
#[derive(Hash,PartialEq,Eq)] #[derive(Debug)]
pub struct PrimitivesError;
impl TryFrom<u32> for Primitives{
type Error=PrimitivesError;
fn try_from(value:u32)->Result<Self,Self::Error>{
match value{
0=>Ok(Primitives::Sphere),
1=>Ok(Primitives::Cube),
2=>Ok(Primitives::Cylinder),
3=>Ok(Primitives::Wedge),
4=>Ok(Primitives::CornerWedge),
_=>Err(PrimitivesError),
}
}
}
#[derive(Clone,Copy,Hash,PartialEq,Eq)]
pub enum CubeFace{ pub enum CubeFace{
Right, Right,
Top, Top,
@ -18,6 +34,22 @@ pub enum CubeFace{
Bottom, Bottom,
Front, Front,
} }
#[derive(Debug)]
pub struct CubeFaceError;
impl TryFrom<u32> for CubeFace{
type Error=CubeFaceError;
fn try_from(value:u32)->Result<Self,Self::Error>{
match value{
0=>Ok(CubeFace::Right),
1=>Ok(CubeFace::Top),
2=>Ok(CubeFace::Back),
3=>Ok(CubeFace::Left),
4=>Ok(CubeFace::Bottom),
5=>Ok(CubeFace::Front),
_=>Err(CubeFaceError),
}
}
}
const CUBE_DEFAULT_TEXTURE_COORDS:[TextureCoordinate;4]=[ const CUBE_DEFAULT_TEXTURE_COORDS:[TextureCoordinate;4]=[
TextureCoordinate::new(0.0,0.0), TextureCoordinate::new(0.0,0.0),
TextureCoordinate::new(1.0,0.0), TextureCoordinate::new(1.0,0.0),
@ -43,103 +75,36 @@ const CUBE_DEFAULT_NORMALS:[Planar64Vec3;6]=[
vec3::int( 0, 0,-1),//CubeFace::Front vec3::int( 0, 0,-1),//CubeFace::Front
]; ];
#[derive(Hash,PartialEq,Eq)] pub struct CubeFaceDescription([FaceDescription;Self::FACES]);
pub enum WedgeFace{
Right,
TopFront,
Back,
Left,
Bottom,
}
const WEDGE_DEFAULT_NORMALS:[Planar64Vec3;5]=[
vec3::int( 1, 0, 0),//Wedge::Right
vec3::int( 0, 1,-1),//Wedge::TopFront
vec3::int( 0, 0, 1),//Wedge::Back
vec3::int(-1, 0, 0),//Wedge::Left
vec3::int( 0,-1, 0),//Wedge::Bottom
];
/*
local cornerWedgeVerticies = {
Vector3.new(-1/2,-1/2,-1/2),7
Vector3.new(-1/2,-1/2, 1/2),0
Vector3.new( 1/2,-1/2,-1/2),6
Vector3.new( 1/2,-1/2, 1/2),1
Vector3.new( 1/2, 1/2,-1/2),5
}
*/
#[derive(Hash,PartialEq,Eq)]
pub enum CornerWedgeFace{
Right,
TopBack,
TopLeft,
Bottom,
Front,
}
const CORNERWEDGE_DEFAULT_NORMALS:[Planar64Vec3;5]=[
vec3::int( 1, 0, 0),//CornerWedge::Right
vec3::int( 0, 1, 1),//CornerWedge::BackTop
vec3::int(-1, 1, 0),//CornerWedge::LeftTop
vec3::int( 0,-1, 0),//CornerWedge::Bottom
vec3::int( 0, 0,-1),//CornerWedge::Front
];
#[derive(Default)]
pub struct CubeFaceDescription([Option<FaceDescription>;6]);
impl CubeFaceDescription{ impl CubeFaceDescription{
pub fn insert(&mut self,index:CubeFace,value:FaceDescription){ pub const FACES:usize=6;
self.0[index as usize]=Some(value); pub fn new(RobloxPartDescription(part_description):RobloxPartDescription,textureless_render_id:RenderConfigId)->Self{
} Self(part_description.map(|face_description|match face_description{
pub fn pairs(self)->impl Iterator<Item=(usize,FaceDescription)>{ Some(roblox_texture_transform)=>roblox_texture_transform.to_face_description(),
self.0.into_iter().enumerate().filter_map(|(i,v)|v.map(|u|(i,u))) None=>FaceDescription::new_with_render_id(textureless_render_id),
}))
} }
} }
pub fn unit_cube(render:RenderConfigId)->Mesh{ pub struct WedgeFaceDescription([FaceDescription;Self::FACES]);
let mut t=CubeFaceDescription::default();
t.insert(CubeFace::Right,FaceDescription::new_with_render_id(render));
t.insert(CubeFace::Top,FaceDescription::new_with_render_id(render));
t.insert(CubeFace::Back,FaceDescription::new_with_render_id(render));
t.insert(CubeFace::Left,FaceDescription::new_with_render_id(render));
t.insert(CubeFace::Bottom,FaceDescription::new_with_render_id(render));
t.insert(CubeFace::Front,FaceDescription::new_with_render_id(render));
generate_partial_unit_cube(t)
}
#[derive(Default)]
pub struct WedgeFaceDescription([Option<FaceDescription>;5]);
impl WedgeFaceDescription{ impl WedgeFaceDescription{
pub fn insert(&mut self,index:WedgeFace,value:FaceDescription){ pub const FACES:usize=5;
self.0[index as usize]=Some(value); pub fn new(RobloxWedgeDescription(part_description):RobloxWedgeDescription,textureless_render_id:RenderConfigId)->Self{
} Self(part_description.map(|face_description|match face_description{
pub fn pairs(self)->std::iter::FilterMap<std::iter::Enumerate<std::array::IntoIter<Option<FaceDescription>,5>>,impl FnMut((usize,Option<FaceDescription>))->Option<(usize,FaceDescription)>>{ Some(roblox_texture_transform)=>roblox_texture_transform.to_face_description(),
self.0.into_iter().enumerate().filter_map(|v|v.1.map(|u|(v.0,u))) None=>FaceDescription::new_with_render_id(textureless_render_id),
}))
} }
} }
// pub fn unit_wedge(render:RenderConfigId)->Mesh{ pub struct CornerWedgeFaceDescription([FaceDescription;Self::FACES]);
// let mut t=WedgeFaceDescription::default();
// t.insert(WedgeFace::Right,FaceDescription::new_with_render_id(render));
// t.insert(WedgeFace::TopFront,FaceDescription::new_with_render_id(render));
// t.insert(WedgeFace::Back,FaceDescription::new_with_render_id(render));
// t.insert(WedgeFace::Left,FaceDescription::new_with_render_id(render));
// t.insert(WedgeFace::Bottom,FaceDescription::new_with_render_id(render));
// generate_partial_unit_wedge(t)
// }
#[derive(Default)]
pub struct CornerWedgeFaceDescription([Option<FaceDescription>;5]);
impl CornerWedgeFaceDescription{ impl CornerWedgeFaceDescription{
pub fn insert(&mut self,index:CornerWedgeFace,value:FaceDescription){ pub const FACES:usize=5;
self.0[index as usize]=Some(value); pub fn new(RobloxCornerWedgeDescription(part_description):RobloxCornerWedgeDescription,textureless_render_id:RenderConfigId)->Self{
} Self(part_description.map(|face_description|match face_description{
pub fn pairs(self)->std::iter::FilterMap<std::iter::Enumerate<std::array::IntoIter<Option<FaceDescription>,5>>,impl FnMut((usize,Option<FaceDescription>))->Option<(usize,FaceDescription)>>{ Some(roblox_texture_transform)=>roblox_texture_transform.to_face_description(),
self.0.into_iter().enumerate().filter_map(|v|v.1.map(|u|(v.0,u))) None=>FaceDescription::new_with_render_id(textureless_render_id),
}))
} }
} }
// pub fn unit_cornerwedge(render:RenderConfigId)->Mesh{
// let mut t=CornerWedgeFaceDescription::default();
// t.insert(CornerWedgeFace::Right,FaceDescription::new_with_render_id(render));
// t.insert(CornerWedgeFace::TopBack,FaceDescription::new_with_render_id(render));
// t.insert(CornerWedgeFace::TopLeft,FaceDescription::new_with_render_id(render));
// t.insert(CornerWedgeFace::Bottom,FaceDescription::new_with_render_id(render));
// t.insert(CornerWedgeFace::Front,FaceDescription::new_with_render_id(render));
// generate_partial_unit_cornerwedge(t)
// }
#[derive(Clone)] #[derive(Clone)]
pub struct FaceDescription{ pub struct FaceDescription{
@ -148,7 +113,7 @@ pub struct FaceDescription{
pub color:Color4, pub color:Color4,
} }
impl FaceDescription{ impl FaceDescription{
pub fn new_with_render_id(render:RenderConfigId)->Self { pub fn new_with_render_id(render:RenderConfigId)->Self{
Self{ Self{
render, render,
transform:glam::Affine2::IDENTITY, transform:glam::Affine2::IDENTITY,
@ -156,49 +121,49 @@ impl FaceDescription{
} }
} }
} }
pub fn generate_partial_unit_cube(face_descriptions:CubeFaceDescription)->Mesh{ pub fn unit_cube(CubeFaceDescription(face_descriptions):CubeFaceDescription)->Mesh{
const CUBE_DEFAULT_POLYS:[[[u32;3];4];6]=[ const CUBE_DEFAULT_POLYS:[[[u32;2];4];6]=[
// right (1, 0, 0) // right (1, 0, 0)
[ [
[6,2,0],//[vertex,tex,norm] [6,2],//[vertex,tex]
[5,1,0], [5,1],
[2,0,0], [2,0],
[1,3,0], [1,3],
], ],
// top (0, 1, 0) // top (0, 1, 0)
[ [
[5,3,1], [5,3],
[4,2,1], [4,2],
[3,1,1], [3,1],
[2,0,1], [2,0],
], ],
// back (0, 0, 1) // back (0, 0, 1)
[ [
[0,3,2], [0,3],
[1,2,2], [1,2],
[2,1,2], [2,1],
[3,0,2], [3,0],
], ],
// left (-1, 0, 0) // left (-1, 0, 0)
[ [
[0,2,3], [0,2],
[3,1,3], [3,1],
[4,0,3], [4,0],
[7,3,3], [7,3],
], ],
// bottom (0,-1, 0) // bottom (0,-1, 0)
[ [
[1,1,4], [1,1],
[0,0,4], [0,0],
[7,3,4], [7,3],
[6,2,4], [6,2],
], ],
// front (0, 0,-1) // front (0, 0,-1)
[ [
[4,1,5], [4,1],
[5,0,5], [5,0],
[6,3,5], [6,3],
[7,2,5], [7,2],
], ],
]; ];
let mut generated_pos=Vec::new(); let mut generated_pos=Vec::new();
@ -211,7 +176,7 @@ pub fn generate_partial_unit_cube(face_descriptions:CubeFaceDescription)->Mesh{
let mut physics_group=IndexedPhysicsGroup::default(); let mut physics_group=IndexedPhysicsGroup::default();
let mut transforms=Vec::new(); let mut transforms=Vec::new();
//note that on a cube every vertex is guaranteed to be unique, so there's no need to hash them against existing vertices. //note that on a cube every vertex is guaranteed to be unique, so there's no need to hash them against existing vertices.
for (face_id,face_description) in face_descriptions.pairs(){ for (face_id,face_description) in face_descriptions.into_iter().enumerate(){
//assume that scanning short lists is faster than hashing. //assume that scanning short lists is faster than hashing.
let transform_index=if let Some(transform_index)=transforms.iter().position(|&transform|transform==face_description.transform){ let transform_index=if let Some(transform_index)=transforms.iter().position(|&transform|transform==face_description.transform){
transform_index transform_index
@ -238,8 +203,8 @@ pub fn generate_partial_unit_cube(face_descriptions:CubeFaceDescription)->Mesh{
//push vertices as they are needed //push vertices as they are needed
let group_id=PolygonGroupId::new(polygon_groups.len() as u32); let group_id=PolygonGroupId::new(polygon_groups.len() as u32);
polygon_groups.push(PolygonGroup::PolygonList(PolygonList::new(vec![ polygon_groups.push(PolygonGroup::PolygonList(PolygonList::new(vec![
CUBE_DEFAULT_POLYS[face_id].map(|tup|{ CUBE_DEFAULT_POLYS[face_id].map(|[pos_id,tex_id]|{
let pos=CUBE_DEFAULT_VERTICES[tup[0] as usize]; let pos=CUBE_DEFAULT_VERTICES[pos_id as usize];
let pos_index=if let Some(pos_index)=generated_pos.iter().position(|&p|p==pos){ let pos_index=if let Some(pos_index)=generated_pos.iter().position(|&p|p==pos){
pos_index pos_index
}else{ }else{
@ -251,7 +216,7 @@ pub fn generate_partial_unit_cube(face_descriptions:CubeFaceDescription)->Mesh{
//always push vertex //always push vertex
let vertex=IndexedVertex{ let vertex=IndexedVertex{
pos:PositionId::new(pos_index), pos:PositionId::new(pos_index),
tex:TextureCoordinateId::new(tup[1]+4*transform_index), tex:TextureCoordinateId::new(tex_id+4*transform_index),
normal:NormalId::new(normal_index), normal:NormalId::new(normal_index),
color:ColorId::new(color_index), color:ColorId::new(color_index),
}; };
@ -278,42 +243,49 @@ pub fn generate_partial_unit_cube(face_descriptions:CubeFaceDescription)->Mesh{
} }
} }
//don't think too hard about the copy paste because this is all going into the map tool eventually... //don't think too hard about the copy paste because this is all going into the map tool eventually...
pub fn generate_partial_unit_wedge(face_descriptions:WedgeFaceDescription)->Mesh{ pub fn unit_wedge(WedgeFaceDescription(face_descriptions):WedgeFaceDescription)->Mesh{
const WEDGE_DEFAULT_POLYS:[&[[u32;3]];5]=[ const WEDGE_DEFAULT_POLYS:[&[[u32;2]];5]=[
// right (1, 0, 0) // right (1, 0, 0)
&[ &[
[6,2,0],//[vertex,tex,norm] [6,2],//[vertex,tex]
[2,0,0], [2,0],
[1,3,0], [1,3],
], ],
// FrontTop (0, 1, -1) // FrontTop (0, 1, -1)
&[ &[
[3,1,1], [3,1],
[2,0,1], [2,0],
[6,3,1], [6,3],
[7,2,1], [7,2],
], ],
// back (0, 0, 1) // back (0, 0, 1)
&[ &[
[0,3,2], [0,3],
[1,2,2], [1,2],
[2,1,2], [2,1],
[3,0,2], [3,0],
], ],
// left (-1, 0, 0) // left (-1, 0, 0)
&[ &[
[0,2,3], [0,2],
[3,1,3], [3,1],
[7,3,3], [7,3],
], ],
// bottom (0,-1, 0) // bottom (0,-1, 0)
&[ &[
[1,1,4], [1,1],
[0,0,4], [0,0],
[7,3,4], [7,3],
[6,2,4], [6,2],
], ],
]; ];
const WEDGE_DEFAULT_NORMALS:[Planar64Vec3;5]=[
vec3::int( 1, 0, 0),//Wedge::Right
vec3::int( 0, 1,-1),//Wedge::TopFront
vec3::int( 0, 0, 1),//Wedge::Back
vec3::int(-1, 0, 0),//Wedge::Left
vec3::int( 0,-1, 0),//Wedge::Bottom
];
let mut generated_pos=Vec::new(); let mut generated_pos=Vec::new();
let mut generated_tex=Vec::new(); let mut generated_tex=Vec::new();
let mut generated_normal=Vec::new(); let mut generated_normal=Vec::new();
@ -324,7 +296,7 @@ pub fn generate_partial_unit_wedge(face_descriptions:WedgeFaceDescription)->Mesh
let mut physics_group=IndexedPhysicsGroup::default(); let mut physics_group=IndexedPhysicsGroup::default();
let mut transforms=Vec::new(); let mut transforms=Vec::new();
//note that on a cube every vertex is guaranteed to be unique, so there's no need to hash them against existing vertices. //note that on a cube every vertex is guaranteed to be unique, so there's no need to hash them against existing vertices.
for (face_id,face_description) in face_descriptions.pairs(){ for (face_id,face_description) in face_descriptions.into_iter().enumerate(){
//assume that scanning short lists is faster than hashing. //assume that scanning short lists is faster than hashing.
let transform_index=if let Some(transform_index)=transforms.iter().position(|&transform|transform==face_description.transform){ let transform_index=if let Some(transform_index)=transforms.iter().position(|&transform|transform==face_description.transform){
transform_index transform_index
@ -351,8 +323,8 @@ pub fn generate_partial_unit_wedge(face_descriptions:WedgeFaceDescription)->Mesh
//push vertices as they are needed //push vertices as they are needed
let group_id=PolygonGroupId::new(polygon_groups.len() as u32); let group_id=PolygonGroupId::new(polygon_groups.len() as u32);
polygon_groups.push(PolygonGroup::PolygonList(PolygonList::new(vec![ polygon_groups.push(PolygonGroup::PolygonList(PolygonList::new(vec![
WEDGE_DEFAULT_POLYS[face_id].iter().map(|tup|{ WEDGE_DEFAULT_POLYS[face_id].iter().map(|&[pos_id,tex_id]|{
let pos=CUBE_DEFAULT_VERTICES[tup[0] as usize]; let pos=CUBE_DEFAULT_VERTICES[pos_id as usize];
let pos_index=if let Some(pos_index)=generated_pos.iter().position(|&p|p==pos){ let pos_index=if let Some(pos_index)=generated_pos.iter().position(|&p|p==pos){
pos_index pos_index
}else{ }else{
@ -364,7 +336,7 @@ pub fn generate_partial_unit_wedge(face_descriptions:WedgeFaceDescription)->Mesh
//always push vertex //always push vertex
let vertex=IndexedVertex{ let vertex=IndexedVertex{
pos:PositionId::new(pos_index), pos:PositionId::new(pos_index),
tex:TextureCoordinateId::new(tup[1]+4*transform_index), tex:TextureCoordinateId::new(tex_id+4*transform_index),
normal:NormalId::new(normal_index), normal:NormalId::new(normal_index),
color:ColorId::new(color_index), color:ColorId::new(color_index),
}; };
@ -391,40 +363,47 @@ pub fn generate_partial_unit_wedge(face_descriptions:WedgeFaceDescription)->Mesh
} }
} }
pub fn generate_partial_unit_cornerwedge(face_descriptions:CornerWedgeFaceDescription)->Mesh{ pub fn unit_cornerwedge(CornerWedgeFaceDescription(face_descriptions):CornerWedgeFaceDescription)->Mesh{
const CORNERWEDGE_DEFAULT_POLYS:[&[[u32;3]];5]=[ const CORNERWEDGE_DEFAULT_POLYS:[&[[u32;2]];5]=[
// right (1, 0, 0) // right (1, 0, 0)
&[ &[
[6,2,0],//[vertex,tex,norm] [6,2],//[vertex,tex]
[5,1,0], [5,1],
[1,3,0], [1,3],
], ],
// BackTop (0, 1, 1) // BackTop (0, 1, 1)
&[ &[
[5,3,1], [5,3],
[0,1,1], [0,1],
[1,0,1], [1,0],
], ],
// LeftTop (-1, 1, 0) // LeftTop (-1, 1, 0)
&[ &[
[5,3,2], [5,3],
[7,2,2], [7,2],
[0,1,2], [0,1],
], ],
// bottom (0,-1, 0) // bottom (0,-1, 0)
&[ &[
[1,1,3], [1,1],
[0,0,3], [0,0],
[7,3,3], [7,3],
[6,2,3], [6,2],
], ],
// front (0, 0,-1) // front (0, 0,-1)
&[ &[
[5,0,4], [5,0],
[6,3,4], [6,3],
[7,2,4], [7,2],
], ],
]; ];
const CORNERWEDGE_DEFAULT_NORMALS:[Planar64Vec3;5]=[
vec3::int( 1, 0, 0),//CornerWedge::Right
vec3::int( 0, 1, 1),//CornerWedge::BackTop
vec3::int(-1, 1, 0),//CornerWedge::LeftTop
vec3::int( 0,-1, 0),//CornerWedge::Bottom
vec3::int( 0, 0,-1),//CornerWedge::Front
];
let mut generated_pos=Vec::new(); let mut generated_pos=Vec::new();
let mut generated_tex=Vec::new(); let mut generated_tex=Vec::new();
let mut generated_normal=Vec::new(); let mut generated_normal=Vec::new();
@ -435,7 +414,7 @@ pub fn generate_partial_unit_cornerwedge(face_descriptions:CornerWedgeFaceDescri
let mut physics_group=IndexedPhysicsGroup::default(); let mut physics_group=IndexedPhysicsGroup::default();
let mut transforms=Vec::new(); let mut transforms=Vec::new();
//note that on a cube every vertex is guaranteed to be unique, so there's no need to hash them against existing vertices. //note that on a cube every vertex is guaranteed to be unique, so there's no need to hash them against existing vertices.
for (face_id,face_description) in face_descriptions.pairs(){ for (face_id,face_description) in face_descriptions.into_iter().enumerate(){
//assume that scanning short lists is faster than hashing. //assume that scanning short lists is faster than hashing.
let transform_index=if let Some(transform_index)=transforms.iter().position(|&transform|transform==face_description.transform){ let transform_index=if let Some(transform_index)=transforms.iter().position(|&transform|transform==face_description.transform){
transform_index transform_index
@ -462,8 +441,8 @@ pub fn generate_partial_unit_cornerwedge(face_descriptions:CornerWedgeFaceDescri
//push vertices as they are needed //push vertices as they are needed
let group_id=PolygonGroupId::new(polygon_groups.len() as u32); let group_id=PolygonGroupId::new(polygon_groups.len() as u32);
polygon_groups.push(PolygonGroup::PolygonList(PolygonList::new(vec![ polygon_groups.push(PolygonGroup::PolygonList(PolygonList::new(vec![
CORNERWEDGE_DEFAULT_POLYS[face_id].iter().map(|tup|{ CORNERWEDGE_DEFAULT_POLYS[face_id].iter().map(|&[pos_id,tex_id]|{
let pos=CUBE_DEFAULT_VERTICES[tup[0] as usize]; let pos=CUBE_DEFAULT_VERTICES[pos_id as usize];
let pos_index=if let Some(pos_index)=generated_pos.iter().position(|&p|p==pos){ let pos_index=if let Some(pos_index)=generated_pos.iter().position(|&p|p==pos){
pos_index pos_index
}else{ }else{
@ -475,7 +454,7 @@ pub fn generate_partial_unit_cornerwedge(face_descriptions:CornerWedgeFaceDescri
//always push vertex //always push vertex
let vertex=IndexedVertex{ let vertex=IndexedVertex{
pos:PositionId::new(pos_index), pos:PositionId::new(pos_index),
tex:TextureCoordinateId::new(tup[1]+4*transform_index), tex:TextureCoordinateId::new(tex_id+4*transform_index),
normal:NormalId::new(normal_index), normal:NormalId::new(normal_index),
color:ColorId::new(color_index), color:ColorId::new(color_index),
}; };
@ -501,3 +480,133 @@ pub fn generate_partial_unit_cornerwedge(face_descriptions:CornerWedgeFaceDescri
physics_groups:vec![physics_group], physics_groups:vec![physics_group],
} }
} }
// TODO: fix face texture orientation
pub fn unit_cylinder(face_descriptions:CubeFaceDescription)->Mesh{
// cylinder is oriented about the x axis
// roblox cylinders use projected grid coordinates
/// how many grid coordinates to use (positive and negative)
const GON:i32=3;
/// grid perimeter
const POINTS:[[i32;2];4*2*GON as usize]=const{
let mut points=[[0;2];{4*2*GON as usize}];
let mut i=-GON;
while i<GON{
points[(i+GON) as usize]=[i,GON];
points[(i+GON+1*2*GON) as usize]=[GON,-i];
points[(i+GON+2*2*GON) as usize]=[-i,-GON];
points[(i+GON+3*2*GON) as usize]=[-GON,i];
i+=1;
}
points
};
let mut mb=MeshBuilder::new();
let mut polygon_groups=Vec::with_capacity(CubeFaceDescription::FACES);
let mut graphics_groups=Vec::with_capacity(CubeFaceDescription::FACES);
let mut physics_group=IndexedPhysicsGroup{groups:Vec::with_capacity(CubeFaceDescription::FACES)};
let CubeFaceDescription([right,top,back,left,bottom,front])=face_descriptions;
macro_rules! end_face{
($face_description:expr,$end:expr,$iter:expr)=>{
let normal=mb.acquire_normal_id($end);
let color=mb.acquire_color_id($face_description.color);
// single polygon for physics
let polygon:Vec<_>=$iter.map(|[x,y]|{
let tex=mb.acquire_tex_id(
$face_description.transform.transform_point2(
(glam::vec2(-x as f32,y as f32).normalize()+1.0)/2.0
)
);
let pos=mb.acquire_pos_id($end+vec3::int(0,-x,y).with_length(Planar64::ONE).divide().wrap_1());
mb.acquire_vertex_id(IndexedVertex{pos,tex,normal,color})
}).collect();
// fanned polygons for graphics
let pos=mb.acquire_pos_id($end);
let tex=mb.acquire_tex_id($face_description.transform.transform_point2(glam::Vec2::ONE/2.0));
let center=mb.acquire_vertex_id(IndexedVertex{pos,tex,normal,color});
let polygon_list=(0..POINTS.len()).map(|i|
vec![center,polygon[i],polygon[(i+1)%POINTS.len()]]
).collect();
// end face graphics
let group_id=PolygonGroupId::new(polygon_groups.len() as u32);
polygon_groups.push(PolygonGroup::PolygonList(PolygonList::new(polygon_list)));
graphics_groups.push(IndexedGraphicsGroup{
render:$face_description.render,
groups:vec![group_id],
});
// end face physics
let polygon_list=vec![polygon];
let group_id=PolygonGroupId::new(polygon_groups.len() as u32);
polygon_groups.push(PolygonGroup::PolygonList(PolygonList::new(polygon_list)));
physics_group.groups.push(group_id);
}
}
macro_rules! tex{
($face_description:expr,$tex:expr)=>{{
let [x,y]=$tex;
$face_description.transform.transform_point2(
glam::vec2((x+GON) as f32,(y+GON) as f32)/(2*GON) as f32
)
}};
}
macro_rules! barrel_face{
($face_description:expr,$loop:ident,$lo_dir:expr,$hi_dir:expr,$tex_0:expr,$tex_1:expr,$tex_2:expr,$tex_3:expr)=>{
let mut polygon_list=Vec::with_capacity(CubeFaceDescription::FACES);
for $loop in -GON..GON{
// lo Z
let lz_dir=$lo_dir.with_length(Planar64::ONE).divide().wrap_1();
// hi Z
let hz_dir=$hi_dir.with_length(Planar64::ONE).divide().wrap_1();
// pos
let lx_lz_pos=mb.acquire_pos_id(vec3::NEG_X+lz_dir);
let lx_hz_pos=mb.acquire_pos_id(vec3::NEG_X+hz_dir);
let hx_hz_pos=mb.acquire_pos_id(vec3::X+hz_dir);
let hx_lz_pos=mb.acquire_pos_id(vec3::X+lz_dir);
// tex
let lx_lz_tex=mb.acquire_tex_id(tex!($face_description,$tex_0));
let lx_hz_tex=mb.acquire_tex_id(tex!($face_description,$tex_1));
let hx_hz_tex=mb.acquire_tex_id(tex!($face_description,$tex_2));
let hx_lz_tex=mb.acquire_tex_id(tex!($face_description,$tex_3));
// norm
let lz_norm=mb.acquire_normal_id(lz_dir);
let hz_norm=mb.acquire_normal_id(hz_dir);
// color
let color=mb.acquire_color_id($face_description.color);
polygon_list.push(vec![
mb.acquire_vertex_id(IndexedVertex{pos:lx_lz_pos,tex:lx_lz_tex,normal:lz_norm,color}),
mb.acquire_vertex_id(IndexedVertex{pos:lx_hz_pos,tex:lx_hz_tex,normal:hz_norm,color}),
mb.acquire_vertex_id(IndexedVertex{pos:hx_hz_pos,tex:hx_hz_tex,normal:hz_norm,color}),
mb.acquire_vertex_id(IndexedVertex{pos:hx_lz_pos,tex:hx_lz_tex,normal:lz_norm,color}),
]);
}
// push face
let group_id=PolygonGroupId::new(polygon_groups.len() as u32);
polygon_groups.push(PolygonGroup::PolygonList(PolygonList::new(polygon_list)));
graphics_groups.push(IndexedGraphicsGroup{
render:$face_description.render,
groups:vec![group_id],
});
physics_group.groups.push(group_id);
};
}
end_face!(right, vec3::X,POINTS.into_iter());
barrel_face!(top, z,vec3::int(0,GON,z),vec3::int(0,GON,z+1), [GON,z],[GON,z+1],[-GON,z+1],[-GON,z]);
barrel_face!(back, y,vec3::int(0,y+1,GON),vec3::int(0,y,GON), [GON,y+1],[GON,y],[-GON,y],[-GON,y+1]);
end_face!(left, vec3::NEG_X,POINTS.into_iter().rev());
barrel_face!(bottom, z,vec3::int(0,-GON,z+1),vec3::int(0,-GON,z), [-GON,z+1],[-GON,z],[GON,z],[GON,z+1]);
barrel_face!(front, y,vec3::int(0,y,-GON),vec3::int(0,y+1,-GON), [-GON,y],[-GON,y+1],[GON,y+1],[GON,y]);
let physics_groups=vec![physics_group];
mb.build(polygon_groups,graphics_groups,physics_groups)
}

@ -1,43 +1,37 @@
use std::collections::HashMap; use std::collections::HashMap;
use crate::loader::MeshIndex; use crate::loader::MeshIndex;
use crate::primitives; use crate::primitives::{self,CubeFace,CubeFaceDescription,WedgeFaceDescription,CornerWedgeFaceDescription,FaceDescription,Primitives};
use rbx_dom_weak::ustr;
use roblox_emulator::types::{WeakDom,Instance};
use strafesnet_common::aabb::Aabb; use strafesnet_common::aabb::Aabb;
use strafesnet_common::map; use strafesnet_common::map;
use strafesnet_common::model; use strafesnet_common::model;
use strafesnet_common::gameplay_modes; use strafesnet_common::gameplay_modes::{NormalizedModes,Mode,ModeId,ModeUpdate,ModesBuilder,Stage,StageElement,StageElementBehaviour,StageId,Zone};
use strafesnet_common::gameplay_style; use strafesnet_common::gameplay_style;
use strafesnet_common::gameplay_attributes as attr; use strafesnet_common::gameplay_attributes as attr;
use strafesnet_common::integer::{self,vec3,Planar64,Planar64Vec3,Planar64Mat3,Planar64Affine3}; use strafesnet_common::integer::{self,vec3,Planar64,Planar64Vec3,Planar64Mat3,Planar64Affine3};
use strafesnet_common::model::RenderConfigId; use strafesnet_common::model::RenderConfigId;
use strafesnet_common::updatable::Updatable;
use strafesnet_deferred_loader::deferred_loader::{RenderConfigDeferredLoader,MeshDeferredLoader}; use strafesnet_deferred_loader::deferred_loader::{RenderConfigDeferredLoader,MeshDeferredLoader};
use strafesnet_deferred_loader::mesh::Meshes; use strafesnet_deferred_loader::mesh::Meshes;
use strafesnet_deferred_loader::texture::{RenderConfigs,Texture}; use strafesnet_deferred_loader::texture::{RenderConfigs,Texture};
fn class_is_a(class: &str, superclass: &str) -> bool { fn recursive_collect_superclass(
if class==superclass { objects:&mut std::vec::Vec<rbx_dom_weak::types::Ref>,
return true dom:&WeakDom,
} instance:&Instance,
let class_descriptor=rbx_reflection_database::get().classes.get(class); superclass:&str
if let Some(descriptor) = &class_descriptor { ){
if let Some(class_super) = &descriptor.superclass { let instance=instance.as_ref();
return class_is_a(&class_super, superclass) let db=rbx_reflection_database::get();
} let Some(superclass)=db.classes.get(superclass)else{
} return;
false };
} objects.extend(
fn recursive_collect_superclass(objects: &mut std::vec::Vec<rbx_dom_weak::types::Ref>,dom: &rbx_dom_weak::WeakDom, instance: &rbx_dom_weak::Instance, superclass: &str){ dom.descendants_of(instance.as_ref().referent()).filter_map(|instance|{
let mut stack=vec![instance]; let class=db.classes.get(instance.as_ref().class.as_str())?;
while let Some(item)=stack.pop(){ db.has_superclass(class,superclass).then(||instance.as_ref().referent())
for &referent in item.children(){ })
if let Some(c)=dom.get_by_ref(referent){ );
if class_is_a(c.class.as_str(),superclass){
objects.push(c.referent());//copy ref
}
stack.push(c);
}
}
}
} }
fn planar64_affine3_from_roblox(cf:&rbx_dom_weak::types::CFrame,size:&rbx_dom_weak::types::Vector3)->Planar64Affine3{ fn planar64_affine3_from_roblox(cf:&rbx_dom_weak::types::CFrame,size:&rbx_dom_weak::types::Vector3)->Planar64Affine3{
Planar64Affine3::new( Planar64Affine3::new(
@ -48,97 +42,11 @@ fn planar64_affine3_from_roblox(cf:&rbx_dom_weak::types::CFrame,size:&rbx_dom_we
*integer::try_from_f32(size.y/2.0).unwrap(), *integer::try_from_f32(size.y/2.0).unwrap(),
vec3::try_from_f32_array([cf.orientation.x.z,cf.orientation.y.z,cf.orientation.z.z]).unwrap() vec3::try_from_f32_array([cf.orientation.x.z,cf.orientation.y.z,cf.orientation.z.z]).unwrap()
*integer::try_from_f32(size.z/2.0).unwrap(), *integer::try_from_f32(size.z/2.0).unwrap(),
].map(|t|t.fix_1())), ].map(|t|t.narrow_1().unwrap())),
vec3::try_from_f32_array([cf.position.x,cf.position.y,cf.position.z]).unwrap() vec3::try_from_f32_array([cf.position.x,cf.position.y,cf.position.z]).unwrap()
) )
} }
struct ModeBuilder{
mode:gameplay_modes::Mode,
final_stage_id_from_builder_stage_id:HashMap<gameplay_modes::StageId,gameplay_modes::StageId>,
}
#[derive(Default)]
struct ModesBuilder{
modes:HashMap<gameplay_modes::ModeId,gameplay_modes::Mode>,
stages:HashMap<gameplay_modes::ModeId,HashMap<gameplay_modes::StageId,gameplay_modes::Stage>>,
mode_updates:Vec<(gameplay_modes::ModeId,gameplay_modes::ModeUpdate)>,
stage_updates:Vec<(gameplay_modes::ModeId,gameplay_modes::StageId,gameplay_modes::StageUpdate)>,
}
impl ModesBuilder{
fn build(mut self)->gameplay_modes::Modes{
//collect modes and stages into contiguous arrays
let mut unique_modes:Vec<(gameplay_modes::ModeId,gameplay_modes::Mode)>
=self.modes.into_iter().collect();
unique_modes.sort_by_key(|&(mode_id,_)|mode_id);
let (mut modes,final_mode_id_from_builder_mode_id):(Vec<ModeBuilder>,HashMap<gameplay_modes::ModeId,gameplay_modes::ModeId>)
=unique_modes.into_iter().enumerate()
.map(|(final_mode_id,(builder_mode_id,mut mode))|{
(
ModeBuilder{
final_stage_id_from_builder_stage_id:self.stages.remove(&builder_mode_id).map_or_else(||HashMap::new(),|stages|{
let mut unique_stages:Vec<(gameplay_modes::StageId,gameplay_modes::Stage)>
=stages.into_iter().collect();
unique_stages.sort_by(|a,b|a.0.cmp(&b.0));
unique_stages.into_iter().enumerate()
.map(|(final_stage_id,(builder_stage_id,stage))|{
mode.push_stage(stage);
(builder_stage_id,gameplay_modes::StageId::new(final_stage_id as u32))
}).collect()
}),
mode,
},
(
builder_mode_id,
gameplay_modes::ModeId::new(final_mode_id as u32)
)
)
}).unzip();
//TODO: failure messages or errors or something
//push stage updates
for (builder_mode_id,builder_stage_id,stage_update) in self.stage_updates{
if let Some(final_mode_id)=final_mode_id_from_builder_mode_id.get(&builder_mode_id){
if let Some(mode)=modes.get_mut(final_mode_id.get() as usize){
if let Some(&final_stage_id)=mode.final_stage_id_from_builder_stage_id.get(&builder_stage_id){
if let Some(stage)=mode.mode.get_stage_mut(final_stage_id){
stage.update(stage_update);
}
}
}
}
}
//push mode updates
for (builder_mode_id,mut mode_update) in self.mode_updates{
if let Some(final_mode_id)=final_mode_id_from_builder_mode_id.get(&builder_mode_id){
if let Some(mode)=modes.get_mut(final_mode_id.get() as usize){
//map stage id on stage elements
mode_update.map_stage_element_ids(|stage_id|
//walk down one stage id at a time until a stage is found
//TODO use better logic like BTreeMap::upper_bound instead of walking
// final_stage_id_from_builder_stage_id.upper_bound(Bound::Included(&stage_id))
// .value().copied().unwrap_or(gameplay_modes::StageId::FIRST)
(0..=stage_id.get()).rev().find_map(|builder_stage_id|
//map the stage element to that stage
mode.final_stage_id_from_builder_stage_id.get(&gameplay_modes::StageId::new(builder_stage_id)).copied()
).unwrap_or(gameplay_modes::StageId::FIRST)
);
mode.mode.update(mode_update);
}
}
}
gameplay_modes::Modes::new(modes.into_iter().map(|mode_builder|mode_builder.mode).collect())
}
fn insert_mode(&mut self,mode_id:gameplay_modes::ModeId,mode:gameplay_modes::Mode){
assert!(self.modes.insert(mode_id,mode).is_none(),"Cannot replace existing mode");
}
fn insert_stage(&mut self,mode_id:gameplay_modes::ModeId,stage_id:gameplay_modes::StageId,stage:gameplay_modes::Stage){
assert!(self.stages.entry(mode_id).or_insert(HashMap::new()).insert(stage_id,stage).is_none(),"Cannot replace existing stage");
}
fn push_mode_update(&mut self,mode_id:gameplay_modes::ModeId,mode_update:gameplay_modes::ModeUpdate){
self.mode_updates.push((mode_id,mode_update));
}
// fn push_stage_update(&mut self,mode_id:gameplay_modes::ModeId,stage_id:gameplay_modes::StageId,stage_update:gameplay_modes::StageUpdate){
// self.stage_updates.push((mode_id,stage_id,stage_update));
// }
}
fn get_attributes(name:&str,can_collide:bool,velocity:Planar64Vec3,model_id:model::ModelId,modes_builder:&mut ModesBuilder,wormhole_in_model_to_id:&mut HashMap<model::ModelId,u32>,wormhole_id_to_out_model:&mut HashMap<u32,model::ModelId>)->attr::CollisionAttributes{ fn get_attributes(name:&str,can_collide:bool,velocity:Planar64Vec3,model_id:model::ModelId,modes_builder:&mut ModesBuilder,wormhole_in_model_to_id:&mut HashMap<model::ModelId,u32>,wormhole_id_to_out_model:&mut HashMap<u32,model::ModelId>)->attr::CollisionAttributes{
let mut general=attr::GeneralAttributes::default(); let mut general=attr::GeneralAttributes::default();
let mut intersecting=attr::IntersectingAttributes::default(); let mut intersecting=attr::IntersectingAttributes::default();
@ -167,8 +75,8 @@ fn get_attributes(name:&str,can_collide:bool,velocity:Planar64Vec3,model_id:mode
force_can_collide=false; force_can_collide=false;
force_intersecting=true; force_intersecting=true;
modes_builder.insert_mode( modes_builder.insert_mode(
gameplay_modes::ModeId::MAIN, ModeId::MAIN,
gameplay_modes::Mode::empty( Mode::empty(
gameplay_style::StyleModifiers::roblox_bhop(), gameplay_style::StyleModifiers::roblox_bhop(),
model_id model_id
) )
@ -178,10 +86,10 @@ fn get_attributes(name:&str,can_collide:bool,velocity:Planar64Vec3,model_id:mode
force_can_collide=false; force_can_collide=false;
force_intersecting=true; force_intersecting=true;
modes_builder.push_mode_update( modes_builder.push_mode_update(
gameplay_modes::ModeId::MAIN, ModeId::MAIN,
gameplay_modes::ModeUpdate::zone( ModeUpdate::zone(
model_id, model_id,
gameplay_modes::Zone::Finish, Zone::Finish,
), ),
); );
}, },
@ -189,19 +97,19 @@ fn get_attributes(name:&str,can_collide:bool,velocity:Planar64Vec3,model_id:mode
force_can_collide=false; force_can_collide=false;
force_intersecting=true; force_intersecting=true;
modes_builder.push_mode_update( modes_builder.push_mode_update(
gameplay_modes::ModeId::MAIN, ModeId::MAIN,
gameplay_modes::ModeUpdate::zone( ModeUpdate::zone(
model_id, model_id,
gameplay_modes::Zone::Anticheat, Zone::Anticheat,
), ),
); );
}, },
"Platform"=>{ "Platform"=>{
modes_builder.push_mode_update( modes_builder.push_mode_update(
gameplay_modes::ModeId::MAIN, ModeId::MAIN,
gameplay_modes::ModeUpdate::element( ModeUpdate::element(
model_id, model_id,
gameplay_modes::StageElement::new(gameplay_modes::StageId::FIRST,false,gameplay_modes::StageElementBehaviour::Platform,None),//roblox does not know which stage the platform belongs to StageElement::new(StageId::FIRST,false,StageElementBehaviour::Platform,None),//roblox does not know which stage the platform belongs to
), ),
); );
}, },
@ -213,8 +121,8 @@ fn get_attributes(name:&str,can_collide:bool,velocity:Planar64Vec3,model_id:mode
force_can_collide=false; force_can_collide=false;
force_intersecting=true; force_intersecting=true;
modes_builder.insert_mode( modes_builder.insert_mode(
gameplay_modes::ModeId::new(captures[2].parse::<u32>().unwrap()), ModeId::new(captures[2].parse::<u32>().unwrap()),
gameplay_modes::Mode::empty( Mode::empty(
gameplay_style::StyleModifiers::roblox_bhop(), gameplay_style::StyleModifiers::roblox_bhop(),
model_id model_id
) )
@ -231,8 +139,8 @@ fn get_attributes(name:&str,can_collide:bool,velocity:Planar64Vec3,model_id:mode
}else if let Some(captures)=lazy_regex::regex!(r"^(Force)?(Spawn|SpawnAt|Trigger|Teleport|Platform)(\d+)$") }else if let Some(captures)=lazy_regex::regex!(r"^(Force)?(Spawn|SpawnAt|Trigger|Teleport|Platform)(\d+)$")
.captures(other){ .captures(other){
force_intersecting=true; force_intersecting=true;
let stage_id=gameplay_modes::StageId::new(captures[3].parse::<u32>().unwrap()); let stage_id=StageId::new(captures[3].parse::<u32>().unwrap());
let stage_element=gameplay_modes::StageElement::new( let stage_element=StageElement::new(
//stage_id: //stage_id:
stage_id, stage_id,
//force: //force:
@ -244,26 +152,26 @@ fn get_attributes(name:&str,can_collide:bool,velocity:Planar64Vec3,model_id:mode
match &captures[2]{ match &captures[2]{
"Spawn"=>{ "Spawn"=>{
modes_builder.insert_stage( modes_builder.insert_stage(
gameplay_modes::ModeId::MAIN, ModeId::MAIN,
stage_id, stage_id,
gameplay_modes::Stage::empty(model_id), Stage::empty(model_id),
); );
//TODO: let denormalize handle this //TODO: let denormalize handle this
gameplay_modes::StageElementBehaviour::SpawnAt StageElementBehaviour::SpawnAt
}, },
"SpawnAt"=>gameplay_modes::StageElementBehaviour::SpawnAt, "SpawnAt"=>StageElementBehaviour::SpawnAt,
//cancollide false so you don't hit the side //cancollide false so you don't hit the side
//NOT a decoration //NOT a decoration
"Trigger"=>{force_can_collide=false;gameplay_modes::StageElementBehaviour::Trigger}, "Trigger"=>{force_can_collide=false;StageElementBehaviour::Trigger},
"Teleport"=>{force_can_collide=false;gameplay_modes::StageElementBehaviour::Teleport}, "Teleport"=>{force_can_collide=false;StageElementBehaviour::Teleport},
"Platform"=>gameplay_modes::StageElementBehaviour::Platform, "Platform"=>StageElementBehaviour::Platform,
_=>panic!("regex1[2] messed up bad"), _=>panic!("regex1[2] messed up bad"),
}, },
None None
); );
modes_builder.push_mode_update( modes_builder.push_mode_update(
gameplay_modes::ModeId::MAIN, ModeId::MAIN,
gameplay_modes::ModeUpdate::element( ModeUpdate::element(
model_id, model_id,
stage_element, stage_element,
), ),
@ -272,14 +180,14 @@ fn get_attributes(name:&str,can_collide:bool,velocity:Planar64Vec3,model_id:mode
.captures(other){ .captures(other){
match &captures[1]{ match &captures[1]{
"Jump"=>modes_builder.push_mode_update( "Jump"=>modes_builder.push_mode_update(
gameplay_modes::ModeId::MAIN, ModeId::MAIN,
gameplay_modes::ModeUpdate::element( ModeUpdate::element(
model_id, model_id,
//jump_limit: //jump_limit:
gameplay_modes::StageElement::new( StageElement::new(
gameplay_modes::StageId::FIRST, StageId::FIRST,
false, false,
gameplay_modes::StageElementBehaviour::Check, StageElementBehaviour::Check,
Some(captures[2].parse::<u8>().unwrap()) Some(captures[2].parse::<u8>().unwrap())
) )
), ),
@ -296,13 +204,13 @@ fn get_attributes(name:&str,can_collide:bool,velocity:Planar64Vec3,model_id:mode
force_can_collide=false; force_can_collide=false;
force_intersecting=true; force_intersecting=true;
modes_builder.push_mode_update( modes_builder.push_mode_update(
gameplay_modes::ModeId::new(captures[2].parse::<u32>().unwrap()), ModeId::new(captures[2].parse::<u32>().unwrap()),
gameplay_modes::ModeUpdate::zone( ModeUpdate::zone(
model_id, model_id,
//zone: //zone:
match &captures[1]{ match &captures[1]{
"Finish"=>gameplay_modes::Zone::Finish, "Finish"=>Zone::Finish,
"Anticheat"=>gameplay_modes::Zone::Anticheat, "Anticheat"=>Zone::Anticheat,
_=>panic!("regex3[1] messed up bad"), _=>panic!("regex3[1] messed up bad"),
}, },
), ),
@ -312,9 +220,9 @@ fn get_attributes(name:&str,can_collide:bool,velocity:Planar64Vec3,model_id:mode
// .captures(other){ // .captures(other){
// match &captures[1]{ // match &captures[1]{
// "OrderedCheckpoint"=>modes_builder.push_stage_update( // "OrderedCheckpoint"=>modes_builder.push_stage_update(
// gameplay_modes::ModeId::MAIN, // ModeId::MAIN,
// gameplay_modes::StageId::new(0), // StageId::new(0),
// gameplay_modes::StageUpdate::ordered_checkpoint(captures[2].parse::<u32>().unwrap()), // StageUpdate::ordered_checkpoint(captures[2].parse::<u32>().unwrap()),
// ), // ),
// _=>panic!("regex3[1] messed up bad"), // _=>panic!("regex3[1] messed up bad"),
// } // }
@ -434,17 +342,40 @@ impl RobloxFaceTextureDescription{
transform:self.transform.to_bits(), transform:self.transform.to_bits(),
} }
} }
pub fn to_face_description(&self)->primitives::FaceDescription{ pub fn to_face_description(&self)->FaceDescription{
primitives::FaceDescription{ FaceDescription{
render:self.render, render:self.render,
transform:self.transform.affine(), transform:self.transform.affine(),
color:self.color, color:self.color,
} }
} }
} }
pub type RobloxPartDescription=[Option<RobloxFaceTextureDescription>;6]; macro_rules! impl_description_index{
type RobloxWedgeDescription=[Option<RobloxFaceTextureDescription>;5]; ($description:ident,$index:ty)=>{
type RobloxCornerWedgeDescription=[Option<RobloxFaceTextureDescription>;5]; impl core::ops::Index<$index> for $description{
type Output=Option<RobloxFaceTextureDescription>;
fn index(&self,index:$index)->&Self::Output{
&self.0[index as usize]
}
}
impl core::ops::IndexMut<$index> for $description{
fn index_mut(&mut self,index:$index)->&mut Self::Output{
&mut self.0[index as usize]
}
}
};
}
#[derive(Clone,Default,Eq,Hash,PartialEq)]
pub struct RobloxPartDescription(pub(crate)[Option<RobloxFaceTextureDescription>;6]);
impl_description_index!(RobloxPartDescription,CubeFace);
#[derive(Clone,Default,Eq,Hash,PartialEq)]
pub struct RobloxWedgeDescription(pub(crate)[Option<RobloxFaceTextureDescription>;5]);
#[derive(Clone,Default,Eq,Hash,PartialEq)]
pub struct RobloxCornerWedgeDescription(pub(crate)[Option<RobloxFaceTextureDescription>;5]);
#[derive(Clone,Eq,Hash,PartialEq)] #[derive(Clone,Eq,Hash,PartialEq)]
enum RobloxBasePartDescription{ enum RobloxBasePartDescription{
Sphere(RobloxPartDescription), Sphere(RobloxPartDescription),
@ -453,88 +384,104 @@ enum RobloxBasePartDescription{
Wedge(RobloxWedgeDescription), Wedge(RobloxWedgeDescription),
CornerWedge(RobloxCornerWedgeDescription), CornerWedge(RobloxCornerWedgeDescription),
} }
// TODO: initialize all ustrs in a top level struct thrown around as a reference
// struct UstrKludge{
// }
fn get_content_url(content:&rbx_dom_weak::types::Content)->Option<&str>{
match content.value(){
rbx_dom_weak::types::ContentType::Uri(uri)=>Some(uri.as_str()),
_=>None,
}
}
fn get_texture_description<'a>( fn get_texture_description<'a>(
temp_objects:&mut Vec<rbx_dom_weak::types::Ref>, temp_objects:&mut Vec<rbx_dom_weak::types::Ref>,
render_config_deferred_loader:&mut RenderConfigDeferredLoader<&'a str>, render_config_deferred_loader:&mut RenderConfigDeferredLoader<&'a str>,
dom:&'a rbx_dom_weak::WeakDom, dom:&'a WeakDom,
object:&rbx_dom_weak::Instance, object:&Instance,
size:&rbx_dom_weak::types::Vector3, size:&rbx_dom_weak::types::Vector3,
)->RobloxPartDescription{ )->RobloxPartDescription{
//use the biggest one and cut it down later... //use the biggest one and cut it down later...
let mut part_texture_description:RobloxPartDescription=[None,None,None,None,None,None]; let mut part_texture_description=RobloxPartDescription::default();
temp_objects.clear(); temp_objects.clear();
recursive_collect_superclass(temp_objects,&dom,object,"Decal"); recursive_collect_superclass(temp_objects,dom,object,"Decal");
for &mut decal_ref in temp_objects{ for &mut decal_ref in temp_objects{
if let Some(decal)=dom.get_by_ref(decal_ref){ let Some(decal)=dom.get_by_ref(decal_ref) else{
if let ( println!("Decal get_by_ref failed");
Some(rbx_dom_weak::types::Variant::Content(content)), continue;
};
let decal=decal.as_ref();
let (
Some(rbx_dom_weak::types::Variant::ContentId(content)),
Some(rbx_dom_weak::types::Variant::Enum(normalid)), Some(rbx_dom_weak::types::Variant::Enum(normalid)),
Some(rbx_dom_weak::types::Variant::Color3(decal_color3)), Some(rbx_dom_weak::types::Variant::Color3(decal_color3)),
Some(rbx_dom_weak::types::Variant::Float32(decal_transparency)), Some(rbx_dom_weak::types::Variant::Float32(decal_transparency)),
) = ( )=(
decal.properties.get("Texture"), decal.properties.get(&ustr("Texture")),
decal.properties.get("Face"), decal.properties.get(&ustr("Face")),
decal.properties.get("Color3"), decal.properties.get(&ustr("Color3")),
decal.properties.get("Transparency"), decal.properties.get(&ustr("Transparency")),
) { )else{
let render_id=render_config_deferred_loader.acquire_render_config_id(Some(content.as_ref())); println!("Decal is missing a required property");
let normal_id=normalid.to_u32(); continue;
if normal_id<6{ };
let (roblox_texture_color,roblox_texture_transform)=if decal.class=="Texture"{ let texture_id=Some(content.as_str());
//generate tranform let render_id=render_config_deferred_loader.acquire_render_config_id(texture_id);
if let ( let Ok(cube_face)=normalid.to_u32().try_into()else{
Some(&rbx_dom_weak::types::Variant::Float32(offset_studs_u)), println!("NormalId is invalid");
Some(&rbx_dom_weak::types::Variant::Float32(offset_studs_v)), continue;
Some(&rbx_dom_weak::types::Variant::Float32(studs_per_tile_u)), };
Some(&rbx_dom_weak::types::Variant::Float32(studs_per_tile_v)), let (roblox_texture_color,roblox_texture_transform)=if decal.class=="Texture"{
) = ( //generate tranform
decal.properties.get("OffsetStudsU"), if let (
decal.properties.get("OffsetStudsV"), Some(&rbx_dom_weak::types::Variant::Float32(offset_studs_u)),
decal.properties.get("StudsPerTileU"), Some(&rbx_dom_weak::types::Variant::Float32(offset_studs_v)),
decal.properties.get("StudsPerTileV"), Some(&rbx_dom_weak::types::Variant::Float32(studs_per_tile_u)),
) Some(&rbx_dom_weak::types::Variant::Float32(studs_per_tile_v)),
{ ) = (
let (size_u,size_v)=match normal_id{ decal.properties.get(&ustr("OffsetStudsU")),
0=>(size.z,size.y),//right decal.properties.get(&ustr("OffsetStudsV")),
1=>(size.x,size.z),//top decal.properties.get(&ustr("StudsPerTileU")),
2=>(size.x,size.y),//back decal.properties.get(&ustr("StudsPerTileV")),
3=>(size.z,size.y),//left )
4=>(size.x,size.z),//bottom {
5=>(size.x,size.y),//front let (size_u,size_v)=match cube_face{
_=>unreachable!(), CubeFace::Right=>(size.z,size.y),//right
}; CubeFace::Top=>(size.x,size.z),//top
( CubeFace::Back=>(size.x,size.y),//back
glam::vec4(decal_color3.r,decal_color3.g,decal_color3.b,1.0-*decal_transparency), CubeFace::Left=>(size.z,size.y),//left
RobloxTextureTransform{ CubeFace::Bottom=>(size.x,size.z),//bottom
offset_studs_u, CubeFace::Front=>(size.x,size.y),//front
offset_studs_v, };
studs_per_tile_u, (
studs_per_tile_v, glam::vec4(decal_color3.r,decal_color3.g,decal_color3.b,1.0-*decal_transparency),
size_u, RobloxTextureTransform{
size_v, offset_studs_u,
} offset_studs_v,
) studs_per_tile_u,
}else{ studs_per_tile_v,
(glam::Vec4::ONE,RobloxTextureTransform::identity()) size_u,
} size_v,
}else{ }
(glam::Vec4::ONE,RobloxTextureTransform::identity()) )
}; }else{
part_texture_description[normal_id as usize]=Some(RobloxFaceTextureDescription{ (glam::Vec4::ONE,RobloxTextureTransform::identity())
render:render_id,
color:roblox_texture_color,
transform:roblox_texture_transform,
});
}else{
println!("NormalId={} is invalid",normal_id);
}
} }
} }else{
(glam::Vec4::ONE,RobloxTextureTransform::identity())
};
part_texture_description[cube_face]=Some(RobloxFaceTextureDescription{
render:render_id,
color:roblox_texture_color,
transform:roblox_texture_transform,
});
} }
part_texture_description part_texture_description
} }
enum Shape{ enum Shape{
Primitive(primitives::Primitives), Primitive(Primitives),
MeshPart, MeshPart,
PhysicsData, PhysicsData,
} }
@ -569,7 +516,7 @@ struct GetAttributesArgs<'a>{
velocity:Planar64Vec3, velocity:Planar64Vec3,
} }
pub fn convert<'a>( pub fn convert<'a>(
dom:&'a rbx_dom_weak::WeakDom, dom:&'a WeakDom,
render_config_deferred_loader:&mut RenderConfigDeferredLoader<&'a str>, render_config_deferred_loader:&mut RenderConfigDeferredLoader<&'a str>,
mesh_deferred_loader:&mut MeshDeferredLoader<MeshIndex<'a>>, mesh_deferred_loader:&mut MeshDeferredLoader<MeshIndex<'a>>,
)->PartialMap1<'a>{ )->PartialMap1<'a>{
@ -584,9 +531,10 @@ pub fn convert<'a>(
let mut object_refs=Vec::new(); let mut object_refs=Vec::new();
let mut temp_objects=Vec::new(); let mut temp_objects=Vec::new();
recursive_collect_superclass(&mut object_refs, &dom, dom.root(),"BasePart"); recursive_collect_superclass(&mut object_refs,dom,dom.root(),"BasePart");
for object_ref in object_refs { for object_ref in object_refs{
if let Some(object)=dom.get_by_ref(object_ref){ if let Some(object_extended)=dom.get_by_ref(object_ref){
let object=object_extended.as_ref();
if let ( if let (
Some(rbx_dom_weak::types::Variant::CFrame(cf)), Some(rbx_dom_weak::types::Variant::CFrame(cf)),
Some(rbx_dom_weak::types::Variant::Vector3(size)), Some(rbx_dom_weak::types::Variant::Vector3(size)),
@ -595,12 +543,12 @@ pub fn convert<'a>(
Some(rbx_dom_weak::types::Variant::Color3uint8(color3)), Some(rbx_dom_weak::types::Variant::Color3uint8(color3)),
Some(rbx_dom_weak::types::Variant::Bool(can_collide)), Some(rbx_dom_weak::types::Variant::Bool(can_collide)),
) = ( ) = (
object.properties.get("CFrame"), object.properties.get(&ustr("CFrame")),
object.properties.get("Size"), object.properties.get(&ustr("Size")),
object.properties.get("Velocity"), object.properties.get(&ustr("Velocity")),
object.properties.get("Transparency"), object.properties.get(&ustr("Transparency")),
object.properties.get("Color"), object.properties.get(&ustr("Color")),
object.properties.get("CanCollide"), object.properties.get(&ustr("CanCollide")),
) )
{ {
let model_transform=planar64_affine3_from_roblox(cf,size); let model_transform=planar64_affine3_from_roblox(cf,size);
@ -609,6 +557,7 @@ pub fn convert<'a>(
let mut parent_ref=object.parent(); let mut parent_ref=object.parent();
let mut full_path=object.name.clone(); let mut full_path=object.name.clone();
while let Some(parent)=dom.get_by_ref(parent_ref){ while let Some(parent)=dom.get_by_ref(parent_ref){
let parent=parent.as_ref();
full_path=format!("{}.{}",parent.name,full_path); full_path=format!("{}.{}",parent.name,full_path);
parent_ref=parent.parent(); parent_ref=parent.parent();
} }
@ -619,62 +568,55 @@ pub fn convert<'a>(
//TODO: also detect "CylinderMesh" etc here //TODO: also detect "CylinderMesh" etc here
let shape=match object.class.as_str(){ let shape=match object.class.as_str(){
"Part"=>if let Some(rbx_dom_weak::types::Variant::Enum(shape))=object.properties.get("Shape"){ "Part"=>if let Some(rbx_dom_weak::types::Variant::Enum(shape))=object.properties.get(&ustr("Shape")){
Shape::Primitive(match shape.to_u32(){ Shape::Primitive(shape.to_u32().try_into().expect("Funky roblox PartType"))
0=>primitives::Primitives::Sphere,
1=>primitives::Primitives::Cube,
2=>primitives::Primitives::Cylinder,
3=>primitives::Primitives::Wedge,
4=>primitives::Primitives::CornerWedge,
other=>panic!("Funky roblox PartType={};",other),
})
}else{ }else{
panic!("Part has no Shape!"); panic!("Part has no Shape!");
}, },
"TrussPart"=>Shape::Primitive(primitives::Primitives::Cube), "TrussPart"=>Shape::Primitive(Primitives::Cube),
"WedgePart"=>Shape::Primitive(primitives::Primitives::Wedge), "WedgePart"=>Shape::Primitive(Primitives::Wedge),
"CornerWedgePart"=>Shape::Primitive(primitives::Primitives::CornerWedge), "CornerWedgePart"=>Shape::Primitive(Primitives::CornerWedge),
"MeshPart"=>Shape::MeshPart, "MeshPart"=>Shape::MeshPart,
"UnionOperation"=>Shape::PhysicsData, "UnionOperation"=>Shape::PhysicsData,
_=>{ _=>{
println!("Unsupported BasePart ClassName={}; defaulting to cube",object.class); println!("Unsupported BasePart ClassName={}; defaulting to cube",object.class);
Shape::Primitive(primitives::Primitives::Cube) Shape::Primitive(Primitives::Cube)
} }
}; };
let (availability,mesh_id)=match shape{ let (availability,mesh_id)=match shape{
Shape::Primitive(primitive_shape)=>{ Shape::Primitive(primitive_shape)=>{
//TODO: TAB TAB //TODO: TAB TAB
let part_texture_description=get_texture_description(&mut temp_objects,render_config_deferred_loader,dom,object,size); let part_texture_description=get_texture_description(&mut temp_objects,render_config_deferred_loader,dom,object_extended,size);
//obscure rust syntax "slice pattern" //obscure rust syntax "slice pattern"
let [ let RobloxPartDescription([
f0,//Cube::Right f0,//Cube::Right
f1,//Cube::Top f1,//Cube::Top
f2,//Cube::Back f2,//Cube::Back
f3,//Cube::Left f3,//Cube::Left
f4,//Cube::Bottom f4,//Cube::Bottom
f5,//Cube::Front f5,//Cube::Front
]=part_texture_description; ])=part_texture_description;
let basepart_description=match primitive_shape{ let basepart_description=match primitive_shape{
primitives::Primitives::Sphere=>RobloxBasePartDescription::Sphere([f0,f1,f2,f3,f4,f5]), Primitives::Sphere=>RobloxBasePartDescription::Sphere(RobloxPartDescription([f0,f1,f2,f3,f4,f5])),
primitives::Primitives::Cube=>RobloxBasePartDescription::Part([f0,f1,f2,f3,f4,f5]), Primitives::Cube=>RobloxBasePartDescription::Part(RobloxPartDescription([f0,f1,f2,f3,f4,f5])),
primitives::Primitives::Cylinder=>RobloxBasePartDescription::Cylinder([f0,f1,f2,f3,f4,f5]), Primitives::Cylinder=>RobloxBasePartDescription::Cylinder(RobloxPartDescription([f0,f1,f2,f3,f4,f5])),
//use front face texture first and use top face texture as a fallback //use front face texture first and use top face texture as a fallback
primitives::Primitives::Wedge=>RobloxBasePartDescription::Wedge([ Primitives::Wedge=>RobloxBasePartDescription::Wedge(RobloxWedgeDescription([
f0,//Cube::Right->Wedge::Right f0,//Cube::Right->Wedge::Right
f5.or(f1),//Cube::Front|Cube::Top->Wedge::TopFront f5.or(f1),//Cube::Front|Cube::Top->Wedge::TopFront
f2,//Cube::Back->Wedge::Back f2,//Cube::Back->Wedge::Back
f3,//Cube::Left->Wedge::Left f3,//Cube::Left->Wedge::Left
f4,//Cube::Bottom->Wedge::Bottom f4,//Cube::Bottom->Wedge::Bottom
]), ])),
//TODO: fix Left+Back texture coordinates to match roblox when not overwridden by Top //TODO: fix Left+Back texture coordinates to match roblox when not overwridden by Top
primitives::Primitives::CornerWedge=>RobloxBasePartDescription::CornerWedge([ Primitives::CornerWedge=>RobloxBasePartDescription::CornerWedge(RobloxCornerWedgeDescription([
f0,//Cube::Right->CornerWedge::Right f0,//Cube::Right->CornerWedge::Right
f2.or(f1.clone()),//Cube::Back|Cube::Top->CornerWedge::TopBack f2.or(f1.clone()),//Cube::Back|Cube::Top->CornerWedge::TopBack
f3.or(f1),//Cube::Left|Cube::Top->CornerWedge::TopLeft f3.or(f1),//Cube::Left|Cube::Top->CornerWedge::TopLeft
f4,//Cube::Bottom->CornerWedge::Bottom f4,//Cube::Bottom->CornerWedge::Bottom
f5,//Cube::Front->CornerWedge::Front f5,//Cube::Front->CornerWedge::Front
]), ])),
}; };
//make new model if unit cube has not been created before //make new model if unit cube has not been created before
let mesh_id=if let Some(&mesh_id)=mesh_id_from_description.get(&basepart_description){ let mesh_id=if let Some(&mesh_id)=mesh_id_from_description.get(&basepart_description){
@ -684,66 +626,11 @@ pub fn convert<'a>(
let mesh_id=model::MeshId::new(primitive_meshes.len() as u32); let mesh_id=model::MeshId::new(primitive_meshes.len() as u32);
mesh_id_from_description.insert(basepart_description.clone(),mesh_id);//borrow checker going crazy mesh_id_from_description.insert(basepart_description.clone(),mesh_id);//borrow checker going crazy
let mesh=match basepart_description{ let mesh=match basepart_description{
RobloxBasePartDescription::Cylinder(part_texture_description)=>primitives::unit_cylinder(CubeFaceDescription::new(part_texture_description,textureless_render_group)),
RobloxBasePartDescription::Sphere(part_texture_description) RobloxBasePartDescription::Sphere(part_texture_description)
|RobloxBasePartDescription::Cylinder(part_texture_description) |RobloxBasePartDescription::Part(part_texture_description)=>primitives::unit_cube(CubeFaceDescription::new(part_texture_description,textureless_render_group)),
|RobloxBasePartDescription::Part(part_texture_description)=>{ RobloxBasePartDescription::Wedge(wedge_texture_description)=>primitives::unit_wedge(WedgeFaceDescription::new(wedge_texture_description,textureless_render_group)),
let mut cube_face_description=primitives::CubeFaceDescription::default(); RobloxBasePartDescription::CornerWedge(cornerwedge_texture_description)=>primitives::unit_cornerwedge(CornerWedgeFaceDescription::new(cornerwedge_texture_description,textureless_render_group)),
for (face_id,roblox_face_description) in part_texture_description.iter().enumerate(){
cube_face_description.insert(
match face_id{
0=>primitives::CubeFace::Right,
1=>primitives::CubeFace::Top,
2=>primitives::CubeFace::Back,
3=>primitives::CubeFace::Left,
4=>primitives::CubeFace::Bottom,
5=>primitives::CubeFace::Front,
_=>unreachable!(),
},
match roblox_face_description{
Some(roblox_texture_transform)=>roblox_texture_transform.to_face_description(),
None=>primitives::FaceDescription::new_with_render_id(textureless_render_group),
});
}
primitives::generate_partial_unit_cube(cube_face_description)
},
RobloxBasePartDescription::Wedge(wedge_texture_description)=>{
let mut wedge_face_description=primitives::WedgeFaceDescription::default();
for (face_id,roblox_face_description) in wedge_texture_description.iter().enumerate(){
wedge_face_description.insert(
match face_id{
0=>primitives::WedgeFace::Right,
1=>primitives::WedgeFace::TopFront,
2=>primitives::WedgeFace::Back,
3=>primitives::WedgeFace::Left,
4=>primitives::WedgeFace::Bottom,
_=>unreachable!(),
},
match roblox_face_description{
Some(roblox_texture_transform)=>roblox_texture_transform.to_face_description(),
None=>primitives::FaceDescription::new_with_render_id(textureless_render_group),
});
}
primitives::generate_partial_unit_wedge(wedge_face_description)
},
RobloxBasePartDescription::CornerWedge(cornerwedge_texture_description)=>{
let mut cornerwedge_face_description=primitives::CornerWedgeFaceDescription::default();
for (face_id,roblox_face_description) in cornerwedge_texture_description.iter().enumerate(){
cornerwedge_face_description.insert(
match face_id{
0=>primitives::CornerWedgeFace::Right,
1=>primitives::CornerWedgeFace::TopBack,
2=>primitives::CornerWedgeFace::TopLeft,
3=>primitives::CornerWedgeFace::Bottom,
4=>primitives::CornerWedgeFace::Front,
_=>unreachable!(),
},
match roblox_face_description{
Some(roblox_texture_transform)=>roblox_texture_transform.to_face_description(),
None=>primitives::FaceDescription::new_with_render_id(textureless_render_group),
});
}
primitives::generate_partial_unit_cornerwedge(cornerwedge_face_description)
},
}; };
primitive_meshes.push(mesh); primitive_meshes.push(mesh);
mesh_id mesh_id
@ -751,15 +638,19 @@ pub fn convert<'a>(
(MeshAvailability::Immediate,mesh_id) (MeshAvailability::Immediate,mesh_id)
}, },
Shape::MeshPart=>if let ( Shape::MeshPart=>if let (
Some(rbx_dom_weak::types::Variant::Content(mesh_asset_id)), Some(rbx_dom_weak::types::Variant::Content(mesh_content)),
Some(rbx_dom_weak::types::Variant::Content(texture_asset_id)), Some(rbx_dom_weak::types::Variant::Content(texture_content)),
)=( )=(
object.properties.get("MeshId"), // mesh must exist
object.properties.get("TextureID"), object.properties.get(&ustr("MeshContent")),
// texture is allowed to be none
object.properties.get(&ustr("TextureContent")),
){ ){
let mesh_asset_id=get_content_url(mesh_content).expect("MeshPart Mesh is not a Uri");
let texture_asset_id=get_content_url(texture_content);
( (
MeshAvailability::DeferredMesh(render_config_deferred_loader.acquire_render_config_id(Some(texture_asset_id.as_ref()))), MeshAvailability::DeferredMesh(render_config_deferred_loader.acquire_render_config_id(texture_asset_id)),
mesh_deferred_loader.acquire_mesh_id(MeshIndex::file_mesh(mesh_asset_id.as_ref())), mesh_deferred_loader.acquire_mesh_id(MeshIndex::file_mesh(mesh_asset_id)),
) )
}else{ }else{
panic!("Mesh has no Mesh or Texture"); panic!("Mesh has no Mesh or Texture");
@ -768,16 +659,16 @@ pub fn convert<'a>(
let mut content=""; let mut content="";
let mut mesh_data:&[u8]=&[]; let mut mesh_data:&[u8]=&[];
let mut physics_data:&[u8]=&[]; let mut physics_data:&[u8]=&[];
if let Some(rbx_dom_weak::types::Variant::Content(asset_id))=object.properties.get("AssetId"){ if let Some(rbx_dom_weak::types::Variant::ContentId(asset_id))=object.properties.get(&ustr("AssetId")){
content=asset_id.as_ref(); content=asset_id.as_ref();
} }
if let Some(rbx_dom_weak::types::Variant::BinaryString(data))=object.properties.get("MeshData"){ if let Some(rbx_dom_weak::types::Variant::BinaryString(data))=object.properties.get(&ustr("MeshData")){
mesh_data=data.as_ref(); mesh_data=data.as_ref();
} }
if let Some(rbx_dom_weak::types::Variant::BinaryString(data))=object.properties.get("PhysicsData"){ if let Some(rbx_dom_weak::types::Variant::BinaryString(data))=object.properties.get(&ustr("PhysicsData")){
physics_data=data.as_ref(); physics_data=data.as_ref();
} }
let part_texture_description=get_texture_description(&mut temp_objects,render_config_deferred_loader,dom,object,size); let part_texture_description=get_texture_description(&mut temp_objects,render_config_deferred_loader,dom,object_extended,size);
let mesh_index=MeshIndex::union(content,mesh_data,physics_data,size,part_texture_description.clone()); let mesh_index=MeshIndex::union(content,mesh_data,physics_data,size,part_texture_description.clone());
let mesh_id=mesh_deferred_loader.acquire_mesh_id(mesh_index); let mesh_id=mesh_deferred_loader.acquire_mesh_id(mesh_index);
(MeshAvailability::DeferredUnion(part_texture_description),mesh_id) (MeshAvailability::DeferredUnion(part_texture_description),mesh_id)
@ -855,7 +746,7 @@ fn acquire_union_id_from_render_config_id<'a>(
let union_id=model::MeshId::new(primitive_meshes.len() as u32); let union_id=model::MeshId::new(primitive_meshes.len() as u32);
let mut union_clone=union_with_aabb.mesh.clone(); let mut union_clone=union_with_aabb.mesh.clone();
//set the render groups //set the render groups
for (graphics_group,maybe_face_texture_description) in union_clone.graphics_groups.iter_mut().zip(part_texture_description){ for (graphics_group,maybe_face_texture_description) in union_clone.graphics_groups.iter_mut().zip(part_texture_description.0){
if let Some(face_texture_description)=maybe_face_texture_description{ if let Some(face_texture_description)=maybe_face_texture_description{
graphics_group.render=face_texture_description.render; graphics_group.render=face_texture_description.render;
} }
@ -924,9 +815,9 @@ impl PartialMap1<'_>{
color:deferred_model_deferred_attributes.model.color, color:deferred_model_deferred_attributes.model.color,
transform:Planar64Affine3::new( transform:Planar64Affine3::new(
Planar64Mat3::from_cols([ Planar64Mat3::from_cols([
(deferred_model_deferred_attributes.model.transform.matrix3.x_axis*2/size.x).divide().fix_1(), (deferred_model_deferred_attributes.model.transform.matrix3.x_axis*2/size.x).divide().narrow_1().unwrap(),
(deferred_model_deferred_attributes.model.transform.matrix3.y_axis*2/size.y).divide().fix_1(), (deferred_model_deferred_attributes.model.transform.matrix3.y_axis*2/size.y).divide().narrow_1().unwrap(),
(deferred_model_deferred_attributes.model.transform.matrix3.z_axis*2/size.z).divide().fix_1() (deferred_model_deferred_attributes.model.transform.matrix3.z_axis*2/size.z).divide().narrow_1().unwrap(),
]), ]),
deferred_model_deferred_attributes.model.transform.translation deferred_model_deferred_attributes.model.transform.translation
), ),
@ -948,9 +839,9 @@ impl PartialMap1<'_>{
color:deferred_union_deferred_attributes.model.color, color:deferred_union_deferred_attributes.model.color,
transform:Planar64Affine3::new( transform:Planar64Affine3::new(
Planar64Mat3::from_cols([ Planar64Mat3::from_cols([
(deferred_union_deferred_attributes.model.transform.matrix3.x_axis*2/size.x).divide().fix_1(), (deferred_union_deferred_attributes.model.transform.matrix3.x_axis*2/size.x).divide().narrow_1().unwrap(),
(deferred_union_deferred_attributes.model.transform.matrix3.y_axis*2/size.y).divide().fix_1(), (deferred_union_deferred_attributes.model.transform.matrix3.y_axis*2/size.y).divide().narrow_1().unwrap(),
(deferred_union_deferred_attributes.model.transform.matrix3.z_axis*2/size.z).divide().fix_1() (deferred_union_deferred_attributes.model.transform.matrix3.z_axis*2/size.z).divide().narrow_1().unwrap(),
]), ]),
deferred_union_deferred_attributes.model.transform.translation deferred_union_deferred_attributes.model.transform.translation
), ),
@ -1009,7 +900,7 @@ impl PartialMap1<'_>{
PartialMap2{ PartialMap2{
meshes:self.primitive_meshes, meshes:self.primitive_meshes,
models, models,
modes:modes_builder.build(), modes:modes_builder.build_normalized(),
attributes:unique_attributes, attributes:unique_attributes,
} }
} }
@ -1018,7 +909,7 @@ impl PartialMap1<'_>{
pub struct PartialMap2{ pub struct PartialMap2{
meshes:Vec<model::Mesh>, meshes:Vec<model::Mesh>,
models:Vec<model::Model>, models:Vec<model::Model>,
modes:gameplay_modes::Modes, modes:NormalizedModes,
attributes:Vec<strafesnet_common::gameplay_attributes::CollisionAttributes>, attributes:Vec<strafesnet_common::gameplay_attributes::CollisionAttributes>,
} }
impl PartialMap2{ impl PartialMap2{

@ -1,4 +1,5 @@
use rbx_mesh::mesh_data::NormalId2 as MeshDataNormalId2; use rbx_mesh::mesh_data::{NormalId2 as MeshDataNormalId2,VertexId as MeshDataVertexId};
use rbx_mesh::physics_data::VertexId as PhysicsDataVertexId;
use strafesnet_common::model::{self,IndexedVertex,PolygonGroup,PolygonGroupId,PolygonList,RenderConfigId}; use strafesnet_common::model::{self,IndexedVertex,PolygonGroup,PolygonGroupId,PolygonList,RenderConfigId};
use strafesnet_common::integer::vec3; use strafesnet_common::integer::vec3;
@ -56,7 +57,7 @@ pub fn convert(
roblox_physics_data:&[u8], roblox_physics_data:&[u8],
roblox_mesh_data:&[u8], roblox_mesh_data:&[u8],
size:glam::Vec3, size:glam::Vec3,
part_texture_description:crate::rbx::RobloxPartDescription, crate::rbx::RobloxPartDescription(part_texture_description):crate::rbx::RobloxPartDescription,
)->Result<model::Mesh,Error>{ )->Result<model::Mesh,Error>{
const NORMAL_FACES:usize=6; const NORMAL_FACES:usize=6;
let mut polygon_groups_normal_id=vec![Vec::new();NORMAL_FACES]; let mut polygon_groups_normal_id=vec![Vec::new();NORMAL_FACES];
@ -79,11 +80,11 @@ pub fn convert(
rbx_mesh::mesh_data::MeshData::CSGMDL(rbx_mesh::mesh_data::CSGMDL::CSGMDL2(mesh_data2))=>mesh_data2.mesh, rbx_mesh::mesh_data::MeshData::CSGMDL(rbx_mesh::mesh_data::CSGMDL::CSGMDL2(mesh_data2))=>mesh_data2.mesh,
rbx_mesh::mesh_data::MeshData::CSGMDL(rbx_mesh::mesh_data::CSGMDL::CSGMDL4(mesh_data4))=>mesh_data4.mesh, rbx_mesh::mesh_data::MeshData::CSGMDL(rbx_mesh::mesh_data::CSGMDL::CSGMDL4(mesh_data4))=>mesh_data4.mesh,
}; };
for [vertex_id0,vertex_id1,vertex_id2] in graphics_mesh.faces{ for [MeshDataVertexId(vertex_id0),MeshDataVertexId(vertex_id1),MeshDataVertexId(vertex_id2)] in graphics_mesh.faces{
let face=[ let face=[
graphics_mesh.vertices.get(vertex_id0.0 as usize).ok_or(Error::MissingVertexId(vertex_id0.0))?, graphics_mesh.vertices.get(vertex_id0 as usize).ok_or(Error::MissingVertexId(vertex_id0))?,
graphics_mesh.vertices.get(vertex_id1.0 as usize).ok_or(Error::MissingVertexId(vertex_id1.0))?, graphics_mesh.vertices.get(vertex_id1 as usize).ok_or(Error::MissingVertexId(vertex_id1))?,
graphics_mesh.vertices.get(vertex_id2.0 as usize).ok_or(Error::MissingVertexId(vertex_id2.0))?, graphics_mesh.vertices.get(vertex_id2 as usize).ok_or(Error::MissingVertexId(vertex_id2))?,
]; ];
let mut normal_agreement_checker=MeshDataNormalChecker::new(); let mut normal_agreement_checker=MeshDataNormalChecker::new();
let face=face.into_iter().map(|vertex|{ let face=face.into_iter().map(|vertex|{
@ -151,11 +152,11 @@ pub fn convert(
let color=mb.acquire_color_id(glam::Vec4::ONE); let color=mb.acquire_color_id(glam::Vec4::ONE);
let tex=mb.acquire_tex_id(glam::Vec2::ZERO); let tex=mb.acquire_tex_id(glam::Vec2::ZERO);
// physics polygon groups (to do physics) // physics polygon groups (to do physics)
Ok(PolygonGroup::PolygonList(PolygonList::new(mesh.faces.into_iter().map(|[vertex_id0,vertex_id1,vertex_id2]|{ Ok(PolygonGroup::PolygonList(PolygonList::new(mesh.faces.into_iter().map(|[PhysicsDataVertexId(vertex_id0),PhysicsDataVertexId(vertex_id1),PhysicsDataVertexId(vertex_id2)]|{
let face=[ let face=[
mesh.vertices.get(vertex_id0.0 as usize).ok_or(Error::MissingVertexId(vertex_id0.0))?, mesh.vertices.get(vertex_id0 as usize).ok_or(Error::MissingVertexId(vertex_id0))?,
mesh.vertices.get(vertex_id1.0 as usize).ok_or(Error::MissingVertexId(vertex_id1.0))?, mesh.vertices.get(vertex_id1 as usize).ok_or(Error::MissingVertexId(vertex_id1))?,
mesh.vertices.get(vertex_id2.0 as usize).ok_or(Error::MissingVertexId(vertex_id2.0))?, mesh.vertices.get(vertex_id2 as usize).ok_or(Error::MissingVertexId(vertex_id2))?,
].map(|v|glam::Vec3::from_slice(v)/size); ].map(|v|glam::Vec3::from_slice(v)/size);
let vertex_norm=(face[1]-face[0]) let vertex_norm=(face[1]-face[0])
.cross(face[2]-face[0]); .cross(face[2]-face[0]);

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

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

@ -1,4 +1,5 @@
use rbx_dom_weak::{types::Ref,InstanceBuilder,WeakDom}; use crate::types::WeakDom;
use rbx_dom_weak::{types::Ref,ustr,InstanceBuilder};
pub fn class_is_a(class:&str,superclass:&str)->bool{ pub fn class_is_a(class:&str,superclass:&str)->bool{
class==superclass class==superclass
@ -19,7 +20,7 @@ impl Context{
pub const fn new(dom:WeakDom)->Self{ pub const fn new(dom:WeakDom)->Self{
Self{dom} Self{dom}
} }
pub fn script_singleton(source:String)->(Context,crate::runner::instance::Instance,Services){ pub fn script_singleton(source:String)->(Context,crate::runner::instance::InstanceRef,Services){
let script=InstanceBuilder::new("Script") let script=InstanceBuilder::new("Script")
.with_property("Source",rbx_types::Variant::String(source)); .with_property("Source",rbx_types::Variant::String(source));
let script_ref=script.referent(); let script_ref=script.referent();
@ -28,7 +29,7 @@ impl Context{
.with_child(script) .with_child(script)
)); ));
let services=context.convert_into_place(); let services=context.convert_into_place();
(context,crate::runner::instance::Instance::new(script_ref),services) (context,crate::runner::instance::InstanceRef::new(script_ref),services)
} }
pub fn from_ref(dom:&WeakDom)->&Context{ pub fn from_ref(dom:&WeakDom)->&Context{
unsafe{&*(dom as *const WeakDom as *const Context)} unsafe{&*(dom as *const WeakDom as *const Context)}
@ -39,24 +40,24 @@ impl Context{
/// Creates an iterator over all items of a particular class. /// Creates an iterator over all items of a particular class.
pub fn superclass_iter<'a>(&'a self,superclass:&'a str)->impl Iterator<Item=Ref>+'a{ pub fn superclass_iter<'a>(&'a self,superclass:&'a str)->impl Iterator<Item=Ref>+'a{
self.dom.descendants().filter(|&instance| self.dom.descendants().filter(|&instance|
class_is_a(instance.class.as_ref(),superclass) class_is_a(instance.as_ref().class.as_ref(),superclass)
).map(|instance|instance.referent()) ).map(|instance|instance.as_ref().referent())
} }
pub fn scripts(&self)->Vec<crate::runner::instance::Instance>{ pub fn scripts(&self)->Vec<crate::runner::instance::InstanceRef>{
self.superclass_iter("LuaSourceContainer").map(crate::runner::instance::Instance::new).collect() self.superclass_iter("LuaSourceContainer").map(crate::runner::instance::InstanceRef::new).collect()
} }
pub fn find_services(&self)->Option<Services>{ pub fn find_services(&self)->Option<Services>{
Some(Services{ Some(Services{
workspace:*self.dom.root().children().iter().find(|&&r| workspace:*self.dom.root().as_ref().children().iter().find(|&&r|
self.dom.get_by_ref(r).is_some_and(|instance|instance.class=="Workspace") self.dom.get_by_ref(r).is_some_and(|instance|instance.as_ref().class=="Workspace")
)?, )?,
game:self.dom.root_ref(), game:self.dom.root_ref(),
}) })
} }
pub fn convert_into_place(&mut self)->Services{ pub fn convert_into_place(&mut self)->Services{
//snapshot root instances //snapshot root instances
let children=self.dom.root().children().to_owned(); let children=self.dom.root().as_ref().children().to_owned();
//insert services //insert services
let game=self.dom.root_ref(); let game=self.dom.root_ref();
@ -69,9 +70,9 @@ impl Context{
); );
{ {
//Lowercase and upper case workspace property! //Lowercase and upper case workspace property!
let game=self.dom.root_mut(); let game=self.dom.root_mut().as_mut();
game.properties.insert("workspace".to_owned(),rbx_types::Variant::Ref(workspace)); game.properties.insert(ustr("workspace"),rbx_types::Variant::Ref(workspace));
game.properties.insert("Workspace".to_owned(),rbx_types::Variant::Ref(workspace)); game.properties.insert(ustr("Workspace"),rbx_types::Variant::Ref(workspace));
} }
self.dom.insert(game,InstanceBuilder::new("Lighting")); self.dom.insert(game,InstanceBuilder::new("Lighting"));

@ -1,3 +1,4 @@
pub mod types;
pub mod runner; pub mod runner;
pub mod context; pub mod context;
#[cfg(feature="run-service")] #[cfg(feature="run-service")]

@ -2,25 +2,25 @@ use std::collections::{hash_map::Entry,HashMap};
use mlua::{FromLua,FromLuaMulti,IntoLua,IntoLuaMulti}; use mlua::{FromLua,FromLuaMulti,IntoLua,IntoLuaMulti};
use rbx_types::Ref; use rbx_types::Ref;
use rbx_dom_weak::{InstanceBuilder,WeakDom}; use rbx_dom_weak::{ustr,Ustr,InstanceBuilder};
use crate::runner::vector3::Vector3; use crate::runner::vector3::Vector3;
use crate::types::{WeakDom,Instance};
pub fn set_globals(lua:&mlua::Lua,globals:&mlua::Table)->Result<(),mlua::Error>{ pub fn set_globals(lua:&mlua::Lua,globals:&mlua::Table)->Result<(),mlua::Error>{
//class functions store //class functions store
lua.set_app_data(ClassMethodsStore::default()); lua.set_app_data(ClassMethodsStore::default());
lua.set_app_data(InstanceValueStore::default());
let instance_table=lua.create_table()?; let instance_table=lua.create_table()?;
//Instance.new //Instance.new
instance_table.raw_set("new", instance_table.raw_set("new",
lua.create_function(|lua,(class_name,parent):(mlua::String,Option<Instance>)|{ lua.create_function(|lua,(class_name,parent):(mlua::String,Option<InstanceRef>)|{
let class_name_str=&*class_name.to_str()?; let class_name_str=&*class_name.to_str()?;
let parent=parent.ok_or_else(||mlua::Error::runtime("Nil Parent not yet supported"))?; let parent=parent.ok_or_else(||mlua::Error::runtime("Nil Parent not yet supported"))?;
dom_mut(lua,|dom|{ dom_mut(lua,|dom|{
//TODO: Nil instances //TODO: Nil instances
Ok(Instance::new(dom.insert(parent.referent,InstanceBuilder::new(class_name_str)))) Ok(InstanceRef::new(dom.insert(parent.referent,InstanceBuilder::new(class_name_str))))
}) })
})? })?
)?; )?;
@ -44,10 +44,12 @@ fn coerce_float32(value:&mlua::Value)->Option<f32>{
} }
} }
fn get_full_name(dom:&rbx_dom_weak::WeakDom,instance:&rbx_dom_weak::Instance)->String{ fn get_full_name(dom:&WeakDom,instance:&Instance)->String{
let instance=instance.as_ref();
let mut full_name=instance.name.clone(); let mut full_name=instance.name.clone();
let mut pref=instance.parent(); let mut pref=instance.parent();
while let Some(parent)=dom.get_by_ref(pref){ while let Some(parent)=dom.get_by_ref(pref){
let parent=parent.as_ref();
full_name.insert(0,'.'); full_name.insert(0,'.');
full_name.insert_str(0,parent.name.as_str()); full_name.insert_str(0,parent.name.as_str());
pref=parent.parent(); pref=parent.parent();
@ -55,10 +57,10 @@ fn get_full_name(dom:&rbx_dom_weak::WeakDom,instance:&rbx_dom_weak::Instance)->S
full_name full_name
} }
//helper function for script //helper function for script
pub fn get_name_source(lua:&mlua::Lua,script:Instance)->Result<(String,String),mlua::Error>{ pub fn get_name_source(lua:&mlua::Lua,script:InstanceRef)->Result<(String,String),mlua::Error>{
dom_mut(lua,|dom|{ dom_mut(lua,|dom|{
let instance=script.get(dom)?; let instance=script.get(dom)?;
let source=match instance.properties.get("Source"){ let source=match instance.as_ref().properties.get(&ustr("Source")){
Some(rbx_dom_weak::types::Variant::String(s))=>s.clone(), Some(rbx_dom_weak::types::Variant::String(s))=>s.clone(),
_=>Err(mlua::Error::external("Missing script.Source"))?, _=>Err(mlua::Error::external("Missing script.Source"))?,
}; };
@ -66,65 +68,46 @@ pub fn get_name_source(lua:&mlua::Lua,script:Instance)->Result<(String,String),m
}) })
} }
pub fn find_first_child<'a>(dom:&'a rbx_dom_weak::WeakDom,instance:&rbx_dom_weak::Instance,name:&str)->Option<&'a rbx_dom_weak::Instance>{ pub fn find_first_child<'a>(dom:&'a WeakDom,instance:&Instance,name:&str)->Option<&'a Instance>{
instance.children().iter().filter_map(|&r|dom.get_by_ref(r)).find(|inst|inst.name==name) instance.as_ref().children().iter().filter_map(|&r|dom.get_by_ref(r)).find(|inst|inst.as_ref().name==name)
} }
pub fn find_first_descendant<'a>(dom:&'a rbx_dom_weak::WeakDom,instance:&rbx_dom_weak::Instance,name:&str)->Option<&'a rbx_dom_weak::Instance>{ pub fn find_first_descendant<'a>(dom:&'a WeakDom,instance:&Instance,name:&str)->Option<&'a Instance>{
dom.descendants_of(instance.referent()).find(|&inst|inst.name==name) dom.descendants_of(instance.as_ref().referent()).find(|&inst|inst.as_ref().name==name)
} }
pub fn find_first_child_of_class<'a>(dom:&'a rbx_dom_weak::WeakDom,instance:&rbx_dom_weak::Instance,class:&str)->Option<&'a rbx_dom_weak::Instance>{ pub fn find_first_child_of_class<'a>(dom:&'a WeakDom,instance:&Instance,class:&str)->Option<&'a Instance>{
instance.children().iter().filter_map(|&r|dom.get_by_ref(r)).find(|inst|inst.class==class) instance.as_ref().children().iter().filter_map(|&r|dom.get_by_ref(r)).find(|inst|inst.as_ref().class==class)
} }
pub fn find_first_descendant_of_class<'a>(dom:&'a rbx_dom_weak::WeakDom,instance:&rbx_dom_weak::Instance,class:&str)->Option<&'a rbx_dom_weak::Instance>{ pub fn find_first_descendant_of_class<'a>(dom:&'a WeakDom,instance:&Instance,class:&str)->Option<&'a Instance>{
dom.descendants_of(instance.referent()).find(|&inst|inst.class==class) dom.descendants_of(instance.as_ref().referent()).find(|&inst|inst.as_ref().class==class)
} }
#[derive(Clone,Copy)] #[derive(Clone,Copy)]
pub struct Instance{ pub struct InstanceRef{
referent:Ref, referent:Ref,
} }
impl Instance{ impl InstanceRef{
pub const fn new(referent:Ref)->Self{ pub const fn new(referent:Ref)->Self{
Self{referent} Self{referent}
} }
pub fn get<'a>(&self,dom:&'a WeakDom)->mlua::Result<&'a rbx_dom_weak::Instance>{ pub fn get<'a>(&self,dom:&'a WeakDom)->mlua::Result<&'a Instance>{
dom.get_by_ref(self.referent).ok_or_else(||mlua::Error::runtime("Instance missing")) dom.get_by_ref(self.referent).ok_or_else(||mlua::Error::runtime("Instance missing"))
} }
pub fn get_mut<'a>(&self,dom:&'a mut WeakDom)->mlua::Result<&'a mut rbx_dom_weak::Instance>{ pub fn get_mut<'a>(&self,dom:&'a mut WeakDom)->mlua::Result<&'a mut Instance>{
dom.get_by_ref_mut(self.referent).ok_or_else(||mlua::Error::runtime("Instance missing")) dom.get_by_ref_mut(self.referent).ok_or_else(||mlua::Error::runtime("Instance missing"))
} }
} }
type_from_lua_userdata!(Instance); type_from_lua_userdata!(InstanceRef);
//TODO: update rbx_reflection and use dom.superclasses_iter impl mlua::UserData for InstanceRef{
pub struct SuperClassIter<'a> {
database: &'a rbx_reflection::ReflectionDatabase<'a>,
descriptor: Option<&'a rbx_reflection::ClassDescriptor<'a>>,
}
impl<'a> SuperClassIter<'a> {
fn next_descriptor(&self) -> Option<&'a rbx_reflection::ClassDescriptor<'a>> {
let superclass = self.descriptor?.superclass.as_ref()?;
self.database.classes.get(superclass)
}
}
impl<'a> Iterator for SuperClassIter<'a> {
type Item = &'a rbx_reflection::ClassDescriptor<'a>;
fn next(&mut self) -> Option<Self::Item> {
let next_descriptor = self.next_descriptor();
std::mem::replace(&mut self.descriptor, next_descriptor)
}
}
impl mlua::UserData for Instance{
fn add_fields<F:mlua::UserDataFields<Self>>(fields:&mut F){ fn add_fields<F:mlua::UserDataFields<Self>>(fields:&mut F){
fields.add_field_method_get("Parent",|lua,this|{ fields.add_field_method_get("Parent",|lua,this|{
dom_mut(lua,|dom|{ dom_mut(lua,|dom|{
let instance=this.get(dom)?; let instance=this.get(dom)?.as_ref();
Ok(Instance::new(instance.parent())) Ok(InstanceRef::new(instance.parent()))
}) })
}); });
fields.add_field_method_set("Parent",|lua,this,val:Option<Instance>|{ fields.add_field_method_set("Parent",|lua,this,val:Option<InstanceRef>|{
let parent=val.ok_or_else(||mlua::Error::runtime("Nil Parent not yet supported"))?; let parent=val.ok_or_else(||mlua::Error::runtime("Nil Parent not yet supported"))?;
dom_mut(lua,|dom|{ dom_mut(lua,|dom|{
dom.transfer_within(this.referent,parent.referent); dom.transfer_within(this.referent,parent.referent);
@ -133,13 +116,13 @@ impl mlua::UserData for Instance{
}); });
fields.add_field_method_get("Name",|lua,this|{ fields.add_field_method_get("Name",|lua,this|{
dom_mut(lua,|dom|{ dom_mut(lua,|dom|{
let instance=this.get(dom)?; let instance=this.get(dom)?.as_ref();
Ok(instance.name.clone()) Ok(instance.name.clone())
}) })
}); });
fields.add_field_method_set("Name",|lua,this,val:mlua::String|{ fields.add_field_method_set("Name",|lua,this,val:mlua::String|{
dom_mut(lua,|dom|{ dom_mut(lua,|dom|{
let instance=this.get_mut(dom)?; let instance=this.get_mut(dom)?.as_mut();
//Why does this need to be cloned? //Why does this need to be cloned?
instance.name=val.to_str()?.to_owned(); instance.name=val.to_str()?.to_owned();
Ok(()) Ok(())
@ -147,25 +130,25 @@ impl mlua::UserData for Instance{
}); });
fields.add_field_method_get("ClassName",|lua,this|{ fields.add_field_method_get("ClassName",|lua,this|{
dom_mut(lua,|dom|{ dom_mut(lua,|dom|{
let instance=this.get(dom)?; let instance=this.get(dom)?.as_ref();
Ok(instance.class.clone()) Ok(instance.class.to_owned())
}) })
}); });
} }
fn add_methods<M:mlua::UserDataMethods<Self>>(methods:&mut M){ fn add_methods<M:mlua::UserDataMethods<Self>>(methods:&mut M){
methods.add_method("GetChildren",|lua,this,_:()| methods.add_method("GetChildren",|lua,this,_:()|
dom_mut(lua,|dom|{ dom_mut(lua,|dom|{
let instance=this.get(dom)?; let instance=this.get(dom)?.as_ref();
let children:Vec<_>=instance let children:Vec<_>=instance
.children() .children()
.iter() .iter()
.copied() .copied()
.map(Instance::new) .map(InstanceRef::new)
.collect(); .collect();
Ok(children) Ok(children)
}) })
); );
fn ffc(lua:&mlua::Lua,this:&Instance,(name,search_descendants):(mlua::String,Option<bool>))->mlua::Result<Option<Instance>>{ fn ffc(lua:&mlua::Lua,this:&InstanceRef,(name,search_descendants):(mlua::String,Option<bool>))->mlua::Result<Option<InstanceRef>>{
let name_str=&*name.to_str()?; let name_str=&*name.to_str()?;
dom_mut(lua,|dom|{ dom_mut(lua,|dom|{
let instance=this.get(dom)?; let instance=this.get(dom)?;
@ -175,7 +158,7 @@ impl mlua::UserData for Instance{
false=>find_first_child(dom,instance,name_str), false=>find_first_child(dom,instance,name_str),
} }
.map(|instance| .map(|instance|
Instance::new(instance.referent()) InstanceRef::new(instance.as_ref().referent())
) )
) )
}) })
@ -191,7 +174,7 @@ impl mlua::UserData for Instance{
false=>find_first_child_of_class(dom,this.get(dom)?,class_str), false=>find_first_child_of_class(dom,this.get(dom)?,class_str),
} }
.map(|instance| .map(|instance|
Instance::new(instance.referent()) InstanceRef::new(instance.as_ref().referent())
) )
) )
}) })
@ -201,7 +184,7 @@ impl mlua::UserData for Instance{
let children:Vec<_>=dom let children:Vec<_>=dom
.descendants_of(this.referent) .descendants_of(this.referent)
.map(|instance| .map(|instance|
Instance::new(instance.referent()) InstanceRef::new(instance.as_ref().referent())
) )
.collect(); .collect();
Ok(children) Ok(children)
@ -209,7 +192,7 @@ impl mlua::UserData for Instance{
); );
methods.add_method("IsA",|lua,this,classname:mlua::String| methods.add_method("IsA",|lua,this,classname:mlua::String|
dom_mut(lua,|dom|{ dom_mut(lua,|dom|{
let instance=this.get(dom)?; let instance=this.get(dom)?.as_ref();
Ok(crate::context::class_is_a(instance.class.as_str(),&*classname.to_str()?)) Ok(crate::context::class_is_a(instance.class.as_str(),&*classname.to_str()?))
}) })
); );
@ -219,52 +202,44 @@ impl mlua::UserData for Instance{
Ok(()) Ok(())
}) })
); );
methods.add_meta_function(mlua::MetaMethod::ToString,|lua,this:Instance|{ methods.add_meta_function(mlua::MetaMethod::ToString,|lua,this:InstanceRef|{
dom_mut(lua,|dom|{ dom_mut(lua,|dom|{
let instance=this.get(dom)?; let instance=this.get(dom)?.as_ref();
Ok(instance.name.clone()) Ok(instance.name.clone())
}) })
}); });
methods.add_meta_function(mlua::MetaMethod::Index,|lua,(this,index):(Instance,mlua::String)|{ methods.add_meta_function(mlua::MetaMethod::Index,|lua,(this,index):(InstanceRef,mlua::String)|{
let index_str=&*index.to_str()?; let index_str=&*index.to_str()?;
let index_ustr=ustr(index_str);
dom_mut(lua,|dom|{ dom_mut(lua,|dom|{
let instance=this.get(dom)?; let instance=this.get(dom)?;
let instance_ref=instance.as_ref();
//println!("__index t={} i={index:?}",instance.name); //println!("__index t={} i={index:?}",instance.name);
let db=rbx_reflection_database::get(); let db=rbx_reflection_database::get();
let class=db.classes.get(instance.class.as_str()).ok_or_else(||mlua::Error::runtime("Class missing"))?; let class=db.classes.get(instance_ref.class.as_str()).ok_or_else(||mlua::Error::runtime("Class missing"))?;
//Find existing property //Find existing property
match instance.properties.get(index_str) match instance_ref.properties.get(&index_ustr)
.cloned() .cloned()
//Find default value //Find default value
.or_else(||db.find_default_property(class,index_str).cloned()) .or_else(||db.find_default_property(class,index_str).cloned())
//Find virtual property //Find virtual property
.or_else(||{ .or_else(||db.superclasses_iter(class).find_map(|class|
SuperClassIter{ find_virtual_property(&instance_ref.properties,class,&index_ustr)
database:db, ))
descriptor:Some(class),
}
.find_map(|class|
find_virtual_property(&instance.properties,class,index_str)
)
})
{ {
Some(rbx_types::Variant::Int32(val))=>return val.into_lua(lua), Some(rbx_types::Variant::Int32(val))=>return val.into_lua(lua),
Some(rbx_types::Variant::Int64(val))=>return val.into_lua(lua), Some(rbx_types::Variant::Int64(val))=>return val.into_lua(lua),
Some(rbx_types::Variant::Float32(val))=>return val.into_lua(lua), Some(rbx_types::Variant::Float32(val))=>return val.into_lua(lua),
Some(rbx_types::Variant::Float64(val))=>return val.into_lua(lua), Some(rbx_types::Variant::Float64(val))=>return val.into_lua(lua),
Some(rbx_types::Variant::Ref(val))=>return Instance::new(val).into_lua(lua), Some(rbx_types::Variant::Ref(val))=>return InstanceRef::new(val).into_lua(lua),
Some(rbx_types::Variant::CFrame(cf))=>return Into::<crate::runner::cframe::CFrame>::into(cf).into_lua(lua), Some(rbx_types::Variant::CFrame(cf))=>return Into::<crate::runner::cframe::CFrame>::into(cf).into_lua(lua),
Some(rbx_types::Variant::Vector3(v))=>return Into::<crate::runner::vector3::Vector3>::into(v).into_lua(lua), Some(rbx_types::Variant::Vector3(v))=>return Into::<crate::runner::vector3::Vector3>::into(v).into_lua(lua),
None=>(), None=>(),
other=>return Err(mlua::Error::runtime(format!("Instance.__index Unsupported property type instance={} index={index_str} value={other:?}",instance.name))), other=>return Err(mlua::Error::runtime(format!("Instance.__index Unsupported property type instance={} index={index_str} value={other:?}",instance_ref.name))),
} }
//find a function with a matching name //find a function with a matching name
if let Some(function)=class_methods_store_mut(lua,|cf|{ if let Some(function)=class_methods_store_mut(lua,|cf|{
let mut iter=SuperClassIter{ db.superclasses_iter(class).find_map(|class|{
database:db,
descriptor:Some(class),
};
iter.find_map(|class|{
let mut class_methods=cf.get_or_create_class_methods(&class.name)?; let mut class_methods=cf.get_or_create_class_methods(&class.name)?;
class_methods.get_or_create_function(lua,index_str) class_methods.get_or_create_function(lua,index_str)
.transpose() .transpose()
@ -274,41 +249,39 @@ impl mlua::UserData for Instance{
} }
//find or create an associated userdata object //find or create an associated userdata object
if let Some(value)=instance_value_store_mut(lua,|ivs|{ let instance=this.get_mut(dom)?;
//TODO: walk class tree somehow if let Some(value)=instance.get_or_create_userdata(lua,index_ustr)?{
match ivs.get_or_create_instance_values(&instance){
Some(mut instance_values)=>instance_values.get_or_create_value(lua,index_str),
None=>Ok(None)
}
})?{
return value.into_lua(lua); return value.into_lua(lua);
} }
// drop mutable borrow
//find a child with a matching name //find a child with a matching name
let instance=this.get(dom)?;
find_first_child(dom,instance,index_str) find_first_child(dom,instance,index_str)
.map(|instance|Instance::new(instance.referent())) .map(|instance|InstanceRef::new(instance.as_ref().referent()))
.into_lua(lua) .into_lua(lua)
}) })
}); });
methods.add_meta_function(mlua::MetaMethod::NewIndex,|lua,(this,index,value):(Instance,mlua::String,mlua::Value)|{ methods.add_meta_function(mlua::MetaMethod::NewIndex,|lua,(this,index,value):(InstanceRef,mlua::String,mlua::Value)|{
dom_mut(lua,|dom|{ dom_mut(lua,|dom|{
let instance=this.get_mut(dom)?; let instance=this.get_mut(dom)?.as_mut();
//println!("__newindex t={} i={index:?} v={value:?}",instance.name); //println!("__newindex t={} i={index:?} v={value:?}",instance.name);
let index_str=&*index.to_str()?; let index_str=&*index.to_str()?;
let index_ustr=ustr(index_str);
let db=rbx_reflection_database::get(); let db=rbx_reflection_database::get();
let class=db.classes.get(instance.class.as_str()).ok_or_else(||mlua::Error::runtime("Class missing"))?; let class=db.classes.get(instance.class.as_str()).ok_or_else(||mlua::Error::runtime("Class missing"))?;
let mut iter=SuperClassIter{ let property=db.superclasses_iter(class).find_map(|cls|
database:db, cls.properties.get(index_str)
descriptor:Some(class), ).ok_or_else(||
}; mlua::Error::runtime(format!("Property '{index_str}' missing on class '{}'",class.name))
let property=iter.find_map(|cls|cls.properties.get(index_str)).ok_or_else(||mlua::Error::runtime(format!("Property '{index_str}' missing on class '{}'",class.name)))?; )?;
match &property.data_type{ match &property.data_type{
rbx_reflection::DataType::Value(rbx_types::VariantType::Vector3)=>{ rbx_reflection::DataType::Value(rbx_types::VariantType::Vector3)=>{
let typed_value:Vector3=*value.as_userdata().ok_or_else(||mlua::Error::runtime("Expected Userdata"))?.borrow()?; let typed_value:Vector3=*value.as_userdata().ok_or_else(||mlua::Error::runtime("Expected Userdata"))?.borrow()?;
instance.properties.insert(index_str.to_owned(),rbx_types::Variant::Vector3(typed_value.into())); instance.properties.insert(index_ustr,rbx_types::Variant::Vector3(typed_value.into()));
}, },
rbx_reflection::DataType::Value(rbx_types::VariantType::Float32)=>{ rbx_reflection::DataType::Value(rbx_types::VariantType::Float32)=>{
let typed_value:f32=coerce_float32(&value).ok_or_else(||mlua::Error::runtime("Expected f32"))?; let typed_value:f32=coerce_float32(&value).ok_or_else(||mlua::Error::runtime("Expected f32"))?;
instance.properties.insert(index_str.to_owned(),rbx_types::Variant::Float32(typed_value)); instance.properties.insert(index_ustr,rbx_types::Variant::Float32(typed_value));
}, },
rbx_reflection::DataType::Enum(enum_name)=>{ rbx_reflection::DataType::Enum(enum_name)=>{
let typed_value=match &value{ let typed_value=match &value{
@ -324,27 +297,27 @@ impl mlua::UserData for Instance{
}, },
_=>Err(mlua::Error::runtime("Expected Enum")), _=>Err(mlua::Error::runtime("Expected Enum")),
}?; }?;
instance.properties.insert(index_str.to_owned(),rbx_types::Variant::Enum(typed_value)); instance.properties.insert(index_ustr,rbx_types::Variant::Enum(typed_value));
}, },
rbx_reflection::DataType::Value(rbx_types::VariantType::Color3)=>{ rbx_reflection::DataType::Value(rbx_types::VariantType::Color3)=>{
let typed_value:crate::runner::color3::Color3=*value.as_userdata().ok_or_else(||mlua::Error::runtime("Expected Color3"))?.borrow()?; let typed_value:crate::runner::color3::Color3=*value.as_userdata().ok_or_else(||mlua::Error::runtime("Expected Color3"))?.borrow()?;
instance.properties.insert(index_str.to_owned(),rbx_types::Variant::Color3(typed_value.into())); instance.properties.insert(index_ustr,rbx_types::Variant::Color3(typed_value.into()));
}, },
rbx_reflection::DataType::Value(rbx_types::VariantType::Bool)=>{ rbx_reflection::DataType::Value(rbx_types::VariantType::Bool)=>{
let typed_value=value.as_boolean().ok_or_else(||mlua::Error::runtime("Expected boolean"))?; let typed_value=value.as_boolean().ok_or_else(||mlua::Error::runtime("Expected boolean"))?;
instance.properties.insert(index_str.to_owned(),rbx_types::Variant::Bool(typed_value)); instance.properties.insert(index_ustr,rbx_types::Variant::Bool(typed_value));
}, },
rbx_reflection::DataType::Value(rbx_types::VariantType::String)=>{ rbx_reflection::DataType::Value(rbx_types::VariantType::String)=>{
let typed_value=value.as_str().ok_or_else(||mlua::Error::runtime("Expected boolean"))?; let typed_value=value.as_str().ok_or_else(||mlua::Error::runtime("Expected boolean"))?;
instance.properties.insert(index_str.to_owned(),rbx_types::Variant::String(typed_value.to_owned())); instance.properties.insert(index_ustr,rbx_types::Variant::String(typed_value.to_owned()));
}, },
rbx_reflection::DataType::Value(rbx_types::VariantType::NumberSequence)=>{ rbx_reflection::DataType::Value(rbx_types::VariantType::NumberSequence)=>{
let typed_value:crate::runner::number_sequence::NumberSequence=*value.as_userdata().ok_or_else(||mlua::Error::runtime("Expected NumberSequence"))?.borrow()?; let typed_value:crate::runner::number_sequence::NumberSequence=*value.as_userdata().ok_or_else(||mlua::Error::runtime("Expected NumberSequence"))?.borrow()?;
instance.properties.insert(index_str.to_owned(),rbx_types::Variant::NumberSequence(typed_value.into())); instance.properties.insert(index_ustr,rbx_types::Variant::NumberSequence(typed_value.into()));
}, },
rbx_reflection::DataType::Value(rbx_types::VariantType::ColorSequence)=>{ rbx_reflection::DataType::Value(rbx_types::VariantType::ColorSequence)=>{
let typed_value:crate::runner::color_sequence::ColorSequence=*value.as_userdata().ok_or_else(||mlua::Error::runtime("Expected ColorSequence"))?.borrow()?; let typed_value:crate::runner::color_sequence::ColorSequence=*value.as_userdata().ok_or_else(||mlua::Error::runtime("Expected ColorSequence"))?.borrow()?;
instance.properties.insert(index_str.to_owned(),rbx_types::Variant::ColorSequence(typed_value.into())); instance.properties.insert(index_ustr,rbx_types::Variant::ColorSequence(typed_value.into()));
}, },
other=>return Err(mlua::Error::runtime(format!("Unimplemented property type: {other:?}"))), other=>return Err(mlua::Error::runtime(format!("Unimplemented property type: {other:?}"))),
} }
@ -358,7 +331,7 @@ impl mlua::UserData for Instance{
macro_rules! cf{ macro_rules! cf{
($f:expr)=>{ ($f:expr)=>{
|lua,mut args|{ |lua,mut args|{
let this=Instance::from_lua(args.pop_front().unwrap_or(mlua::Value::Nil),lua)?; let this=InstanceRef::from_lua(args.pop_front().unwrap_or(mlua::Value::Nil),lua)?;
$f(lua,this,FromLuaMulti::from_lua_multi(args,lua)?)?.into_lua_multi(lua) $f(lua,this,FromLuaMulti::from_lua_multi(args,lua)?)?.into_lua_multi(lua)
} }
}; };
@ -381,13 +354,13 @@ static CLASS_FUNCTION_DATABASE:CFD=phf::phf_map!{
match service{ match service{
"Lighting"|"RunService"=>{ "Lighting"|"RunService"=>{
let referent=find_first_child_of_class(dom,dom.root(),service) let referent=find_first_child_of_class(dom,dom.root(),service)
.map(|instance|instance.referent()) .map(|instance|instance.as_ref().referent())
.unwrap_or_else(|| .unwrap_or_else(||
dom.insert(dom.root_ref(),InstanceBuilder::new(service)) dom.insert(dom.root_ref(),InstanceBuilder::new(service))
); );
Ok(Instance::new(referent)) Ok(InstanceRef::new(referent))
}, },
other=>Err::<Instance,_>(mlua::Error::runtime(format!("Service '{other}' not supported"))), other=>Err::<InstanceRef,_>(mlua::Error::runtime(format!("Service '{other}' not supported"))),
} }
}) })
}), }),
@ -480,78 +453,17 @@ static VIRTUAL_PROPERTY_DATABASE:VPD=phf::phf_map!{
}; };
fn find_virtual_property( fn find_virtual_property(
properties:&HashMap<String,rbx_types::Variant>, properties:&rbx_dom_weak::UstrMap<rbx_types::Variant>,
class:&rbx_reflection::ClassDescriptor, class:&rbx_reflection::ClassDescriptor,
index:&str index:&Ustr,
)->Option<rbx_types::Variant>{ )->Option<rbx_types::Variant>{
//Find virtual property //Find virtual property
let class_virtual_properties=VIRTUAL_PROPERTY_DATABASE.get(&class.name)?; let class_virtual_properties=VIRTUAL_PROPERTY_DATABASE.get(&class.name)?;
let virtual_property=class_virtual_properties.get(index)?; let virtual_property=class_virtual_properties.get(index)?;
//Get source property //Get source property
let variant=properties.get(virtual_property.property)?; let variant=properties.get(&ustr(virtual_property.property))?;
//Transform Source property with provided function //Transform Source property with provided function
(virtual_property.pointer)(variant) (virtual_property.pointer)(variant)
} }
// lazy-loaded per-instance userdata values
// This whole thing is a bad idea and a garbage collection nightmare.
// TODO: recreate rbx_dom_weak with my own instance type that owns this data.
type CreateUserData=fn(&mlua::Lua)->mlua::Result<mlua::AnyUserData>;
type LUD=phf::Map<&'static str,// Class name
phf::Map<&'static str,// Value name
CreateUserData
>
>;
static LAZY_USER_DATA:LUD=phf::phf_map!{
"RunService"=>phf::phf_map!{
"RenderStepped"=>|lua|{
lua.create_any_userdata(crate::runner::script_signal::ScriptSignal::new())
},
},
};
#[derive(Default)]
pub struct InstanceValueStore{
values:HashMap<Ref,
HashMap<&'static str,
mlua::AnyUserData
>
>,
}
pub struct InstanceValues<'a>{
named_values:&'static phf::Map<&'static str,CreateUserData>,
values:&'a mut HashMap<&'static str,mlua::AnyUserData>,
}
impl InstanceValueStore{
pub fn get_or_create_instance_values(&mut self,instance:&rbx_dom_weak::Instance)->Option<InstanceValues>{
LAZY_USER_DATA.get(instance.class.as_str())
.map(|named_values|
InstanceValues{
named_values,
values:self.values.entry(instance.referent())
.or_insert_with(||HashMap::new()),
}
)
}
}
impl InstanceValues<'_>{
pub fn get_or_create_value(&mut self,lua:&mlua::Lua,index:&str)->mlua::Result<Option<mlua::AnyUserData>>{
Ok(match self.named_values.get_entry(index){
Some((&static_index_str,&function_pointer))=>Some(
match self.values.entry(static_index_str){
Entry::Occupied(entry)=>entry.get().clone(),
Entry::Vacant(entry)=>entry.insert(
function_pointer(lua)?
).clone(),
}
),
None=>None,
})
}
}
pub fn instance_value_store_mut<T>(lua:&mlua::Lua,mut f:impl FnMut(&mut InstanceValueStore)->mlua::Result<T>)->mlua::Result<T>{
let mut cf=lua.app_data_mut::<InstanceValueStore>().ok_or_else(||mlua::Error::runtime("InstanceValueStore missing"))?;
f(&mut *cf)
}

@ -1,2 +1,2 @@
pub mod instance; pub mod instance;
pub use instance::Instance; pub use instance::InstanceRef;

@ -7,7 +7,7 @@ mod color3;
mod cframe; mod cframe;
mod vector3; mod vector3;
pub mod instance; pub mod instance;
mod script_signal; pub mod script_signal;
mod color_sequence; mod color_sequence;
mod number_sequence; mod number_sequence;

@ -1,6 +1,7 @@
use crate::context::Context; use crate::context::Context;
#[cfg(feature="run-service")] #[cfg(feature="run-service")]
use crate::scheduler::scheduler_mut; use crate::scheduler::scheduler_mut;
use crate::types::WeakDom;
pub struct Runner{ pub struct Runner{
lua:mlua::Lua, lua:mlua::Lua,
@ -60,11 +61,11 @@ impl Runner{
pub fn runnable_context_with_services<'a>(self,context:&'a mut Context,services:&crate::context::Services)->Result<Runnable<'a>,Error>{ pub fn runnable_context_with_services<'a>(self,context:&'a mut Context,services:&crate::context::Services)->Result<Runnable<'a>,Error>{
{ {
let globals=self.lua.globals(); let globals=self.lua.globals();
globals.set("game",super::instance::Instance::new(services.game)).map_err(Error::RustLua)?; globals.set("game",super::instance::InstanceRef::new(services.game)).map_err(Error::RustLua)?;
globals.set("workspace",super::instance::Instance::new(services.workspace)).map_err(Error::RustLua)?; globals.set("workspace",super::instance::InstanceRef::new(services.workspace)).map_err(Error::RustLua)?;
} }
//this makes set_app_data shut up about the lifetime //this makes set_app_data shut up about the lifetime
self.lua.set_app_data::<&'static mut rbx_dom_weak::WeakDom>(unsafe{core::mem::transmute(&mut context.dom)}); self.lua.set_app_data::<&'static mut WeakDom>(unsafe{core::mem::transmute(&mut context.dom)});
#[cfg(feature="run-service")] #[cfg(feature="run-service")]
self.lua.set_app_data::<crate::scheduler::Scheduler>(crate::scheduler::Scheduler::default()); self.lua.set_app_data::<crate::scheduler::Scheduler>(crate::scheduler::Scheduler::default());
Ok(Runnable{ Ok(Runnable{
@ -81,14 +82,14 @@ pub struct Runnable<'a>{
} }
impl Runnable<'_>{ impl Runnable<'_>{
pub fn drop_context(self)->Runner{ pub fn drop_context(self)->Runner{
self.lua.remove_app_data::<&'static mut rbx_dom_weak::WeakDom>(); self.lua.remove_app_data::<&'static mut WeakDom>();
#[cfg(feature="run-service")] #[cfg(feature="run-service")]
self.lua.remove_app_data::<crate::scheduler::Scheduler>(); self.lua.remove_app_data::<crate::scheduler::Scheduler>();
Runner{ Runner{
lua:self.lua, lua:self.lua,
} }
} }
pub fn run_script(&self,script:super::instance::Instance)->Result<(),Error>{ pub fn run_script(&self,script:super::instance::InstanceRef)->Result<(),Error>{
let (name,source)=super::instance::instance::get_name_source(&self.lua,script).map_err(Error::RustLua)?; let (name,source)=super::instance::instance::get_name_source(&self.lua,script).map_err(Error::RustLua)?;
self.lua.globals().raw_set("script",script).map_err(Error::RustLua)?; self.lua.globals().raw_set("script",script).map_err(Error::RustLua)?;
let f=self.lua.load(source.as_str()) let f=self.lua.load(source.as_str())
@ -123,20 +124,15 @@ impl Runnable<'_>{
} }
#[cfg(feature="run-service")] #[cfg(feature="run-service")]
pub fn run_service_step(&self)->Result<(),mlua::Error>{ pub fn run_service_step(&self)->Result<(),mlua::Error>{
let render_stepped=super::instance::instance::dom_mut(&self.lua,|dom|{ let render_stepped_signal=super::instance::instance::dom_mut(&self.lua,|dom|{
let run_service=super::instance::instance::find_first_child_of_class(dom,dom.root(),"RunService").ok_or_else(||mlua::Error::runtime("RunService missing"))?; let run_service=super::instance::instance::find_first_child_of_class(dom,dom.root(),"RunService").ok_or_else(||mlua::Error::runtime("RunService missing"))?;
super::instance::instance::instance_value_store_mut(&self.lua,|instance_value_store|{ Ok(match run_service.userdata.get(&rbx_dom_weak::ustr("RenderStepped")){
//unwrap because I trust my find_first_child_of_class function to Some(render_stepped)=>Some(render_stepped.borrow::<super::script_signal::ScriptSignal>()?.clone()),
let mut instance_values=instance_value_store.get_or_create_instance_values(run_service).ok_or_else(||mlua::Error::runtime("RunService InstanceValues missing"))?; None=>None
let render_stepped=instance_values.get_or_create_value(&self.lua,"RenderStepped")?;
//let stepped=instance_values.get_or_create_value(&self.lua,"Stepped")?;
//let heartbeat=instance_values.get_or_create_value(&self.lua,"Heartbeat")?;
Ok(render_stepped)
}) })
})?; })?;
if let Some(render_stepped)=render_stepped{ if let Some(render_stepped_signal)=render_stepped_signal{
let signal:&super::script_signal::ScriptSignal=&*render_stepped.borrow()?; render_stepped_signal.fire(&mlua::MultiValue::new());
signal.fire(&mlua::MultiValue::new());
} }
Ok(()) Ok(())
} }

@ -0,0 +1,57 @@
pub struct Instance{
pub(crate) instance:rbx_dom_weak::Instance,
pub(crate) userdata:rbx_dom_weak::UstrMap<mlua::AnyUserData>,
}
impl AsRef<rbx_dom_weak::Instance> for Instance{
fn as_ref(&self)->&rbx_dom_weak::Instance{
&self.instance
}
}
impl AsMut<rbx_dom_weak::Instance> for Instance{
fn as_mut(&mut self)->&mut rbx_dom_weak::Instance{
&mut self.instance
}
}
impl From<Instance> for rbx_dom_weak::Instance{
fn from(instance:Instance)->Self{
instance.instance
}
}
impl From<rbx_dom_weak::Instance> for Instance{
fn from(instance:rbx_dom_weak::Instance)->Self{
Self{
instance,
userdata:Default::default(),
}
}
}
pub type WeakDom=rbx_dom_weak::GenericWeakDom<Instance>;
// lazy-loaded per-instance userdata values
type CreateUserData=fn(&mlua::Lua)->mlua::Result<mlua::AnyUserData>;
type LUD=phf::Map<&'static str,// Class name
phf::Map<&'static str,// Value name
CreateUserData
>
>;
static LAZY_USER_DATA:LUD=phf::phf_map!{
"RunService"=>phf::phf_map!{
"RenderStepped"=>|lua|lua.create_any_userdata(crate::runner::script_signal::ScriptSignal::new()),
},
};
impl Instance{
pub fn get_or_create_userdata(&mut self,lua:&mlua::Lua,index:rbx_dom_weak::Ustr)->mlua::Result<Option<mlua::AnyUserData>>{
use std::collections::hash_map::Entry;
Ok(match LAZY_USER_DATA.get(self.instance.class.as_str()){
Some(userdata_map)=>match self.userdata.entry(index){
Entry::Occupied(entry)=>Some(entry.get().clone()),
Entry::Vacant(entry)=>match userdata_map.get(index.as_str()){
Some(create_userdata)=>Some(entry.insert(create_userdata(lua)?).clone()),
None=>None,
},
},
None=>None,
})
}
}

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

@ -138,7 +138,7 @@ struct MapHeader{
//#[br(count=num_resources_external)] //#[br(count=num_resources_external)]
//external_resources:Vec<ResourceExternalHeader>, //external_resources:Vec<ResourceExternalHeader>,
#[br(count=num_modes)] #[br(count=num_modes)]
modes:Vec<newtypes::gameplay_modes::Mode>, modes:Vec<newtypes::gameplay_modes::NormalizedMode>,
#[br(count=num_attributes)] #[br(count=num_attributes)]
attributes:Vec<newtypes::gameplay_attributes::CollisionAttributes>, attributes:Vec<newtypes::gameplay_attributes::CollisionAttributes>,
#[br(count=num_render_configs)] #[br(count=num_render_configs)]
@ -181,7 +181,7 @@ fn read_texture<R:BinReaderExt>(file:&mut crate::file::File<R>,block_id:BlockId)
pub struct StreamableMap<R:BinReaderExt>{ pub struct StreamableMap<R:BinReaderExt>{
file:crate::file::File<R>, file:crate::file::File<R>,
//this includes every platform... move the unconstrained datas to their appropriate data block? //this includes every platform... move the unconstrained datas to their appropriate data block?
modes:gameplay_modes::Modes, modes:gameplay_modes::NormalizedModes,
//this is every possible attribute... need some sort of streaming system //this is every possible attribute... need some sort of streaming system
attributes:Vec<strafesnet_common::gameplay_attributes::CollisionAttributes>, attributes:Vec<strafesnet_common::gameplay_attributes::CollisionAttributes>,
//this is every possible render configuration... shaders and such... need streaming //this is every possible render configuration... shaders and such... need streaming
@ -223,7 +223,7 @@ impl<R:BinReaderExt> StreamableMap<R>{
} }
Ok(Self{ Ok(Self{
file, file,
modes:strafesnet_common::gameplay_modes::Modes::new(modes), modes:strafesnet_common::gameplay_modes::NormalizedModes::new(modes),
attributes, attributes,
render_configs, render_configs,
bvh:strafesnet_common::bvh::generate_bvh(bvh), bvh:strafesnet_common::bvh::generate_bvh(bvh),
@ -386,7 +386,7 @@ pub fn write_map<W:BinWriterExt>(mut writer:W,map:strafesnet_common::map::Comple
let mesh=map.meshes.get(model.mesh.get() as usize).ok_or(Error::InvalidMeshId(model.mesh))?; let mesh=map.meshes.get(model.mesh.get() as usize).ok_or(Error::InvalidMeshId(model.mesh))?;
let mut aabb=strafesnet_common::aabb::Aabb::default(); let mut aabb=strafesnet_common::aabb::Aabb::default();
for &pos in &mesh.unique_pos{ for &pos in &mesh.unique_pos{
aabb.grow(model.transform.transform_point3(pos).fix_1()); aabb.grow(model.transform.transform_point3(pos).narrow_1().unwrap());
} }
Ok(((model::ModelId::new(model_id as u32),model.into()),aabb)) Ok(((model::ModelId::new(model_id as u32),model.into()),aabb))
}).collect::<Result<Vec<_>,_>>()?; }).collect::<Result<Vec<_>,_>>()?;
@ -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_spacial_blocks:spacial_blocks.len() as u32,
num_resource_blocks:resource_blocks.len() as u32, num_resource_blocks:resource_blocks.len() as u32,
//num_resources_external:0, //num_resources_external:0,
num_modes:map.modes.modes.len() as u32, num_modes:map.modes.len() as u32,
num_attributes:map.attributes.len() as u32, num_attributes:map.attributes.len() as u32,
num_render_configs:map.render_configs.len() as u32, num_render_configs:map.render_configs.len() as u32,
spacial_blocks, spacial_blocks,
resource_blocks, resource_blocks,
//external_resources:Vec::new(), //external_resources:Vec::new(),
modes:map.modes.modes.into_iter().map(Into::into).collect(), modes:map.modes.into_iter().map(Into::into).collect(),
attributes:map.attributes.into_iter().map(Into::into).collect(), attributes:map.attributes.into_iter().map(Into::into).collect(),
render_configs:map.render_configs.into_iter().map(Into::into).collect(), render_configs:map.render_configs.into_iter().map(Into::into).collect(),
}; };

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

@ -1,7 +1,7 @@
[package] [package]
name = "map-tool" name = "map-tool"
version = "1.7.0" version = "1.7.0"
edition = "2021" edition = "2024"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
@ -13,11 +13,11 @@ futures = "0.3.31"
image = "0.25.2" image = "0.25.2"
image_dds = "0.7.1" image_dds = "0.7.1"
lazy-regex = "3.1.0" lazy-regex = "3.1.0"
rbx_asset = { version = "0.2.5", registry = "strafesnet" } rbx_asset = { version = "0.4.4", registry = "strafesnet" }
rbx_binary = { version = "0.7.4", registry = "strafesnet" } rbx_binary = { version = "1.1.0-sn3", registry = "strafesnet"}
rbx_dom_weak = { version = "2.7.0", registry = "strafesnet" } rbx_dom_weak = { version = "3.1.0-sn1", registry = "strafesnet"}
rbx_reflection_database = { version = "0.2.10", registry = "strafesnet" } rbx_reflection_database = "1.0.0"
rbx_xml = { version = "0.13.3", registry = "strafesnet" } rbx_xml = { version = "1.1.0-sn3", registry = "strafesnet"}
rbxassetid = { version = "0.1.0", registry = "strafesnet" } rbxassetid = { version = "0.1.0", registry = "strafesnet" }
strafesnet_bsp_loader = { version = "0.3.0", path = "../lib/bsp_loader", registry = "strafesnet" } strafesnet_bsp_loader = { version = "0.3.0", path = "../lib/bsp_loader", registry = "strafesnet" }
strafesnet_deferred_loader = { version = "0.5.0", path = "../lib/deferred_loader", registry = "strafesnet" } strafesnet_deferred_loader = { version = "0.5.0", path = "../lib/deferred_loader", registry = "strafesnet" }
@ -25,10 +25,11 @@ strafesnet_rbx_loader = { version = "0.6.0", path = "../lib/rbx_loader", registr
strafesnet_snf = { version = "0.3.0", path = "../lib/snf", registry = "strafesnet" } strafesnet_snf = { version = "0.3.0", path = "../lib/snf", registry = "strafesnet" }
thiserror = "2.0.11" thiserror = "2.0.11"
tokio = { version = "1.43.0", features = ["macros", "rt-multi-thread", "fs"] } tokio = { version = "1.43.0", features = ["macros", "rt-multi-thread", "fs"] }
vbsp = { version = "0.7.0-codegen1", registry = "strafesnet" } vbsp = "0.8.0"
vbsp-entities-css = "0.6.0"
vmdl = "0.2.0" vmdl = "0.2.0"
vmt-parser = "0.2.0" vmt-parser = "0.2.0"
vpk = "0.2.0" vpk = "0.3.0"
vtf = "0.3.0" vtf = "0.3.0"
#[profile.release] #[profile.release]

@ -3,7 +3,7 @@ use std::io::{Cursor,Read,Seek};
use std::collections::HashSet; use std::collections::HashSet;
use clap::{Args,Subcommand}; use clap::{Args,Subcommand};
use anyhow::Result as AResult; use anyhow::Result as AResult;
use rbx_dom_weak::Instance; use rbx_dom_weak::{ustr,Instance};
use strafesnet_deferred_loader::deferred_loader::LoadFailureMode; use strafesnet_deferred_loader::deferred_loader::LoadFailureMode;
use rbxassetid::RobloxAssetId; use rbxassetid::RobloxAssetId;
use tokio::io::AsyncReadExt; use tokio::io::AsyncReadExt;
@ -88,17 +88,45 @@ SurfaceAppearance.NormalMap
SurfaceAppearance.RoughnessMap SurfaceAppearance.RoughnessMap
SurfaceAppearance.TexturePack SurfaceAppearance.TexturePack
*/ */
fn accumulate_content_id(content_list:&mut HashSet<RobloxAssetId>,object:&Instance,property:&str){ /* These properties now use Content
if let Some(rbx_dom_weak::types::Variant::Content(content))=object.properties.get(property){ BaseWrap.CageMeshContent
let url:&str=content.as_ref(); Decal.TextureContent
if let Ok(asset_id)=url.parse(){ ImageButton.ImageContent
content_list.insert(asset_id); ImageLabel.ImageContent
}else{ MeshPart.MeshContent
println!("Content failed to parse into AssetID: {:?}",content); MeshPart.TextureContent
} SurfaceAppearance.ColorMapContent
}else{ SurfaceAppearance.MetalnessMapContent
SurfaceAppearance.NormalMapContent
SurfaceAppearance.RoughnessMapContent
WrapLayer.ReferenceMeshContent
*/
fn accumulate_content(content_list:&mut HashSet<RobloxAssetId>,object:&Instance,property:&str){
let Some(rbx_dom_weak::types::Variant::Content(content))=object.properties.get(&ustr(property))else{
println!("property={} does not exist for class={}",property,object.class.as_str()); println!("property={} does not exist for class={}",property,object.class.as_str());
} return;
};
let rbx_dom_weak::types::ContentType::Uri(uri)=content.value()else{
println!("ContentType is not Uri");
return;
};
let Ok(asset_id)=uri.parse()else{
println!("Content failed to parse into AssetID: {:?}",content);
return;
};
content_list.insert(asset_id);
}
fn accumulate_content_id(content_list:&mut HashSet<RobloxAssetId>,object:&Instance,property:&str){
let Some(rbx_dom_weak::types::Variant::ContentId(content))=object.properties.get(&ustr(property))else{
println!("property={} does not exist for class={}",property,object.class.as_str());
return;
};
let Ok(asset_id)=content.as_str().parse()else{
println!("Content failed to parse into AssetID: {:?}",content);
return;
};
content_list.insert(asset_id);
} }
async fn read_entire_file(path:impl AsRef<Path>)->Result<Cursor<Vec<u8>>,std::io::Error>{ async fn read_entire_file(path:impl AsRef<Path>)->Result<Cursor<Vec<u8>>,std::io::Error>{
let mut file=tokio::fs::File::open(path).await?; let mut file=tokio::fs::File::open(path).await?;
@ -120,8 +148,8 @@ impl UniqueAssets{
"Texture"=>accumulate_content_id(&mut self.textures,object,"Texture"), "Texture"=>accumulate_content_id(&mut self.textures,object,"Texture"),
"FileMesh"=>accumulate_content_id(&mut self.textures,object,"TextureId"), "FileMesh"=>accumulate_content_id(&mut self.textures,object,"TextureId"),
"MeshPart"=>{ "MeshPart"=>{
accumulate_content_id(&mut self.textures,object,"TextureID"); accumulate_content(&mut self.textures,object,"TextureContent");
accumulate_content_id(&mut self.meshes,object,"MeshId"); accumulate_content(&mut self.meshes,object,"MeshContent");
}, },
"SpecialMesh"=>accumulate_content_id(&mut self.meshes,object,"MeshId"), "SpecialMesh"=>accumulate_content_id(&mut self.meshes,object,"MeshId"),
"ParticleEmitter"=>accumulate_content_id(&mut self.textures,object,"Texture"), "ParticleEmitter"=>accumulate_content_id(&mut self.textures,object,"Texture"),
@ -165,16 +193,16 @@ enum DownloadType{
impl DownloadType{ impl DownloadType{
fn path(&self)->PathBuf{ fn path(&self)->PathBuf{
match self{ match self{
DownloadType::Texture(asset_id)=>format!("downloaded_textures/{}",asset_id.0.to_string()).into(), DownloadType::Texture(RobloxAssetId(asset_id))=>format!("downloaded_textures/{asset_id}").into(),
DownloadType::Mesh(asset_id)=>format!("meshes/{}",asset_id.0.to_string()).into(), DownloadType::Mesh(RobloxAssetId(asset_id))=>format!("meshes/{asset_id}").into(),
DownloadType::Union(asset_id)=>format!("unions/{}",asset_id.0.to_string()).into(), DownloadType::Union(RobloxAssetId(asset_id))=>format!("unions/{asset_id}").into(),
} }
} }
fn asset_id(&self)->u64{ fn asset_id(&self)->u64{
match self{ match self{
DownloadType::Texture(asset_id)=>asset_id.0, &DownloadType::Texture(RobloxAssetId(asset_id))=>asset_id,
DownloadType::Mesh(asset_id)=>asset_id.0, &DownloadType::Mesh(RobloxAssetId(asset_id))=>asset_id,
DownloadType::Union(asset_id)=>asset_id.0, &DownloadType::Union(RobloxAssetId(asset_id))=>asset_id,
} }
} }
} }
@ -191,9 +219,8 @@ struct Stats{
failed_downloads:u32, failed_downloads:u32,
timed_out_downloads:u32, timed_out_downloads:u32,
} }
async fn download_retry(stats:&mut Stats,context:&rbx_asset::cookie::CookieContext,download_instruction:DownloadType)->Result<DownloadResult,std::io::Error>{ async fn download_retry(stats:&mut Stats,context:&rbx_asset::cookie::Context,download_instruction:DownloadType)->Result<DownloadResult,std::io::Error>{
stats.total_assets+=1; stats.total_assets+=1;
let download_instruction=download_instruction;
// check if file exists on disk // check if file exists on disk
let path=download_instruction.path(); let path=download_instruction.path();
if tokio::fs::try_exists(path.as_path()).await?{ if tokio::fs::try_exists(path.as_path()).await?{
@ -213,10 +240,11 @@ async fn download_retry(stats:&mut Stats,context:&rbx_asset::cookie::CookieConte
match asset_result{ match asset_result{
Ok(asset_result)=>{ Ok(asset_result)=>{
stats.downloaded_assets+=1; stats.downloaded_assets+=1;
tokio::fs::write(path,&asset_result).await?; let data=asset_result.to_vec()?;
break Ok(DownloadResult::Data(asset_result)); tokio::fs::write(path,&data).await?;
break Ok(DownloadResult::Data(data));
}, },
Err(rbx_asset::cookie::GetError::Response(rbx_asset::ResponseError::StatusCodeWithUrlAndBody(scwuab)))=>{ Err(rbx_asset::cookie::GetError::Response(rbx_asset::types::ResponseError::StatusCodeWithUrlAndBody(scwuab)))=>{
if scwuab.status_code.as_u16()==429{ if scwuab.status_code.as_u16()==429{
if retry==12{ if retry==12{
println!("Giving up asset download {asset_id}"); println!("Giving up asset download {asset_id}");
@ -252,7 +280,7 @@ enum ConvertTextureError{
#[error("DDS write error {0:?}")] #[error("DDS write error {0:?}")]
DDSWrite(#[from]image_dds::ddsfile::Error), DDSWrite(#[from]image_dds::ddsfile::Error),
} }
async fn convert_texture(asset_id:RobloxAssetId,download_result:DownloadResult)->Result<(),ConvertTextureError>{ async fn convert_texture(RobloxAssetId(asset_id):RobloxAssetId,download_result:DownloadResult)->Result<(),ConvertTextureError>{
let data=match download_result{ let data=match download_result{
DownloadResult::Cached(path)=>tokio::fs::read(path).await?, DownloadResult::Cached(path)=>tokio::fs::read(path).await?,
DownloadResult::Data(data)=>data, DownloadResult::Data(data)=>data,
@ -277,7 +305,7 @@ async fn convert_texture(asset_id:RobloxAssetId,download_result:DownloadResult)-
image_dds::Mipmaps::GeneratedAutomatic, image_dds::Mipmaps::GeneratedAutomatic,
)?; )?;
let file_name=format!("textures/{}.dds",asset_id.0); let file_name=format!("textures/{asset_id}.dds");
let mut file=std::fs::File::create(file_name)?; let mut file=std::fs::File::create(file_name)?;
dds.write(&mut file)?; dds.write(&mut file)?;
Ok(()) Ok(())
@ -315,7 +343,7 @@ async fn download_assets(paths:Vec<PathBuf>,cookie:rbx_asset::cookie::Cookie)->A
// insert into global unique assets guy // insert into global unique assets guy
// add to download queue if the asset is globally unique and does not already exist on disk // add to download queue if the asset is globally unique and does not already exist on disk
let mut stats=Stats::default(); let mut stats=Stats::default();
let context=rbx_asset::cookie::CookieContext::new(cookie); let context=rbx_asset::cookie::Context::new(cookie);
let mut globally_unique_assets=UniqueAssets::default(); let mut globally_unique_assets=UniqueAssets::default();
// pop a job = retry_queue.pop_front() or ingest(recv.recv().await) // pop a job = retry_queue.pop_front() or ingest(recv.recv().await)
// SLOW MODE: // SLOW MODE:

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

@ -1,7 +1,7 @@
[package] [package]
name = "strafe-client" name = "strafe-client"
version = "0.11.0" version = "0.11.0"
edition = "2021" edition = "2024"
repository = "https://git.itzana.me/StrafesNET/strafe-project" repository = "https://git.itzana.me/StrafesNET/strafe-project"
license = "Custom" license = "Custom"
description = "StrafesNET game client for bhop and surf." description = "StrafesNET game client for bhop and surf."
@ -16,7 +16,7 @@ source = ["dep:strafesnet_deferred_loader", "dep:strafesnet_bsp_loader"]
roblox = ["dep:strafesnet_deferred_loader", "dep:strafesnet_rbx_loader"] roblox = ["dep:strafesnet_deferred_loader", "dep:strafesnet_rbx_loader"]
[dependencies] [dependencies]
glam = "0.29.0" glam = "0.30.0"
parking_lot = "0.12.1" parking_lot = "0.12.1"
pollster = "0.4.0" pollster = "0.4.0"
strafesnet_bsp_loader = { path = "../lib/bsp_loader", registry = "strafesnet", optional = true } strafesnet_bsp_loader = { path = "../lib/bsp_loader", registry = "strafesnet", optional = true }
@ -28,5 +28,5 @@ strafesnet_rbx_loader = { path = "../lib/rbx_loader", registry = "strafesnet", o
strafesnet_session = { path = "../engine/session", registry = "strafesnet" } strafesnet_session = { path = "../engine/session", registry = "strafesnet" }
strafesnet_settings = { path = "../engine/settings", registry = "strafesnet" } strafesnet_settings = { path = "../engine/settings", registry = "strafesnet" }
strafesnet_snf = { path = "../lib/snf", registry = "strafesnet", optional = true } strafesnet_snf = { path = "../lib/snf", registry = "strafesnet", optional = true }
wgpu = "24.0.0" wgpu = "25.0.0"
winit = "0.30.7" winit = "0.30.7"

@ -2,14 +2,12 @@ pub type QNWorker<'a,Task>=CompatNWorker<'a,Task>;
pub type INWorker<'a,Task>=CompatNWorker<'a,Task>; pub type INWorker<'a,Task>=CompatNWorker<'a,Task>;
pub struct CompatNWorker<'a,Task>{ pub struct CompatNWorker<'a,Task>{
data:std::marker::PhantomData<Task>,
f:Box<dyn FnMut(Task)+Send+'a>, f:Box<dyn FnMut(Task)+Send+'a>,
} }
impl<'a,Task> CompatNWorker<'a,Task>{ impl<'a,Task> CompatNWorker<'a,Task>{
pub fn new(f:impl FnMut(Task)+Send+'a)->CompatNWorker<'a,Task>{ pub fn new(f:impl FnMut(Task)+Send+'a)->CompatNWorker<'a,Task>{
Self{ Self{
data:std::marker::PhantomData,
f:Box::new(f), f:Box::new(f),
} }
} }

@ -117,7 +117,6 @@ impl<'a> SetupContextPartial3<'a>{
// Make sure we use the texture resolution limits from the adapter, so we can support images the size of the surface. // Make sure we use the texture resolution limits from the adapter, so we can support images the size of the surface.
let needed_limits=strafesnet_graphics::graphics::required_limits().using_resolution(self.adapter.limits()); let needed_limits=strafesnet_graphics::graphics::required_limits().using_resolution(self.adapter.limits());
let trace_dir=std::env::var("WGPU_TRACE");
let (device, queue)=pollster::block_on(self.adapter let (device, queue)=pollster::block_on(self.adapter
.request_device( .request_device(
&wgpu::DeviceDescriptor { &wgpu::DeviceDescriptor {
@ -125,8 +124,8 @@ impl<'a> SetupContextPartial3<'a>{
required_features: (optional_features & self.adapter.features()) | required_features, required_features: (optional_features & self.adapter.features()) | required_features,
required_limits: needed_limits, required_limits: needed_limits,
memory_hints:wgpu::MemoryHints::Performance, memory_hints:wgpu::MemoryHints::Performance,
trace: wgpu::Trace::Off,
}, },
trace_dir.ok().as_ref().map(std::path::Path::new),
)) ))
.expect("Unable to find a suitable GPU adapter!"); .expect("Unable to find a suitable GPU adapter!");

1
tools/dev Executable file

@ -0,0 +1 @@
RUST_BACKTRACE=1 mangohud ../target/debug/strafe-client "$@"