Compare commits

..

38 Commits

Author SHA1 Message Date
38fd812f52 rbxassetid: add crate stuff and test 2025-02-04 09:11:20 -08:00
8680d28671 snf: 0.3.0 bot files 2025-02-04 09:11:20 -08:00
35af659aa8 specify explicit versions 2025-02-04 09:11:20 -08:00
2e1a28bd51 fixed_wide: 0.1.2 2025-02-04 08:35:28 -08:00
8eca99fd9c bsp_loader: rewrite world models to use mesh builder 2025-02-03 17:17:04 -08:00
d504b82441 into_indices 2025-02-03 15:28:54 -08:00
6aeb06a263 bsp_loader: elid lifetime on convert_mesh 2025-02-03 15:01:22 -08:00
4c081c3646 MeshLoader != ModelLoader 2025-02-03 15:00:59 -08:00
2b92e78476 introduce BspFinder and go crazy with lifetimes 2025-02-03 14:37:30 -08:00
b8aa93e70e bsp_loader: more powerful mesh locate ability 2025-02-03 12:07:49 -08:00
f3d7d9b49f split bsp model loader into 2 stages 2025-02-03 11:11:45 -08:00
6df5ee1af3 use MeshBuilder in bsp_loader 2025-02-03 11:08:16 -08:00
ae48542626 refactor Loader trait to use generics 2025-02-03 10:02:15 -08:00
b4f1b017f7 update rbx_mesh 2025-01-31 10:35:11 -08:00
99bcf6d661 use render config from face description 2025-01-31 09:13:14 -08:00
4acef656e5 use const 2025-01-31 09:12:51 -08:00
f6aacca82c use idiomatic Option::or() 2025-01-31 09:12:43 -08:00
c48beced05 plumb decal into union convert 2025-01-31 09:12:18 -08:00
4313ce036c tweak primitives 2025-01-31 08:26:49 -08:00
d80039b890 include union symbolic link 2025-01-30 16:10:24 -08:00
88d17cf51d divide physics_data by size instead of using sizeless transform 2025-01-30 11:32:17 -08:00
b42b29b994 hooray 2025-01-30 10:55:01 -08:00
1f95c608f0 implement union loading with unsafe 2025-01-30 10:09:58 -08:00
196a5f2461 fix file 2025-01-30 10:09:42 -08:00
0a6b2423e2 generate union graphics mesh 2025-01-30 09:12:58 -08:00
15b8a2c54e rbx_loader: use mesh builder 2025-01-30 08:19:10 -08:00
139c5cc3b8 common: invent mesh builder 2025-01-30 08:18:57 -08:00
7e3eeed23f load good 2025-01-30 07:32:23 -08:00
82d68311fd the errors went away but im not done 2025-01-30 07:32:23 -08:00
77b41e7ebf tweak get_texture_description 2025-01-30 07:32:23 -08:00
d24baa365b data thing 2025-01-30 07:32:23 -08:00
3f5505e2b5 noice Directories idea 2025-01-30 07:32:23 -08:00
cd9cbc2f7f wip: convert unions 2025-01-30 07:32:23 -08:00
7e12e1eb49 wip: actually unions not fake 2025-01-30 07:32:23 -08:00
1d17385b19 get_texture_description function 2025-01-30 07:32:23 -08:00
40fff6cb8e store typed id directly in hashmaps 2025-01-30 07:32:23 -08:00
a279698a53 rbx_loader: load unions 2025-01-30 07:32:23 -08:00
8ed087c4c6 rbx_loader: update rbx_mesh 2025-01-30 07:32:23 -08:00
111 changed files with 2198 additions and 6947 deletions

2622
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -15,7 +15,6 @@ members = [
"lib/rbxassetid",
"lib/roblox_emulator",
"lib/snf",
"map-tool",
"strafe-client",
]
resolver = "2"
@@ -24,7 +23,3 @@ resolver = "2"
#lto = true
strip = true
codegen-units = 1
[profile.dev]
strip = false
opt-level = 3

View File

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

View File

@@ -103,26 +103,6 @@ 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{
pipelines:GraphicsPipelines,
bind_groups:GraphicsBindGroups,
@@ -987,3 +967,22 @@ impl GraphicsState{
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
}

View File

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

View File

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

View File

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

View File

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

View File

@@ -107,7 +107,6 @@ enum TransientAcceleration{
#[derive(Clone,Debug)]
struct ContactMoveState{
jump_direction:JumpDirection,
// TODO: this is bad data normalization, remove this
contact:ContactCollision,
target:TransientAcceleration,
}
@@ -118,8 +117,8 @@ impl TransientAcceleration{
}else{
//normal friction acceleration is clippedAcceleration.dot(normal)*friction
TransientAcceleration::Reachable{
acceleration:target_diff.with_length(accel).divide().wrap_1(),
time:time+Time::from((target_diff.length()/accel).divide().clamp_1())
acceleration:target_diff.with_length(accel).divide().fix_1(),
time:time+Time::from((target_diff.length()/accel).divide().fix_1())
}
}
}
@@ -408,7 +407,7 @@ impl HitboxMesh{
let transform=PhysicsMeshTransform::new(transform);
let transformed_mesh=TransformedMesh::new(mesh.complete_mesh_view(),&transform);
for vert in transformed_mesh.verts(){
aabb.grow(vert.narrow_1().unwrap());
aabb.grow(vert.fix_1());
}
Self{
halfsize:aabb.size()>>1,
@@ -461,12 +460,12 @@ impl StyleHelper for StyleModifiers{
}
fn get_y_control_dir(&self,camera:&PhysicsCamera,controls:Controls)->Planar64Vec3{
(camera.rotation_y()*self.get_control_dir(controls)).wrap_1()
(camera.rotation_y()*self.get_control_dir(controls)).fix_1()
}
fn get_propulsion_control_dir(&self,camera:&PhysicsCamera,controls:Controls)->Planar64Vec3{
//don't interpolate this! discrete mouse movement, constant acceleration
(camera.rotation()*self.get_control_dir(controls)).wrap_1()
(camera.rotation()*self.get_control_dir(controls)).fix_1()
}
fn calculate_mesh(&self)->HitboxMesh{
let mesh=match self.hitbox.mesh{
@@ -557,7 +556,7 @@ impl MoveState{
=>None,
}
}
fn next_move_instruction(&self,strafe:&Option<gameplay_style::StrafeSettings>,time:Time)->Option<TimedInstruction<InternalInstruction,Time>>{
fn next_move_instruction(&self,strafe:&Option<gameplay_style::StrafeSettings>,time:Time)->Option<TimedInstruction<InternalInstruction,TimeInner>>{
//check if you have a valid walk state and create an instruction
match self{
MoveState::Walk(walk_state)|MoveState::Ladder(walk_state)=>match &walk_state.target{
@@ -602,17 +601,12 @@ 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){
//TODO: be more precise about contacts
if set_velocity_cull(body,touching,models,hitbox_mesh,velocity){
// TODO do better
// TODO: unduplicate this code
//TODO do better
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){
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),
}
}
@@ -790,7 +784,7 @@ impl TouchingState{
}).collect();
crate::push_solve::push_solve(&contacts,acceleration)
}
fn predict_collision_end(&self,collector:&mut instruction::InstructionCollector<InternalInstruction,Time>,models:&PhysicsModels,hitbox_mesh:&HitboxMesh,body:&Body,start_time:Time){
fn predict_collision_end(&self,collector:&mut instruction::InstructionCollector<InternalInstruction,TimeInner>,models:&PhysicsModels,hitbox_mesh:&HitboxMesh,body:&Body,start_time:Time){
// let relative_body=body.relative_to(&Body::ZERO);
let relative_body=body;
for contact in &self.contacts{
@@ -884,7 +878,7 @@ impl PhysicsState{
fn reset_to_default(&mut self){
*self=Self::default();
}
fn next_move_instruction(&self)->Option<TimedInstruction<InternalInstruction,Time>>{
fn next_move_instruction(&self)->Option<TimedInstruction<InternalInstruction,TimeInner>>{
self.move_state.next_move_instruction(&self.style.strafe,self.time)
}
fn cull_velocity(&mut self,data:&PhysicsData,velocity:Planar64Vec3){
@@ -941,7 +935,7 @@ pub struct PhysicsData{
impl Default for PhysicsData{
fn default()->Self{
Self{
bvh:bvh::BvhNode::empty(),
bvh:bvh::BvhNode::default(),
models:Default::default(),
modes:Default::default(),
hitbox_mesh:StyleModifiers::default().calculate_mesh(),
@@ -956,21 +950,21 @@ pub struct PhysicsContext<'a>{
// the physics consumes both Instruction and PhysicsInternalInstruction,
// but can only emit PhysicsInternalInstruction
impl InstructionConsumer<InternalInstruction> for PhysicsContext<'_>{
type Time=Time;
fn process_instruction(&mut self,ins:TimedInstruction<InternalInstruction,Time>){
type TimeInner=TimeInner;
fn process_instruction(&mut self,ins:TimedInstruction<InternalInstruction,TimeInner>){
atomic_internal_instruction(&mut self.state,&self.data,ins)
}
}
impl InstructionConsumer<Instruction> for PhysicsContext<'_>{
type Time=Time;
fn process_instruction(&mut self,ins:TimedInstruction<Instruction,Time>){
type TimeInner=TimeInner;
fn process_instruction(&mut self,ins:TimedInstruction<Instruction,TimeInner>){
atomic_input_instruction(&mut self.state,&self.data,ins)
}
}
impl InstructionEmitter<InternalInstruction> for PhysicsContext<'_>{
type Time=Time;
type TimeInner=TimeInner;
//this little next instruction function could cache its return value and invalidate the cached value by watching the State.
fn next_instruction(&self,time_limit:Time)->Option<TimedInstruction<InternalInstruction,Time>>{
fn next_instruction(&self,time_limit:Time)->Option<TimedInstruction<InternalInstruction,TimeInner>>{
next_instruction_internal(&self.state,&self.data,time_limit)
}
}
@@ -978,7 +972,7 @@ impl PhysicsContext<'_>{
pub fn run_input_instruction(
state:&mut PhysicsState,
data:&PhysicsData,
instruction:TimedInstruction<Instruction,Time>
instruction:TimedInstruction<Instruction,TimeInner>
){
let mut context=PhysicsContext{state,data};
context.process_exhaustive(instruction.time);
@@ -988,7 +982,10 @@ impl PhysicsContext<'_>{
impl PhysicsData{
/// use with caution, this is the only non-instruction way to mess with physics
pub fn generate_models(&mut self,map:&map::CompleteMap){
let modes=map.modes.clone().denormalize();
let mut modes=map.modes.clone();
for mode in &mut modes.modes{
mode.denormalize_data();
}
let mut used_contact_attributes=Vec::new();
let mut used_intersect_attributes=Vec::new();
@@ -1006,30 +1003,31 @@ impl PhysicsData{
let mut used_meshes=Vec::new();
let mut physics_mesh_id_from_model_mesh_id=HashMap::<MeshId,PhysicsMeshId>::new();
for (model_id,model) in map.models.iter().enumerate(){
let attr_id=match physics_attr_id_from_model_attr_id.entry(model.attributes){
std::collections::hash_map::Entry::Occupied(entry)=>*entry.get(),
std::collections::hash_map::Entry::Vacant(entry)=>{
//check if it's real
match map.attributes.get(model.attributes.get() as usize).and_then(|m_attr|{
PhysicsCollisionAttributes::try_from(m_attr).map_or(None,|p_attr|{
let attr_id=match p_attr{
PhysicsCollisionAttributes::Contact(attr)=>{
let attr_id=ContactAttributesId::new(used_contact_attributes.len() as u32);
used_contact_attributes.push(attr);
PhysicsAttributesId::Contact(attr_id)
},
PhysicsCollisionAttributes::Intersect(attr)=>{
let attr_id=IntersectAttributesId::new(used_intersect_attributes.len() as u32);
used_intersect_attributes.push(attr);
PhysicsAttributesId::Intersect(attr_id)
},
};
Some(*entry.insert(attr_id))
})
}){
Some(attr_id)=>attr_id,
None=>continue,
}
//TODO: use .entry().or_insert_with(||{
let attr_id=if let Some(&attr_id)=physics_attr_id_from_model_attr_id.get(&model.attributes){
attr_id
}else{
//check if it's real
match map.attributes.get(model.attributes.get() as usize).and_then(|m_attr|{
PhysicsCollisionAttributes::try_from(m_attr).map_or(None,|p_attr|{
let attr_id=match p_attr{
PhysicsCollisionAttributes::Contact(attr)=>{
let attr_id=ContactAttributesId::new(used_contact_attributes.len() as u32);
used_contact_attributes.push(attr);
PhysicsAttributesId::Contact(attr_id)
},
PhysicsCollisionAttributes::Intersect(attr)=>{
let attr_id=IntersectAttributesId::new(used_intersect_attributes.len() as u32);
used_intersect_attributes.push(attr);
PhysicsAttributesId::Intersect(attr_id)
},
};
physics_attr_id_from_model_attr_id.insert(model.attributes,attr_id);
Some(attr_id)
})
}){
Some(attr_id)=>attr_id,
None=>continue,
}
};
let mesh_id=if let Some(&mesh_id)=physics_mesh_id_from_model_mesh_id.get(&model.mesh){
@@ -1089,7 +1087,7 @@ impl PhysicsData{
let mut aabb=aabb::Aabb::default();
let transformed_mesh=TransformedMesh::new(view,transform);
for v in transformed_mesh.verts(){
aabb.grow(v.narrow_1().unwrap());
aabb.grow(v.fix_1());
}
(ConvexMeshId{
model_id,
@@ -1123,7 +1121,7 @@ impl PhysicsData{
}
//this is the one who asks
fn next_instruction_internal(state:&PhysicsState,data:&PhysicsData,time_limit:Time)->Option<TimedInstruction<InternalInstruction,Time>>{
fn next_instruction_internal(state:&PhysicsState,data:&PhysicsData,time_limit:Time)->Option<TimedInstruction<InternalInstruction,TimeInner>>{
//JUST POLLING!!! NO MUTATION
let mut collector=instruction::InstructionCollector::new(time_limit);
@@ -1138,7 +1136,7 @@ impl PhysicsData{
//relative to moving platforms
//let relative_body=state.body.relative_to(&Body::ZERO);
let relative_body=&state.body;
data.bvh.sample_aabb(&aabb,&mut |&convex_mesh_id|{
data.bvh.the_tester(&aabb,&mut |&convex_mesh_id|{
//no checks are needed because of the time limits.
let model_mesh=data.models.mesh(convex_mesh_id);
let minkowski=model_physics::MinkowskiMesh::minkowski_sum(model_mesh,data.hitbox_mesh.transformed_mesh());
@@ -1167,8 +1165,7 @@ fn contact_normal(models:&PhysicsModels,hitbox_mesh:&HitboxMesh,contact:&Contact
let model_mesh=models.contact_mesh(contact);
let minkowski=model_physics::MinkowskiMesh::minkowski_sum(model_mesh,hitbox_mesh.transformed_mesh());
// TODO: normalize to i64::MAX>>1
// wrap for speed
minkowski.face_nd(contact.face_id).0.wrap_1()
minkowski.face_nd(contact.face_id).0.fix_1()
}
fn recalculate_touching(
@@ -1201,7 +1198,7 @@ fn recalculate_touching(
aabb.inflate(hitbox_mesh.halfsize);
//relative to moving platforms
//let relative_body=state.body.relative_to(&Body::ZERO);
bvh.sample_aabb(&aabb,&mut |&convex_mesh_id|{
bvh.the_tester(&aabb,&mut |&convex_mesh_id|{
//no checks are needed because of the time limits.
let model_mesh=models.mesh(convex_mesh_id);
let minkowski=model_physics::MinkowskiMesh::minkowski_sum(model_mesh,hitbox_mesh.transformed_mesh());
@@ -1262,7 +1259,7 @@ fn set_velocity_cull(body:&mut Body,touching:&mut TouchingState,models:&PhysicsM
culled
}
fn set_velocity(body:&mut Body,touching:&TouchingState,models:&PhysicsModels,hitbox_mesh:&HitboxMesh,v:Planar64Vec3){
body.velocity=touching.constrain_velocity(models,hitbox_mesh,v);
body.velocity=touching.constrain_velocity(models,hitbox_mesh,v);;
}
fn set_acceleration_cull(body:&mut Body,touching:&mut TouchingState,models:&PhysicsModels,hitbox_mesh:&HitboxMesh,a:Planar64Vec3)->bool{
//This is not correct but is better than what I have
@@ -1327,7 +1324,7 @@ fn teleport_to_spawn(
const EPSILON:Planar64=Planar64::raw((1<<32)/16);
let transform=models.get_model_transform(spawn_model_id).ok_or(TeleportToSpawnError::NoModel)?;
//TODO: transform.vertex.matrix3.col(1)+transform.vertex.translation
let point=transform.vertex.transform_point3(vec3::Y).clamp_1()+Planar64Vec3::new([Planar64::ZERO,style.hitbox.halfsize.y+EPSILON,Planar64::ZERO]);
let point=transform.vertex.transform_point3(vec3::Y).fix_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);
Ok(())
}
@@ -1491,7 +1488,7 @@ fn collision_start_contact(
Some(gameplay_attributes::ContactingBehaviour::Surf)=>(),
Some(gameplay_attributes::ContactingBehaviour::Cling)=>println!("Unimplemented!"),
&Some(gameplay_attributes::ContactingBehaviour::Elastic(elasticity))=>{
let reflected_velocity=body.velocity+((body.velocity-incident_velocity)*Planar64::raw(elasticity as i64+1)).wrap_1();
let reflected_velocity=body.velocity+((body.velocity-incident_velocity)*Planar64::raw(elasticity as i64+1)).fix_1();
set_velocity(body,touching,models,hitbox_mesh,reflected_velocity);
},
Some(gameplay_attributes::ContactingBehaviour::Ladder(contacting_ladder))=>
@@ -1625,14 +1622,10 @@ fn collision_end_contact(
//TODO do better
//this is inner code from move_state.cull_velocity
match move_state.get_walk_state(){
// did you stop touching the thing you were walking on?
//did you stop touching the thing you were walking on?
Some(walk_state)=>if walk_state.contact==contact{
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),
}
}
@@ -1658,7 +1651,7 @@ fn collision_end_intersect(
}
}
}
fn atomic_internal_instruction(state:&mut PhysicsState,data:&PhysicsData,ins:TimedInstruction<InternalInstruction,Time>){
fn atomic_internal_instruction(state:&mut PhysicsState,data:&PhysicsData,ins:TimedInstruction<InternalInstruction,TimeInner>){
state.time=ins.time;
let (should_advance_body,goober_time)=match ins.instruction{
InternalInstruction::CollisionStart(_,dt)
@@ -1718,7 +1711,7 @@ fn atomic_internal_instruction(state:&mut PhysicsState,data:&PhysicsData,ins:Tim
let control_dir=state.style.get_control_dir(masked_controls);
if control_dir!=vec3::ZERO{
let camera_mat=state.camera.simulate_move_rotation_y(state.input_state.lerp_delta(state.time).x);
if let Some(ticked_velocity)=strafe_settings.tick_velocity(state.body.velocity,(camera_mat*control_dir).with_length(Planar64::ONE).divide().wrap_1()){
if let Some(ticked_velocity)=strafe_settings.tick_velocity(state.body.velocity,(camera_mat*control_dir).with_length(Planar64::ONE).divide().fix_1()){
//this is wrong but will work ig
//need to note which push planes activate in push solve and keep those
state.cull_velocity(data,ticked_velocity);
@@ -1754,7 +1747,7 @@ fn atomic_internal_instruction(state:&mut PhysicsState,data:&PhysicsData,ins:Tim
}
}
fn atomic_input_instruction(state:&mut PhysicsState,data:&PhysicsData,ins:TimedInstruction<Instruction,Time>){
fn atomic_input_instruction(state:&mut PhysicsState,data:&PhysicsData,ins:TimedInstruction<Instruction,TimeInner>){
state.time=ins.time;
let should_advance_body=match ins.instruction{
//the body may as well be a quantum wave function
@@ -1821,18 +1814,14 @@ fn atomic_input_instruction(state:&mut PhysicsState,data:&PhysicsData,ins:TimedI
},
Instruction::Mode(ModeInstruction::Restart(mode_id))=>{
//teleport to mode start zone
let mut spawn_point=vec3::ZERO;
let mode=data.modes.get_mode(mode_id);
if let Some(mode)=mode{
// set style
state.style=mode.get_style().clone();
let spawn_point=mode.and_then(|mode|
//TODO: spawn at the bottom of the start zone plus the hitbox size
//TODO: set camera angles to face the same way as the start zone
if let Some(transform)=data.models.get_model_transform(mode.get_start()){
// NOTE: this value may be 0,0,0 on source maps
spawn_point=transform.vertex.translation;
}
}
//TODO: set camera andles to face the same way as the start zone
data.models.get_model_transform(mode.get_start().into()).map(|transform|
transform.vertex.translation
)
).unwrap_or(vec3::ZERO);
set_position(spawn_point,&mut state.move_state,&mut state.body,&mut state.touching,&mut state.run,&mut state.mode_state,mode,&data.models,&data.hitbox_mesh,&data.bvh,&state.style,&state.camera,&state.input_state,state.time);
set_velocity(&mut state.body,&state.touching,&data.models,&data.hitbox_mesh,vec3::ZERO);
state.set_move_state(data,MoveState::Air);
@@ -1842,9 +1831,6 @@ fn atomic_input_instruction(state:&mut PhysicsState,data:&PhysicsData,ins:TimedI
Instruction::Mode(ModeInstruction::Spawn(mode_id,stage_id))=>{
//spawn at a particular stage
if let Some(mode)=data.modes.get_mode(mode_id){
// set style
state.style=mode.get_style().clone();
// teleport to stage in mode
if let Some(stage)=mode.get_stage(stage_id){
let _=teleport_to_spawn(
stage.spawn(),

View File

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

View File

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

View File

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

View File

@@ -88,11 +88,11 @@ impl Simulation{
#[derive(Default)]
pub struct Recording{
instructions:Vec<TimedInstruction<PhysicsInputInstruction,PhysicsTime>>,
instructions:Vec<TimedInstruction<PhysicsInputInstruction,PhysicsTimeInner>>,
}
impl Recording{
pub fn new(
instructions:Vec<TimedInstruction<PhysicsInputInstruction,PhysicsTime>>,
instructions:Vec<TimedInstruction<PhysicsInputInstruction,PhysicsTimeInner>>,
)->Self{
Self{instructions}
}
@@ -207,8 +207,8 @@ impl Session{
// Session emits DoStep
impl InstructionConsumer<Instruction<'_>> for Session{
type Time=SessionTime;
fn process_instruction(&mut self,ins:TimedInstruction<Instruction,Self::Time>){
type TimeInner=SessionTimeInner;
fn process_instruction(&mut self,ins:TimedInstruction<Instruction,Self::TimeInner>){
// repetitive procedure macro
macro_rules! run_mouse_interpolator_instruction{
($instruction:expr)=>{
@@ -425,8 +425,8 @@ impl InstructionConsumer<Instruction<'_>> for Session{
}
}
impl InstructionConsumer<StepInstruction> for Session{
type Time=SessionTime;
fn process_instruction(&mut self,ins:TimedInstruction<StepInstruction,Self::Time>){
type TimeInner=SessionTimeInner;
fn process_instruction(&mut self,ins:TimedInstruction<StepInstruction,Self::TimeInner>){
let time=self.simulation.timer.time(ins.time);
if let Some(instruction)=self.mouse_interpolator.pop_buffered_instruction(ins.set_time(time)){
//record
@@ -436,8 +436,8 @@ impl InstructionConsumer<StepInstruction> for Session{
}
}
impl InstructionEmitter<StepInstruction> for Session{
type Time=SessionTime;
fn next_instruction(&self,time_limit:SessionTime)->Option<TimedInstruction<StepInstruction,Self::Time>>{
type TimeInner=SessionTimeInner;
fn next_instruction(&self,time_limit:SessionTime)->Option<TimedInstruction<StepInstruction,Self::TimeInner>>{
self.mouse_interpolator.next_instruction(time_limit)
}
}

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,343 +0,0 @@
use strafesnet_common::integer::Planar64;
use strafesnet_common::{model,integer};
use strafesnet_common::integer::{vec3::Vector3,Fixed,Ratio};
use crate::{valve_transform_normal,valve_transform_dist};
#[derive(Hash,Eq,PartialEq)]
struct Face{
normal:integer::Planar64Vec3,
dot:integer::Planar64,
}
#[derive(Debug)]
struct Faces{
faces:Vec<Vec<integer::Planar64Vec3>>,
}
fn solve3(c0:&Face,c1:&Face,c2:&Face)->Option<Ratio<Vector3<Fixed<3,96>>,Fixed<3,96>>>{
let n0_n1=c0.normal.cross(c1.normal);
let det=c2.normal.dot(n0_n1);
if det.abs().is_zero(){
return None;
}
Some((
c1.normal.cross(c2.normal)*c0.dot
+c2.normal.cross(c0.normal)*c1.dot
+c0.normal.cross(c1.normal)*c2.dot
)/det)
}
#[derive(Debug)]
pub enum PlanesToFacesError{
InitFace1,
InitFace2,
InitIntersection,
FindNewIntersection,
// Narrow(strafesnet_common::integer::NarrowError),
EmptyFaces,
InfiniteLoop1,
InfiniteLoop2,
}
impl std::fmt::Display for PlanesToFacesError{
fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{
write!(f,"{self:?}")
}
}
impl core::error::Error for PlanesToFacesError{}
fn planes_to_faces(face_list:std::collections::HashSet<Face>)->Result<Faces,PlanesToFacesError>{
let mut faces=Vec::new();
// for each face, determine one edge at a time until you complete the face
'face: for face0 in &face_list{
// 1. find first edge
// 2. follow edges around face
// === finding first edge ===
// 1. pick the most perpendicular set of 3 faces
// 2. check if any faces occlude the intersection
// 3. use this test to replace left and right alternating until they are not occluded
// find the most perpendicular face to face0
let mut face1=face_list.iter().min_by_key(|&p|{
face0.normal.dot(p.normal).abs()
}).ok_or(PlanesToFacesError::InitFace1)?;
// direction of edge formed by face0 x face1
let edge_dir=face0.normal.cross(face1.normal);
// find the most perpendicular face to both face0 and face1
let mut face2=face_list.iter().max_by_key(|&p|{
// find the best *oriented* face (no .abs())
edge_dir.dot(p.normal)
}).ok_or(PlanesToFacesError::InitFace2)?;
let mut detect_loop=200u8;
let mut intersection=solve3(face0,face1,face2).ok_or(PlanesToFacesError::InitIntersection)?;
// repeatedly update face1, face2 until all faces form part of the convex solid
'find: loop{
detect_loop=detect_loop.checked_sub(1).ok_or(PlanesToFacesError::InfiniteLoop1)?;
// test if any *other* faces occlude the intersection
for new_face in &face_list{
// new face occludes intersection point
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
// dont' try to replace face0 because we are exploring that face in particular
if let Some(new_intersection)=solve3(face0,new_face,face2){
// face1 does not occlude (or intersect) the new intersection
if (face1.dot.widen_2()/Planar64::ONE).gt_ratio(face1.normal.dot(new_intersection.num)/new_intersection.den){
face1=new_face;
intersection=new_intersection;
continue 'find;
}
}
if let Some(new_intersection)=solve3(face0,face1,new_face){
// face2 does not occlude (or intersect) the new intersection
if (face2.dot.widen_2()/Planar64::ONE).gt_ratio(face2.normal.dot(new_intersection.num)/new_intersection.den){
face2=new_face;
intersection=new_intersection;
continue 'find;
}
}
}
}
// we have found a set of faces for which the intersection is on the convex solid
break 'find;
}
// check if face0 must go, meaning it is a degenerate face and does not contribute anything to the convex solid
for new_face in &face_list{
if core::ptr::eq(face0,new_face){
continue;
}
if core::ptr::eq(face1,new_face){
continue;
}
if core::ptr::eq(face2,new_face){
continue;
}
// new_face occludes intersection meaning intersection is not on convex solid and face0 is degenrate
if (new_face.dot.widen_2()/Planar64::ONE).lt_ratio(new_face.normal.dot(intersection.num)/intersection.den){
// abort! reject face0 entirely
continue 'face;
}
}
// === follow edges around face ===
// Note that we chose face2 such that the 3 faces create a particular winding order.
// If we choose a consistent face to follow (face1, face2) it will always wind with a consistent chirality
let mut detect_loop=200u8;
// keep looping until we meet this face again
let face1=face1;
let mut face=Vec::new();
loop{
// push point onto vertices
// problem: this may push a vertex that does not fit in the fixed point range and is thus meaningless
face.push(intersection.divide().narrow_1().unwrap());
// we looped back around to face1, we're done!
if core::ptr::eq(face1,face2){
break;
}
// the measure
let edge_dir=face0.normal.cross(face2.normal);
// the dot product to beat
let d_intersection=edge_dir.dot(intersection.num)/intersection.den;
// find the next face moving clockwise around face0
let (new_face,new_intersection,_)=face_list.iter().filter_map(|new_face|{
// ignore faces that are part of the current edge
if core::ptr::eq(face0,new_face)
|core::ptr::eq(face2,new_face){
return None;
}
let new_intersection=solve3(face0,face2,new_face)?;
// the d value must be larger
let d_new_intersection=edge_dir.dot(new_intersection.num)/new_intersection.den;
if d_new_intersection.le_ratio(d_intersection){
return None;
}
Some((new_face,new_intersection,d_new_intersection))
}).min_by_key(|&(_,_,d)|d).ok_or(PlanesToFacesError::FindNewIntersection)?;
face2=new_face;
intersection=new_intersection;
detect_loop=detect_loop.checked_sub(1).ok_or(PlanesToFacesError::InfiniteLoop2)?;
}
faces.push(face);
}
if faces.is_empty(){
Err(PlanesToFacesError::EmptyFaces)
}else{
Ok(Faces{
faces,
})
}
}
#[allow(dead_code)]
#[derive(Debug)]
pub enum BrushToMeshError{
SliceBrushSides,
MissingPlane,
InvalidFaceCount{
count:usize,
},
InvalidPlanes(PlanesToFacesError),
}
impl std::fmt::Display for BrushToMeshError{
fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{
write!(f,"{self:?}")
}
}
impl core::error::Error for BrushToMeshError{}
pub fn faces_to_mesh(faces:Vec<Vec<integer::Planar64Vec3>>)->model::Mesh{
// generate the mesh
let mut mb=model::MeshBuilder::new();
let color=mb.acquire_color_id(glam::Vec4::ONE);
let tex=mb.acquire_tex_id(glam::Vec2::ZERO);
// normals are ignored by physics
let normal=mb.acquire_normal_id(integer::vec3::ZERO);
let polygon_list=faces.into_iter().map(|face|{
face.into_iter().map(|pos|{
let pos=mb.acquire_pos_id(pos);
mb.acquire_vertex_id(model::IndexedVertex{
pos,
tex,
normal,
color,
})
}).collect()
}).collect();
let polygon_groups=vec![model::PolygonGroup::PolygonList(model::PolygonList::new(polygon_list))];
let physics_groups=vec![model::IndexedPhysicsGroup{
groups:vec![model::PolygonGroupId::new(0)],
}];
let graphics_groups=vec![];
mb.build(polygon_groups,graphics_groups,physics_groups)
}
pub fn brush_to_mesh(bsp:&vbsp::Bsp,brush:&vbsp::Brush)->Result<model::Mesh,BrushToMeshError>{
let brush_start_idx=brush.brush_side as usize;
let sides_range=brush_start_idx..brush_start_idx+brush.num_brush_sides as usize;
let sides=bsp.brush_sides.get(sides_range).ok_or(BrushToMeshError::SliceBrushSides)?;
let face_list=sides.iter().map(|side|{
// The so-called tumor brushes have TRIGGER bit set
// but also ignore visleaf hint and skip sides
const TUMOR:vbsp::TextureFlags=vbsp::TextureFlags::HINT.union(vbsp::TextureFlags::SKIP).union(vbsp::TextureFlags::TRIGGER);
if let Some(texture_info)=bsp.texture_info(side.texture_info as usize){
if texture_info.flags.intersects(TUMOR){
return None;
}
}
let plane=bsp.plane(side.plane as usize)?;
Some(Face{
normal:valve_transform_normal(plane.normal.into()),
dot:valve_transform_dist(plane.dist.into()),
})
}).collect::<Option<std::collections::HashSet<_>>>().ok_or(BrushToMeshError::MissingPlane)?;
if face_list.len()<4{
return Err(BrushToMeshError::InvalidFaceCount{count:face_list.len()});
}
let faces=planes_to_faces(face_list).map_err(BrushToMeshError::InvalidPlanes)?;
let mesh=faces_to_mesh(faces.faces);
Ok(mesh)
}
pub fn unit_cube()->model::Mesh{
let face_list=[
Face{normal:integer::vec3::X,dot:Planar64::ONE},
Face{normal:integer::vec3::Y,dot:Planar64::ONE},
Face{normal:integer::vec3::Z,dot:Planar64::ONE},
Face{normal:integer::vec3::NEG_X,dot:Planar64::ONE},
Face{normal:integer::vec3::NEG_Y,dot:Planar64::ONE},
Face{normal:integer::vec3::NEG_Z,dot:Planar64::ONE},
].into_iter().collect();
let faces=planes_to_faces(face_list).unwrap();
let mesh=faces_to_mesh(faces.faces);
mesh
}
#[cfg(test)]
mod test{
use super::*;
#[test]
fn test_cube(){
let face_list=[
Face{normal:integer::vec3::X,dot:Planar64::ONE},
Face{normal:integer::vec3::Y,dot:Planar64::ONE},
Face{normal:integer::vec3::Z,dot:Planar64::ONE},
Face{normal:integer::vec3::NEG_X,dot:Planar64::ONE},
Face{normal:integer::vec3::NEG_Y,dot:Planar64::ONE},
Face{normal:integer::vec3::NEG_Z,dot:Planar64::ONE},
].into_iter().collect();
let faces=planes_to_faces(face_list).unwrap();
assert_eq!(faces.faces.len(),6);
dbg!(faces);
}
#[test]
fn test_cube_with_degernate_face(){
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_Z,dot:Planar64::EPSILON},
].into_iter().collect();
let faces=planes_to_faces(face_list).unwrap();
assert_eq!(faces.faces.len(),6);
dbg!(faces);
}
#[test]
fn test_cube_with_degernate_face2(){
let face_list=[
Face{normal:integer::vec3::X,dot:Planar64::ONE},
Face{normal:integer::vec3::Y,dot:Planar64::ONE},
Face{normal:integer::vec3::Z,dot:Planar64::ONE},
Face{normal:integer::vec3::NEG_X,dot:Planar64::ONE},
Face{normal:integer::vec3::NEG_Y,dot:Planar64::ONE},
Face{normal:integer::vec3::NEG_Z,dot:Planar64::ONE},
Face{normal:integer::vec3::NEG_X+integer::vec3::NEG_Z,dot:-Planar64::EPSILON},
].into_iter().collect();
let faces=planes_to_faces(face_list).unwrap();
assert_eq!(faces.faces.len(),5);
dbg!(faces);
}
#[test]
fn test_cube_with_degernate_face3(){
let face_list=[
Face{normal:integer::vec3::X,dot:Planar64::ONE},
Face{normal:integer::vec3::Y,dot:Planar64::ONE},
Face{normal:integer::vec3::Z,dot:Planar64::ONE},
Face{normal:integer::vec3::NEG_X,dot:Planar64::ONE},
Face{normal:integer::vec3::NEG_Y,dot:Planar64::ONE},
Face{normal:integer::vec3::NEG_Z,dot:Planar64::ONE},
Face{normal:integer::vec3::NEG_X+integer::vec3::NEG_Z,dot:Planar64::EPSILON},
].into_iter().collect();
let faces=planes_to_faces(face_list).unwrap();
assert_eq!(faces.faces.len(),7);
dbg!(faces);
}
}

View File

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

View File

@@ -2,16 +2,9 @@ use strafesnet_deferred_loader::deferred_loader::{LoadFailureMode,MeshDeferredLo
mod bsp;
mod mesh;
mod brush;
pub mod loader;
const VALVE_SCALE:f32=1.0/16.0;
pub(crate) fn valve_transform_dist(d:f32)->strafesnet_common::integer::Planar64{
(d*VALVE_SCALE).try_into().unwrap()
}
pub(crate) fn valve_transform_normal([x,y,z]:[f32;3])->strafesnet_common::integer::Planar64Vec3{
strafesnet_common::integer::vec3::try_from_f32_array([x,z,-y]).unwrap()
}
pub(crate) fn valve_transform([x,y,z]:[f32;3])->strafesnet_common::integer::Planar64Vec3{
strafesnet_common::integer::vec3::try_from_f32_array([x*VALVE_SCALE,z*VALVE_SCALE,-y*VALVE_SCALE]).unwrap()
}
@@ -50,13 +43,10 @@ impl From<loader::MeshError> for LoadError{
Self::Mesh(value)
}
}
pub struct Bsp{
bsp:vbsp::Bsp,
case_folded_file_names:std::collections::HashMap<String,String>,
}
pub struct Bsp(vbsp::Bsp);
impl AsRef<vbsp::Bsp> for Bsp{
fn as_ref(&self)->&vbsp::Bsp{
&self.bsp
&self.0
}
}
@@ -69,22 +59,10 @@ pub fn read<R:std::io::Read>(mut input:R)->Result<Bsp,ReadError>{
vbsp::Bsp::read(s.as_slice()).map(Bsp::new).map_err(ReadError::Bsp)
}
impl Bsp{
pub fn new(bsp:vbsp::Bsp)->Self{
let case_folded_file_names=bsp.pack.clone().into_zip().lock().unwrap().file_names().map(|s|{
(s.to_lowercase(),s.to_owned())
}).collect();
Self{
bsp,
case_folded_file_names,
}
pub const fn new(value:vbsp::Bsp)->Self{
Self(value)
}
pub fn pack_get(&self,name_lowercase:&str)->Result<Option<Vec<u8>>,vbsp::BspError>{
match self.case_folded_file_names.get(name_lowercase){
Some(name_folded)=>self.bsp.pack.get(name_folded),
None=>Ok(None),
}
}
pub fn to_snf(&self,failure_mode:LoadFailureMode,vpk_list:&[Vpk])->Result<strafesnet_common::map::CompleteMap,LoadError>{
pub fn to_snf(&self,failure_mode:LoadFailureMode,vpk_list:&[vpk::VPK])->Result<strafesnet_common::map::CompleteMap,LoadError>{
let mut texture_deferred_loader=RenderConfigDeferredLoader::new();
let mut mesh_deferred_loader=MeshDeferredLoader::new();
@@ -107,27 +85,3 @@ impl Bsp{
Ok(map)
}
}
pub struct Vpk{
vpk:vpk::VPK,
case_folded_file_names:std::collections::HashMap<String,String>,
}
impl AsRef<vpk::VPK> for Vpk{
fn as_ref(&self)->&vpk::VPK{
&self.vpk
}
}
impl Vpk{
pub fn new(vpk:vpk::VPK)->Vpk{
let case_folded_file_names=vpk.tree.keys().map(|s|{
(s.to_lowercase(),s.to_owned())
}).collect();
Vpk{
vpk,
case_folded_file_names,
}
}
pub fn tree_get(&self,name_lowercase:&str)->Option<&vpk::entry::VPKEntry>{
let name_folded=self.case_folded_file_names.get(name_lowercase)?;
self.vpk.tree.get(name_folded)
}
}

View File

@@ -3,7 +3,7 @@ use std::{borrow::Cow, io::Read};
use strafesnet_common::model::Mesh;
use strafesnet_deferred_loader::{loader::Loader,texture::Texture};
use crate::{Bsp,Vpk};
use crate::Bsp;
#[allow(dead_code)]
#[derive(Debug)]
@@ -22,17 +22,17 @@ impl From<std::io::Error> for TextureError{
}
}
pub struct TextureLoader;
impl TextureLoader{
pub struct TextureLoader<'a>(std::marker::PhantomData<&'a ()>);
impl TextureLoader<'_>{
pub fn new()->Self{
Self
Self(std::marker::PhantomData)
}
}
impl Loader for TextureLoader{
impl<'a> Loader for TextureLoader<'a>{
type Error=TextureError;
type Index<'a>=Cow<'a,str>;
type Index=Cow<'a,str>;
type Resource=Texture;
fn load<'a>(&mut self,index:Self::Index<'a>)->Result<Self::Resource,Self::Error>{
fn load(&mut self,index:Self::Index)->Result<Self::Resource,Self::Error>{
let file_name=format!("textures/{}.dds",index);
let mut file=std::fs::File::open(file_name)?;
let mut data=Vec::new();
@@ -47,7 +47,7 @@ pub enum MeshError{
Io(std::io::Error),
VMDL(vmdl::ModelError),
VBSP(vbsp::BspError),
MissingMdl(String),
MissingMdl,
MissingVtx,
MissingVvd,
}
@@ -76,7 +76,7 @@ impl From<vbsp::BspError> for MeshError{
#[derive(Clone,Copy)]
pub struct BspFinder<'bsp,'vpk>{
pub bsp:&'bsp Bsp,
pub vpks:&'vpk [Vpk],
pub vpks:&'vpk [vpk::VPK],
}
impl<'bsp,'vpk> BspFinder<'bsp,'vpk>{
pub fn find<'a>(&self,path:&str)->Result<Option<Cow<'a,[u8]>>,vbsp::BspError>
@@ -85,13 +85,13 @@ impl<'bsp,'vpk> BspFinder<'bsp,'vpk>{
'vpk:'a,
{
// search bsp
if let Some(data)=self.bsp.pack_get(path)?{
if let Some(data)=self.bsp.as_ref().pack.get(path)?{
return Ok(Some(Cow::Owned(data)));
}
//search each vpk
for vpk in self.vpks{
if let Some(vpk_entry)=vpk.tree_get(path){
if let Some(vpk_entry)=vpk.tree.get(path){
return Ok(Some(vpk_entry.get()?));
}
}
@@ -100,24 +100,30 @@ impl<'bsp,'vpk> BspFinder<'bsp,'vpk>{
}
}
pub struct ModelLoader<'bsp,'vpk>{
pub struct ModelLoader<'bsp,'vpk,'a>{
finder:BspFinder<'bsp,'vpk>,
life:core::marker::PhantomData<&'a ()>,
}
impl ModelLoader<'_,'_>{
impl ModelLoader<'_,'_,'_>{
#[inline]
pub const fn new<'bsp,'vpk>(
pub const fn new<'bsp,'vpk,'a>(
finder:BspFinder<'bsp,'vpk>,
)->ModelLoader<'bsp,'vpk>{
)->ModelLoader<'bsp,'vpk,'a>{
ModelLoader{
finder,
life:core::marker::PhantomData,
}
}
}
impl<'bsp,'vpk> Loader for ModelLoader<'bsp,'vpk>{
impl<'bsp,'vpk,'a> Loader for ModelLoader<'bsp,'vpk,'a>
where
'bsp:'a,
'vpk:'a,
{
type Error=MeshError;
type Index<'a>=&'a str where Self:'a;
type Index=&'a str;
type Resource=vmdl::Model;
fn load<'a>(&'a mut self,index:Self::Index<'a>)->Result<Self::Resource,Self::Error>{
fn load(&mut self,index:Self::Index)->Result<Self::Resource,Self::Error>{
let mdl_path_lower=index.to_lowercase();
//.mdl, .vvd, .dx90.vtx
let path=std::path::PathBuf::from(mdl_path_lower.as_str());
@@ -126,7 +132,7 @@ impl<'bsp,'vpk> Loader for ModelLoader<'bsp,'vpk>{
vvd_path.set_extension("vvd");
vtx_path.set_extension("dx90.vtx");
// TODO: search more packs, possibly using an index of multiple packs
let mdl=self.finder.find(mdl_path_lower.as_str())?.ok_or(MeshError::MissingMdl(mdl_path_lower))?;
let mdl=self.finder.find(mdl_path_lower.as_str())?.ok_or(MeshError::MissingMdl)?;
let vtx=self.finder.find(vtx_path.as_os_str().to_str().unwrap())?.ok_or(MeshError::MissingVtx)?;
let vvd=self.finder.find(vvd_path.as_os_str().to_str().unwrap())?.ok_or(MeshError::MissingVvd)?;
Ok(vmdl::Model::from_parts(
@@ -137,27 +143,31 @@ impl<'bsp,'vpk> Loader for ModelLoader<'bsp,'vpk>{
}
}
pub struct MeshLoader<'bsp,'vpk,'load,'str>{
pub struct MeshLoader<'bsp,'vpk,'load,'a>{
finder:BspFinder<'bsp,'vpk>,
deferred_loader:&'load mut strafesnet_deferred_loader::deferred_loader::RenderConfigDeferredLoader<Cow<'str,str>>,
deferred_loader:&'load mut strafesnet_deferred_loader::deferred_loader::RenderConfigDeferredLoader<Cow<'a,str>>,
}
impl MeshLoader<'_,'_,'_,'_>{
#[inline]
pub const fn new<'bsp,'vpk,'load,'str>(
pub const fn new<'bsp,'vpk,'load,'a>(
finder:BspFinder<'bsp,'vpk>,
deferred_loader:&'load mut strafesnet_deferred_loader::deferred_loader::RenderConfigDeferredLoader<Cow<'str,str>>,
)->MeshLoader<'bsp,'vpk,'load,'str>{
deferred_loader:&'load mut strafesnet_deferred_loader::deferred_loader::RenderConfigDeferredLoader<Cow<'a,str>>,
)->MeshLoader<'bsp,'vpk,'load,'a>{
MeshLoader{
finder,
deferred_loader
}
}
}
impl<'str,'bsp,'vpk,'load> Loader for MeshLoader<'bsp,'vpk,'load,'str>{
impl<'bsp,'vpk,'load,'a> Loader for MeshLoader<'bsp,'vpk,'load,'a>
where
'bsp:'a,
'vpk:'a,
{
type Error=MeshError;
type Index<'a>=&'a str where Self:'a;
type Index=&'a str;
type Resource=Mesh;
fn load<'a>(&'a mut self,index:Self::Index<'a>)->Result<Self::Resource,Self::Error>{
fn load(&mut self,index:Self::Index)->Result<Self::Resource,Self::Error>{
let model=ModelLoader::new(self.finder).load(index)?;
let mesh=crate::mesh::convert_mesh(model,&mut self.deferred_loader);
Ok(mesh)

View File

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

View File

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

View File

@@ -1,10 +1,4 @@
use std::cmp::Ordering;
use std::collections::BTreeMap;
use crate::aabb::Aabb;
use crate::ray::Ray;
use crate::integer::{Ratio,Planar64};
use crate::instruction::{InstructionCollector,TimedInstruction};
//da algaritum
//lista boxens
@@ -16,117 +10,35 @@ use crate::instruction::{InstructionCollector,TimedInstruction};
//sort the centerpoints on each axis (3 lists)
//bv is put into octant based on whether it is upper or lower in each list
pub fn intersect_aabb(ray:&Ray,aabb:&Aabb)->Option<Ratio<Planar64,Planar64>>{
// n.(o+d*t)==n.p
// n.o + n.d * t == n.p
// t == (n.p - n.o)/n.d
let mut hit=None;
match ray.direction.x.cmp(&Planar64::ZERO){
Ordering::Less=>{
let rel_min=aabb.min()-ray.origin;
let rel_max=aabb.max()-ray.origin;
let dy=rel_max.x*ray.direction.y;
let dz=rel_max.x*ray.direction.z;
// x is negative, so inequalities are flipped
if rel_min.y*ray.direction.x>dy&&dy>rel_max.y*ray.direction.x
&&rel_min.z*ray.direction.x>dz&&dz>rel_max.z*ray.direction.x{
let t=rel_max.x/ray.direction.x;
hit=Some(hit.map_or(t,|best_t|t.min(best_t)));
}
},
Ordering::Equal=>(),
Ordering::Greater=>{
let rel_min=aabb.min()-ray.origin;
let rel_max=aabb.max()-ray.origin;
let dy=rel_min.x*ray.direction.y;
let dz=rel_min.x*ray.direction.z;
// x is positive, so inequalities are normal
if rel_min.y*ray.direction.x<dy&&dy<rel_max.y*ray.direction.x
&&rel_min.z*ray.direction.x<dz&&dz<rel_max.z*ray.direction.x{
let t=rel_min.x/ray.direction.x;
hit=Some(hit.map_or(t,|best_t|t.min(best_t)));
}
},
}
match ray.direction.z.cmp(&Planar64::ZERO){
Ordering::Less=>{
let rel_min=aabb.min()-ray.origin;
let rel_max=aabb.max()-ray.origin;
let dx=rel_max.z*ray.direction.x;
let dy=rel_max.z*ray.direction.y;
// z is negative, so inequalities are flipped
if rel_min.x*ray.direction.z>dx&&dx>rel_max.x*ray.direction.z
&&rel_min.y*ray.direction.z>dy&&dy>rel_max.y*ray.direction.z{
let t=rel_max.z/ray.direction.z;
hit=Some(hit.map_or(t,|best_t|t.min(best_t)));
}
},
Ordering::Equal=>(),
Ordering::Greater=>{
let rel_min=aabb.min()-ray.origin;
let rel_max=aabb.max()-ray.origin;
let dx=rel_min.z*ray.direction.x;
let dy=rel_min.z*ray.direction.y;
// z is positive, so inequalities are normal
if rel_min.x*ray.direction.z<dx&&dx<rel_max.x*ray.direction.z
&&rel_min.y*ray.direction.z<dy&&dy<rel_max.y*ray.direction.z{
let t=rel_min.z/ray.direction.z;
hit=Some(hit.map_or(t,|best_t|t.min(best_t)));
}
},
}
match ray.direction.y.cmp(&Planar64::ZERO){
Ordering::Less=>{
let rel_min=aabb.min()-ray.origin;
let rel_max=aabb.max()-ray.origin;
let dz=rel_max.y*ray.direction.z;
let dx=rel_max.y*ray.direction.x;
// y is negative, so inequalities are flipped
if rel_min.z*ray.direction.y>dz&&dz>rel_max.z*ray.direction.y
&&rel_min.x*ray.direction.y>dx&&dx>rel_max.x*ray.direction.y{
let t=rel_max.y/ray.direction.y;
hit=Some(hit.map_or(t,|best_t|t.min(best_t)));
}
},
Ordering::Equal=>(),
Ordering::Greater=>{
let rel_min=aabb.min()-ray.origin;
let rel_max=aabb.max()-ray.origin;
let dz=rel_min.y*ray.direction.z;
let dx=rel_min.y*ray.direction.x;
// y is positive, so inequalities are normal
if rel_min.z*ray.direction.y<dz&&dz<rel_max.z*ray.direction.y
&&rel_min.x*ray.direction.y<dx&&dx<rel_max.x*ray.direction.y{
let t=rel_min.y/ray.direction.y;
hit=Some(hit.map_or(t,|best_t|t.min(best_t)));
}
},
}
hit
pub enum RecursiveContent<R,T>{
Branch(Vec<R>),
Leaf(T),
}
pub enum RecursiveContent<N,L>{
Branch(Vec<N>),
Leaf(L),
}
impl<N,L> RecursiveContent<N,L>{
pub fn empty()->Self{
impl<R,T> Default for RecursiveContent<R,T>{
fn default()->Self{
Self::Branch(Vec::new())
}
}
pub struct BvhNode<L>{
content:RecursiveContent<BvhNode<L>,L>,
pub struct BvhNode<T>{
content:RecursiveContent<BvhNode<T>,T>,
aabb:Aabb,
}
impl<L> BvhNode<L>{
pub fn empty()->Self{
impl<T> Default for BvhNode<T>{
fn default()->Self{
Self{
content:RecursiveContent::empty(),
content:Default::default(),
aabb:Aabb::default(),
}
}
pub fn sample_aabb<F:FnMut(&L)>(&self,aabb:&Aabb,f:&mut F){
}
pub struct BvhWeightNode<W,T>{
content:RecursiveContent<BvhWeightNode<W,T>,T>,
weight:W,
aabb:Aabb,
}
impl<T> BvhNode<T>{
pub fn the_tester<F:FnMut(&T)>(&self,aabb:&Aabb,f:&mut F){
match &self.content{
RecursiveContent::Leaf(model)=>f(model),
RecursiveContent::Branch(children)=>for child in children{
@@ -135,101 +47,51 @@ impl<L> BvhNode<L>{
//you're probably not going to spend a lot of time outside the map,
//so the test is extra work for nothing
if aabb.intersects(&child.aabb){
child.sample_aabb(aabb,f);
child.the_tester(aabb,f);
}
},
}
}
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));
}
},
pub fn into_visitor<F:FnMut(T)>(self,f:&mut F){
match self.content{
RecursiveContent::Leaf(model)=>f(model),
RecursiveContent::Branch(children)=>for child in children{
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);
}
}
child.into_visitor(f)
},
}
}
pub fn weigh_contents<W:Copy+std::iter::Sum<W>,F:Fn(&T)->W>(self,f:&F)->BvhWeightNode<W,T>{
match self.content{
RecursiveContent::Leaf(model)=>BvhWeightNode{
weight:f(&model),
content:RecursiveContent::Leaf(model),
aabb:self.aabb,
},
RecursiveContent::Branch(children)=>{
let branch:Vec<BvhWeightNode<W,T>>=children.into_iter().map(|child|
child.weigh_contents(f)
).collect();
BvhWeightNode{
weight:branch.iter().map(|node|node.weight).sum(),
content:RecursiveContent::Branch(branch),
aabb:self.aabb,
}
},
}
}
pub fn sample_ray<T,F>(
&self,
ray:&Ray,
start_time:T,
time_limit:T,
f:F,
)->Option<(T,&L)>
where
T:Ord+Copy,
T:From<Ratio<Planar64,Planar64>>,
Ratio<Planar64,Planar64>:From<T>,
F:Fn(&L,&Ray)->Option<T>,
{
// source of nondeterminism when Aabb boundaries are coplanar
let mut nodes=BTreeMap::new();
}
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))
impl <W,T> BvhWeightNode<W,T>{
pub const fn weight(&self)->&W{
&self.weight
}
pub fn into_inner(self)->(RecursiveContent<BvhNode<L>,L>,Aabb){
(self.content,self.aabb)
pub const fn aabb(&self)->&Aabb{
&self.aabb
}
pub fn into_visitor<F:FnMut(L)>(self,f:&mut F){
pub fn into_content(self)->RecursiveContent<BvhWeightNode<W,T>,T>{
self.content
}
pub fn into_visitor<F:FnMut(T)>(self,f:&mut F){
match self.content{
RecursiveContent::Leaf(model)=>f(model),
RecursiveContent::Branch(children)=>for child in children{
@@ -268,9 +130,9 @@ fn generate_bvh_node<T>(boxen:Vec<(T,Aabb)>,force:bool)->BvhNode<T>{
sort_y.push((i,center.y));
sort_z.push((i,center.z));
}
sort_x.sort_by_key(|&(_,c)|c);
sort_y.sort_by_key(|&(_,c)|c);
sort_z.sort_by_key(|&(_,c)|c);
sort_x.sort_by(|tup0,tup1|tup0.1.cmp(&tup1.1));
sort_y.sort_by(|tup0,tup1|tup0.1.cmp(&tup1.1));
sort_z.sort_by(|tup0,tup1|tup0.1.cmp(&tup1.1));
let h=n/2;
let median_x=sort_x[h].1;
let median_y=sort_y[h].1;

View File

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

View File

@@ -1,6 +1,7 @@
use std::collections::{HashSet,HashMap};
use crate::model::ModelId;
use crate::gameplay_style;
use crate::updatable::Updatable;
#[derive(Clone)]
pub struct StageElement{
@@ -127,6 +128,18 @@ impl Stage{
self.unordered_checkpoints.contains(&model_id)
}
}
#[derive(Default)]
pub struct StageUpdate{
//other behaviour models of this stage can have
ordered_checkpoints:HashMap<CheckpointId,ModelId>,
unordered_checkpoints:HashSet<ModelId>,
}
impl Updatable<StageUpdate> for Stage{
fn update(&mut self,update:StageUpdate){
self.ordered_checkpoints.extend(update.ordered_checkpoints);
self.unordered_checkpoints.extend(update.unordered_checkpoints);
}
}
#[derive(Clone,Copy,Hash,Eq,PartialEq)]
pub enum Zone{
@@ -198,47 +211,34 @@ impl Mode{
pub fn push_stage(&mut self,stage:Stage){
self.stages.push(stage)
}
pub fn get_stage_mut(&mut self,StageId(stage_id):StageId)->Option<&mut Stage>{
self.stages.get_mut(stage_id as usize)
pub fn get_stage_mut(&mut self,stage:StageId)->Option<&mut Stage>{
self.stages.get_mut(stage.0 as usize)
}
pub fn get_spawn_model_id(&self,StageId(stage_id):StageId)->Option<ModelId>{
self.stages.get(stage_id as usize).map(|s|s.spawn)
pub fn get_spawn_model_id(&self,stage:StageId)->Option<ModelId>{
self.stages.get(stage.0 as usize).map(|s|s.spawn)
}
pub fn get_zone(&self,model_id:ModelId)->Option<&Zone>{
self.zones.get(&model_id)
}
pub fn get_stage(&self,StageId(stage_id):StageId)->Option<&Stage>{
self.stages.get(stage_id as usize)
pub fn get_stage(&self,stage_id:StageId)->Option<&Stage>{
self.stages.get(stage_id.0 as usize)
}
pub fn get_element(&self,model_id:ModelId)->Option<&StageElement>{
self.elements.get(&model_id)
}
}
#[derive(Clone)]
pub struct NormalizedMode(Mode);
impl NormalizedMode{
pub fn new(mode:Mode)->Self{
Self(mode)
}
pub fn into_inner(self)->Mode{
let Self(mode)=self;
mode
}
//TODO: put this in the SNF
pub fn denormalize(self)->Mode{
let NormalizedMode(mut mode)=self;
pub fn denormalize_data(&mut self){
//expand and index normalized data
mode.zones.insert(mode.start,Zone::Start);
for (stage_id,stage) in mode.stages.iter().enumerate(){
mode.elements.insert(stage.spawn,StageElement{
self.zones.insert(self.start,Zone::Start);
for (stage_id,stage) in self.stages.iter().enumerate(){
self.elements.insert(stage.spawn,StageElement{
stage_id:StageId(stage_id as u32),
force:false,
behaviour:StageElementBehaviour::SpawnAt,
jump_limit:None,
});
for (_,&model) in &stage.ordered_checkpoints{
mode.elements.insert(model,StageElement{
self.elements.insert(model,StageElement{
stage_id:StageId(stage_id as u32),
force:false,
behaviour:StageElementBehaviour::Checkpoint,
@@ -246,7 +246,7 @@ impl NormalizedMode{
});
}
for &model in &stage.unordered_checkpoints{
mode.elements.insert(model,StageElement{
self.elements.insert(model,StageElement{
stage_id:StageId(stage_id as u32),
force:false,
behaviour:StageElementBehaviour::Checkpoint,
@@ -254,13 +254,53 @@ impl NormalizedMode{
});
}
}
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)]
pub struct Modes{
modes:Vec<Mode>,
pub modes:Vec<Mode>,
}
impl Modes{
pub const fn new(modes:Vec<Mode>)->Self{
@@ -274,191 +314,19 @@ impl Modes{
pub fn push_mode(&mut self,mode:Mode){
self.modes.push(mode)
}
pub fn get_mode(&self,ModeId(mode_id):ModeId)->Option<&Mode>{
self.modes.get(mode_id as usize)
pub fn get_mode(&self,mode:ModeId)->Option<&Mode>{
self.modes.get(mode.0 as usize)
}
}
#[derive(Clone)]
pub struct NormalizedModes{
modes:Vec<NormalizedMode>,
pub struct ModesUpdate{
modes:HashMap<ModeId,ModeUpdate>,
}
impl NormalizedModes{
pub fn new(modes:Vec<NormalizedMode>)->Self{
Self{modes}
}
pub fn len(&self)->usize{
self.modes.len()
}
pub fn denormalize(self)->Modes{
Modes{
modes:self.modes.into_iter().map(NormalizedMode::denormalize).collect(),
}
}
}
impl IntoIterator for NormalizedModes{
type Item=<Vec<NormalizedMode> as IntoIterator>::Item;
type IntoIter=<Vec<NormalizedMode> as IntoIterator>::IntoIter;
fn into_iter(self)->Self::IntoIter{
self.modes.into_iter()
}
}
#[derive(Default)]
pub struct StageUpdate{
//other behaviour models of this stage can have
ordered_checkpoints:HashMap<CheckpointId,ModelId>,
unordered_checkpoints:HashSet<ModelId>,
}
impl StageUpdate{
fn apply_to(self,stage:&mut Stage){
stage.ordered_checkpoints.extend(self.ordered_checkpoints);
stage.unordered_checkpoints.extend(self.unordered_checkpoints);
}
}
//this would be nice as a macro
#[derive(Default)]
pub struct ModeUpdate{
zones:Option<(ModelId,Zone)>,
stages:Option<(StageId,StageUpdate)>,
//mutually exlusive stage element behaviour
elements:Option<(ModelId,StageElement)>,
}
impl ModeUpdate{
fn apply_to(self,mode:&mut Mode){
mode.zones.extend(self.zones);
if let Some((StageId(stage_id),stage_update))=self.stages{
if let Some(stage)=mode.stages.get_mut(stage_id as usize){
stage_update.apply_to(stage);
impl Updatable<ModesUpdate> for Modes{
fn update(&mut self,update:ModesUpdate){
for (mode,mode_update) in update.modes{
if let Some(mode)=self.modes.get_mut(mode.0 as usize){
mode.update(mode_update);
}
}
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)->Result<(),ExistingEntryError>{
error_if_exists(self.modes.insert(mode_id,mode))
}
pub fn insert_stage(&mut self,mode_id:ModeId,stage_id:StageId,stage:Stage)->Result<(),ExistingEntryError>{
error_if_exists(self.stages.entry(mode_id).or_insert(HashMap::new()).insert(stage_id,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));
// }
}
#[derive(Debug)]
pub struct ExistingEntryError;
fn error_if_exists<T>(value:Option<T>)->Result<(),ExistingEntryError>{
match value{
Some(_)=>Err(ExistingEntryError),
None=>Ok(())
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,20 +0,0 @@
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())
}
}

View File

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

View File

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

View File

@@ -0,0 +1,57 @@
// 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),
};
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -38,95 +38,40 @@ macro_rules! impl_fixed_wide_vector {
$crate::macro_4!(impl_fixed_wide_vector_not_const_generic,());
// I LOVE NOT BEING ABLE TO USE CONST GENERICS
$crate::macro_repeated!(
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),
(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),
(5,4),(6,4),(7,4),(8,4),(9,4),(10,4),(11,4),(12,4),(13,4),(14,4),(15,4),(16,4),
(6,5),(7,5),(8,5),(9,5),(10,5),(11,5),(12,5),(13,5),(14,5),(15,5),(16,5),
(7,6),(8,6),(9,6),(10,6),(11,6),(12,6),(13,6),(14,6),(15,6),(16,6),
(8,7),(9,7),(10,7),(11,7),(12,7),(13,7),(14,7),(15,7),(16,7),
(9,8),(10,8),(11,8),(12,8),(13,8),(14,8),(15,8),(16,8),
(10,9),(11,9),(12,9),(13,9),(14,9),(15,9),(16,9),
(11,10),(12,10),(13,10),(14,10),(15,10),(16,10),
(12,11),(13,11),(14,11),(15,11),(16,11),
(13,12),(14,12),(15,12),(16,12),
(14,13),(15,13),(16,13),
(15,14),(16,14),
(16,15)
impl_fix_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),
(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),
(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),
(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),
(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),
(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),
(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),
(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),
(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),
(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),
(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),
(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),
(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),
(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),
(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),
(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)]
#[macro_export(local_inner_macros)]
macro_rules! impl_narrow_not_const_generic{
macro_rules! impl_fix_not_const_generic{
(
(),
($lhs:expr,$rhs:expr)
)=>{
paste::item!{
impl<const N:usize> Vector<N,fixed_wide::fixed::Fixed<$lhs,{$lhs*32}>>{
impl<const N:usize> Vector<N,fixed_wide::fixed::Fixed<$lhs,{$lhs*32}>>
{
paste::item!{
#[inline]
pub fn [<wrap_ $rhs>](self)->Vector<N,fixed_wide::fixed::Fixed<$rhs,{$rhs*32}>>{
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>]())
pub fn [<fix_ $rhs>](self)->Vector<N,fixed_wide::fixed::Fixed<$rhs,{$rhs*32}>>{
self.map(|t|t.[<fix_ $rhs>]())
}
}
}

View File

@@ -204,19 +204,13 @@ macro_rules! impl_matrix_named_fields_shape {
type Target=$struct_outer<Vector<$size_inner,T>>;
#[inline]
fn deref(&self)->&Self::Target{
// This cast is valid because Matrix has #[repr(transparent)]
let ptr=&self.array as *const [[T;$size_inner];$size_outer] as *const Self::Target;
// SAFETY: this pointer is non-null because it comes from a reference
unsafe{&*ptr}
unsafe{core::mem::transmute(&self.array)}
}
}
impl<T> core::ops::DerefMut for Matrix<$size_outer,$size_inner,T>{
#[inline]
fn deref_mut(&mut self)->&mut Self::Target{
// This cast is valid because Matrix has #[repr(transparent)]
let ptr=&mut self.array as *mut [[T;$size_inner];$size_outer] as *mut Self::Target;
// SAFETY: this pointer is non-null because it comes from a reference
unsafe{&mut*ptr}
unsafe{core::mem::transmute(&mut self.array)}
}
}
}

View File

@@ -58,15 +58,6 @@ 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>{
#[inline]
pub fn min(self,rhs:Self)->Self{
@@ -330,19 +321,13 @@ macro_rules! impl_vector_named_fields {
type Target=$struct<T>;
#[inline]
fn deref(&self)->&Self::Target{
// This cast is valid because Vector has #[repr(transparent)]
let ptr=&self.array as *const [T;$size] as *const Self::Target;
// SAFETY: this pointer is non-null because it comes from a reference
unsafe{&*ptr}
unsafe{core::mem::transmute(&self.array)}
}
}
impl<T> core::ops::DerefMut for Vector<$size,T>{
#[inline]
fn deref_mut(&mut self)->&mut Self::Target{
// This cast is valid because Vector has #[repr(transparent)]
let ptr=&mut self.array as *mut [T;$size] as *mut Self::Target;
// SAFETY: this pointer is non-null because it comes from a reference
unsafe{&mut*ptr}
unsafe{core::mem::transmute(&mut self.array)}
}
}
}

View File

@@ -1,6 +1,5 @@
use crate::vector::Vector;
#[repr(transparent)]
#[derive(Clone,Copy,Debug,Hash,Eq,PartialEq)]
pub struct Matrix<const X:usize,const Y:usize,T>{
pub(crate) array:[[T;Y];X],

View File

@@ -3,7 +3,6 @@
/// v.x += v.z;
/// println!("v.x={}",v.x);
#[repr(transparent)]
#[derive(Clone,Copy,Debug,Hash,Eq,PartialEq)]
pub struct Vector<const N:usize,T>{
pub(crate) array:[T;N],

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,6 @@
// TODO: make a directories structure like strafe client
struct Directories{
textures:PathBuf,
meshes:PathBuf,
unions:PathBuf,
}

View File

@@ -1,6 +1,5 @@
use std::io::Read;
use rbx_dom_weak::WeakDom;
use roblox_emulator::context::Context;
use strafesnet_deferred_loader::deferred_loader::{LoadFailureMode,MeshDeferredLoader,RenderConfigDeferredLoader};
mod rbx;
@@ -21,38 +20,47 @@ pub mod data{
}
}
pub struct Model<'a>{
dom:WeakDom<'a>,
pub struct Model{
dom:WeakDom,
}
impl<'a> Model<'a>{
fn new(dom:WeakDom<'a>)->Self{
impl Model{
fn new(dom:WeakDom)->Self{
Self{dom}
}
pub fn into_place(self)->Place{
let Self{mut dom}=self;
let context=roblox_emulator::context::Context::from_mut(&mut dom);
let services=context.convert_into_place();
Place{dom,services}
}
pub fn to_snf(&self,failure_mode:LoadFailureMode)->Result<strafesnet_common::map::CompleteMap,LoadError>{
to_snf(self,failure_mode)
}
}
impl<'a> AsRef<WeakDom<'a>> for Model<'a>{
fn as_ref(&self)->&WeakDom<'a>{
impl AsRef<WeakDom> for Model{
fn as_ref(&self)->&WeakDom{
&self.dom
}
}
pub struct Place<'a>{
context:Context<'a>,
pub struct Place{
dom:WeakDom,
services:roblox_emulator::context::Services,
}
impl<'a> Place<'a>{
pub fn new(dom:WeakDom<'a>)->Result<Self,roblox_emulator::context::ServicesError>{
let context=Context::from_place(dom)?;
Ok(Self{
context,
impl Place{
pub fn new(dom:WeakDom)->Option<Self>{
let context=roblox_emulator::context::Context::from_ref(&dom);
Some(Self{
services:context.find_services()?,
dom,
})
}
pub fn run_scripts(&mut self){
let Place{context}=self;
let Place{dom,services}=self;
let runner=roblox_emulator::runner::Runner::new().unwrap();
let context=roblox_emulator::context::Context::from_mut(dom);
let scripts=context.scripts();
let runnable=runner.runnable_context(context).unwrap();
let runnable=runner.runnable_context_with_services(context,services).unwrap();
for script in scripts{
if let Err(e)=runnable.run_script(script){
println!("runner error: {e}");
@@ -63,17 +71,9 @@ impl<'a> Place<'a>{
to_snf(self,failure_mode)
}
}
impl<'a> AsRef<WeakDom<'a>> for Place<'a>{
fn as_ref(&self)->&WeakDom<'a>{
self.context.as_ref()
}
}
impl<'a> From<Model<'a>> for Place<'a>{
fn from(model:Model<'a>)->Self{
let context=Context::from_model(model.dom);
Self{
context,
}
impl AsRef<WeakDom> for Place{
fn as_ref(&self)->&WeakDom{
&self.dom
}
}
@@ -91,12 +91,12 @@ impl std::fmt::Display for ReadError{
}
impl std::error::Error for ReadError{}
pub fn read<R:Read>(input:R)->Result<Model<'static>,ReadError>{
pub fn read<R:Read>(input:R)->Result<Model,ReadError>{
let mut buf=std::io::BufReader::new(input);
let peek=std::io::BufRead::fill_buf(&mut buf).map_err(ReadError::Io)?;
match peek.get(0..8){
Some(b"<roblox!")=>rbx_binary::from_reader_default(buf).map(Model::new).map_err(ReadError::RbxBinary),
Some(b"<roblox ")=>rbx_xml::from_reader_default(buf).map(Model::new).map_err(ReadError::RbxXml),
match &peek[0..8]{
b"<roblox!"=>rbx_binary::from_reader(buf).map(Model::new).map_err(ReadError::RbxBinary),
b"<roblox "=>rbx_xml::from_reader_default(buf).map(Model::new).map_err(ReadError::RbxXml),
_=>Err(ReadError::UnknownFileFormat),
}
}
@@ -123,7 +123,7 @@ impl From<loader::MeshError> for LoadError{
}
}
fn to_snf<'a>(dom:impl AsRef<WeakDom<'a>>,failure_mode:LoadFailureMode)->Result<strafesnet_common::map::CompleteMap,LoadError>{
fn to_snf(dom:impl AsRef<WeakDom>,failure_mode:LoadFailureMode)->Result<strafesnet_common::map::CompleteMap,LoadError>{
let dom=dom.as_ref();
let mut texture_deferred_loader=RenderConfigDeferredLoader::new();

View File

@@ -4,8 +4,7 @@ use strafesnet_common::model::Mesh;
use strafesnet_deferred_loader::{loader::Loader,texture::Texture};
use crate::data::RobloxMeshBytes;
use crate::rbx::RobloxPartDescription;
use crate::rbx::RobloxFaceTextureDescription;
fn read_entire_file(path:impl AsRef<std::path::Path>)->Result<Vec<u8>,std::io::Error>{
let mut file=std::fs::File::open(path)?;
@@ -37,17 +36,17 @@ impl From<RobloxAssetIdParseErr> for TextureError{
}
}
pub struct TextureLoader;
impl TextureLoader{
pub struct TextureLoader<'a>(std::marker::PhantomData<&'a ()>);
impl TextureLoader<'_>{
pub fn new()->Self{
Self
Self(std::marker::PhantomData)
}
}
impl Loader for TextureLoader{
impl<'a> Loader for TextureLoader<'a>{
type Error=TextureError;
type Index<'a>=&'a str;
type Index=&'a str;
type Resource=Texture;
fn load<'a>(&mut self,index:Self::Index<'a>)->Result<Self::Resource,Self::Error>{
fn load(&mut self,index:Self::Index)->Result<Self::Resource,Self::Error>{
let RobloxAssetId(asset_id)=index.parse()?;
let file_name=format!("textures/{}.dds",asset_id);
let data=read_entire_file(file_name)?;
@@ -105,7 +104,7 @@ pub enum MeshType<'a>{
mesh_data:&'a [u8],
physics_data:&'a [u8],
size_float_bits:[u32;3],
part_texture_description:RobloxPartDescription,
part_texture_description:[Option<RobloxFaceTextureDescription>;6],
},
}
#[derive(Hash,Eq,PartialEq)]
@@ -125,7 +124,7 @@ impl MeshIndex<'_>{
mesh_data:&'a [u8],
physics_data:&'a [u8],
size:&rbx_dom_weak::types::Vector3,
part_texture_description:RobloxPartDescription,
part_texture_description:crate::rbx::RobloxPartDescription,
)->MeshIndex<'a>{
MeshIndex{
mesh_type:MeshType::Union{
@@ -139,17 +138,17 @@ impl MeshIndex<'_>{
}
}
pub struct MeshLoader;
impl MeshLoader{
pub struct MeshLoader<'a>(std::marker::PhantomData<&'a ()>);
impl MeshLoader<'_>{
pub fn new()->Self{
Self
Self(std::marker::PhantomData)
}
}
impl Loader for MeshLoader{
impl<'a> Loader for MeshLoader<'a>{
type Error=MeshError;
type Index<'a>=MeshIndex<'a>;
type Index=MeshIndex<'a>;
type Resource=Mesh;
fn load<'a>(&mut self,index:Self::Index<'a>)->Result<Self::Resource,Self::Error>{
fn load(&mut self,index:Self::Index)->Result<Self::Resource,Self::Error>{
let mesh=match index.mesh_type{
MeshType::FileMesh=>{
let RobloxAssetId(asset_id)=index.content.parse()?;
@@ -164,7 +163,7 @@ impl Loader for MeshLoader{
let RobloxAssetId(asset_id)=index.content.parse()?;
let file_name=format!("unions/{}",asset_id);
let data=read_entire_file(file_name)?;
let dom=rbx_binary::from_reader_default(data.as_slice())?;
let dom=rbx_binary::from_reader(std::io::Cursor::new(data))?;
let &[referent]=dom.root().children()else{
return Err(MeshError::OneChildPolicy);
};

View File

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

View File

@@ -1,6 +1,5 @@
use crate::rbx::{RobloxPartDescription,RobloxWedgeDescription,RobloxCornerWedgeDescription};
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};
use strafesnet_common::model::{Color4,TextureCoordinate,Mesh,IndexedGraphicsGroup,IndexedPhysicsGroup,IndexedVertex,PolygonGroupId,PolygonGroup,PolygonList,PositionId,TextureCoordinateId,NormalId,ColorId,VertexId,RenderConfigId};
use strafesnet_common::integer::{vec3,Planar64Vec3};
#[derive(Debug)]
pub enum Primitives{
@@ -10,22 +9,7 @@ pub enum Primitives{
Wedge,
CornerWedge,
}
#[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)]
#[derive(Hash,PartialEq,Eq)]
pub enum CubeFace{
Right,
Top,
@@ -34,22 +18,6 @@ pub enum CubeFace{
Bottom,
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]=[
TextureCoordinate::new(0.0,0.0),
TextureCoordinate::new(1.0,0.0),
@@ -75,36 +43,103 @@ const CUBE_DEFAULT_NORMALS:[Planar64Vec3;6]=[
vec3::int( 0, 0,-1),//CubeFace::Front
];
pub struct CubeFaceDescription([FaceDescription;Self::FACES]);
#[derive(Hash,PartialEq,Eq)]
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{
pub const FACES:usize=6;
pub fn new(RobloxPartDescription(part_description):RobloxPartDescription,textureless_render_id:RenderConfigId)->Self{
Self(part_description.map(|face_description|match face_description{
Some(roblox_texture_transform)=>roblox_texture_transform.to_face_description(),
None=>FaceDescription::new_with_render_id(textureless_render_id),
}))
pub fn insert(&mut self,index:CubeFace,value:FaceDescription){
self.0[index as usize]=Some(value);
}
pub fn pairs(self)->impl Iterator<Item=(usize,FaceDescription)>{
self.0.into_iter().enumerate().filter_map(|(i,v)|v.map(|u|(i,u)))
}
}
pub struct WedgeFaceDescription([FaceDescription;Self::FACES]);
pub fn unit_cube(render:RenderConfigId)->Mesh{
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{
pub const FACES:usize=5;
pub fn new(RobloxWedgeDescription(part_description):RobloxWedgeDescription,textureless_render_id:RenderConfigId)->Self{
Self(part_description.map(|face_description|match face_description{
Some(roblox_texture_transform)=>roblox_texture_transform.to_face_description(),
None=>FaceDescription::new_with_render_id(textureless_render_id),
}))
pub fn insert(&mut self,index:WedgeFace,value:FaceDescription){
self.0[index as usize]=Some(value);
}
pub fn pairs(self)->std::iter::FilterMap<std::iter::Enumerate<std::array::IntoIter<Option<FaceDescription>,5>>,impl FnMut((usize,Option<FaceDescription>))->Option<(usize,FaceDescription)>>{
self.0.into_iter().enumerate().filter_map(|v|v.1.map(|u|(v.0,u)))
}
}
pub struct CornerWedgeFaceDescription([FaceDescription;Self::FACES]);
// pub fn unit_wedge(render:RenderConfigId)->Mesh{
// 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{
pub const FACES:usize=5;
pub fn new(RobloxCornerWedgeDescription(part_description):RobloxCornerWedgeDescription,textureless_render_id:RenderConfigId)->Self{
Self(part_description.map(|face_description|match face_description{
Some(roblox_texture_transform)=>roblox_texture_transform.to_face_description(),
None=>FaceDescription::new_with_render_id(textureless_render_id),
}))
pub fn insert(&mut self,index:CornerWedgeFace,value:FaceDescription){
self.0[index as usize]=Some(value);
}
pub fn pairs(self)->std::iter::FilterMap<std::iter::Enumerate<std::array::IntoIter<Option<FaceDescription>,5>>,impl FnMut((usize,Option<FaceDescription>))->Option<(usize,FaceDescription)>>{
self.0.into_iter().enumerate().filter_map(|v|v.1.map(|u|(v.0,u)))
}
}
// 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)]
pub struct FaceDescription{
@@ -113,7 +148,7 @@ pub struct FaceDescription{
pub color:Color4,
}
impl FaceDescription{
pub fn new_with_render_id(render:RenderConfigId)->Self{
pub fn new_with_render_id(render:RenderConfigId)->Self {
Self{
render,
transform:glam::Affine2::IDENTITY,
@@ -121,49 +156,49 @@ impl FaceDescription{
}
}
}
pub fn unit_cube(CubeFaceDescription(face_descriptions):CubeFaceDescription)->Mesh{
const CUBE_DEFAULT_POLYS:[[[u32;2];4];6]=[
pub fn generate_partial_unit_cube(face_descriptions:CubeFaceDescription)->Mesh{
const CUBE_DEFAULT_POLYS:[[[u32;3];4];6]=[
// right (1, 0, 0)
[
[6,2],//[vertex,tex]
[5,1],
[2,0],
[1,3],
[6,2,0],//[vertex,tex,norm]
[5,1,0],
[2,0,0],
[1,3,0],
],
// top (0, 1, 0)
[
[5,3],
[4,2],
[3,1],
[2,0],
[5,3,1],
[4,2,1],
[3,1,1],
[2,0,1],
],
// back (0, 0, 1)
[
[0,3],
[1,2],
[2,1],
[3,0],
[0,3,2],
[1,2,2],
[2,1,2],
[3,0,2],
],
// left (-1, 0, 0)
[
[0,2],
[3,1],
[4,0],
[7,3],
[0,2,3],
[3,1,3],
[4,0,3],
[7,3,3],
],
// bottom (0,-1, 0)
[
[1,1],
[0,0],
[7,3],
[6,2],
[1,1,4],
[0,0,4],
[7,3,4],
[6,2,4],
],
// front (0, 0,-1)
[
[4,1],
[5,0],
[6,3],
[7,2],
[4,1,5],
[5,0,5],
[6,3,5],
[7,2,5],
],
];
let mut generated_pos=Vec::new();
@@ -176,7 +211,7 @@ pub fn unit_cube(CubeFaceDescription(face_descriptions):CubeFaceDescription)->Me
let mut physics_group=IndexedPhysicsGroup::default();
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.
for (face_id,face_description) in face_descriptions.into_iter().enumerate(){
for (face_id,face_description) in face_descriptions.pairs(){
//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){
transform_index
@@ -203,8 +238,8 @@ pub fn unit_cube(CubeFaceDescription(face_descriptions):CubeFaceDescription)->Me
//push vertices as they are needed
let group_id=PolygonGroupId::new(polygon_groups.len() as u32);
polygon_groups.push(PolygonGroup::PolygonList(PolygonList::new(vec![
CUBE_DEFAULT_POLYS[face_id].map(|[pos_id,tex_id]|{
let pos=CUBE_DEFAULT_VERTICES[pos_id as usize];
CUBE_DEFAULT_POLYS[face_id].map(|tup|{
let pos=CUBE_DEFAULT_VERTICES[tup[0] as usize];
let pos_index=if let Some(pos_index)=generated_pos.iter().position(|&p|p==pos){
pos_index
}else{
@@ -216,7 +251,7 @@ pub fn unit_cube(CubeFaceDescription(face_descriptions):CubeFaceDescription)->Me
//always push vertex
let vertex=IndexedVertex{
pos:PositionId::new(pos_index),
tex:TextureCoordinateId::new(tex_id+4*transform_index),
tex:TextureCoordinateId::new(tup[1]+4*transform_index),
normal:NormalId::new(normal_index),
color:ColorId::new(color_index),
};
@@ -243,49 +278,42 @@ pub fn unit_cube(CubeFaceDescription(face_descriptions):CubeFaceDescription)->Me
}
}
//don't think too hard about the copy paste because this is all going into the map tool eventually...
pub fn unit_wedge(WedgeFaceDescription(face_descriptions):WedgeFaceDescription)->Mesh{
const WEDGE_DEFAULT_POLYS:[&[[u32;2]];5]=[
pub fn generate_partial_unit_wedge(face_descriptions:WedgeFaceDescription)->Mesh{
const WEDGE_DEFAULT_POLYS:[&[[u32;3]];5]=[
// right (1, 0, 0)
&[
[6,2],//[vertex,tex]
[2,0],
[1,3],
[6,2,0],//[vertex,tex,norm]
[2,0,0],
[1,3,0],
],
// FrontTop (0, 1, -1)
&[
[3,1],
[2,0],
[6,3],
[7,2],
[3,1,1],
[2,0,1],
[6,3,1],
[7,2,1],
],
// back (0, 0, 1)
&[
[0,3],
[1,2],
[2,1],
[3,0],
[0,3,2],
[1,2,2],
[2,1,2],
[3,0,2],
],
// left (-1, 0, 0)
&[
[0,2],
[3,1],
[7,3],
[0,2,3],
[3,1,3],
[7,3,3],
],
// bottom (0,-1, 0)
&[
[1,1],
[0,0],
[7,3],
[6,2],
[1,1,4],
[0,0,4],
[7,3,4],
[6,2,4],
],
];
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_tex=Vec::new();
let mut generated_normal=Vec::new();
@@ -296,7 +324,7 @@ pub fn unit_wedge(WedgeFaceDescription(face_descriptions):WedgeFaceDescription)-
let mut physics_group=IndexedPhysicsGroup::default();
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.
for (face_id,face_description) in face_descriptions.into_iter().enumerate(){
for (face_id,face_description) in face_descriptions.pairs(){
//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){
transform_index
@@ -323,8 +351,8 @@ pub fn unit_wedge(WedgeFaceDescription(face_descriptions):WedgeFaceDescription)-
//push vertices as they are needed
let group_id=PolygonGroupId::new(polygon_groups.len() as u32);
polygon_groups.push(PolygonGroup::PolygonList(PolygonList::new(vec![
WEDGE_DEFAULT_POLYS[face_id].iter().map(|&[pos_id,tex_id]|{
let pos=CUBE_DEFAULT_VERTICES[pos_id as usize];
WEDGE_DEFAULT_POLYS[face_id].iter().map(|tup|{
let pos=CUBE_DEFAULT_VERTICES[tup[0] as usize];
let pos_index=if let Some(pos_index)=generated_pos.iter().position(|&p|p==pos){
pos_index
}else{
@@ -336,7 +364,7 @@ pub fn unit_wedge(WedgeFaceDescription(face_descriptions):WedgeFaceDescription)-
//always push vertex
let vertex=IndexedVertex{
pos:PositionId::new(pos_index),
tex:TextureCoordinateId::new(tex_id+4*transform_index),
tex:TextureCoordinateId::new(tup[1]+4*transform_index),
normal:NormalId::new(normal_index),
color:ColorId::new(color_index),
};
@@ -363,47 +391,40 @@ pub fn unit_wedge(WedgeFaceDescription(face_descriptions):WedgeFaceDescription)-
}
}
pub fn unit_cornerwedge(CornerWedgeFaceDescription(face_descriptions):CornerWedgeFaceDescription)->Mesh{
const CORNERWEDGE_DEFAULT_POLYS:[&[[u32;2]];5]=[
pub fn generate_partial_unit_cornerwedge(face_descriptions:CornerWedgeFaceDescription)->Mesh{
const CORNERWEDGE_DEFAULT_POLYS:[&[[u32;3]];5]=[
// right (1, 0, 0)
&[
[6,2],//[vertex,tex]
[5,1],
[1,3],
[6,2,0],//[vertex,tex,norm]
[5,1,0],
[1,3,0],
],
// BackTop (0, 1, 1)
&[
[5,3],
[0,1],
[1,0],
[5,3,1],
[0,1,1],
[1,0,1],
],
// LeftTop (-1, 1, 0)
&[
[5,3],
[7,2],
[0,1],
[5,3,2],
[7,2,2],
[0,1,2],
],
// bottom (0,-1, 0)
&[
[1,1],
[0,0],
[7,3],
[6,2],
[1,1,3],
[0,0,3],
[7,3,3],
[6,2,3],
],
// front (0, 0,-1)
&[
[5,0],
[6,3],
[7,2],
[5,0,4],
[6,3,4],
[7,2,4],
],
];
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_tex=Vec::new();
let mut generated_normal=Vec::new();
@@ -414,7 +435,7 @@ pub fn unit_cornerwedge(CornerWedgeFaceDescription(face_descriptions):CornerWedg
let mut physics_group=IndexedPhysicsGroup::default();
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.
for (face_id,face_description) in face_descriptions.into_iter().enumerate(){
for (face_id,face_description) in face_descriptions.pairs(){
//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){
transform_index
@@ -441,8 +462,8 @@ pub fn unit_cornerwedge(CornerWedgeFaceDescription(face_descriptions):CornerWedg
//push vertices as they are needed
let group_id=PolygonGroupId::new(polygon_groups.len() as u32);
polygon_groups.push(PolygonGroup::PolygonList(PolygonList::new(vec![
CORNERWEDGE_DEFAULT_POLYS[face_id].iter().map(|&[pos_id,tex_id]|{
let pos=CUBE_DEFAULT_VERTICES[pos_id as usize];
CORNERWEDGE_DEFAULT_POLYS[face_id].iter().map(|tup|{
let pos=CUBE_DEFAULT_VERTICES[tup[0] as usize];
let pos_index=if let Some(pos_index)=generated_pos.iter().position(|&p|p==pos){
pos_index
}else{
@@ -454,7 +475,7 @@ pub fn unit_cornerwedge(CornerWedgeFaceDescription(face_descriptions):CornerWedg
//always push vertex
let vertex=IndexedVertex{
pos:PositionId::new(pos_index),
tex:TextureCoordinateId::new(tex_id+4*transform_index),
tex:TextureCoordinateId::new(tup[1]+4*transform_index),
normal:NormalId::new(normal_index),
color:ColorId::new(color_index),
};
@@ -480,133 +501,3 @@ pub fn unit_cornerwedge(CornerWedgeFaceDescription(face_descriptions):CornerWedg
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)
}

View File

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

View File

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

View File

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

View File

@@ -1,21 +1,21 @@
[package]
name = "roblox_emulator"
version = "0.5.0"
edition = "2024"
version = "0.4.7"
edition = "2021"
repository = "https://git.itzana.me/StrafesNET/strafe-project"
license = "MIT OR Apache-2.0"
description = "Run embedded Luau scripts which manipulate the DOM."
authors = ["Rhys Lloyd <krakow20@gmail.com>"]
[features]
default=[]
default=["run-service"]
run-service=[]
[dependencies]
glam = "0.30.0"
glam = "0.29.0"
mlua = { version = "0.10.1", features = ["luau"] }
phf = { version = "0.11.2", features = ["macros"] }
rbx_dom_weak = { path = "../../../../git/rbx-dom/rbx_dom_weak", registry = "strafesnet" }
rbx_reflection = { path = "../../../../git/rbx-dom/rbx_reflection" }
rbx_reflection_database = { path = "../../../../git/rbx-dom/rbx_reflection_database" }
rbx_types = { path = "../../../../git/rbx-dom/rbx_types" }
rbx_dom_weak = { version = "2.7.0", registry = "strafesnet" }
rbx_reflection = { version = "4.7.0", registry = "strafesnet" }
rbx_reflection_database = { version = "0.2.10", registry = "strafesnet" }
rbx_types = { version = "1.10.0", registry = "strafesnet" }

View File

@@ -1,112 +1,93 @@
use rbx_dom_weak::{types::Ref,InstanceBuilder,WeakDom};
#[derive(Debug)]
pub enum ServicesError{
WorkspaceNotFound,
pub fn class_is_a(class:&str,superclass:&str)->bool{
class==superclass
||rbx_reflection_database::get().classes.get(class)
.is_some_and(|descriptor|
descriptor.superclass.as_ref().is_some_and(|class_super|
class_is_a(class_super,superclass)
)
)
}
impl std::fmt::Display for ServicesError{
fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{
write!(f,"{self:?}")
#[repr(transparent)]
pub struct Context{
pub(crate)dom:WeakDom,
}
impl Context{
pub const fn new(dom:WeakDom)->Self{
Self{dom}
}
}
impl std::error::Error for ServicesError{}
pub struct Services{
pub(crate) game:Ref,
pub(crate) workspace:Ref,
}
impl Services{
fn find_services(dom:&WeakDom)->Result<Services,ServicesError>{
Ok(Services{
workspace:*dom.root().children().iter().find(|&&r|
dom.get_by_ref(r).is_some_and(|instance|instance.class=="Workspace")
).ok_or(ServicesError::WorkspaceNotFound)?,
game:dom.root_ref(),
})
}
}
pub type LuaAppData=&'static mut WeakDom<'static>;
pub struct Context<'a>{
pub(crate)dom:WeakDom<'a>,
pub(crate)services:Services,
}
impl<'a> Context<'a>{
pub fn from_place(dom:WeakDom<'a>)->Result<Context<'a>,ServicesError>{
let services=Services::find_services(&dom)?;
Ok(Self{dom,services})
}
pub fn script_singleton(source:String)->(Context<'a>,crate::runner::instance::Instance){
pub fn script_singleton(source:String)->(Context,crate::runner::instance::Instance,Services){
let script=InstanceBuilder::new("Script")
.with_property("Source",rbx_types::Variant::String(source));
let script_ref=script.referent();
let dom=WeakDom::new(
let mut context=Self::new(WeakDom::new(
InstanceBuilder::new("DataModel")
.with_child(script)
);
let context=Self::from_model(dom);
(context,crate::runner::instance::Instance::new_unchecked(script_ref))
));
let services=context.convert_into_place();
(context,crate::runner::instance::Instance::new(script_ref),services)
}
pub fn from_ref(dom:&WeakDom)->&Context{
unsafe{&*(dom as *const WeakDom as *const Context)}
}
pub fn from_mut(dom:&mut WeakDom)->&mut Context{
unsafe{&mut *(dom as *mut WeakDom as *mut Context)}
}
/// Creates an iterator over all items of a particular class.
pub fn superclass_iter<'b>(&'b self,superclass:&'b str)->impl Iterator<Item=Ref>+'b{
let db=rbx_reflection_database::get();
let Some(superclass)=db.classes.get(superclass)else{
panic!("Invalid class");
};
self.dom.descendants().filter_map(|instance|{
let class=db.classes.get(instance.class)?;
db.has_superclass(class,superclass).then(||instance.referent())
})
pub fn superclass_iter<'a>(&'a self,superclass:&'a str)->impl Iterator<Item=Ref>+'a{
self.dom.descendants().filter(|&instance|
class_is_a(instance.class.as_ref(),superclass)
).map(|instance|instance.referent())
}
pub fn scripts(&self)->Vec<crate::runner::instance::Instance>{
self.superclass_iter("Script")
.filter_map(|script_ref|{
let script=self.dom.get_by_ref(script_ref)?;
if let None|Some(rbx_dom_weak::types::Variant::Bool(false))=script.properties.get("Disabled"){
return Some(crate::runner::instance::Instance::new_unchecked(script_ref));
}
None
})
.collect()
self.superclass_iter("LuaSourceContainer").map(crate::runner::instance::Instance::new).collect()
}
pub fn from_model(mut dom:WeakDom<'a>)->Context<'a>{
pub fn find_services(&self)->Option<Services>{
Some(Services{
workspace:*self.dom.root().children().iter().find(|&&r|
self.dom.get_by_ref(r).is_some_and(|instance|instance.class=="Workspace")
)?,
game:self.dom.root_ref(),
})
}
pub fn convert_into_place(&mut self)->Services{
//snapshot root instances
let children=dom.root().children().to_owned();
let children=self.dom.root().children().to_owned();
//insert services
let game=dom.root_ref();
let game=self.dom.root_ref();
let terrain_bldr=InstanceBuilder::new("Terrain");
let workspace=dom.insert(game,
let workspace=self.dom.insert(game,
InstanceBuilder::new("Workspace")
//Set Workspace.Terrain property equal to Terrain
.with_property("Terrain",terrain_bldr.referent())
.with_child(terrain_bldr)
);
{
//Lowercase and upper case workspace property!
let game=self.dom.root_mut();
game.properties.insert("workspace".to_owned(),rbx_types::Variant::Ref(workspace));
game.properties.insert("Workspace".to_owned(),rbx_types::Variant::Ref(workspace));
}
self.dom.insert(game,InstanceBuilder::new("Lighting"));
//transfer original root instances into workspace
for instance in children{
dom.transfer_within(instance,workspace);
self.dom.transfer_within(instance,workspace);
}
{
//Lowercase and upper case workspace property!
let game=dom.root_mut();
// TODO: DELETE THIS!
game.properties.insert("workspace",rbx_types::Variant::Ref(workspace));
game.properties.insert("Workspace",rbx_types::Variant::Ref(workspace));
Services{
game,
workspace,
}
dom.insert(game,InstanceBuilder::new("Lighting"));
let services=Services{game,workspace};
Self{dom,services}
}
}
impl<'a> AsRef<WeakDom<'a>> for Context<'a>{
fn as_ref(&self)->&WeakDom<'a>{
&self.dom
}
pub struct Services{
pub game:Ref,
pub workspace:Ref,
}

View File

@@ -5,7 +5,3 @@ pub(crate) mod scheduler;
#[cfg(test)]
mod tests;
pub mod mlua{
pub use mlua::{Result,Error};
}

View File

@@ -1,59 +0,0 @@
use super::color3::Color3;
#[derive(Clone,Copy)]
pub struct BrickColor(rbx_types::BrickColor);
impl BrickColor{
pub fn from_name(name:&str)->Option<Self>{
Some(BrickColor(rbx_types::BrickColor::from_name(name)?))
}
pub fn from_number(number:u16)->Option<Self>{
Some(BrickColor(rbx_types::BrickColor::from_number(number)?))
}
}
pub fn set_globals(lua:&mlua::Lua,globals:&mlua::Table)->Result<(),mlua::Error>{
let table=lua.create_table()?;
table.raw_set("new",
lua.create_function(|_,r:mlua::Value|
match r{
mlua::Value::String(name)=>Ok(BrickColor::from_name(&*name.to_str()?)),
mlua::Value::Integer(number)=>Ok(BrickColor::from_number(number as u16)),
_=>Err(mlua::Error::runtime("Unsupported arguments"))
}
)?
)?;
macro_rules! brickcolor_constructor{
($fname:expr,$internal:ident)=>{
table.raw_set($fname,
lua.create_function(|_,_:()|
Ok(BrickColor(rbx_types::BrickColor::$internal))
)?
)?;
};
}
brickcolor_constructor!("White",White);
brickcolor_constructor!("Gray",MediumStoneGrey);
brickcolor_constructor!("DarkGray",DarkStoneGrey);
brickcolor_constructor!("Black",Black);
brickcolor_constructor!("Red",BrightRed);
brickcolor_constructor!("Yellow",BrightYellow);
brickcolor_constructor!("Green",DarkGreen);
brickcolor_constructor!("Blue",BrightBlue);
globals.set("BrickColor",table)?;
Ok(())
}
impl mlua::UserData for BrickColor{
fn add_fields<F:mlua::UserDataFields<Self>>(fields:&mut F){
fields.add_field_method_get("Color",|_,BrickColor(this)|{
let rbx_types::Color3uint8{r,g,b}=this.to_color3uint8();
Ok(Color3::from_rgb(r,g,b))
});
}
}
type_from_lua_userdata!(BrickColor);

View File

@@ -1,10 +1,7 @@
use mlua::FromLua;
use super::number::Number;
use super::vector3::Vector3;
#[derive(Clone,Copy)]
pub struct CFrame(glam::Affine3A);
pub struct CFrame(pub(crate)glam::Affine3A);
impl CFrame{
pub fn new(
@@ -37,14 +34,14 @@ fn vec3_from_glam(v:rbx_types::Vector3)->glam::Vec3A{
glam::vec3a(v.x,v.y,v.z)
}
impl From<CFrame> for rbx_types::CFrame{
fn from(CFrame(cf):CFrame)->rbx_types::CFrame{
impl Into<rbx_types::CFrame> for CFrame{
fn into(self)->rbx_types::CFrame{
rbx_types::CFrame::new(
vec3_to_glam(cf.translation),
vec3_to_glam(self.0.translation),
rbx_types::Matrix3::new(
vec3_to_glam(cf.matrix3.x_axis),
vec3_to_glam(cf.matrix3.y_axis),
vec3_to_glam(cf.matrix3.z_axis),
vec3_to_glam(self.0.matrix3.x_axis),
vec3_to_glam(self.0.matrix3.y_axis),
vec3_to_glam(self.0.matrix3.z_axis),
)
)
}
@@ -63,25 +60,16 @@ impl From<rbx_types::CFrame> for CFrame{
}
pub fn set_globals(lua:&mlua::Lua,globals:&mlua::Table)->Result<(),mlua::Error>{
let table=lua.create_table()?;
let cframe_table=lua.create_table()?;
//CFrame.new
table.raw_set("new",
lua.create_function(|lua,tuple:(
mlua::Value,mlua::Value,Option<Number>,
Option<Number>,Option<Number>,Option<Number>,
Option<Number>,Option<Number>,Option<Number>,
Option<Number>,Option<Number>,Option<Number>,
cframe_table.raw_set("new",
lua.create_function(|_,tuple:(
mlua::Value,mlua::Value,Option<f32>,
Option<f32>,Option<f32>,Option<f32>,
Option<f32>,Option<f32>,Option<f32>,
Option<f32>,Option<f32>,Option<f32>,
)|match tuple{
//CFrame.new()
(
mlua::Value::Nil,mlua::Value::Nil,None,
None,None,None,
None,None,None,
None,None,None,
)=>{
Ok(CFrame(glam::Affine3A::IDENTITY))
},
//CFrame.new(pos)
(
mlua::Value::UserData(pos),mlua::Value::Nil,None,
@@ -89,8 +77,8 @@ pub fn set_globals(lua:&mlua::Lua,globals:&mlua::Table)->Result<(),mlua::Error>{
None,None,None,
None,None,None,
)=>{
let Vector3(pos):&Vector3=&*pos.borrow()?;
Ok(CFrame::point(pos.x,pos.y,pos.z))
let pos:Vector3=pos.take()?;
Ok(CFrame::point(pos.0.x,pos.0.y,pos.0.z))
},
//TODO: CFrame.new(pos,look)
(
@@ -99,99 +87,85 @@ pub fn set_globals(lua:&mlua::Lua,globals:&mlua::Table)->Result<(),mlua::Error>{
None,None,None,
None,None,None,
)=>{
let _pos:&Vector3=&*pos.borrow()?;
let _look:&Vector3=&*look.borrow()?;
let _pos:Vector3=pos.take()?;
let _look:Vector3=look.take()?;
Err(mlua::Error::runtime("Not yet implemented"))
},
//CFrame.new(x,y,z)
(
x,y,Some(z),
mlua::Value::Number(x),mlua::Value::Number(y),Some(z),
None,None,None,
None,None,None,
None,None,None,
)=>Ok(CFrame::point(Number::from_lua(x,lua)?.into(),Number::from_lua(y,lua)?.into(),z.into())),
)=>Ok(CFrame::point(x as f32,y as f32,z)),
//CFrame.new(x,y,z,xx,yx,zx,xy,yy,zy,xz,yz,zz)
(
x,y,Some(z),
mlua::Value::Number(x),mlua::Value::Number(y),Some(z),
Some(xx),Some(yx),Some(zx),
Some(xy),Some(yy),Some(zy),
Some(xz),Some(yz),Some(zz),
)=>Ok(CFrame::new(Number::from_lua(x,lua)?.into(),Number::from_lua(y,lua)?.into(),z.into(),
xx.into(),yx.into(),zx.into(),
xy.into(),yy.into(),zy.into(),
xz.into(),yz.into(),zz.into(),
)=>Ok(CFrame::new(x as f32,y as f32,z,
xx,yx,zx,
xy,yy,zy,
xz,yz,zz,
)),
_=>Err(mlua::Error::runtime("Invalid arguments"))
})?
)?;
//CFrame.Angles
let from_euler_angles=lua.create_function(|_,(x,y,z):(Number,Number,Number)|
Ok(CFrame::angles(x.into(),y.into(),z.into()))
cframe_table.raw_set("Angles",
lua.create_function(|_,(x,y,z):(f32,f32,f32)|
Ok(CFrame::angles(x,y,z))
)?
)?;
table.raw_set("Angles",from_euler_angles.clone())?;
table.raw_set("fromEulerAnglesXYZ",from_euler_angles.clone())?;
table.raw_set("FromEulerAnglesXYZ",from_euler_angles)?;
globals.set("CFrame",table)?;
globals.set("CFrame",cframe_table)?;
Ok(())
}
impl mlua::UserData for CFrame{
fn add_fields<F:mlua::UserDataFields<Self>>(fields:&mut F){
fields.add_field_method_get("p",|_,CFrame(this)|Ok(Vector3(this.translation)));
fields.add_field_method_get("x",|_,CFrame(this)|Ok(this.translation.x));
fields.add_field_method_get("X",|_,CFrame(this)|Ok(this.translation.x));
fields.add_field_method_get("y",|_,CFrame(this)|Ok(this.translation.y));
fields.add_field_method_get("Y",|_,CFrame(this)|Ok(this.translation.y));
fields.add_field_method_get("z",|_,CFrame(this)|Ok(this.translation.z));
fields.add_field_method_get("Z",|_,CFrame(this)|Ok(this.translation.z));
fields.add_field_method_get("rightVector",|_,CFrame(this)|Ok(Vector3(this.matrix3.x_axis)));
fields.add_field_method_get("RightVector",|_,CFrame(this)|Ok(Vector3(this.matrix3.x_axis)));
fields.add_field_method_get("upVector",|_,CFrame(this)|Ok(Vector3(this.matrix3.y_axis)));
fields.add_field_method_get("UpVector",|_,CFrame(this)|Ok(Vector3(this.matrix3.y_axis)));
fields.add_field_method_get("lookVector",|_,CFrame(this)|Ok(Vector3(-this.matrix3.z_axis)));
fields.add_field_method_get("LookVector",|_,CFrame(this)|Ok(Vector3(-this.matrix3.z_axis)));
fields.add_field_method_get("XVector",|_,CFrame(this)|Ok(Vector3(this.matrix3.row(0))));
fields.add_field_method_get("YVector",|_,CFrame(this)|Ok(Vector3(this.matrix3.row(1))));
fields.add_field_method_get("ZVector",|_,CFrame(this)|Ok(Vector3(this.matrix3.row(2))));
//CFrame.p
fields.add_field_method_get("p",|_,this|Ok(Vector3(this.0.translation)));
}
fn add_methods<M:mlua::UserDataMethods<Self>>(methods:&mut M){
methods.add_method("components",|_,CFrame(this),()|Ok((
this.translation.x,
this.translation.y,
this.translation.z,
this.matrix3.x_axis.x,
this.matrix3.y_axis.x,
this.matrix3.z_axis.x,
this.matrix3.x_axis.y,
this.matrix3.y_axis.y,
this.matrix3.z_axis.y,
this.matrix3.x_axis.z,
this.matrix3.y_axis.z,
this.matrix3.z_axis.z,
methods.add_method("components",|_,this,()|Ok((
this.0.translation.x,
this.0.translation.y,
this.0.translation.z,
this.0.matrix3.x_axis.x,
this.0.matrix3.y_axis.x,
this.0.matrix3.z_axis.x,
this.0.matrix3.x_axis.y,
this.0.matrix3.y_axis.y,
this.0.matrix3.z_axis.y,
this.0.matrix3.x_axis.z,
this.0.matrix3.y_axis.z,
this.0.matrix3.z_axis.z,
)));
methods.add_method("VectorToWorldSpace",|_,CFrame(this),Vector3(v):Vector3|
Ok(Vector3(this.transform_vector3a(v)))
methods.add_method("VectorToWorldSpace",|_,this,v:Vector3|
Ok(Vector3(this.0.transform_vector3a(v.0)))
);
methods.add_meta_function(mlua::MetaMethod::Mul,|_,(CFrame(this),CFrame(val)):(Self,Self)|Ok(Self(this*val)));
methods.add_meta_function(mlua::MetaMethod::ToString,|_,CFrame(this):Self|
//methods.add_meta_method(mlua::MetaMethod::Mul,|_,this,val:&Vector3|Ok(Vector3(this.0.matrix3*val.0+this.0.translation)));
methods.add_meta_function(mlua::MetaMethod::Mul,|_,(this,val):(Self,Self)|Ok(Self(this.0*val.0)));
methods.add_meta_function(mlua::MetaMethod::ToString,|_,this:Self|
Ok(format!("CFrame.new({},{},{},{},{},{},{},{},{},{},{},{})",
this.translation.x,
this.translation.y,
this.translation.z,
this.matrix3.x_axis.x,
this.matrix3.y_axis.x,
this.matrix3.z_axis.x,
this.matrix3.x_axis.y,
this.matrix3.y_axis.y,
this.matrix3.z_axis.y,
this.matrix3.x_axis.z,
this.matrix3.y_axis.z,
this.matrix3.z_axis.z,
this.0.translation.x,
this.0.translation.y,
this.0.translation.z,
this.0.matrix3.x_axis.x,
this.0.matrix3.y_axis.x,
this.0.matrix3.z_axis.x,
this.0.matrix3.x_axis.y,
this.0.matrix3.y_axis.y,
this.0.matrix3.z_axis.y,
this.0.matrix3.x_axis.z,
this.0.matrix3.y_axis.z,
this.0.matrix3.z_axis.z,
))
);
}

View File

@@ -1,5 +1,3 @@
use super::number::Number;
#[derive(Clone,Copy)]
pub struct Color3{
r:f32,
@@ -10,37 +8,28 @@ impl Color3{
pub const fn new(r:f32,g:f32,b:f32)->Self{
Self{r,g,b}
}
pub const fn from_rgb(r:u8,g:u8,b:u8)->Self{
Color3::new(r as f32/255.0,g as f32/255.0,b as f32/255.0)
}
}
impl From<rbx_types::Color3> for Color3{
fn from(value:rbx_types::Color3)->Color3{
Color3::new(value.r,value.g,value.b)
}
}
impl From<Color3> for rbx_types::Color3{
fn from(value:Color3)->rbx_types::Color3{
rbx_types::Color3::new(value.r,value.g,value.b)
impl Into<rbx_types::Color3> for Color3{
fn into(self)->rbx_types::Color3{
rbx_types::Color3::new(self.r,self.g,self.b)
}
}
pub fn set_globals(lua:&mlua::Lua,globals:&mlua::Table)->Result<(),mlua::Error>{
let table=lua.create_table()?;
let color3_table=lua.create_table()?;
table.raw_set("new",
lua.create_function(|_,(r,g,b):(Number,Number,Number)|
Ok(Color3::new(r.into(),g.into(),b.into()))
color3_table.raw_set("new",
lua.create_function(|_,(r,g,b):(f32,f32,f32)|
Ok(Color3::new(r,g,b))
)?
)?;
color3_table.raw_set("fromRGB",
lua.create_function(|_,(r,g,b):(u8,u8,u8)|
Ok(Color3::new(r as f32/255.0,g as f32/255.0,b as f32/255.0))
)?
)?;
let from_rgb=lua.create_function(|_,(r,g,b):(u8,u8,u8)|
Ok(Color3::from_rgb(r,g,b))
)?;
table.raw_set("fromRGB",from_rgb.clone())?;
table.raw_set("FromRGB",from_rgb)?;
globals.set("Color3",table)?;
globals.set("Color3",color3_table)?;
Ok(())
}

View File

@@ -1,29 +1,31 @@
#[derive(Clone)]
pub struct ColorSequence(rbx_types::ColorSequence);
#[derive(Clone,Copy)]
pub struct ColorSequence{}
impl ColorSequence{
pub const fn new(keypoints:Vec<rbx_types::ColorSequenceKeypoint>)->Self{
Self(rbx_types::ColorSequence{keypoints})
pub const fn new()->Self{
Self{}
}
}
impl From<ColorSequence> for rbx_types::ColorSequence{
fn from(ColorSequence(value):ColorSequence)->rbx_types::ColorSequence{
value
impl Into<rbx_types::ColorSequence> for ColorSequence{
fn into(self)->rbx_types::ColorSequence{
rbx_types::ColorSequence{
keypoints:Vec::new()
}
}
}
pub fn set_globals(lua:&mlua::Lua,globals:&mlua::Table)->Result<(),mlua::Error>{
let table=lua.create_table()?;
let number_sequence_table=lua.create_table()?;
table.raw_set("new",
number_sequence_table.raw_set("new",
lua.create_function(|_,_:mlua::MultiValue|
Ok(ColorSequence::new(Vec::new()))
Ok(ColorSequence::new())
)?
)?;
globals.set("ColorSequence",table)?;
globals.set("ColorSequence",number_sequence_table)?;
Ok(())
}
impl mlua::UserData for ColorSequence{}
type_from_lua_userdata_clone!(ColorSequence);
type_from_lua_userdata!(ColorSequence);

View File

@@ -1,136 +1,63 @@
#[derive(Clone,Copy)]
pub struct EnumItem<'a>{
name:Option<&'a str>,
value:u32,
}
impl<'a> EnumItem<'a>{
fn known_name((name,&value):(&&'a str,&u32))->Self{
Self{name:Some(name),value}
}
}
impl<'a> From<rbx_types::Enum> for EnumItem<'a>{
fn from(e:rbx_types::Enum)->Self{
EnumItem{
name:None,
value:e.to_u32(),
}
}
}
impl From<EnumItem<'_>> for rbx_types::Enum{
fn from(e:EnumItem)->rbx_types::Enum{
rbx_types::Enum::from_u32(e.value)
}
}
impl PartialEq for EnumItem<'_>{
fn eq(&self,other:&EnumItem<'_>)->bool{
self.value==other.value&&{
// if both names are known, they must match, otherwise whatever
match (self.name,other.name){
(Some(lhs),Some(rhs))=>lhs==rhs,
_=>true,
}
}
}
}
use mlua::IntoLua;
#[derive(Clone,Copy)]
pub struct Enums;
impl Enums{
pub fn get(&self,index:&str)->Option<EnumItems<'static>>{
let db=rbx_reflection_database::get();
db.enums.get(index).map(|ed|EnumItems{ed})
}
}
pub struct Enum(u32);
#[derive(Clone,Copy)]
pub struct EnumItems<'a>{
pub struct EnumItems;
#[derive(Clone,Copy)]
pub struct EnumItem<'a>{
ed:&'a rbx_reflection::EnumDescriptor<'a>,
}
impl<'a> EnumItems<'a>{
pub fn from_value(&self,value:u32)->Option<EnumItem<'a>>{
self.ed.items.iter().find(|&(_,&v)|v==value).map(EnumItem::known_name)
}
pub fn from_name(&self,name:&str)->Option<EnumItem<'a>>{
self.ed.items.get_key_value(name).map(EnumItem::known_name)
}
pub fn from_enum(&self,enum_item:EnumItem)->Option<EnumItem<'a>>{
match enum_item.name{
Some(s)=>{
let got=self.from_name(s)?;
(got.value==enum_item.value).then_some(got)
},
None=>self.from_value(enum_item.value)
}
impl Into<rbx_types::Enum> for Enum{
fn into(self)->rbx_types::Enum{
rbx_types::Enum::from_u32(self.0)
}
}
pub enum CoerceEnum<'a>{
Integer(i32),
String(mlua::String),
Enum(EnumItem<'a>),
}
impl CoerceEnum<'_>{
pub fn coerce_to<'a>(self,enum_items:EnumItems<'a>)->mlua::Result<EnumItem<'a>>{
match self{
CoerceEnum::Integer(int)=>enum_items.from_value(int as u32),
CoerceEnum::String(s)=>enum_items.from_name(&*s.to_str()?),
CoerceEnum::Enum(enum_item)=>enum_items.from_enum(enum_item),
}.ok_or_else(||mlua::Error::runtime(format!("Bad {} EnumItem",enum_items.ed.name)))
}
}
impl mlua::FromLua for CoerceEnum<'_>{
fn from_lua(value:mlua::Value,_lua:&mlua::Lua)->Result<Self,mlua::Error>{
match value{
mlua::Value::Integer(int)=>Ok(CoerceEnum::Integer(int)),
mlua::Value::String(s)=>Ok(CoerceEnum::String(s)),
mlua::Value::UserData(ud)=>Ok(CoerceEnum::Enum(*ud.borrow()?)),
other=>Err(mlua::Error::runtime(format!("Expected {} got {:?}",stringify!(Enum),other))),
}
impl<'a> EnumItem<'a>{
const fn new(ed:&'a rbx_reflection::EnumDescriptor)->Self{
Self{ed}
}
}
pub fn set_globals(_lua:&mlua::Lua,globals:&mlua::Table)->Result<(),mlua::Error>{
globals.set("Enum",Enums)
globals.set("Enum",EnumItems)
}
impl mlua::UserData for EnumItems<'static>{
fn add_fields<F:mlua::UserDataFields<Self>>(_fields:&mut F){
}
fn add_methods<M:mlua::UserDataMethods<Self>>(methods:&mut M){
methods.add_method("FromName",|_,this:&EnumItems,name:mlua::String|Ok(this.from_name(&*name.to_str()?)));
methods.add_method("FromValue",|_,this:&EnumItems,value:u32|Ok(this.from_value(value)));
methods.add_method("GetEnumItems",|_,this:&EnumItems,()|->mlua::Result<Vec<EnumItem>>{
Ok(this.ed.items.iter().map(EnumItem::known_name).collect())
});
methods.add_meta_function(mlua::MetaMethod::Index,|_,(this,val):(EnumItems,mlua::String)|{
let index=&*val.to_str()?;
Ok(this.ed.items.get_key_value(index).map(EnumItem::known_name))
});
}
}
type_from_lua_userdata_lua_lifetime!(EnumItems);
impl mlua::UserData for Enums{
fn add_fields<F:mlua::UserDataFields<Self>>(_fields:&mut F){
}
fn add_methods<M:mlua::UserDataMethods<Self>>(methods:&mut M){
methods.add_meta_function(mlua::MetaMethod::Index,|_,(enums,val):(Self,mlua::String)|{
Ok(enums.get(&*val.to_str()?))
});
}
}
type_from_lua_userdata!(Enums);
impl mlua::UserData for EnumItem<'_>{
fn add_fields<F:mlua::UserDataFields<Self>>(fields:&mut F){
fields.add_field_method_get("Name",|_,this|Ok(this.name));
fields.add_field_method_get("Value",|_,this|Ok(this.value));
fn add_fields<F:mlua::UserDataFields<Self>>(_fields:&mut F){
}
fn add_methods<M:mlua::UserDataMethods<Self>>(methods:&mut M){
methods.add_meta_function(mlua::MetaMethod::Eq,|_,(lhs,rhs):(EnumItem<'_>,EnumItem<'_>)|{
Ok(lhs==rhs)
methods.add_meta_function(mlua::MetaMethod::Index,|lua,(this,val):(EnumItem<'_>,mlua::String)|{
match this.ed.items.get(&*val.to_str()?){
Some(&id)=>Enum(id).into_lua(lua),
None=>mlua::Value::Nil.into_lua(lua),
}
});
}
}
type_from_lua_userdata_lua_lifetime!(EnumItem);
impl mlua::UserData for EnumItems{
fn add_fields<F:mlua::UserDataFields<Self>>(_fields:&mut F){
}
fn add_methods<M:mlua::UserDataMethods<Self>>(methods:&mut M){
methods.add_meta_function(mlua::MetaMethod::Index,|lua,(_,val):(Self,mlua::String)|{
let db=rbx_reflection_database::get();
match db.enums.get(&*val.to_str()?){
Some(ed)=>EnumItem::new(ed).into_lua(lua),
None=>mlua::Value::Nil.into_lua(lua),
}
});
}
}
type_from_lua_userdata!(EnumItems);
impl mlua::UserData for Enum{
fn add_fields<F:mlua::UserDataFields<Self>>(_fields:&mut F){
}
fn add_methods<M:mlua::UserDataMethods<Self>>(_methods:&mut M){
}
}
type_from_lua_userdata!(Enum);

View File

@@ -5,47 +5,45 @@ use rbx_types::Ref;
use rbx_dom_weak::{InstanceBuilder,WeakDom};
use crate::runner::vector3::Vector3;
use crate::runner::number::Number;
pub fn set_globals(lua:&mlua::Lua,globals:&mlua::Table)->Result<(),mlua::Error>{
//class functions store
lua.set_app_data(ClassMethodsStore::default());
lua.set_app_data(InstanceValueStore::default());
let table=lua.create_table()?;
let instance_table=lua.create_table()?;
//Instance.new
table.raw_set("new",
instance_table.raw_set("new",
lua.create_function(|lua,(class_name,parent):(mlua::String,Option<Instance>)|{
let class_name_str=&*class_name.to_str()?;
let parent_ref=parent.map_or(Ref::none(),|instance|instance.referent);
let db=rbx_reflection_database::get();
let Some((known_class_name,_))=db.classes.get_key_value(class_name_str)else{
return Err(mlua::Error::runtime("Unknown ClassName"));
};
let parent=parent.ok_or_else(||mlua::Error::runtime("Nil Parent not yet supported"))?;
dom_mut(lua,|dom|{
Ok(Instance::new_unchecked(dom.insert(parent_ref,InstanceBuilder::new(known_class_name))))
//TODO: Nil instances
Ok(Instance::new(dom.insert(parent.referent,InstanceBuilder::new(class_name_str))))
})
})?
)?;
globals.set("Instance",table)?;
globals.set("Instance",instance_table)?;
Ok(())
}
// LMAO look at this function!
pub fn dom_mut<T>(lua:&mlua::Lua,mut f:impl FnMut(&mut WeakDom)->mlua::Result<T>)->mlua::Result<T>{
let mut dom=lua.app_data_mut::<crate::context::LuaAppData>().ok_or_else(||mlua::Error::runtime("DataModel missing"))?;
let mut dom=lua.app_data_mut::<&'static mut WeakDom>().ok_or_else(||mlua::Error::runtime("DataModel missing"))?;
f(*dom)
}
pub fn class_is_a(class:&str,superclass:&str)->bool{
let db=rbx_reflection_database::get();
let (Some(class),Some(superclass))=(db.classes.get(class),db.classes.get(superclass))else{
return false;
};
db.has_superclass(class,superclass)
fn coerce_float32(value:&mlua::Value)->Option<f32>{
match value{
&mlua::Value::Integer(i)=>Some(i as f32),
&mlua::Value::Number(f)=>Some(f as f32),
_=>None,
}
}
fn get_full_name(dom:&rbx_dom_weak::WeakDom,instance:&rbx_dom_weak::Instance)->String{
let mut full_name=instance.name.clone();
let mut pref=instance.parent();
@@ -68,74 +66,71 @@ 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<'a>,instance:&rbx_dom_weak::Instance<'a>,name:&str)->Option<&'a rbx_dom_weak::Instance<'a>>{
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>{
instance.children().iter().filter_map(|&r|dom.get_by_ref(r)).find(|inst|inst.name==name)
}
pub fn find_first_descendant<'a>(dom:&'a rbx_dom_weak::WeakDom<'a>,instance:&rbx_dom_weak::Instance<'a>,name:&str)->Option<&'a rbx_dom_weak::Instance<'a>>{
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>{
dom.descendants_of(instance.referent()).find(|&inst|inst.name==name)
}
pub fn find_first_child_of_class<'a>(dom:&'a rbx_dom_weak::WeakDom<'a>,instance:&rbx_dom_weak::Instance<'a>,class:&str)->Option<&'a rbx_dom_weak::Instance<'a>>{
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>{
instance.children().iter().filter_map(|&r|dom.get_by_ref(r)).find(|inst|inst.class==class)
}
pub fn find_first_descendant_of_class<'a>(dom:&'a rbx_dom_weak::WeakDom<'a>,instance:&rbx_dom_weak::Instance<'a>,class:&str)->Option<&'a rbx_dom_weak::Instance<'a>>{
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>{
dom.descendants_of(instance.referent()).find(|&inst|inst.class==class)
}
pub fn find_first_child_which_is_a<'a>(dom:&'a rbx_dom_weak::WeakDom<'a>,instance:&rbx_dom_weak::Instance<'a>,superclass:&str)->Option<&'a rbx_dom_weak::Instance<'a>>{
let db=rbx_reflection_database::get();
let superclass_descriptor=db.classes.get(superclass)?;
instance.children().iter().filter_map(|&r|dom.get_by_ref(r)).find(|inst|{
db.classes.get(inst.class).is_some_and(|descriptor|db.has_superclass(descriptor,superclass_descriptor))
})
}
pub fn find_first_descendant_which_is_a<'a>(dom:&'a rbx_dom_weak::WeakDom<'a>,instance:&rbx_dom_weak::Instance<'a>,superclass:&str)->Option<&'a rbx_dom_weak::Instance<'a>>{
let db=rbx_reflection_database::get();
let superclass_descriptor=db.classes.get(superclass)?;
dom.descendants_of(instance.referent()).find(|inst|{
db.classes.get(inst.class).is_some_and(|descriptor|db.has_superclass(descriptor,superclass_descriptor))
})
}
#[derive(Clone,Copy)]
pub struct Instance{
referent:Ref,
}
impl Instance{
pub const fn new_unchecked(referent:Ref)->Self{
pub const fn new(referent:Ref)->Self{
Self{referent}
}
pub fn new(referent:Ref)->Option<Self>{
referent.is_some().then_some(Self{referent})
}
pub fn get<'a>(&self,dom:&'a WeakDom<'a>)->mlua::Result<&'a rbx_dom_weak::Instance<'a>>{
pub fn get<'a>(&self,dom:&'a WeakDom)->mlua::Result<&'a rbx_dom_weak::Instance>{
dom.get_by_ref(self.referent).ok_or_else(||mlua::Error::runtime("Instance missing"))
}
pub fn get_mut<'a,'b>(&self,dom:&'a mut WeakDom<'b>)->mlua::Result<&'a mut rbx_dom_weak::Instance<'b>>{
pub fn get_mut<'a>(&self,dom:&'a mut WeakDom)->mlua::Result<&'a mut rbx_dom_weak::Instance>{
dom.get_by_ref_mut(self.referent).ok_or_else(||mlua::Error::runtime("Instance missing"))
}
}
type_from_lua_userdata!(Instance);
//TODO: update rbx_reflection and use dom.superclasses_iter
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 get_parent(lua:&mlua::Lua,this:&Instance)->mlua::Result<Option<Instance>>{
fields.add_field_method_get("Parent",|lua,this|{
dom_mut(lua,|dom|{
let instance=this.get(dom)?;
Ok(Instance::new(instance.parent()))
})
}
fields.add_field_method_get("parent",get_parent);
fields.add_field_method_get("Parent",get_parent);
fn set_parent(lua:&mlua::Lua,this:&mut Instance,new_parent:Option<Instance>)->mlua::Result<()>{
let parent_ref=new_parent.map_or(Ref::none(),|instance|instance.referent);
});
fields.add_field_method_set("Parent",|lua,this,val:Option<Instance>|{
let parent=val.ok_or_else(||mlua::Error::runtime("Nil Parent not yet supported"))?;
dom_mut(lua,|dom|{
dom.transfer_within(this.referent,parent_ref);
dom.transfer_within(this.referent,parent.referent);
Ok(())
})
}
fields.add_field_method_set("parent",set_parent);
fields.add_field_method_set("Parent",set_parent);
});
fields.add_field_method_get("Name",|lua,this|{
dom_mut(lua,|dom|{
let instance=this.get(dom)?;
@@ -153,38 +148,22 @@ impl mlua::UserData for Instance{
fields.add_field_method_get("ClassName",|lua,this|{
dom_mut(lua,|dom|{
let instance=this.get(dom)?;
Ok(instance.class.to_owned())
Ok(instance.class.clone())
})
});
}
fn add_methods<M:mlua::UserDataMethods<Self>>(methods:&mut M){
fn clone(lua:&mlua::Lua,this:&Instance,_:())->mlua::Result<Instance>{
dom_mut(lua,|dom|{
let instance_ref=dom.clone_within(this.referent);
Ok(Instance::new_unchecked(instance_ref))
})
}
methods.add_method("clone",clone);
methods.add_method("Clone",clone);
fn get_children(lua:&mlua::Lua,this:&Instance,_:())->mlua::Result<Vec<Instance>>{
methods.add_method("GetChildren",|lua,this,_:()|
dom_mut(lua,|dom|{
let instance=this.get(dom)?;
let children:Vec<_>=instance
.children()
.iter()
.copied()
.map(Instance::new_unchecked)
.map(Instance::new)
.collect();
Ok(children)
})
}
methods.add_method("children",get_children);
methods.add_method("GetChildren",get_children);
methods.add_method("GetFullName",|lua,this,()|
dom_mut(lua,|dom|{
let instance=this.get(dom)?;
Ok(get_full_name(dom,instance))
})
);
fn ffc(lua:&mlua::Lua,this:&Instance,(name,search_descendants):(mlua::String,Option<bool>))->mlua::Result<Option<Instance>>{
let name_str=&*name.to_str()?;
@@ -196,7 +175,7 @@ impl mlua::UserData for Instance{
false=>find_first_child(dom,instance,name_str),
}
.map(|instance|
Instance::new_unchecked(instance.referent())
Instance::new(instance.referent())
)
)
})
@@ -206,29 +185,13 @@ impl mlua::UserData for Instance{
methods.add_method("FindFirstChildOfClass",|lua,this,(class,search_descendants):(mlua::String,Option<bool>)|{
let class_str=&*class.to_str()?;
dom_mut(lua,|dom|{
let inst=this.get(dom)?;
Ok(
match search_descendants.unwrap_or(false){
true=>find_first_descendant_of_class(dom,inst,class_str),
false=>find_first_child_of_class(dom,inst,class_str),
true=>find_first_descendant_of_class(dom,this.get(dom)?,class_str),
false=>find_first_child_of_class(dom,this.get(dom)?,class_str),
}
.map(|instance|
Instance::new_unchecked(instance.referent())
)
)
})
});
methods.add_method("FindFirstChildWhichIsA",|lua,this,(class,search_descendants):(mlua::String,Option<bool>)|{
let class_str=&*class.to_str()?;
dom_mut(lua,|dom|{
let inst=this.get(dom)?;
Ok(
match search_descendants.unwrap_or(false){
true=>find_first_descendant_which_is_a(dom,inst,class_str),
false=>find_first_child_which_is_a(dom,inst,class_str),
}
.map(|instance|
Instance::new_unchecked(instance.referent())
Instance::new(instance.referent())
)
)
})
@@ -238,42 +201,24 @@ impl mlua::UserData for Instance{
let children:Vec<_>=dom
.descendants_of(this.referent)
.map(|instance|
Instance::new_unchecked(instance.referent())
Instance::new(instance.referent())
)
.collect();
Ok(children)
})
);
methods.add_method("IsAncestorOf",|lua,this,descendant:Instance|
dom_mut(lua,|dom|{
let instance=descendant.get(dom)?;
Ok(std::iter::successors(Some(instance),|inst|dom.get_by_ref(inst.parent())).any(|inst|inst.referent()==this.referent))
})
);
methods.add_method("IsDescendantOf",|lua,this,ancestor:Instance|
methods.add_method("IsA",|lua,this,classname:mlua::String|
dom_mut(lua,|dom|{
let instance=this.get(dom)?;
Ok(std::iter::successors(Some(instance),|inst|dom.get_by_ref(inst.parent())).any(|inst|inst.referent()==ancestor.referent))
Ok(crate::context::class_is_a(instance.class.as_str(),&*classname.to_str()?))
})
);
fn is_a(lua:&mlua::Lua,this:&Instance,classname:mlua::String)->mlua::Result<bool>{
methods.add_method("Destroy",|lua,this,()|
dom_mut(lua,|dom|{
let instance=this.get(dom)?;
Ok(class_is_a(instance.class,&*classname.to_str()?))
})
}
methods.add_method("isA",is_a);
methods.add_method("IsA",is_a);
fn destroy(lua:&mlua::Lua,this:&Instance,_:())->mlua::Result<()>{
dom_mut(lua,|dom|{
dom.transfer_within(this.referent,Ref::none());
dom.destroy(this.referent);
Ok(())
})
}
methods.add_method("remove",destroy);
methods.add_method("Remove",destroy);
methods.add_method("destroy",destroy);
methods.add_method("Destroy",destroy);
);
methods.add_meta_function(mlua::MetaMethod::ToString,|lua,this:Instance|{
dom_mut(lua,|dom|{
let instance=this.get(dom)?;
@@ -286,34 +231,40 @@ impl mlua::UserData for Instance{
let instance=this.get(dom)?;
//println!("__index t={} i={index:?}",instance.name);
let db=rbx_reflection_database::get();
let class=db.classes.get(instance.class).ok_or_else(||mlua::Error::runtime("Class missing"))?;
// Find existing property
match instance.properties.get(index_str).cloned()
let class=db.classes.get(instance.class.as_str()).ok_or_else(||mlua::Error::runtime("Class missing"))?;
//Find existing property
match instance.properties.get(index_str)
.cloned()
//Find default value
.or_else(||db.find_default_property(class,index_str).cloned())
//Find virtual property
.or_else(||db.superclasses_iter(class).find_map(|class|
find_virtual_property(&instance.properties,class,index_str)
))
.or_else(||{
SuperClassIter{
database:db,
descriptor:Some(class),
}
.find_map(|class|
find_virtual_property(&instance.properties,class,index_str)
)
})
{
Some(rbx_types::Variant::Bool(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::Float32(val))=>return val.into_lua(lua),
Some(rbx_types::Variant::Float64(val))=>return val.into_lua(lua),
Some(rbx_types::Variant::String(val))=>return val.into_lua(lua),
Some(rbx_types::Variant::Ref(val))=>return Instance::new_unchecked(val).into_lua(lua),
Some(rbx_types::Variant::Enum(e))=>return crate::runner::r#enum::EnumItem::from(e).into_lua(lua),
Some(rbx_types::Variant::Color3(c))=>return crate::runner::color3::Color3::from(c).into_lua(lua),
Some(rbx_types::Variant::CFrame(cf))=>return crate::runner::cframe::CFrame::from(cf).into_lua(lua),
Some(rbx_types::Variant::Vector2(v))=>return crate::runner::vector2::Vector2::from(v).into_lua(lua),
Some(rbx_types::Variant::Vector3(v))=>return crate::runner::vector3::Vector3::from(v).into_lua(lua),
Some(rbx_types::Variant::Ref(val))=>return Instance::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::Vector3(v))=>return Into::<crate::runner::vector3::Vector3>::into(v).into_lua(lua),
None=>(),
other=>return Err(mlua::Error::runtime(format!("Instance.__index Unsupported property type instance={} index={index_str} value={other:?}",instance.name))),
}
//find a function with a matching name
if let Some(function)=class_methods_store_mut(lua,|cf|{
db.superclasses_iter(class).find_map(|class|{
let mut iter=SuperClassIter{
database:db,
descriptor:Some(class),
};
iter.find_map(|class|{
let mut class_methods=cf.get_or_create_class_methods(&class.name)?;
class_methods.get_or_create_function(lua,index_str)
.transpose()
@@ -323,111 +274,80 @@ impl mlua::UserData for Instance{
}
//find or create an associated userdata object
let instance=this.get_mut(dom)?;
if let Some(value)=get_or_create_userdata(instance,lua,index_str)?{
if let Some(value)=instance_value_store_mut(lua,|ivs|{
//TODO: walk class tree somehow
match ivs.get_or_create_instance_values(&instance){
Some(mut instance_values)=>instance_values.get_or_create_value(lua,index_str),
None=>Ok(None)
}
})?{
return value.into_lua(lua);
}
// drop mutable borrow
//find a child with a matching name
let instance=this.get(dom)?;
find_first_child(dom,instance,index_str)
.map(|instance|Instance::new_unchecked(instance.referent()))
.map(|instance|Instance::new(instance.referent()))
.into_lua(lua)
})
});
methods.add_meta_function(mlua::MetaMethod::NewIndex,|lua,(this,index,value):(Instance,mlua::String,mlua::Value)|{
let index_str=&*index.to_str()?;
dom_mut(lua,|dom|{
let instance=this.get_mut(dom)?;
//println!("__newindex t={} i={index:?} v={value:?}",instance.name);
let index_str=&*index.to_str()?;
let db=rbx_reflection_database::get();
let class=db.classes.get(instance.class).ok_or_else(||mlua::Error::runtime("Class missing"))?;
let (prop_name,property)=db.superclasses_iter(class).find_map(|cls|
cls.properties.get_key_value(index_str)
).ok_or_else(||
mlua::Error::runtime(format!("Property '{index_str}' missing on class '{}'",class.name))
)?;
let value=match &property.data_type{
let class=db.classes.get(instance.class.as_str()).ok_or_else(||mlua::Error::runtime("Class missing"))?;
let mut iter=SuperClassIter{
database:db,
descriptor:Some(class),
};
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{
rbx_reflection::DataType::Value(rbx_types::VariantType::Vector3)=>{
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()));
},
rbx_reflection::DataType::Value(rbx_types::VariantType::Float32)=>{
let typed_value=Number::from_lua(value.clone(),lua)?.to_f32();
rbx_types::Variant::Float32(typed_value)
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));
},
rbx_reflection::DataType::Enum(enum_name)=>{
let typed_value=match &value{
&mlua::Value::Integer(int)=>Ok(rbx_types::Enum::from_u32(int as u32)),
&mlua::Value::Number(num)=>Ok(rbx_types::Enum::from_u32(num as u32)),
mlua::Value::String(s)=>{
let e=db.enums.get(enum_name).ok_or_else(||mlua::Error::runtime("Database DataType Enum name does not exist"))?;
let e=db.enums.get(enum_name).ok_or_else(||mlua::Error::runtime("Database DataType Enum name does not exist"))?;
Ok(rbx_types::Enum::from_u32(*e.items.get(&*s.to_str()?).ok_or_else(||mlua::Error::runtime("Invalid enum item"))?))
},
mlua::Value::UserData(any_user_data)=>{
let e:crate::runner::r#enum::EnumItem=*any_user_data.borrow()?;
let e:crate::runner::r#enum::Enum=*any_user_data.borrow()?;
Ok(e.into())
},
_=>Err(mlua::Error::runtime("Expected Enum")),
}?;
rbx_types::Variant::Enum(typed_value)
instance.properties.insert(index_str.to_owned(),rbx_types::Variant::Enum(typed_value));
},
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()?;
rbx_types::Variant::Color3(typed_value.into())
instance.properties.insert(index_str.to_owned(),rbx_types::Variant::Color3(typed_value.into()));
},
rbx_reflection::DataType::Value(rbx_types::VariantType::Bool)=>{
let typed_value=value.as_boolean().ok_or_else(||mlua::Error::runtime("Expected boolean"))?;
rbx_types::Variant::Bool(typed_value)
},
rbx_reflection::DataType::Value(rbx_types::VariantType::Int32)=>{
let typed_value=value.as_i32().ok_or_else(||mlua::Error::runtime("Expected Int32"))?;
rbx_types::Variant::Int32(typed_value)
instance.properties.insert(index_str.to_owned(),rbx_types::Variant::Bool(typed_value));
},
rbx_reflection::DataType::Value(rbx_types::VariantType::String)=>{
let typed_value=match &value{
mlua::Value::Integer(i)=>i.to_string(),
mlua::Value::Number(n)=>n.to_string(),
mlua::Value::String(s)=>s.to_str()?.to_owned(),
_=>return Err(mlua::Error::runtime("Expected string")),
};
rbx_types::Variant::String(typed_value)
},
rbx_reflection::DataType::Value(rbx_types::VariantType::UDim2)=>{
let typed_value:&crate::runner::udim2::UDim2=&*value.as_userdata().ok_or_else(||mlua::Error::runtime("Expected UDim2"))?.borrow()?;
rbx_types::Variant::UDim2(typed_value.clone().into())
},
rbx_reflection::DataType::Value(rbx_types::VariantType::NumberRange)=>{
let typed_value:&crate::runner::number_range::NumberRange=&*value.as_userdata().ok_or_else(||mlua::Error::runtime("Expected NumberRange"))?.borrow()?;
rbx_types::Variant::NumberRange(typed_value.clone().into())
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()));
},
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()?;
rbx_types::Variant::NumberSequence(typed_value.clone().into())
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()));
},
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()?;
rbx_types::Variant::ColorSequence(typed_value.clone().into())
},
rbx_reflection::DataType::Value(rbx_types::VariantType::Vector2)=>{
let typed_value:crate::runner::vector2::Vector2=*value.as_userdata().ok_or_else(||mlua::Error::runtime("Expected Vector2"))?.borrow()?;
rbx_types::Variant::Vector2(typed_value.clone().into())
},
rbx_reflection::DataType::Value(rbx_types::VariantType::Vector3)=>{
let typed_value:crate::runner::vector3::Vector3=*value.as_userdata().ok_or_else(||mlua::Error::runtime("Expected Vector3"))?.borrow()?;
rbx_types::Variant::Vector3(typed_value.clone().into())
},
rbx_reflection::DataType::Value(rbx_types::VariantType::CFrame)=>{
let typed_value:crate::runner::cframe::CFrame=*value.as_userdata().ok_or_else(||mlua::Error::runtime("Expected CFrame"))?.borrow()?;
rbx_types::Variant::CFrame(typed_value.clone().into())
},
rbx_reflection::DataType::Value(rbx_types::VariantType::ContentId)=>{
let typed_value=value.as_str().ok_or_else(||mlua::Error::runtime("Expected string"))?.to_owned();
rbx_types::Variant::ContentId(typed_value.into())
},
rbx_reflection::DataType::Value(rbx_types::VariantType::Ref)=>{
// why clone?
let typed_value=Option::<Instance>::from_lua(value.clone(),lua)?;
rbx_types::Variant::Ref(typed_value.map_or(Ref::none(),|instance|instance.referent))
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()));
},
other=>return Err(mlua::Error::runtime(format!("Unimplemented property type: {other:?}"))),
};
instance.properties.insert(prop_name,value);
}
Ok(())
})
});
@@ -452,59 +372,28 @@ type CFD=phf::Map<&'static str,// Class name
ClassFunctionPointer
>
>;
const SERVICES:phf::Map<&'static str,()> =phf::phf_map!{
"Lighting"=>(),
"RunService"=>(),
"Players"=>(),
"Workspace"=>(),
"MaterialService"=>(),
"TweenService"=>(),
};
const GET_SERVICE:ClassFunctionPointer=cf!(|lua,_this,service:mlua::String|{
dom_mut(lua,|dom|{
//dom.root_ref()==this.referent ?
let service=&*service.to_str()?;
let Some(known_service)=SERVICES.get_key(service)else{
return Err(mlua::Error::runtime(format!("Service '{service}' not supported")));
};
let referent=find_first_child_of_class(dom,dom.root(),known_service)
.map(|instance|instance.referent())
.unwrap_or_else(||
dom.insert(dom.root_ref(),InstanceBuilder::new(known_service))
);
Ok(Instance::new_unchecked(referent))
})
});
const GET_PLAYERS:ClassFunctionPointer=cf!(|_lua,_this,()|->mlua::Result<_>{
Ok(Vec::<Instance>::new())
});
const NO_OP:ClassFunctionPointer=cf!(|_lua,_this,_:mlua::MultiValue|->mlua::Result<_>{Ok(())});
static CLASS_FUNCTION_DATABASE:CFD=phf::phf_map!{
"DataModel"=>phf::phf_map!{
"service"=>GET_SERVICE,
"GetService"=>GET_SERVICE,
"GetService"=>cf!(|lua,_this,service:mlua::String|{
dom_mut(lua,|dom|{
//dom.root_ref()==this.referent ?
let service=&*service.to_str()?;
match service{
"Lighting"|"RunService"=>{
let referent=find_first_child_of_class(dom,dom.root(),service)
.map(|instance|instance.referent())
.unwrap_or_else(||
dom.insert(dom.root_ref(),InstanceBuilder::new(service))
);
Ok(Instance::new(referent))
},
other=>Err::<Instance,_>(mlua::Error::runtime(format!("Service '{other}' not supported"))),
}
})
}),
},
"Terrain"=>phf::phf_map!{
"FillBall"=>cf!(|_lua,_,_:(Vector3,Number,crate::runner::r#enum::CoerceEnum)|mlua::Result::Ok(())),
"FillBlock"=>cf!(|_lua,_,_:(crate::runner::cframe::CFrame,Vector3,crate::runner::r#enum::CoerceEnum)|mlua::Result::Ok(())),
"FillCylinder"=>cf!(|_lua,_,_:(crate::runner::cframe::CFrame,Number,Number,crate::runner::r#enum::CoerceEnum)|mlua::Result::Ok(())),
"SetMaterialColor"=>cf!(|_lua,_,_:(crate::runner::r#enum::CoerceEnum,crate::runner::color3::Color3)|mlua::Result::Ok(())),
},
"Players"=>phf::phf_map!{
"players"=>GET_PLAYERS,
"GetPlayers"=>GET_PLAYERS,
},
"Sound"=>phf::phf_map!{
"Play"=>NO_OP,
},
"TweenService"=>phf::phf_map!{
"Create"=>cf!(|_lua,_,(instance,tween_info,goal):(Instance,crate::runner::tween_info::TweenInfo,mlua::Table)|->mlua::Result<_>{
Ok(crate::runner::tween::Tween::create(
instance,
tween_info,
goal,
))
}),
"FillBlock"=>cf!(|_lua,_,_:(crate::runner::cframe::CFrame,Vector3,crate::runner::r#enum::Enum)|mlua::Result::Ok(()))
},
};
@@ -591,9 +480,9 @@ static VIRTUAL_PROPERTY_DATABASE:VPD=phf::phf_map!{
};
fn find_virtual_property(
properties:&rbx_dom_weak::AHashMap<&str,rbx_types::Variant>,
properties:&HashMap<String,rbx_types::Variant>,
class:&rbx_reflection::ClassDescriptor,
index:&str,
index:&str
)->Option<rbx_types::Variant>{
//Find virtual property
let class_virtual_properties=VIRTUAL_PROPERTY_DATABASE.get(&class.name)?;
@@ -607,54 +496,62 @@ fn find_virtual_property(
}
// lazy-loaded per-instance userdata values
// This whole thing is a bad idea and a garbage collection nightmare.
// TODO: recreate rbx_dom_weak with my own instance type that owns this data.
type CreateUserData=fn(&mlua::Lua)->mlua::Result<mlua::AnyUserData>;
type LUD=phf::Map<&'static str,// Class name
phf::Map<&'static str,// Value name
CreateUserData
>
>;
fn create_script_signal(lua:&mlua::Lua)->mlua::Result<mlua::AnyUserData>{
lua.create_any_userdata(crate::runner::script_signal::ScriptSignal::new())
}
static LAZY_USER_DATA:LUD=phf::phf_map!{
"RunService"=>phf::phf_map!{
"Stepped"=>create_script_signal,
"Heartbeat"=>create_script_signal,
"RenderStepped"=>create_script_signal,
},
"Players"=>phf::phf_map!{
"PlayerAdded"=>create_script_signal,
},
"BasePart"=>phf::phf_map!{
"Touched"=>create_script_signal,
"TouchEnded"=>create_script_signal,
},
"Instance"=>phf::phf_map!{
"ChildAdded"=>create_script_signal,
"ChildRemoved"=>create_script_signal,
"DescendantAdded"=>create_script_signal,
"DescendantRemoved"=>create_script_signal,
},
"ClickDetector"=>phf::phf_map!{
"MouseClick"=>create_script_signal,
"RenderStepped"=>|lua|{
lua.create_any_userdata(crate::runner::script_signal::ScriptSignal::new())
},
},
};
fn get_or_create_userdata(instance:&mut rbx_dom_weak::Instance,lua:&mlua::Lua,index:&str)->mlua::Result<Option<mlua::AnyUserData>>{
use std::collections::hash_map::Entry;
let db=rbx_reflection_database::get();
let Some(class)=db.classes.get(instance.class)else{
return Ok(None)
};
if let Some((&static_str,create_userdata))=db.superclasses_iter(class).find_map(|superclass|
// find pair (class,index)
LAZY_USER_DATA.get(&superclass.name)
.and_then(|map|map.get_entry(index))
){
panic!();
// return Ok(Some(match instance.userdata.entry(static_str){
// Entry::Occupied(entry)=>entry.get().clone(),
// Entry::Vacant(entry)=>entry.insert(create_userdata(lua)?).clone(),
// }));
}
Ok(None)
#[derive(Default)]
pub struct InstanceValueStore{
values:HashMap<Ref,
HashMap<&'static str,
mlua::AnyUserData
>
>,
}
pub struct InstanceValues<'a>{
named_values:&'static phf::Map<&'static str,CreateUserData>,
values:&'a mut HashMap<&'static str,mlua::AnyUserData>,
}
impl InstanceValueStore{
pub fn get_or_create_instance_values(&mut self,instance:&rbx_dom_weak::Instance)->Option<InstanceValues>{
LAZY_USER_DATA.get(instance.class.as_str())
.map(|named_values|
InstanceValues{
named_values,
values:self.values.entry(instance.referent())
.or_insert_with(||HashMap::new()),
}
)
}
}
impl InstanceValues<'_>{
pub fn get_or_create_value(&mut self,lua:&mlua::Lua,index:&str)->mlua::Result<Option<mlua::AnyUserData>>{
Ok(match self.named_values.get_entry(index){
Some((&static_index_str,&function_pointer))=>Some(
match self.values.entry(static_index_str){
Entry::Occupied(entry)=>entry.get().clone(),
Entry::Vacant(entry)=>entry.insert(
function_pointer(lua)?
).clone(),
}
),
None=>None,
})
}
}
pub fn instance_value_store_mut<T>(lua:&mlua::Lua,mut f:impl FnMut(&mut InstanceValueStore)->mlua::Result<T>)->mlua::Result<T>{
let mut cf=lua.app_data_mut::<InstanceValueStore>().ok_or_else(||mlua::Error::runtime("InstanceValueStore missing"))?;
f(&mut *cf)
}

View File

@@ -10,18 +10,6 @@ macro_rules! type_from_lua_userdata{
}
};
}
macro_rules! type_from_lua_userdata_clone{
($ty:ident)=>{
impl mlua::FromLua for $ty{
fn from_lua(value:mlua::Value,_lua:&mlua::Lua)->Result<Self,mlua::Error>{
match value{
mlua::Value::UserData(ud)=>Ok(ud.borrow::<Self>()?.clone()),
other=>Err(mlua::Error::runtime(format!("Expected {} got {:?}",stringify!($ty),other))),
}
}
}
};
}
macro_rules! type_from_lua_userdata_lua_lifetime{
($ty:ident)=>{
impl mlua::FromLua for $ty<'static>{

View File

@@ -3,19 +3,10 @@ mod macros;
mod runner;
mod r#enum;
mod task;
mod udim;
mod tween;
mod udim2;
mod color3;
mod cframe;
mod number;
mod vector2;
mod vector3;
mod brickcolor;
mod tween_info;
pub mod instance;
mod number_range;
mod script_signal;
mod color_sequence;
mod number_sequence;

View File

@@ -1,43 +0,0 @@
// the goal of this module is to provide an intermediate type
// that is guaranteed to be some kind of number, and provide
// methods to coerce it into various more specific types.
#[derive(Clone,Copy)]
pub enum Number{
Integer(i32),
Number(f64),
}
macro_rules! impl_ty{
($ident:ident,$ty:ty)=>{
impl Number{
#[inline]
pub fn $ident(self)->$ty{
match self{
Self::Integer(int)=>int as $ty,
Self::Number(num)=>num as $ty,
}
}
}
impl From<Number> for $ty{
fn from(value:Number)->$ty{
value.$ident()
}
}
};
}
impl_ty!(to_u32,u32);
impl_ty!(to_i32,i32);
impl_ty!(to_f32,f32);
impl_ty!(to_u64,u64);
impl_ty!(to_i64,i64);
impl_ty!(to_f64,f64);
impl mlua::FromLua for Number{
fn from_lua(value:mlua::Value,_lua:&mlua::Lua)->Result<Self,mlua::Error>{
match value{
mlua::Value::Integer(int)=>Ok(Number::Integer(int)),
mlua::Value::Number(num)=>Ok(Number::Number(num)),
other=>Err(mlua::Error::runtime(format!("Expected {} got {:?}",stringify!(Number),other))),
}
}
}

View File

@@ -1,34 +0,0 @@
use super::number::Number;
#[derive(Clone)]
pub struct NumberRange(rbx_types::NumberRange);
impl NumberRange{
pub const fn new(min:f32,max:f32)->Self{
Self(rbx_types::NumberRange{min,max})
}
}
impl From<NumberRange> for rbx_types::NumberRange{
fn from(NumberRange(value):NumberRange)->rbx_types::NumberRange{
value
}
}
pub fn set_globals(lua:&mlua::Lua,globals:&mlua::Table)->Result<(),mlua::Error>{
let table=lua.create_table()?;
table.raw_set("new",
lua.create_function(|_,(min,max):(Number,Option<Number>)|{
Ok(match max{
Some(max)=>NumberRange::new(min.into(),max.into()),
None=>NumberRange::new(min.into(),min.into()),
})
})?
)?;
globals.set("NumberRange",table)?;
Ok(())
}
impl mlua::UserData for NumberRange{}
type_from_lua_userdata_clone!(NumberRange);

View File

@@ -1,36 +1,31 @@
#[derive(Clone)]
pub struct NumberSequence(rbx_types::NumberSequence);
#[derive(Clone,Copy)]
pub struct NumberSequence{}
impl NumberSequence{
pub const fn new(keypoints:Vec<rbx_types::NumberSequenceKeypoint>)->Self{
Self(rbx_types::NumberSequence{keypoints})
pub const fn new()->Self{
Self{}
}
}
impl From<NumberSequence> for rbx_types::NumberSequence{
fn from(NumberSequence(value):NumberSequence)->rbx_types::NumberSequence{
value
impl Into<rbx_types::NumberSequence> for NumberSequence{
fn into(self)->rbx_types::NumberSequence{
rbx_types::NumberSequence{
keypoints:Vec::new()
}
}
}
pub fn set_globals(lua:&mlua::Lua,globals:&mlua::Table)->Result<(),mlua::Error>{
let table=lua.create_table()?;
let number_sequence_table=lua.create_table()?;
table.raw_set("new",
number_sequence_table.raw_set("new",
lua.create_function(|_,_:mlua::MultiValue|
Ok(NumberSequence::new(Vec::new()))
Ok(NumberSequence::new())
)?
)?;
globals.set("NumberSequence",table)?;
globals.set("NumberSequence",number_sequence_table)?;
Ok(())
}
impl mlua::UserData for NumberSequence{}
impl mlua::FromLua for NumberSequence{
fn from_lua(value:mlua::Value,_lua:&mlua::Lua)->Result<Self,mlua::Error>{
match value{
mlua::Value::UserData(ud)=>Ok(ud.borrow::<Self>()?.clone()),
other=>Err(mlua::Error::runtime(format!("Expected {} got {:?}",stringify!(NumberSequence),other))),
}
}
}
type_from_lua_userdata!(NumberSequence);

View File

@@ -12,7 +12,7 @@ pub enum Error{
error:mlua::Error
},
RustLua(mlua::Error),
Services(crate::context::ServicesError),
NoServices,
}
impl std::fmt::Display for Error{
fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{
@@ -31,19 +31,14 @@ fn init(lua:&mlua::Lua)->mlua::Result<()>{
//global environment
let globals=lua.globals();
super::task::set_globals(lua,&globals)?;
#[cfg(feature="run-service")]
crate::scheduler::set_globals(lua,&globals)?;
super::script_signal::set_globals(lua,&globals)?;
super::r#enum::set_globals(lua,&globals)?;
super::udim::set_globals(lua,&globals)?;
super::udim2::set_globals(lua,&globals)?;
super::color3::set_globals(lua,&globals)?;
super::brickcolor::set_globals(lua,&globals)?;
super::vector2::set_globals(lua,&globals)?;
super::vector3::set_globals(lua,&globals)?;
super::cframe::set_globals(lua,&globals)?;
super::instance::instance::set_globals(lua,&globals)?;
super::tween_info::set_globals(lua,&globals)?;
super::number_range::set_globals(lua,&globals)?;
super::number_sequence::set_globals(lua,&globals)?;
super::color_sequence::set_globals(lua,&globals)?;
@@ -59,20 +54,22 @@ impl Runner{
Ok(runner)
}
pub fn runnable_context<'a>(self,context:&'a mut Context)->Result<Runnable<'a>,Error>{
let services=context.find_services().ok_or(Error::NoServices)?;
self.runnable_context_with_services(context,&services)
}
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();
globals.set("game",super::instance::Instance::new_unchecked(context.services.game)).map_err(Error::RustLua)?;
globals.set("workspace",super::instance::Instance::new_unchecked(context.services.workspace)).map_err(Error::RustLua)?;
globals.set("game",super::instance::Instance::new(services.game)).map_err(Error::RustLua)?;
globals.set("workspace",super::instance::Instance::new(services.workspace)).map_err(Error::RustLua)?;
}
// SAFETY: This is not a &'static mut WeakDom,
// but as long as Runnable<'a> holds the lifetime of &'a mut Context
// it is a valid unique reference.
self.lua.set_app_data::<crate::context::LuaAppData>(unsafe{core::mem::transmute(&mut context.dom)});
//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)});
#[cfg(feature="run-service")]
self.lua.set_app_data::<crate::scheduler::Scheduler>(crate::scheduler::Scheduler::default());
Ok(Runnable{
lua:self.lua,
_lifetime:std::marker::PhantomData
_lifetime:&std::marker::PhantomData
})
}
}
@@ -80,11 +77,11 @@ impl Runner{
//Runnable is the same thing but has context set, which it holds the lifetime for.
pub struct Runnable<'a>{
lua:mlua::Lua,
_lifetime:std::marker::PhantomData<&'a ()>
_lifetime:&'a std::marker::PhantomData<()>
}
impl Runnable<'_>{
pub fn drop_context(self)->Runner{
self.lua.remove_app_data::<crate::context::LuaAppData>();
self.lua.remove_app_data::<&'static mut rbx_dom_weak::WeakDom>();
#[cfg(feature="run-service")]
self.lua.remove_app_data::<crate::scheduler::Scheduler>();
Runner{
@@ -126,15 +123,20 @@ impl Runnable<'_>{
}
#[cfg(feature="run-service")]
pub fn run_service_step(&self)->Result<(),mlua::Error>{
let render_stepped_signal=super::instance::instance::dom_mut(&self.lua,|dom|{
let render_stepped=super::instance::instance::dom_mut(&self.lua,|dom|{
let run_service=super::instance::instance::find_first_child_of_class(dom,dom.root(),"RunService").ok_or_else(||mlua::Error::runtime("RunService missing"))?;
Ok(match run_service.userdata.get(&static_ustr("RenderStepped")){
Some(render_stepped)=>Some(render_stepped.borrow::<super::script_signal::ScriptSignal>()?.clone()),
None=>None
super::instance::instance::instance_value_store_mut(&self.lua,|instance_value_store|{
//unwrap because I trust my find_first_child_of_class function to
let mut instance_values=instance_value_store.get_or_create_instance_values(run_service).ok_or_else(||mlua::Error::runtime("RunService InstanceValues missing"))?;
let render_stepped=instance_values.get_or_create_value(&self.lua,"RenderStepped")?;
//let stepped=instance_values.get_or_create_value(&self.lua,"Stepped")?;
//let heartbeat=instance_values.get_or_create_value(&self.lua,"Heartbeat")?;
Ok(render_stepped)
})
})?;
if let Some(render_stepped_signal)=render_stepped_signal{
render_stepped_signal.fire(&mlua::MultiValue::new());
if let Some(render_stepped)=render_stepped{
let signal:&super::script_signal::ScriptSignal=&*render_stepped.borrow()?;
signal.fire(&mlua::MultiValue::new());
}
Ok(())
}

View File

@@ -98,9 +98,12 @@ impl ScriptConnection{
impl mlua::UserData for ScriptSignal{
fn add_methods<M:mlua::UserDataMethods<Self>>(methods:&mut M){
methods.add_method("connect",|_lua,this,f:mlua::Function|Ok(this.connect(f)));
methods.add_method("Connect",|_lua,this,f:mlua::Function|Ok(this.connect(f)));
methods.add_method("Once",|_lua,this,f:mlua::Function|Ok(this.once(f)));
methods.add_method("Connect",|_lua,this,f:mlua::Function|
Ok(this.connect(f))
);
methods.add_method("Once",|_lua,this,f:mlua::Function|
Ok(this.once(f))
);
// Fire is not allowed to be called from Lua
// methods.add_method("Fire",|_lua,this,args:mlua::MultiValue|
// Ok(this.fire(args))
@@ -161,7 +164,6 @@ pub fn set_globals(lua:&mlua::Lua,globals:&mlua::Table)->Result<(),mlua::Error>{
.call::<mlua::Function>(())?;
lua.register_userdata_type::<ScriptSignal>(|reg|{
reg.add_field("wait",wait.clone());
reg.add_field("Wait",wait);
mlua::UserData::register(reg);
})?;

View File

@@ -1,42 +0,0 @@
#[cfg(not(feature="run-service"))]
fn no_op(_lua:&mlua::Lua,_time:Option<super::number::Number>)->mlua::Result<f64>{
Ok(0.0)
}
fn tick(_lua:&mlua::Lua,_:())->mlua::Result<f64>{
Ok(0.0)
}
// This is used to avoid calling coroutine.yield from the rust side.
const LUA_WAIT:&str=
"local coroutine_yield=coroutine.yield
local schedule_thread=schedule_thread
return function(dt)
schedule_thread(dt)
return coroutine_yield()
end";
pub fn set_globals(lua:&mlua::Lua,globals:&mlua::Table)->Result<(),mlua::Error>{
let coroutine_table=globals.get::<mlua::Table>("coroutine")?;
#[cfg(feature="run-service")]
let schedule_thread=lua.create_function(crate::scheduler::schedule_thread)?;
#[cfg(not(feature="run-service"))]
let schedule_thread=lua.create_function(no_op)?;
//create wait function environment
let wait_env=lua.create_table()?;
wait_env.raw_set("coroutine",coroutine_table)?;
wait_env.raw_set("schedule_thread",schedule_thread)?;
//construct wait function from Lua code
let wait=lua.load(LUA_WAIT)
.set_name("wait")
.set_environment(wait_env)
.call::<mlua::Function>(())?;
globals.raw_set("wait",wait)?;
// TODO: move this somewhere it belongs
let tick=lua.create_function(tick)?;
globals.raw_set("tick",tick)?;
Ok(())
}

View File

@@ -1,35 +0,0 @@
use super::instance::Instance;
use super::tween_info::TweenInfo;
#[allow(dead_code)]
#[derive(Clone)]
pub struct Tween{
instance:Instance,
tween_info:TweenInfo,
goal:mlua::Table,
playback_state:rbx_types::Enum,
}
impl Tween{
pub fn create(
instance:Instance,
tween_info:TweenInfo,
goal:mlua::Table,
)->Self{
Self{
instance,
tween_info,
goal,
// Enum.PlaybackState.Begin
playback_state:rbx_types::Enum::from_u32(0),
}
}
pub fn play(&mut self){
}
}
impl mlua::UserData for Tween{
fn add_methods<M:mlua::UserDataMethods<Self>>(methods:&mut M){
methods.add_method_mut("Play",|_,this,()|Ok(this.play()))
}
}
type_from_lua_userdata_clone!(Tween);

View File

@@ -1,45 +0,0 @@
use super::number::Number;
use super::r#enum::{CoerceEnum,Enums};
#[allow(dead_code)]
#[derive(Clone)]
pub struct TweenInfo{
time:f64,
easing_style:rbx_types::Enum,
easing_direction:rbx_types::Enum,
repeat_count:u32,
reverses:bool,
delay_time:f64,
}
pub fn set_globals(lua:&mlua::Lua,globals:&mlua::Table)->Result<(),mlua::Error>{
let table=lua.create_table()?;
table.raw_set("new",
lua.create_function(|_,(time,easing_style,easing_direction,repeat_count,reverses,delay_time):(Option<Number>,Option<CoerceEnum>,Option<CoerceEnum>,Option<u32>,Option<bool>,Option<Number>)|{
Ok(TweenInfo{
time:time.map_or(1.0,Number::to_f64),
easing_style:match easing_style{
// Enum.EasingStyle.Quad
None=>rbx_types::Enum::from_u32(3),
Some(e)=>e.coerce_to(Enums.get("EasingStyle").unwrap())?.into(),
},
easing_direction:match easing_direction{
// Enum.EasingDirection.Out
None=>rbx_types::Enum::from_u32(1),
Some(e)=>e.coerce_to(Enums.get("EasingDirection").unwrap())?.into(),
},
repeat_count:repeat_count.unwrap_or(0),
reverses:reverses.unwrap_or(false),
delay_time:delay_time.map_or(0.0,Number::to_f64),
})
})?
)?;
globals.set("TweenInfo",table)?;
Ok(())
}
impl mlua::UserData for TweenInfo{}
type_from_lua_userdata_clone!(TweenInfo);

View File

@@ -1,36 +0,0 @@
use super::number::Number;
#[derive(Clone,Copy)]
pub struct UDim(rbx_types::UDim);
impl UDim{
pub fn new(scale:f32,offset:i32)->Self{
UDim(rbx_types::UDim::new(scale,offset))
}
}
impl From<rbx_types::UDim> for UDim{
fn from(value:rbx_types::UDim)->Self{
Self(value)
}
}
pub fn set_globals(lua:&mlua::Lua,globals:&mlua::Table)->Result<(),mlua::Error>{
let table=lua.create_table()?;
table.raw_set("new",
lua.create_function(|_,(scale,offset):(Number,i32)|
Ok(UDim::new(scale.into(),offset))
)?
)?;
globals.set("UDim",table)?;
Ok(())
}
impl mlua::UserData for UDim{
fn add_fields<F:mlua::UserDataFields<Self>>(fields:&mut F){
fields.add_field_method_get("Scale",|_,UDim(this)|Ok(this.scale));
fields.add_field_method_get("Offset",|_,UDim(this)|Ok(this.offset));
}
}
type_from_lua_userdata!(UDim);

View File

@@ -1,40 +0,0 @@
use super::udim::UDim;
use super::number::Number;
#[derive(Clone,Copy)]
pub struct UDim2(rbx_types::UDim2);
impl UDim2{
pub fn new(sx:f32,ox:i32,sy:f32,oy:i32)->Self{
UDim2(rbx_types::UDim2::new(
rbx_types::UDim::new(sx,ox),
rbx_types::UDim::new(sy,oy),
))
}
}
impl From<UDim2> for rbx_types::UDim2{
fn from(UDim2(value):UDim2)->rbx_types::UDim2{
value
}
}
pub fn set_globals(lua:&mlua::Lua,globals:&mlua::Table)->Result<(),mlua::Error>{
let table=lua.create_table()?;
table.raw_set("new",
lua.create_function(|_,(sx,ox,sy,oy):(Number,i32,Number,i32)|
Ok(UDim2::new(sx.into(),ox,sy.into(),oy))
)?
)?;
globals.set("UDim2",table)?;
Ok(())
}
impl mlua::UserData for UDim2{
fn add_fields<F:mlua::UserDataFields<Self>>(fields:&mut F){
fields.add_field_method_get("X",|_,UDim2(this)|Ok(UDim::from(this.x)));
fields.add_field_method_get("Y",|_,UDim2(this)|Ok(UDim::from(this.y)));
}
}
type_from_lua_userdata!(UDim2);

View File

@@ -1,93 +0,0 @@
use mlua::FromLua;
use super::number::Number;
#[derive(Clone,Copy)]
pub struct Vector2(glam::Vec2);
impl Vector2{
pub const fn new(x:f32,y:f32)->Self{
Self(glam::vec2(x,y))
}
}
pub fn set_globals(lua:&mlua::Lua,globals:&mlua::Table)->Result<(),mlua::Error>{
let table=lua.create_table()?;
//Vector2.new
table.raw_set("new",
lua.create_function(|_,(x,y):(Option<Number>,Option<Number>)|
match (x,y){
(Some(x),Some(y))=>Ok(Vector2::new(x.into(),y.into())),
(None,None)=>Ok(Vector2(glam::Vec2::ZERO)),
_=>Err(mlua::Error::runtime("Unsupported arguments to Vector2.new")),
}
)?
)?;
globals.set("Vector2",table)?;
Ok(())
}
impl From<Vector2> for rbx_types::Vector2{
fn from(Vector2(v):Vector2)->rbx_types::Vector2{
rbx_types::Vector2::new(v.x,v.y)
}
}
impl From<rbx_types::Vector2> for Vector2{
fn from(value:rbx_types::Vector2)->Vector2{
Vector2::new(value.x,value.y)
}
}
impl mlua::UserData for Vector2{
fn add_fields<F:mlua::UserDataFields<Self>>(fields:&mut F){
fields.add_field_method_get("magnitude",|_,Vector2(this)|Ok(this.length()));
fields.add_field_method_get("Magnitude",|_,Vector2(this)|Ok(this.length()));
fields.add_field_method_get("unit",|_,Vector2(this)|Ok(Vector2(this.normalize())));
fields.add_field_method_get("Unit",|_,Vector2(this)|Ok(Vector2(this.normalize())));
fields.add_field_method_get("x",|_,Vector2(this)|Ok(this.x));
fields.add_field_method_get("X",|_,Vector2(this)|Ok(this.x));
fields.add_field_method_get("y",|_,Vector2(this)|Ok(this.y));
fields.add_field_method_get("Y",|_,Vector2(this)|Ok(this.y));
}
fn add_methods<M:mlua::UserDataMethods<Self>>(methods:&mut M){
//methods.add_method("area",|_,this,()| Ok(this.length * this.width));
methods.add_meta_function(mlua::MetaMethod::Add,|_,(Vector2(this),Vector2(val)):(Self,Self)|Ok(Self(this+val)));
methods.add_meta_function(mlua::MetaMethod::Sub,|_,(Vector2(this),Vector2(val)):(Self,Self)|Ok(Self(this-val)));
methods.add_meta_function(mlua::MetaMethod::Mul,|lua,(lhs,rhs):(mlua::Value,mlua::Value)|{
match (lhs,rhs){
(mlua::Value::UserData(lhs),mlua::Value::UserData(rhs))=>lhs.borrow_scoped(|Vector2(lhs):&Vector2|rhs.borrow_scoped(|Vector2(rhs):&Vector2|Self(lhs*rhs)))?,
(lhs,mlua::Value::UserData(rhs))=>{
let lhs=Number::from_lua(lhs,lua)?;
rhs.borrow_scoped(|Vector2(rhs):&Vector2|Self(lhs.to_f32()*rhs))
},
(mlua::Value::UserData(lhs),rhs)=>{
let rhs=Number::from_lua(rhs,lua)?;
lhs.borrow_scoped(|Vector2(lhs):&Vector2|Self(lhs*rhs.to_f32()))
},
_=>Err(mlua::Error::runtime(format!("Expected Vector2")))
}
});
methods.add_meta_function(mlua::MetaMethod::Div,|_,(Vector2(this),val):(Self,mlua::Value)|{
match val{
mlua::Value::Integer(n)=>Ok(Self(this/(n as f32))),
mlua::Value::Number(n)=>Ok(Self(this/(n as f32))),
mlua::Value::UserData(ud)=>ud.borrow_scoped(|Vector2(rhs):&Vector2|Self(this/rhs)),
other=>Err(mlua::Error::runtime(format!("Attempt to divide Vector2 by {other:?}"))),
}
});
methods.add_meta_function(mlua::MetaMethod::ToString,|_,Vector2(this):Self|
Ok(format!("Vector2.new({},{})",
this.x,
this.y,
))
);
}
}
type_from_lua_userdata!(Vector2);

View File

@@ -1,7 +1,3 @@
use mlua::FromLua;
use super::number::Number;
#[derive(Clone,Copy)]
pub struct Vector3(pub(crate)glam::Vec3A);
@@ -12,27 +8,23 @@ impl Vector3{
}
pub fn set_globals(lua:&mlua::Lua,globals:&mlua::Table)->Result<(),mlua::Error>{
let table=lua.create_table()?;
let vector3_table=lua.create_table()?;
//Vector3.new
table.raw_set("new",
lua.create_function(|_,(x,y,z):(Option<Number>,Option<Number>,Option<Number>)|
match (x,y,z){
(Some(x),Some(y),Some(z))=>Ok(Vector3::new(x.into(),y.into(),z.into())),
(None,None,None)=>Ok(Vector3(glam::Vec3A::ZERO)),
_=>Err(mlua::Error::runtime("Unsupported arguments to Vector3.new")),
}
vector3_table.raw_set("new",
lua.create_function(|_,(x,y,z):(f32,f32,f32)|
Ok(Vector3::new(x,y,z))
)?
)?;
globals.set("Vector3",table)?;
globals.set("Vector3",vector3_table)?;
Ok(())
}
impl From<Vector3> for rbx_types::Vector3{
fn from(Vector3(v):Vector3)->rbx_types::Vector3{
rbx_types::Vector3::new(v.x,v.y,v.z)
impl Into<rbx_types::Vector3> for Vector3{
fn into(self)->rbx_types::Vector3{
rbx_types::Vector3::new(self.0.x,self.0.y,self.0.z)
}
}
@@ -44,50 +36,44 @@ impl From<rbx_types::Vector3> for Vector3{
impl mlua::UserData for Vector3{
fn add_fields<F:mlua::UserDataFields<Self>>(fields:&mut F){
fields.add_field_method_get("magnitude",|_,Vector3(this)|Ok(this.length()));
fields.add_field_method_get("Magnitude",|_,Vector3(this)|Ok(this.length()));
fields.add_field_method_get("unit",|_,Vector3(this)|Ok(Vector3(this.normalize())));
fields.add_field_method_get("Unit",|_,Vector3(this)|Ok(Vector3(this.normalize())));
fields.add_field_method_get("x",|_,Vector3(this)|Ok(this.x));
fields.add_field_method_get("X",|_,Vector3(this)|Ok(this.x));
fields.add_field_method_get("y",|_,Vector3(this)|Ok(this.y));
fields.add_field_method_get("Y",|_,Vector3(this)|Ok(this.y));
fields.add_field_method_get("z",|_,Vector3(this)|Ok(this.z));
fields.add_field_method_get("Z",|_,Vector3(this)|Ok(this.z));
fields.add_field_method_get("magnitude",|_,this|Ok(this.0.length()));
fields.add_field_method_get("x",|_,this|Ok(this.0.x));
fields.add_field_method_set("x",|_,this,val|{
this.0.x=val;
Ok(())
});
fields.add_field_method_get("y",|_,this|Ok(this.0.y));
fields.add_field_method_set("y",|_,this,val|{
this.0.y=val;
Ok(())
});
fields.add_field_method_get("z",|_,this|Ok(this.0.z));
fields.add_field_method_set("z",|_,this,val|{
this.0.z=val;
Ok(())
});
}
fn add_methods<M:mlua::UserDataMethods<Self>>(methods:&mut M){
//methods.add_method("area",|_,this,()| Ok(this.length * this.width));
methods.add_meta_function(mlua::MetaMethod::Add,|_,(Vector3(this),Vector3(val)):(Self,Self)|Ok(Self(this+val)));
methods.add_meta_function(mlua::MetaMethod::Sub,|_,(Vector3(this),Vector3(val)):(Self,Self)|Ok(Self(this-val)));
methods.add_meta_function(mlua::MetaMethod::Mul,|lua,(lhs,rhs):(mlua::Value,mlua::Value)|{
match (lhs,rhs){
(mlua::Value::UserData(lhs),mlua::Value::UserData(rhs))=>lhs.borrow_scoped(|Vector3(lhs):&Vector3|rhs.borrow_scoped(|Vector3(rhs):&Vector3|Self(lhs*rhs)))?,
(lhs,mlua::Value::UserData(rhs))=>{
let lhs=Number::from_lua(lhs,lua)?;
rhs.borrow_scoped(|Vector3(rhs):&Vector3|Self(lhs.to_f32()*rhs))
},
(mlua::Value::UserData(lhs),rhs)=>{
let rhs=Number::from_lua(rhs,lua)?;
lhs.borrow_scoped(|Vector3(lhs):&Vector3|Self(lhs*rhs.to_f32()))
},
_=>Err(mlua::Error::runtime(format!("Expected Vector3")))
}
});
methods.add_meta_function(mlua::MetaMethod::Div,|_,(Vector3(this),val):(Self,mlua::Value)|{
methods.add_meta_function(mlua::MetaMethod::Add,|_,(this,val):(Self,Self)|Ok(Self(this.0+val.0)));
methods.add_meta_function(mlua::MetaMethod::Div,|_,(this,val):(Self,mlua::Value)|{
match val{
mlua::Value::Integer(n)=>Ok(Self(this/(n as f32))),
mlua::Value::Number(n)=>Ok(Self(this/(n as f32))),
mlua::Value::UserData(ud)=>ud.borrow_scoped(|Vector3(rhs):&Vector3|Self(this/rhs)),
mlua::Value::Integer(n)=>Ok(Self(this.0/(n as f32))),
mlua::Value::Number(n)=>Ok(Self(this.0/(n as f32))),
mlua::Value::UserData(ud)=>{
let rhs:Vector3=ud.take()?;
Ok(Self(this.0/rhs.0))
},
other=>Err(mlua::Error::runtime(format!("Attempt to divide Vector3 by {other:?}"))),
}
});
methods.add_meta_function(mlua::MetaMethod::ToString,|_,Vector3(this):Self|
methods.add_meta_function(mlua::MetaMethod::ToString,|_,this:Self|
Ok(format!("Vector3.new({},{},{})",
this.x,
this.y,
this.z,
this.0.x,
this.0.y,
this.0.z,
))
);
}

View File

@@ -51,7 +51,7 @@ pub fn scheduler_mut<T>(lua:&mlua::Lua,mut f:impl FnMut(&mut crate::scheduler::S
f(&mut *scheduler)
}
pub fn schedule_thread(lua:&mlua::Lua,dt:mlua::Value)->Result<(),mlua::Error>{
fn schedule_thread(lua:&mlua::Lua,dt:mlua::Value)->Result<(),mlua::Error>{
let delay=match dt{
mlua::Value::Integer(i)=>i.max(0) as u64*60,
mlua::Value::Number(f)=>{
@@ -75,3 +75,32 @@ pub fn schedule_thread(lua:&mlua::Lua,dt:mlua::Value)->Result<(),mlua::Error>{
Ok(())
})
}
// This is used to avoid calling coroutine.yield from the rust side.
const LUA_WAIT:&str=
"local coroutine_yield=coroutine.yield
local schedule_thread=schedule_thread
return function(dt)
schedule_thread(dt)
return coroutine_yield()
end";
pub fn set_globals(lua:&mlua::Lua,globals:&mlua::Table)->Result<(),mlua::Error>{
let coroutine_table=globals.get::<mlua::Table>("coroutine")?;
let schedule_thread=lua.create_function(schedule_thread)?;
//create wait function environment
let wait_env=lua.create_table()?;
wait_env.raw_set("coroutine",coroutine_table)?;
wait_env.raw_set("schedule_thread",schedule_thread)?;
//construct wait function from Lua code
let wait=lua.load(LUA_WAIT)
.set_name("wait")
.set_environment(wait_env)
.call::<mlua::Function>(())?;
globals.raw_set("wait",wait)?;
Ok(())
}

View File

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

View File

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

View File

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

View File

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

View File

@@ -56,6 +56,8 @@ impl From<strafesnet_common::integer::Ratio64Vec2> for Ratio64Vec2{
}
}
pub type Angle32=i32;
pub type Planar64=i64;
pub type Planar64Vec3=[i64;3];
pub type Planar64Mat3=[i64;9];
pub type Planar64Affine3=[i64;12];

View File

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

View File

@@ -1,38 +0,0 @@
[package]
name = "map-tool"
version = "1.7.0"
edition = "2024"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
anyhow = "1.0.75"
clap = { version = "4.4.2", features = ["derive"] }
flate2 = "1.0.27"
futures = "0.3.31"
image = "0.25.2"
image_dds = "0.7.1"
lazy-regex = "3.1.0"
rbx_asset = { version = "0.4.4", registry = "strafesnet" }
rbx_binary = { path = "../../../git/rbx-dom/rbx_binary", registry = "strafesnet" }
rbx_dom_weak = { path = "../../../git/rbx-dom/rbx_dom_weak", registry = "strafesnet" }
rbx_reflection_database = { path = "../../../git/rbx-dom/rbx_reflection_database"}
rbx_xml = { path = "../../../git/rbx-dom/rbx_xml", registry = "strafesnet" }
rbxassetid = { version = "0.1.0", 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_rbx_loader = { version = "0.6.0", path = "../lib/rbx_loader", registry = "strafesnet" }
strafesnet_snf = { version = "0.3.0", path = "../lib/snf", registry = "strafesnet" }
thiserror = "2.0.11"
tokio = { version = "1.43.0", features = ["macros", "rt-multi-thread", "fs"] }
vbsp = "0.9.1"
vbsp-entities-css = "0.6.0"
vmdl = "0.2.0"
vmt-parser = "0.2.0"
vpk = "0.3.0"
vtf = "0.3.0"
#[profile.release]
#lto = true
#strip = true
#codegen-units = 1

View File

@@ -1,23 +0,0 @@
Permission is hereby granted, free of charge, to any
person obtaining a copy of this software and associated
documentation files (the "Software"), to deal in the
Software without restriction, including without
limitation the rights to use, copy, modify, merge,
publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software
is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice
shall be included in all copies or substantial portions
of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.

View File

@@ -1,2 +0,0 @@
# map-tool

View File

@@ -1,30 +0,0 @@
mod roblox;
mod source;
use clap::{Parser,Subcommand};
use anyhow::Result as AResult;
#[derive(Parser)]
#[command(author, version, about, long_about = None)]
#[command(propagate_version = true)]
struct Cli {
#[command(subcommand)]
command: Commands,
}
#[derive(Subcommand)]
enum Commands{
#[command(flatten)]
Roblox(roblox::Commands),
#[command(flatten)]
Source(source::Commands),
}
#[tokio::main]
async fn main()->AResult<()>{
let cli=Cli::parse();
match cli.command{
Commands::Roblox(commands)=>commands.run().await,
Commands::Source(commands)=>commands.run().await,
}
}

View File

@@ -1,459 +0,0 @@
use std::path::{Path,PathBuf};
use std::io::{Cursor,Read,Seek};
use std::collections::HashSet;
use clap::{Args,Subcommand};
use anyhow::Result as AResult;
use rbx_dom_weak::Instance;
use strafesnet_deferred_loader::deferred_loader::LoadFailureMode;
use rbxassetid::RobloxAssetId;
use tokio::io::AsyncReadExt;
const DOWNLOAD_LIMIT:usize=16;
#[derive(Subcommand)]
pub enum Commands{
RobloxToSNF(RobloxToSNFSubcommand),
DownloadAssets(DownloadAssetsSubcommand),
}
#[derive(Args)]
pub struct RobloxToSNFSubcommand {
#[arg(long)]
output_folder:PathBuf,
#[arg(required=true)]
input_files:Vec<PathBuf>,
}
#[derive(Args)]
pub struct DownloadAssetsSubcommand{
#[arg(required=true)]
roblox_files:Vec<PathBuf>,
// #[arg(long)]
// cookie_file:Option<String>,
}
impl Commands{
pub async fn run(self)->AResult<()>{
match self{
Commands::RobloxToSNF(subcommand)=>roblox_to_snf(subcommand.input_files,subcommand.output_folder).await,
Commands::DownloadAssets(subcommand)=>download_assets(
subcommand.roblox_files,
rbx_asset::cookie::Cookie::new("".to_string()),
).await,
}
}
}
#[allow(unused)]
#[derive(Debug)]
enum LoadDomError{
IO(std::io::Error),
Binary(rbx_binary::DecodeError),
Xml(rbx_xml::DecodeError),
UnknownFormat,
}
fn load_dom<R:Read+Seek>(mut input:R)->Result<rbx_dom_weak::WeakDom<'static>,LoadDomError>{
let mut first_8=[0u8;8];
input.read_exact(&mut first_8).map_err(LoadDomError::IO)?;
input.rewind().map_err(LoadDomError::IO)?;
match &first_8{
b"<roblox!"=>rbx_binary::from_reader_default(input).map_err(LoadDomError::Binary),
b"<roblox "=>rbx_xml::from_reader_default(input).map_err(LoadDomError::Xml),
_=>Err(LoadDomError::UnknownFormat),
}
}
/* The ones I'm interested in:
Beam.Texture
Decal.Texture
FileMesh.MeshId
FileMesh.TextureId
MaterialVariant.ColorMap
MaterialVariant.MetalnessMap
MaterialVariant.NormalMap
MaterialVariant.RoughnessMap
MeshPart.MeshId
MeshPart.TextureID
ParticleEmitter.Texture
Sky.MoonTextureId
Sky.SkyboxBk
Sky.SkyboxDn
Sky.SkyboxFt
Sky.SkyboxLf
Sky.SkyboxRt
Sky.SkyboxUp
Sky.SunTextureId
SurfaceAppearance.ColorMap
SurfaceAppearance.MetalnessMap
SurfaceAppearance.NormalMap
SurfaceAppearance.RoughnessMap
SurfaceAppearance.TexturePack
*/
/* These properties now use Content
BaseWrap.CageMeshContent
Decal.TextureContent
ImageButton.ImageContent
ImageLabel.ImageContent
MeshPart.MeshContent
MeshPart.TextureContent
SurfaceAppearance.ColorMapContent
SurfaceAppearance.MetalnessMapContent
SurfaceAppearance.NormalMapContent
SurfaceAppearance.RoughnessMapContent
WrapLayer.ReferenceMeshContent
*/
fn accumulate_content(content_list:&mut HashSet<RobloxAssetId>,object:&Instance,property:&'static str){
let Some(rbx_dom_weak::types::Variant::Content(content))=object.properties.get(property)else{
println!("property={} does not exist for class={}",property,object.class);
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:&'static str){
let Some(rbx_dom_weak::types::Variant::ContentId(content))=object.properties.get(property)else{
println!("property={} does not exist for class={}",property,object.class);
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>{
let mut file=tokio::fs::File::open(path).await?;
let mut data=Vec::new();
file.read_to_end(&mut data).await?;
Ok(Cursor::new(data))
}
#[derive(Default)]
struct UniqueAssets{
meshes:HashSet<RobloxAssetId>,
unions:HashSet<RobloxAssetId>,
textures:HashSet<RobloxAssetId>,
}
impl UniqueAssets{
fn collect(&mut self,object:&Instance){
match object.class{
"Beam"=>accumulate_content_id(&mut self.textures,object,"Texture"),
"Decal"=>accumulate_content(&mut self.textures,object,"TextureContent"),
"Texture"=>accumulate_content(&mut self.textures,object,"TextureContent"),
"FileMesh"=>accumulate_content_id(&mut self.textures,object,"TextureId"),
"MeshPart"=>{
accumulate_content(&mut self.textures,object,"TextureContent");
accumulate_content(&mut self.meshes,object,"MeshContent");
},
"SpecialMesh"=>accumulate_content_id(&mut self.meshes,object,"MeshId"),
"ParticleEmitter"=>accumulate_content_id(&mut self.textures,object,"Texture"),
"Sky"=>{
accumulate_content_id(&mut self.textures,object,"MoonTextureId");
accumulate_content_id(&mut self.textures,object,"SkyboxBk");
accumulate_content_id(&mut self.textures,object,"SkyboxDn");
accumulate_content_id(&mut self.textures,object,"SkyboxFt");
accumulate_content_id(&mut self.textures,object,"SkyboxLf");
accumulate_content_id(&mut self.textures,object,"SkyboxRt");
accumulate_content_id(&mut self.textures,object,"SkyboxUp");
accumulate_content_id(&mut self.textures,object,"SunTextureId");
},
"UnionOperation"=>accumulate_content_id(&mut self.unions,object,"AssetId"),
_=>(),
}
}
}
#[allow(unused)]
#[derive(Debug)]
enum UniqueAssetError{
IO(std::io::Error),
LoadDom(LoadDomError),
}
async fn unique_assets(path:&Path)->Result<UniqueAssets,UniqueAssetError>{
// read entire file
let mut assets=UniqueAssets::default();
let data=read_entire_file(path).await.map_err(UniqueAssetError::IO)?;
let dom=load_dom(data).map_err(UniqueAssetError::LoadDom)?;
for object in dom.into_raw().1.into_values(){
assets.collect(&object);
}
Ok(assets)
}
enum DownloadType{
Texture(RobloxAssetId),
Mesh(RobloxAssetId),
Union(RobloxAssetId),
}
impl DownloadType{
fn path(&self)->PathBuf{
match self{
DownloadType::Texture(RobloxAssetId(asset_id))=>format!("downloaded_textures/{asset_id}").into(),
DownloadType::Mesh(RobloxAssetId(asset_id))=>format!("meshes/{asset_id}").into(),
DownloadType::Union(RobloxAssetId(asset_id))=>format!("unions/{asset_id}").into(),
}
}
fn asset_id(&self)->u64{
match self{
&DownloadType::Texture(RobloxAssetId(asset_id))=>asset_id,
&DownloadType::Mesh(RobloxAssetId(asset_id))=>asset_id,
&DownloadType::Union(RobloxAssetId(asset_id))=>asset_id,
}
}
}
enum DownloadResult{
Cached(PathBuf),
Data(Vec<u8>),
Failed,
}
#[derive(Default,Debug)]
struct Stats{
total_assets:u32,
cached_assets:u32,
downloaded_assets:u32,
failed_downloads:u32,
timed_out_downloads:u32,
}
async fn download_retry(stats:&mut Stats,context:&rbx_asset::cookie::Context,download_instruction:DownloadType)->Result<DownloadResult,std::io::Error>{
stats.total_assets+=1;
// check if file exists on disk
let path=download_instruction.path();
if tokio::fs::try_exists(path.as_path()).await?{
stats.cached_assets+=1;
return Ok(DownloadResult::Cached(path));
}
let asset_id=download_instruction.asset_id();
// if not, download file
let mut retry=0;
const BACKOFF_MUL:f32=1.3956124250860895286;//exp(1/3)
let mut backoff=1000f32;
loop{
let asset_result=context.get_asset(rbx_asset::cookie::GetAssetRequest{
asset_id,
version:None,
}).await;
match asset_result{
Ok(asset_result)=>{
stats.downloaded_assets+=1;
let data=asset_result.to_vec()?;
tokio::fs::write(path,&data).await?;
break Ok(DownloadResult::Data(data));
},
Err(rbx_asset::cookie::GetError::Response(rbx_asset::types::ResponseError::StatusCodeWithUrlAndBody(scwuab)))=>{
if scwuab.status_code.as_u16()==429{
if retry==12{
println!("Giving up asset download {asset_id}");
stats.timed_out_downloads+=1;
break Ok(DownloadResult::Failed);
}
println!("Hit roblox rate limit, waiting {:.0}ms...",backoff);
tokio::time::sleep(std::time::Duration::from_millis(backoff as u64)).await;
backoff*=BACKOFF_MUL;
retry+=1;
}else{
stats.failed_downloads+=1;
println!("weird scuwab error: {scwuab:?}");
break Ok(DownloadResult::Failed);
}
},
Err(e)=>{
stats.failed_downloads+=1;
println!("sadly error: {e}");
break Ok(DownloadResult::Failed);
},
}
}
}
#[derive(Debug,thiserror::Error)]
enum ConvertTextureError{
#[error("Io error {0:?}")]
Io(#[from]std::io::Error),
#[error("Image error {0:?}")]
Image(#[from]image::ImageError),
#[error("DDS create error {0:?}")]
DDS(#[from]image_dds::CreateDdsError),
#[error("DDS write error {0:?}")]
DDSWrite(#[from]image_dds::ddsfile::Error),
}
async fn convert_texture(RobloxAssetId(asset_id):RobloxAssetId,download_result:DownloadResult)->Result<(),ConvertTextureError>{
let data=match download_result{
DownloadResult::Cached(path)=>tokio::fs::read(path).await?,
DownloadResult::Data(data)=>data,
DownloadResult::Failed=>return Ok(()),
};
// image::ImageFormat::Png
// image::ImageFormat::Jpeg
let image=image::load_from_memory(&data)?.to_rgba8();
// pick format
let format=if image.width()%4!=0||image.height()%4!=0{
image_dds::ImageFormat::Rgba8UnormSrgb
}else{
image_dds::ImageFormat::BC7RgbaUnormSrgb
};
//this fails if the image dimensions are not a multiple of 4
let dds=image_dds::dds_from_image(
&image,
format,
image_dds::Quality::Slow,
image_dds::Mipmaps::GeneratedAutomatic,
)?;
let file_name=format!("textures/{asset_id}.dds");
let mut file=std::fs::File::create(file_name)?;
dds.write(&mut file)?;
Ok(())
}
async fn download_assets(paths:Vec<PathBuf>,cookie:rbx_asset::cookie::Cookie)->AResult<()>{
tokio::try_join!(
tokio::fs::create_dir_all("downloaded_textures"),
tokio::fs::create_dir_all("textures"),
tokio::fs::create_dir_all("meshes"),
tokio::fs::create_dir_all("unions"),
)?;
// use mpsc
let thread_limit=std::thread::available_parallelism()?.get();
let (send_assets,mut recv_assets)=tokio::sync::mpsc::channel(DOWNLOAD_LIMIT);
let (send_texture,mut recv_texture)=tokio::sync::mpsc::channel(thread_limit);
// map decode dispatcher
// read files multithreaded
// produce UniqueAssetsResult per file
tokio::spawn(async move{
// move send so it gets dropped when all maps have been decoded
// closing the channel
let mut it=paths.into_iter();
static SEM:tokio::sync::Semaphore=tokio::sync::Semaphore::const_new(0);
SEM.add_permits(thread_limit);
while let (Ok(permit),Some(path))=(SEM.acquire().await,it.next()){
let send=send_assets.clone();
tokio::spawn(async move{
let result=unique_assets(path.as_path()).await;
_=send.send(result).await;
drop(permit);
});
}
});
// download manager
// insert into global unique assets guy
// add to download queue if the asset is globally unique and does not already exist on disk
let mut stats=Stats::default();
let context=rbx_asset::cookie::Context::new(cookie);
let mut globally_unique_assets=UniqueAssets::default();
// pop a job = retry_queue.pop_front() or ingest(recv.recv().await)
// SLOW MODE:
// acquire all permits
// drop all permits
// pop one job
// if it succeeds go into fast mode
// FAST MODE:
// acquire one permit
// pop a job
let download_thread=tokio::spawn(async move{
while let Some(result)=recv_assets.recv().await{
let unique_assets=match result{
Ok(unique_assets)=>unique_assets,
Err(e)=>{
println!("error: {e:?}");
continue;
},
};
for texture_id in unique_assets.textures{
if globally_unique_assets.textures.insert(texture_id){
let data=download_retry(&mut stats,&context,DownloadType::Texture(texture_id)).await?;
send_texture.send((texture_id,data)).await?;
}
}
for mesh_id in unique_assets.meshes{
if globally_unique_assets.meshes.insert(mesh_id){
download_retry(&mut stats,&context,DownloadType::Mesh(mesh_id)).await?;
}
}
for union_id in unique_assets.unions{
if globally_unique_assets.unions.insert(union_id){
download_retry(&mut stats,&context,DownloadType::Union(union_id)).await?;
}
}
}
dbg!(stats);
Ok::<(),anyhow::Error>(())
});
static SEM:tokio::sync::Semaphore=tokio::sync::Semaphore::const_new(0);
SEM.add_permits(thread_limit);
while let (Ok(permit),Some((asset_id,download_result)))=(SEM.acquire().await,recv_texture.recv().await){
tokio::spawn(async move{
let result=convert_texture(asset_id,download_result).await;
drop(permit);
result.unwrap();
});
}
download_thread.await??;
_=SEM.acquire_many(thread_limit as u32).await.unwrap();
Ok(())
}
#[derive(Debug)]
#[allow(dead_code)]
enum ConvertError{
IO(std::io::Error),
SNFMap(strafesnet_snf::map::Error),
RobloxRead(strafesnet_rbx_loader::ReadError),
RobloxLoad(strafesnet_rbx_loader::LoadError),
}
impl std::fmt::Display for ConvertError{
fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{
write!(f,"{self:?}")
}
}
impl std::error::Error for ConvertError{}
async fn convert_to_snf(path:&Path,output_folder:PathBuf)->AResult<()>{
let entire_file=tokio::fs::read(path).await?;
let model=strafesnet_rbx_loader::read(
std::io::Cursor::new(entire_file)
).map_err(ConvertError::RobloxRead)?;
let mut place=strafesnet_rbx_loader::Place::from(model);
place.run_scripts();
let map=place.to_snf(LoadFailureMode::DefaultToNone).map_err(ConvertError::RobloxLoad)?;
let mut dest=output_folder;
dest.push(path.file_stem().unwrap());
dest.set_extension("snfm");
let file=std::fs::File::create(dest).map_err(ConvertError::IO)?;
strafesnet_snf::map::write_map(file,map).map_err(ConvertError::SNFMap)?;
Ok(())
}
async fn roblox_to_snf(paths:Vec<std::path::PathBuf>,output_folder:PathBuf)->AResult<()>{
let start=std::time::Instant::now();
let thread_limit=std::thread::available_parallelism()?.get();
let mut it=paths.into_iter();
static SEM:tokio::sync::Semaphore=tokio::sync::Semaphore::const_new(0);
SEM.add_permits(thread_limit);
while let (Ok(permit),Some(path))=(SEM.acquire().await,it.next()){
let output_folder=output_folder.clone();
tokio::spawn(async move{
let result=convert_to_snf(path.as_path(),output_folder).await;
drop(permit);
match result{
Ok(())=>(),
Err(e)=>println!("Convert error: {e:?}"),
}
});
}
_=SEM.acquire_many(thread_limit as u32).await.unwrap();
println!("elapsed={:?}", start.elapsed());
Ok(())
}

View File

@@ -1,514 +0,0 @@
use std::path::{Path,PathBuf};
use std::borrow::Cow;
use clap::{Args,Subcommand};
use anyhow::Result as AResult;
use futures::StreamExt;
use strafesnet_bsp_loader::loader::BspFinder;
use strafesnet_deferred_loader::loader::Loader;
use strafesnet_deferred_loader::deferred_loader::{LoadFailureMode,MeshDeferredLoader,RenderConfigDeferredLoader};
use vbsp_entities_css::Entity;
#[derive(Subcommand)]
pub enum Commands{
SourceToSNF(SourceToSNFSubcommand),
ExtractTextures(ExtractTexturesSubcommand),
VPKContents(VPKContentsSubcommand),
BSPContents(BSPContentsSubcommand),
}
#[derive(Args)]
pub struct SourceToSNFSubcommand {
#[arg(long)]
output_folder:PathBuf,
#[arg(required=true)]
input_files:Vec<PathBuf>,
#[arg(long)]
vpks:Vec<PathBuf>,
}
#[derive(Args)]
pub struct ExtractTexturesSubcommand{
#[arg(required=true)]
bsp_files:Vec<PathBuf>,
#[arg(long)]
vpks:Vec<PathBuf>,
}
#[derive(Args)]
pub struct VPKContentsSubcommand {
#[arg(long)]
input_file:PathBuf,
}
#[derive(Args)]
pub struct BSPContentsSubcommand {
#[arg(long)]
input_file:PathBuf,
}
impl Commands{
pub async fn run(self)->AResult<()>{
match self{
Commands::SourceToSNF(subcommand)=>source_to_snf(subcommand.input_files,subcommand.output_folder,subcommand.vpks).await,
Commands::ExtractTextures(subcommand)=>extract_textures(subcommand.bsp_files,subcommand.vpks).await,
Commands::VPKContents(subcommand)=>vpk_contents(subcommand.input_file),
Commands::BSPContents(subcommand)=>bsp_contents(subcommand.input_file),
}
}
}
enum VMTContent{
VMT(String),
VTF(String),
Patch(vmt_parser::material::PatchMaterial),
Unsupported,//don't want to deal with whatever vmt variant
Unresolved,//could not locate a texture because of vmt content
}
impl VMTContent{
fn vtf(opt:Option<String>)->Self{
match opt{
Some(s)=>Self::VTF(s),
None=>Self::Unresolved,
}
}
}
fn get_some_texture(material:vmt_parser::material::Material)->VMTContent{
//just grab some texture from somewhere for now
match material{
vmt_parser::material::Material::LightMappedGeneric(mat)=>VMTContent::vtf(Some(mat.base_texture)),
vmt_parser::material::Material::VertexLitGeneric(mat)=>VMTContent::vtf(mat.base_texture.or(mat.decal_texture)),//this just dies if there is none
vmt_parser::material::Material::VertexLitGenericDx6(mat)=>VMTContent::vtf(mat.base_texture.or(mat.decal_texture)),
vmt_parser::material::Material::UnlitGeneric(mat)=>VMTContent::vtf(mat.base_texture),
vmt_parser::material::Material::UnlitTwoTexture(mat)=>VMTContent::vtf(mat.base_texture),
vmt_parser::material::Material::Water(mat)=>VMTContent::vtf(mat.base_texture),
vmt_parser::material::Material::WorldVertexTransition(mat)=>VMTContent::vtf(Some(mat.base_texture)),
vmt_parser::material::Material::EyeRefract(mat)=>VMTContent::vtf(Some(mat.cornea_texture)),
vmt_parser::material::Material::SubRect(mat)=>VMTContent::VMT(mat.material),//recursive
vmt_parser::material::Material::Sprite(mat)=>VMTContent::vtf(Some(mat.base_texture)),
vmt_parser::material::Material::SpriteCard(mat)=>VMTContent::vtf(mat.base_texture),
vmt_parser::material::Material::Cable(mat)=>VMTContent::vtf(Some(mat.base_texture)),
vmt_parser::material::Material::Refract(mat)=>VMTContent::vtf(mat.base_texture),
vmt_parser::material::Material::Modulate(mat)=>VMTContent::vtf(Some(mat.base_texture)),
vmt_parser::material::Material::DecalModulate(mat)=>VMTContent::vtf(Some(mat.base_texture)),
vmt_parser::material::Material::Sky(mat)=>VMTContent::vtf(Some(mat.base_texture)),
vmt_parser::material::Material::Replacements(_mat)=>VMTContent::Unsupported,
vmt_parser::material::Material::Patch(mat)=>VMTContent::Patch(mat),
_=>unreachable!(),
}
}
#[derive(Debug,thiserror::Error)]
enum GetVMTError{
#[error("Bsp error {0:?}")]
Bsp(#[from]vbsp::BspError),
#[error("Utf8 error {0:?}")]
Utf8(#[from]std::str::Utf8Error),
#[error("Vdf error {0:?}")]
Vdf(#[from]vmt_parser::VdfError),
#[error("Vmt not found")]
NotFound,
}
fn get_vmt(finder:BspFinder,search_name:&str)->Result<vmt_parser::material::Material,GetVMTError>{
let vmt_data=finder.find(search_name)?.ok_or(GetVMTError::NotFound)?;
//decode vmt and then write
let vmt_str=core::str::from_utf8(&vmt_data)?;
let material=vmt_parser::from_str(vmt_str)?;
//println!("vmt material={:?}",material);
Ok(material)
}
#[derive(Debug,thiserror::Error)]
enum LoadVMTError{
#[error("Bsp error {0:?}")]
Bsp(#[from]vbsp::BspError),
#[error("GetVMT error {0:?}")]
GetVMT(#[from]GetVMTError),
#[error("FromUtf8 error {0:?}")]
FromUtf8(#[from]std::string::FromUtf8Error),
#[error("Vdf error {0:?}")]
Vdf(#[from]vmt_parser::VdfError),
#[error("Vmt unsupported")]
Unsupported,
#[error("Vmt unresolved")]
Unresolved,
#[error("Vmt not found")]
NotFound,
}
fn recursive_vmt_loader<'bsp,'vpk,'a>(finder:BspFinder<'bsp,'vpk>,material:vmt_parser::material::Material)->Result<Option<Cow<'a,[u8]>>,LoadVMTError>
where
'bsp:'a,
'vpk:'a,
{
match get_some_texture(material){
VMTContent::VMT(mut s)=>{
s.make_ascii_lowercase();
recursive_vmt_loader(finder,get_vmt(finder,&s)?)
},
VMTContent::VTF(s)=>{
let mut texture_file_name=PathBuf::from("materials");
texture_file_name.push(s);
texture_file_name.set_extension("vtf");
texture_file_name.as_mut_os_str().make_ascii_lowercase();
Ok(finder.find(texture_file_name.to_str().unwrap())?)
},
VMTContent::Patch(mat)=>recursive_vmt_loader(finder,
mat.resolve(|search_name|{
let name_lowercase=search_name.to_lowercase();
match finder.find(&name_lowercase)?{
Some(bytes)=>Ok(String::from_utf8(bytes.into_owned())?),
None=>Err(LoadVMTError::NotFound),
}
})?
),
VMTContent::Unsupported=>Err(LoadVMTError::Unsupported),
VMTContent::Unresolved=>Err(LoadVMTError::Unresolved),
}
}
fn load_texture<'bsp,'vpk,'a>(finder:BspFinder<'bsp,'vpk>,texture_name:&str)->Result<Option<Cow<'a,[u8]>>,LoadVMTError>
where
'bsp:'a,
'vpk:'a,
{
let mut texture_file_name=PathBuf::from("materials");
//lower case
texture_file_name.push(texture_name);
texture_file_name.as_mut_os_str().make_ascii_lowercase();
//remove stem and search for both vtf and vmt files
let stem=texture_file_name.file_stem().unwrap().to_owned();
texture_file_name.pop();
texture_file_name.push(stem);
if let Some(stuff)=finder.find(texture_file_name.to_str().unwrap())?{
return Ok(Some(stuff));
}
// search for both vmt,vtf
let mut texture_file_name_vmt=texture_file_name.clone();
texture_file_name_vmt.set_extension("vmt");
let get_vmt_result=get_vmt(finder,texture_file_name_vmt.to_str().unwrap());
match get_vmt_result{
Ok(material)=>{
let vmt_result=recursive_vmt_loader(finder,material);
match vmt_result{
Ok(Some(stuff))=>return Ok(Some(stuff)),
Ok(None)
|Err(LoadVMTError::NotFound)=>(),
|Err(LoadVMTError::GetVMT(GetVMTError::NotFound))=>(),
Err(e)=>return Err(e),
}
}
|Err(GetVMTError::NotFound)=>(),
Err(e)=>Err(e)?,
}
// try looking for vtf
let mut texture_file_name_vtf=texture_file_name.clone();
texture_file_name_vtf.set_extension("vtf");
let get_vtf_result=get_vmt(finder,texture_file_name_vtf.to_str().unwrap());
match get_vtf_result{
Ok(material)=>{
let vtf_result=recursive_vmt_loader(finder,material);
match vtf_result{
Ok(Some(stuff))=>return Ok(Some(stuff)),
Ok(None)
|Err(LoadVMTError::NotFound)=>(),
|Err(LoadVMTError::GetVMT(GetVMTError::NotFound))=>(),
Err(e)=>return Err(e),
}
}
|Err(GetVMTError::NotFound)=>(),
Err(e)=>Err(e)?,
}
Ok(None)
}
#[derive(Debug,thiserror::Error)]
enum ExtractTextureError{
#[error("Io error {0:?}")]
Io(#[from]std::io::Error),
#[error("Bsp error {0:?}")]
Bsp(#[from]vbsp::BspError),
#[error("MeshLoad error {0:?}")]
MeshLoad(#[from]strafesnet_bsp_loader::loader::MeshError),
#[error("Load VMT error {0:?}")]
LoadVMT(#[from]LoadVMTError),
}
async fn gimme_them_textures(path:&Path,vpk_list:&[strafesnet_bsp_loader::Vpk],send_texture:tokio::sync::mpsc::Sender<(Vec<u8>,String)>)->Result<(),ExtractTextureError>{
let bsp=vbsp::Bsp::read(tokio::fs::read(path).await?.as_ref())?;
let loader_bsp=strafesnet_bsp_loader::Bsp::new(bsp);
let bsp=loader_bsp.as_ref();
let mut texture_deferred_loader=RenderConfigDeferredLoader::new();
for texture in bsp.textures(){
texture_deferred_loader.acquire_render_config_id(Some(Cow::Borrowed(texture.name())));
}
let mut mesh_deferred_loader=MeshDeferredLoader::new();
for name in &bsp.static_props.dict.name{
mesh_deferred_loader.acquire_mesh_id(name.as_str());
}
for raw_ent in &bsp.entities{
let model=match raw_ent.parse(){
Ok(Entity::Cycler(brush))=>brush.model,
Ok(Entity::EnvSprite(brush))=>brush.model,
Ok(Entity::FuncBreakable(brush))=>brush.model,
Ok(Entity::FuncBrush(brush))=>brush.model,
Ok(Entity::FuncButton(brush))=>brush.model,
Ok(Entity::FuncDoor(brush))=>brush.model,
Ok(Entity::FuncDoorRotating(brush))=>brush.model,
Ok(Entity::FuncIllusionary(brush))=>brush.model,
Ok(Entity::FuncMonitor(brush))=>brush.model,
Ok(Entity::FuncMovelinear(brush))=>brush.model,
Ok(Entity::FuncPhysbox(brush))=>brush.model,
Ok(Entity::FuncPhysboxMultiplayer(brush))=>brush.model,
Ok(Entity::FuncRotButton(brush))=>brush.model,
Ok(Entity::FuncRotating(brush))=>brush.model,
Ok(Entity::FuncTracktrain(brush))=>brush.model,
Ok(Entity::FuncTrain(brush))=>brush.model,
Ok(Entity::FuncWall(brush))=>brush.model,
Ok(Entity::FuncWallToggle(brush))=>brush.model,
Ok(Entity::FuncWaterAnalog(brush))=>brush.model,
Ok(Entity::PropDoorRotating(brush))=>brush.model,
Ok(Entity::PropDynamic(brush))=>brush.model,
Ok(Entity::PropDynamicOverride(brush))=>brush.model,
Ok(Entity::PropPhysics(brush))=>brush.model,
Ok(Entity::PropPhysicsMultiplayer(brush))=>brush.model,
Ok(Entity::PropPhysicsOverride(brush))=>brush.model,
Ok(Entity::PropRagdoll(brush))=>brush.model,
Ok(Entity::TriggerGravity(brush))=>brush.model,
Ok(Entity::TriggerHurt(brush))=>brush.model,
Ok(Entity::TriggerLook(brush))=>brush.model,
Ok(Entity::TriggerMultiple(brush))=>brush.model.unwrap_or_default(),
Ok(Entity::TriggerOnce(brush))=>brush.model,
Ok(Entity::TriggerProximity(brush))=>brush.model,
Ok(Entity::TriggerPush(brush))=>brush.model,
Ok(Entity::TriggerSoundscape(brush))=>brush.model,
Ok(Entity::TriggerTeleport(brush))=>brush.model.unwrap_or_default(),
Ok(Entity::TriggerVphysicsMotion(brush))=>brush.model,
Ok(Entity::TriggerWind(brush))=>brush.model,
_=>continue,
};
match model.chars().next(){
Some('*')=>(),
_=>{
mesh_deferred_loader.acquire_mesh_id(model);
},
}
}
let finder=BspFinder{
bsp:&loader_bsp,
vpks:vpk_list
};
let mut mesh_loader=strafesnet_bsp_loader::loader::ModelLoader::new(finder);
// load models and collect requested textures
for model_path in mesh_deferred_loader.into_indices(){
let model:vmdl::Model=match mesh_loader.load(model_path){
Ok(model)=>model,
Err(e)=>{
println!("Model={model_path} Load model error: {e}");
continue;
},
};
for texture in model.textures(){
for search_path in &texture.search_paths{
let mut path=PathBuf::from(search_path.as_str());
path.push(texture.name.as_str());
let path=path.to_str().unwrap().to_owned();
texture_deferred_loader.acquire_render_config_id(Some(Cow::Owned(path)));
}
}
}
for texture_path in texture_deferred_loader.into_indices(){
match load_texture(finder,&texture_path){
Ok(Some(texture))=>send_texture.send(
(texture.into_owned(),texture_path.into_owned())
).await.unwrap(),
Ok(None)=>(),
Err(e)=>println!("Texture={texture_path} Load error: {e}"),
}
}
Ok(())
}
#[derive(Debug,thiserror::Error)]
enum ConvertTextureError{
#[error("Bsp error {0:?}")]
Bsp(#[from]vbsp::BspError),
#[error("Vtf error {0:?}")]
Vtf(#[from]vtf::Error),
#[error("DDS create error {0:?}")]
DDS(#[from]image_dds::CreateDdsError),
#[error("DDS write error {0:?}")]
DDSWrite(#[from]image_dds::ddsfile::Error),
#[error("Io error {0:?}")]
Io(#[from]std::io::Error),
}
async fn convert_texture(texture:Vec<u8>,write_file_name:impl AsRef<Path>)->Result<(),ConvertTextureError>{
let image=vtf::from_bytes(&texture)?.highres_image.decode(0)?.to_rgba8();
let format=if image.width()%4!=0||image.height()%4!=0{
image_dds::ImageFormat::Rgba8UnormSrgb
}else{
image_dds::ImageFormat::BC7RgbaUnormSrgb
};
//this fails if the image dimensions are not a multiple of 4
let dds = image_dds::dds_from_image(
&image,
format,
image_dds::Quality::Slow,
image_dds::Mipmaps::GeneratedAutomatic,
)?;
//write dds
let mut dest=PathBuf::from("textures");
dest.push(write_file_name);
dest.set_extension("dds");
std::fs::create_dir_all(dest.parent().unwrap())?;
let mut writer=std::io::BufWriter::new(std::fs::File::create(dest)?);
dds.write(&mut writer)?;
Ok(())
}
async fn read_vpks(vpk_paths:Vec<PathBuf>,thread_limit:usize)->Vec<strafesnet_bsp_loader::Vpk>{
futures::stream::iter(vpk_paths).map(|vpk_path|async{
// idk why it doesn't want to pass out the errors but this is fatal anyways
tokio::task::spawn_blocking(move||Ok::<_,vpk::Error>(strafesnet_bsp_loader::Vpk::new(vpk::VPK::read(&vpk_path)?))).await.unwrap().unwrap()
})
.buffer_unordered(thread_limit)
.collect().await
}
async fn extract_textures(paths:Vec<PathBuf>,vpk_paths:Vec<PathBuf>)->AResult<()>{
tokio::try_join!(
tokio::fs::create_dir_all("extracted_textures"),
tokio::fs::create_dir_all("textures"),
tokio::fs::create_dir_all("meshes"),
)?;
let thread_limit=std::thread::available_parallelism()?.get();
// load vpk list and leak for static lifetime
let vpk_list:&[strafesnet_bsp_loader::Vpk]=read_vpks(vpk_paths,thread_limit).await.leak();
let (send_texture,mut recv_texture)=tokio::sync::mpsc::channel(thread_limit);
let mut it=paths.into_iter();
let extract_thread=tokio::spawn(async move{
static SEM:tokio::sync::Semaphore=tokio::sync::Semaphore::const_new(0);
SEM.add_permits(thread_limit);
while let (Ok(permit),Some(path))=(SEM.acquire().await,it.next()){
let send=send_texture.clone();
tokio::spawn(async move{
let result=gimme_them_textures(&path,vpk_list,send).await;
drop(permit);
match result{
Ok(())=>(),
Err(e)=>println!("Map={path:?} Decode error: {e:?}"),
}
});
}
});
// convert images
static SEM:tokio::sync::Semaphore=tokio::sync::Semaphore::const_new(0);
SEM.add_permits(thread_limit);
while let (Ok(permit),Some((data,dest)))=(SEM.acquire().await,recv_texture.recv().await){
// TODO: dedup dest?
tokio::spawn(async move{
let result=convert_texture(data,dest).await;
drop(permit);
match result{
Ok(())=>(),
Err(e)=>println!("Convert error: {e:?}"),
}
});
}
extract_thread.await?;
_=SEM.acquire_many(thread_limit as u32).await?;
Ok(())
}
fn vpk_contents(vpk_path:PathBuf)->AResult<()>{
let vpk_index=vpk::VPK::read(&vpk_path)?;
for (label,entry) in vpk_index.tree.into_iter(){
println!("vpk label={} entry={:?}",label,entry);
}
Ok(())
}
fn bsp_contents(path:PathBuf)->AResult<()>{
let bsp=vbsp::Bsp::read(std::fs::read(path)?.as_ref())?;
for file_name in bsp.pack.into_zip().into_inner().unwrap().file_names(){
println!("file_name={:?}",file_name);
}
Ok(())
}
#[derive(Debug)]
#[allow(dead_code)]
enum ConvertError{
IO(std::io::Error),
SNFMap(strafesnet_snf::map::Error),
BspRead(strafesnet_bsp_loader::ReadError),
BspLoad(strafesnet_bsp_loader::LoadError),
}
impl std::fmt::Display for ConvertError{
fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{
write!(f,"{self:?}")
}
}
impl std::error::Error for ConvertError{}
async fn convert_to_snf(path:&Path,vpk_list:&[strafesnet_bsp_loader::Vpk],output_folder:PathBuf)->AResult<()>{
let entire_file=tokio::fs::read(path).await?;
let bsp=strafesnet_bsp_loader::read(
std::io::Cursor::new(entire_file)
).map_err(ConvertError::BspRead)?;
let map=bsp.to_snf(LoadFailureMode::DefaultToNone,vpk_list).map_err(ConvertError::BspLoad)?;
let mut dest=output_folder;
dest.push(path.file_stem().unwrap());
dest.set_extension("snfm");
let file=std::fs::File::create(dest).map_err(ConvertError::IO)?;
strafesnet_snf::map::write_map(file,map).map_err(ConvertError::SNFMap)?;
Ok(())
}
async fn source_to_snf(paths:Vec<std::path::PathBuf>,output_folder:PathBuf,vpk_paths:Vec<PathBuf>)->AResult<()>{
let start=std::time::Instant::now();
let thread_limit=std::thread::available_parallelism()?.get();
// load vpk list and leak for static lifetime
let vpk_list:&[strafesnet_bsp_loader::Vpk]=read_vpks(vpk_paths,thread_limit).await.leak();
let mut it=paths.into_iter();
static SEM:tokio::sync::Semaphore=tokio::sync::Semaphore::const_new(0);
SEM.add_permits(thread_limit);
while let (Ok(permit),Some(path))=(SEM.acquire().await,it.next()){
let output_folder=output_folder.clone();
tokio::spawn(async move{
let result=convert_to_snf(path.as_path(),vpk_list,output_folder).await;
drop(permit);
match result{
Ok(())=>(),
Err(e)=>println!("Convert error: {e:?}"),
}
});
}
_=SEM.acquire_many(thread_limit as u32).await.unwrap();
println!("elapsed={:?}", start.elapsed());
Ok(())
}

View File

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

View File

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

View File

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

View File

@@ -28,7 +28,7 @@ impl std::error::Error for ReadError{}
pub enum ReadFormat{
#[cfg(feature="roblox")]
Roblox(strafesnet_rbx_loader::Model<'static>),
Roblox(strafesnet_rbx_loader::Model),
#[cfg(feature="source")]
Source(strafesnet_bsp_loader::Bsp),
#[cfg(feature="snf")]
@@ -97,7 +97,7 @@ pub fn load<P:AsRef<std::path::Path>>(path:P)->Result<LoadFormat,LoadError>{
ReadFormat::SNFM(map)=>Ok(LoadFormat::Map(map)),
#[cfg(feature="roblox")]
ReadFormat::Roblox(model)=>{
let mut place=strafesnet_rbx_loader::Place::from(model);
let mut place=model.into_place();
place.run_scripts();
Ok(LoadFormat::Map(
place.to_snf(LoadFailureMode::DefaultToNone).map_err(LoadError::LoadRoblox)?

Some files were not shown because too many files have changed in this diff Show More