Compare commits
4 Commits
master
...
roblox-bot
Author | SHA1 | Date | |
---|---|---|---|
89d51c60f4 | |||
d04a5e4625 | |||
904338d7c6 | |||
40ad61fed7 |
2463
Cargo.lock
generated
2463
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@ -1,10 +1,5 @@
|
|||||||
[workspace]
|
[workspace]
|
||||||
members = [
|
members = [
|
||||||
"engine/graphics",
|
|
||||||
"engine/physics",
|
|
||||||
"engine/session",
|
|
||||||
"engine/settings",
|
|
||||||
"integration-testing",
|
|
||||||
"lib/bsp_loader",
|
"lib/bsp_loader",
|
||||||
"lib/common",
|
"lib/common",
|
||||||
"lib/deferred_loader",
|
"lib/deferred_loader",
|
||||||
@ -12,10 +7,8 @@ members = [
|
|||||||
"lib/linear_ops",
|
"lib/linear_ops",
|
||||||
"lib/ratio_ops",
|
"lib/ratio_ops",
|
||||||
"lib/rbx_loader",
|
"lib/rbx_loader",
|
||||||
"lib/rbxassetid",
|
|
||||||
"lib/roblox_emulator",
|
"lib/roblox_emulator",
|
||||||
"lib/snf",
|
"lib/snf",
|
||||||
"map-tool",
|
|
||||||
"strafe-client",
|
"strafe-client",
|
||||||
]
|
]
|
||||||
resolver = "2"
|
resolver = "2"
|
||||||
|
@ -3,9 +3,6 @@
|
|||||||
# Strafe Project
|
# Strafe Project
|
||||||
Monorepo for working on projects related to strafe client.
|
Monorepo for working on projects related to strafe client.
|
||||||
|
|
||||||
## Try it out
|
|
||||||
See [releases](https://git.itzana.me/StrafesNET/strafe-project/releases) for downloads.
|
|
||||||
|
|
||||||
## How to build and run
|
## How to build and run
|
||||||
1. Have rust and git installed
|
1. Have rust and git installed
|
||||||
2. `git clone https://git.itzana.me/StrafesNET/strafe-project`
|
2. `git clone https://git.itzana.me/StrafesNET/strafe-project`
|
||||||
@ -13,4 +10,4 @@ See [releases](https://git.itzana.me/StrafesNET/strafe-project/releases) for dow
|
|||||||
4. `cargo run --release --bin strafe-client`
|
4. `cargo run --release --bin strafe-client`
|
||||||
|
|
||||||
## Licenses
|
## Licenses
|
||||||
Each project has its own license. Most crates are MIT/Apache but notably the Strafe Client and engine crates have a sole proprietor license.
|
Each project has its own license. Most crates are MIT/Apache but notably the Strafe Client has a sole proprietor license.
|
||||||
|
@ -1,14 +0,0 @@
|
|||||||
[package]
|
|
||||||
name = "strafesnet_graphics"
|
|
||||||
version = "0.1.0"
|
|
||||||
edition = "2021"
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
bytemuck = { version = "1.13.1", features = ["derive"] }
|
|
||||||
ddsfile = "0.5.1"
|
|
||||||
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 = "24.0.0"
|
|
@ -1,8 +0,0 @@
|
|||||||
/*******************************************************
|
|
||||||
* Copyright (C) 2023-2024 Rhys Lloyd <krakow20@gmail.com>
|
|
||||||
*
|
|
||||||
* This file is part of the StrafesNET bhop/surf client.
|
|
||||||
*
|
|
||||||
* StrafesNET can not be copied and/or distributed
|
|
||||||
* without the express permission of Rhys Lloyd
|
|
||||||
*******************************************************/
|
|
@ -1,2 +0,0 @@
|
|||||||
pub mod model;
|
|
||||||
pub mod graphics;
|
|
@ -1,10 +0,0 @@
|
|||||||
[package]
|
|
||||||
name = "strafesnet_physics"
|
|
||||||
version = "0.1.0"
|
|
||||||
edition = "2021"
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
arrayvec = "0.7.6"
|
|
||||||
glam = "0.29.0"
|
|
||||||
id = { version = "0.1.0", registry = "strafesnet" }
|
|
||||||
strafesnet_common = { path = "../../lib/common", registry = "strafesnet" }
|
|
@ -1,8 +0,0 @@
|
|||||||
/*******************************************************
|
|
||||||
* Copyright (C) 2023-2024 Rhys Lloyd <krakow20@gmail.com>
|
|
||||||
*
|
|
||||||
* This file is part of the StrafesNET bhop/surf client.
|
|
||||||
*
|
|
||||||
* StrafesNET can not be copied and/or distributed
|
|
||||||
* without the express permission of Rhys Lloyd
|
|
||||||
*******************************************************/
|
|
@ -1,160 +0,0 @@
|
|||||||
use strafesnet_common::aabb;
|
|
||||||
use strafesnet_common::integer::{self,vec3,Time,Planar64,Planar64Vec3};
|
|
||||||
#[derive(Clone,Copy,Debug,Hash)]
|
|
||||||
pub struct Body<T>{
|
|
||||||
pub position:Planar64Vec3,//I64 where 2^32 = 1 u
|
|
||||||
pub velocity:Planar64Vec3,//I64 where 2^32 = 1 u/s
|
|
||||||
pub acceleration:Planar64Vec3,//I64 where 2^32 = 1 u/s/s
|
|
||||||
pub time:Time<T>,//nanoseconds x xxxxD!
|
|
||||||
}
|
|
||||||
impl<T> std::ops::Neg for Body<T>{
|
|
||||||
type Output=Self;
|
|
||||||
fn neg(self)->Self::Output{
|
|
||||||
Self{
|
|
||||||
position:self.position,
|
|
||||||
velocity:-self.velocity,
|
|
||||||
acceleration:self.acceleration,
|
|
||||||
time:-self.time,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> Body<T>
|
|
||||||
where Time<T>:Copy,
|
|
||||||
{
|
|
||||||
pub const ZERO:Self=Self::new(vec3::ZERO,vec3::ZERO,vec3::ZERO,Time::ZERO);
|
|
||||||
pub const fn new(position:Planar64Vec3,velocity:Planar64Vec3,acceleration:Planar64Vec3,time:Time<T>)->Self{
|
|
||||||
Self{
|
|
||||||
position,
|
|
||||||
velocity,
|
|
||||||
acceleration,
|
|
||||||
time,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub const fn relative_to<'a>(&'a self,body0:&'a Body<T>)->VirtualBody<'a,T>{
|
|
||||||
//(p0,v0,a0,t0)
|
|
||||||
//(p1,v1,a1,t1)
|
|
||||||
VirtualBody{
|
|
||||||
body0,
|
|
||||||
body1:self,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub fn extrapolated_position(&self,time:Time<T>)->Planar64Vec3{
|
|
||||||
let dt=time-self.time;
|
|
||||||
self.position
|
|
||||||
+(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().fix_1())
|
|
||||||
}
|
|
||||||
pub fn advance_time(&mut self,time:Time<T>){
|
|
||||||
self.position=self.extrapolated_position(time);
|
|
||||||
self.velocity=self.extrapolated_velocity(time);
|
|
||||||
self.time=time;
|
|
||||||
}
|
|
||||||
pub fn extrapolated_position_ratio_dt<Num,Den,N1,D1,N2,N3,D2,N4,T1>(&self,dt:integer::Ratio<Num,Den>)->Planar64Vec3
|
|
||||||
where
|
|
||||||
// Why?
|
|
||||||
// All of this can be removed with const generics because the type can be specified as
|
|
||||||
// Ratio<Fixed<N,NF>,Fixed<D,DF>>
|
|
||||||
// which is known to implement all the necessary traits
|
|
||||||
Num:Copy,
|
|
||||||
Den:Copy+core::ops::Mul<i64,Output=D1>,
|
|
||||||
D1:Copy,
|
|
||||||
Num:core::ops::Mul<Planar64,Output=N1>,
|
|
||||||
Planar64:core::ops::Mul<D1,Output=N2>,
|
|
||||||
N1:core::ops::Add<N2,Output=N3>,
|
|
||||||
Num:core::ops::Mul<N3,Output=N4>,
|
|
||||||
Den:core::ops::Mul<D1,Output=D2>,
|
|
||||||
D2:Copy,
|
|
||||||
Planar64:core::ops::Mul<D2,Output=N4>,
|
|
||||||
N4:integer::Divide<D2,Output=T1>,
|
|
||||||
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().fix())+self.position
|
|
||||||
}
|
|
||||||
pub fn extrapolated_velocity_ratio_dt<Num,Den,N1,T1>(&self,dt:integer::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>,
|
|
||||||
{
|
|
||||||
// a*dt + v
|
|
||||||
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);
|
|
||||||
self.velocity=self.extrapolated_velocity_ratio_dt(dt);
|
|
||||||
self.time+=dt.into();
|
|
||||||
}
|
|
||||||
pub fn infinity_dir(&self)->Option<Planar64Vec3>{
|
|
||||||
if self.velocity==vec3::ZERO{
|
|
||||||
if self.acceleration==vec3::ZERO{
|
|
||||||
None
|
|
||||||
}else{
|
|
||||||
Some(self.acceleration)
|
|
||||||
}
|
|
||||||
}else{
|
|
||||||
Some(self.velocity)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub fn grow_aabb(&self,aabb:&mut aabb::Aabb,t0:Time<T>,t1:Time<T>){
|
|
||||||
aabb.grow(self.extrapolated_position(t0));
|
|
||||||
aabb.grow(self.extrapolated_position(t1));
|
|
||||||
//v+a*t==0
|
|
||||||
//goober code
|
|
||||||
if !self.acceleration.x.is_zero(){
|
|
||||||
let t=-self.velocity.x/self.acceleration.x;
|
|
||||||
if t0.to_ratio().lt_ratio(t)&&t.lt_ratio(t1.to_ratio()){
|
|
||||||
aabb.grow(self.extrapolated_position_ratio_dt(t));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !self.acceleration.y.is_zero(){
|
|
||||||
let t=-self.velocity.y/self.acceleration.y;
|
|
||||||
if t0.to_ratio().lt_ratio(t)&&t.lt_ratio(t1.to_ratio()){
|
|
||||||
aabb.grow(self.extrapolated_position_ratio_dt(t));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !self.acceleration.z.is_zero(){
|
|
||||||
let t=-self.velocity.z/self.acceleration.z;
|
|
||||||
if t0.to_ratio().lt_ratio(t)&&t.lt_ratio(t1.to_ratio()){
|
|
||||||
aabb.grow(self.extrapolated_position_ratio_dt(t));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
impl<T> std::fmt::Display for Body<T>{
|
|
||||||
fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{
|
|
||||||
write!(f,"p({}) v({}) a({}) t({})",self.position,self.velocity,self.acceleration,self.time)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct VirtualBody<'a,T>{
|
|
||||||
body0:&'a Body<T>,
|
|
||||||
body1:&'a Body<T>,
|
|
||||||
}
|
|
||||||
impl<T> VirtualBody<'_,T>
|
|
||||||
where Time<T>:Copy,
|
|
||||||
{
|
|
||||||
pub fn extrapolated_position(&self,time:Time<T>)->Planar64Vec3{
|
|
||||||
self.body1.extrapolated_position(time)-self.body0.extrapolated_position(time)
|
|
||||||
}
|
|
||||||
pub fn extrapolated_velocity(&self,time:Time<T>)->Planar64Vec3{
|
|
||||||
self.body1.extrapolated_velocity(time)-self.body0.extrapolated_velocity(time)
|
|
||||||
}
|
|
||||||
pub fn acceleration(&self)->Planar64Vec3{
|
|
||||||
self.body1.acceleration-self.body0.acceleration
|
|
||||||
}
|
|
||||||
pub fn body(&self,time:Time<T>)->Body<T>{
|
|
||||||
Body::new(self.extrapolated_position(time),self.extrapolated_velocity(time),self.acceleration(),time)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,45 +0,0 @@
|
|||||||
mod body;
|
|
||||||
mod push_solve;
|
|
||||||
mod face_crawler;
|
|
||||||
mod model;
|
|
||||||
|
|
||||||
pub mod physics;
|
|
||||||
|
|
||||||
// Physics bug fixes can easily desync all bots.
|
|
||||||
//
|
|
||||||
// When replaying a bot, use the exact physics version which it was recorded with.
|
|
||||||
//
|
|
||||||
// When validating a new bot, ignore the version and use the latest version,
|
|
||||||
// and overwrite the version in the file.
|
|
||||||
//
|
|
||||||
// Compatible physics versions should be determined
|
|
||||||
// empirically at development time via leaderboard resimulation.
|
|
||||||
//
|
|
||||||
// Compatible physics versions should result in an identical leaderboard state,
|
|
||||||
// or the only bots which fail are ones exploiting a surgically patched bug.
|
|
||||||
#[derive(Clone,Copy,Hash,Debug,id::Id,Eq,PartialEq,Ord,PartialOrd)]
|
|
||||||
pub struct PhysicsVersion(u32);
|
|
||||||
pub const VERSION:PhysicsVersion=PhysicsVersion(0);
|
|
||||||
const LATEST_COMPATIBLE_VERSION:[u32;1+VERSION.0 as usize]=const{
|
|
||||||
let compat=[0];
|
|
||||||
|
|
||||||
let mut input_version=0;
|
|
||||||
while input_version<compat.len(){
|
|
||||||
// compatible version must be greater than or equal to the input version
|
|
||||||
assert!(input_version as u32<=compat[input_version]);
|
|
||||||
// compatible version must be a version that exists
|
|
||||||
assert!(compat[input_version]<=VERSION.0);
|
|
||||||
input_version+=1;
|
|
||||||
}
|
|
||||||
compat
|
|
||||||
};
|
|
||||||
pub enum PhysicsVersionError{
|
|
||||||
UnknownPhysicsVersion,
|
|
||||||
}
|
|
||||||
pub const fn get_latest_compatible_version(PhysicsVersion(version):PhysicsVersion)->Result<PhysicsVersion,PhysicsVersionError>{
|
|
||||||
if (version as usize)<LATEST_COMPATIBLE_VERSION.len(){
|
|
||||||
Ok(PhysicsVersion(LATEST_COMPATIBLE_VERSION[version as usize]))
|
|
||||||
}else{
|
|
||||||
Err(PhysicsVersionError::UnknownPhysicsVersion)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,349 +0,0 @@
|
|||||||
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
|
|
||||||
|
|
||||||
// EPSILON=1/2^10
|
|
||||||
|
|
||||||
// A stack-allocated variable-size list that holds up to 4 elements
|
|
||||||
// Direct references are used instead of indices i0, i1, i2, i3
|
|
||||||
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,
|
|
||||||
pub velocity:Planar64Vec3,
|
|
||||||
pub normal:Planar64Vec3,
|
|
||||||
}
|
|
||||||
impl Contact{
|
|
||||||
fn relative_to(&self,point:Planar64Vec3)->Self{
|
|
||||||
Self{
|
|
||||||
position:self.position-point,
|
|
||||||
velocity:self.velocity,
|
|
||||||
normal:self.normal,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fn relative_dot(&self,direction:Planar64Vec3)->Fixed<2,64>{
|
|
||||||
(direction-self.velocity).dot(self.normal)
|
|
||||||
}
|
|
||||||
/// Calculate the time of intersection. (previously get_touch_time)
|
|
||||||
fn solve(&self,ray:&Ray)->Ratio<Fixed<2,64>,Fixed<2,64>>{
|
|
||||||
(self.position-ray.origin).dot(self.normal)/(ray.direction-self.velocity).dot(self.normal)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//note that this is horrible with fixed point arithmetic
|
|
||||||
fn solve1(c0:&Contact)->Option<Ratio<Vector3<Fixed<3,96>>,Fixed<2,64>>>{
|
|
||||||
const EPSILON:Fixed<2,64>=Fixed::from_bits(Fixed::<2,64>::ONE.to_bits().shr(10));
|
|
||||||
let det=c0.normal.dot(c0.velocity);
|
|
||||||
if det.abs()<EPSILON{
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
let d0=c0.normal.dot(c0.position);
|
|
||||||
Some(c0.normal*d0/det)
|
|
||||||
}
|
|
||||||
fn solve2(c0:&Contact,c1:&Contact)->Option<Ratio<Vector3<Fixed<5,160>>,Fixed<4,128>>>{
|
|
||||||
const EPSILON:Fixed<4,128>=Fixed::from_bits(Fixed::<4,128>::ONE.to_bits().shr(10));
|
|
||||||
let u0_u1=c0.velocity.cross(c1.velocity);
|
|
||||||
let n0_n1=c0.normal.cross(c1.normal);
|
|
||||||
let det=u0_u1.dot(n0_n1);
|
|
||||||
if det.abs()<EPSILON{
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
let d0=c0.normal.dot(c0.position);
|
|
||||||
let d1=c1.normal.dot(c1.position);
|
|
||||||
Some((c1.normal.cross(u0_u1)*d0+u0_u1.cross(c0.normal)*d1)/det)
|
|
||||||
}
|
|
||||||
fn solve3(c0:&Contact,c1:&Contact,c2:&Contact)->Option<Ratio<Vector3<Fixed<4,128>>,Fixed<3,96>>>{
|
|
||||||
const EPSILON:Fixed<3,96>=Fixed::from_bits(Fixed::<3,96>::ONE.to_bits().shr(10));
|
|
||||||
let n0_n1=c0.normal.cross(c1.normal);
|
|
||||||
let det=c2.normal.dot(n0_n1);
|
|
||||||
if det.abs()<EPSILON{
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
let d0=c0.normal.dot(c0.position);
|
|
||||||
let d1=c1.normal.dot(c1.position);
|
|
||||||
let d2=c2.normal.dot(c2.position);
|
|
||||||
Some((c1.normal.cross(c2.normal)*d0+c2.normal.cross(c0.normal)*d1+c0.normal.cross(c1.normal)*d2)/det)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn decompose1(point:Planar64Vec3,u0:Planar64Vec3)->Option<[Ratio<Fixed<2,64>,Fixed<2,64>>;1]>{
|
|
||||||
let det=u0.dot(u0);
|
|
||||||
if det==Fixed::ZERO{
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
let s0=u0.dot(point)/det;
|
|
||||||
Some([s0])
|
|
||||||
}
|
|
||||||
fn decompose2(point:Planar64Vec3,u0:Planar64Vec3,u1:Planar64Vec3)->Option<[Ratio<Fixed<4,128>,Fixed<4,128>>;2]>{
|
|
||||||
let u0_u1=u0.cross(u1);
|
|
||||||
let det=u0_u1.dot(u0_u1);
|
|
||||||
if det==Fixed::ZERO{
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
let s0=u0_u1.dot(point.cross(u1))/det;
|
|
||||||
let s1=u0_u1.dot(u0.cross(point))/det;
|
|
||||||
Some([s0,s1])
|
|
||||||
}
|
|
||||||
fn decompose3(point:Planar64Vec3,u0:Planar64Vec3,u1:Planar64Vec3,u2:Planar64Vec3)->Option<[Ratio<Fixed<3,96>,Fixed<3,96>>;3]>{
|
|
||||||
let det=u0.cross(u1).dot(u2);
|
|
||||||
if det==Fixed::ZERO{
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
let s0=point.cross(u1).dot(u2)/det;
|
|
||||||
let s1=u0.cross(point).dot(u2)/det;
|
|
||||||
let s2=u0.cross(u1).dot(point)/det;
|
|
||||||
Some([s0,s1,s2])
|
|
||||||
}
|
|
||||||
|
|
||||||
fn is_space_enclosed_2(
|
|
||||||
a:Planar64Vec3,
|
|
||||||
b:Planar64Vec3,
|
|
||||||
)->bool{
|
|
||||||
a.cross(b)==Vector3::new([Fixed::ZERO;3])
|
|
||||||
&&a.dot(b).is_negative()
|
|
||||||
}
|
|
||||||
fn is_space_enclosed_3(
|
|
||||||
a:Planar64Vec3,
|
|
||||||
b:Planar64Vec3,
|
|
||||||
c:Planar64Vec3
|
|
||||||
)->bool{
|
|
||||||
a.cross(b).dot(c)==Fixed::ZERO
|
|
||||||
&&{
|
|
||||||
let det_abac=a.cross(b).dot(a.cross(c));
|
|
||||||
let det_abbc=a.cross(b).dot(b.cross(c));
|
|
||||||
let det_acbc=a.cross(c).dot(b.cross(c));
|
|
||||||
return!( det_abac*det_abbc).is_positive()
|
|
||||||
&&!( det_abbc*det_acbc).is_positive()
|
|
||||||
&&!(-det_acbc*det_abac).is_positive()
|
|
||||||
||is_space_enclosed_2(a,b)
|
|
||||||
||is_space_enclosed_2(a,c)
|
|
||||||
||is_space_enclosed_2(b,c)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fn is_space_enclosed_4(
|
|
||||||
a:Planar64Vec3,
|
|
||||||
b:Planar64Vec3,
|
|
||||||
c:Planar64Vec3,
|
|
||||||
d:Planar64Vec3,
|
|
||||||
)->bool{
|
|
||||||
let det_abc=a.cross(b).dot(c);
|
|
||||||
let det_abd=a.cross(b).dot(d);
|
|
||||||
let det_acd=a.cross(c).dot(d);
|
|
||||||
let det_bcd=b.cross(c).dot(d);
|
|
||||||
return( det_abc*det_abd).is_negative()
|
|
||||||
&&(-det_abc*det_acd).is_negative()
|
|
||||||
&&( det_abd*det_acd).is_negative()
|
|
||||||
&&( det_abc*det_bcd).is_negative()
|
|
||||||
&&(-det_abd*det_bcd).is_negative()
|
|
||||||
&&( det_acd*det_bcd).is_negative()
|
|
||||||
||is_space_enclosed_3(a,b,c)
|
|
||||||
||is_space_enclosed_3(a,b,d)
|
|
||||||
||is_space_enclosed_3(a,c,d)
|
|
||||||
||is_space_enclosed_3(b,c,d)
|
|
||||||
}
|
|
||||||
|
|
||||||
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>{
|
|
||||||
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().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().fix_1();
|
|
||||||
let [s0,s1]=decompose2(direction,c0.velocity,c1.velocity)?;
|
|
||||||
if s0.lt_ratio(RATIO_ZERO)||s1.lt_ratio(RATIO_ZERO){
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
let origin=point+solve2(
|
|
||||||
&c0.relative_to(point),
|
|
||||||
&c1.relative_to(point),
|
|
||||||
)?.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().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;
|
|
||||||
}
|
|
||||||
let origin=point+solve3(
|
|
||||||
&c0.relative_to(point),
|
|
||||||
&c1.relative_to(point),
|
|
||||||
&c2.relative_to(point),
|
|
||||||
)?.divide().fix_1();
|
|
||||||
Some(Ray{origin,direction})
|
|
||||||
}
|
|
||||||
|
|
||||||
const fn get_best_push_ray_and_conts_0<'a>(point:Planar64Vec3)->(Ray,Conts<'a>){
|
|
||||||
(get_push_ray_0(point),Conts::new_const())
|
|
||||||
}
|
|
||||||
fn get_best_push_ray_and_conts_1(point:Planar64Vec3,c0:&Contact)->Option<(Ray,Conts)>{
|
|
||||||
get_push_ray_1(point,c0)
|
|
||||||
.map(|ray|(ray,Conts::from_iter([c0])))
|
|
||||||
}
|
|
||||||
fn get_best_push_ray_and_conts_2<'a>(point:Planar64Vec3,c0:&'a Contact,c1:&'a Contact)->Option<(Ray,Conts<'a>)>{
|
|
||||||
if is_space_enclosed_2(c0.normal,c1.normal){
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
if let Some(ray)=get_push_ray_2(point,c0,c1){
|
|
||||||
return Some((ray,Conts::from_iter([c0,c1])));
|
|
||||||
}
|
|
||||||
if let Some(ray)=get_push_ray_1(point,c0){
|
|
||||||
if !c1.relative_dot(ray.direction).is_negative(){
|
|
||||||
return Some((ray,Conts::from_iter([c0])));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
fn get_best_push_ray_and_conts_3<'a>(point:Planar64Vec3,c0:&'a Contact,c1:&'a Contact,c2:&'a Contact)->Option<(Ray,Conts<'a>)>{
|
|
||||||
if is_space_enclosed_3(c0.normal,c1.normal,c2.normal){
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
if let Some(ray)=get_push_ray_3(point,c0,c1,c2){
|
|
||||||
return Some((ray,Conts::from_iter([c0,c1,c2])));
|
|
||||||
}
|
|
||||||
if let Some(ray)=get_push_ray_2(point,c0,c1){
|
|
||||||
if !c2.relative_dot(ray.direction).is_negative(){
|
|
||||||
return Some((ray,Conts::from_iter([c0,c1])));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if let Some(ray)=get_push_ray_2(point,c0,c2){
|
|
||||||
if !c1.relative_dot(ray.direction).is_negative(){
|
|
||||||
return Some((ray,Conts::from_iter([c0,c2])));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if let Some(ray)=get_push_ray_1(point,c0){
|
|
||||||
if !c1.relative_dot(ray.direction).is_negative()
|
|
||||||
&&!c2.relative_dot(ray.direction).is_negative(){
|
|
||||||
return Some((ray,Conts::from_iter([c0])));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
fn get_best_push_ray_and_conts_4<'a>(point:Planar64Vec3,c0:&'a Contact,c1:&'a Contact,c2:&'a Contact,c3:&'a Contact)->Option<(Ray,Conts<'a>)>{
|
|
||||||
if is_space_enclosed_4(c0.normal,c1.normal,c2.normal,c3.normal){
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
let (ray012,conts012)=get_best_push_ray_and_conts_3(point,c0,c1,c2)?;
|
|
||||||
let (ray013,conts013)=get_best_push_ray_and_conts_3(point,c0,c1,c3)?;
|
|
||||||
let (ray023,conts023)=get_best_push_ray_and_conts_3(point,c0,c2,c3)?;
|
|
||||||
|
|
||||||
let err012=c3.relative_dot(ray012.direction);
|
|
||||||
let err013=c2.relative_dot(ray013.direction);
|
|
||||||
let err023=c1.relative_dot(ray023.direction);
|
|
||||||
|
|
||||||
let best_err=err012.max(err013).max(err023);
|
|
||||||
|
|
||||||
if best_err==err012{
|
|
||||||
return Some((ray012,conts012))
|
|
||||||
}else if best_err==err013{
|
|
||||||
return Some((ray013,conts013))
|
|
||||||
}else if best_err==err023{
|
|
||||||
return Some((ray023,conts023))
|
|
||||||
}
|
|
||||||
unreachable!()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_best_push_ray_and_conts<'a>(
|
|
||||||
point:Planar64Vec3,
|
|
||||||
conts:&[&'a Contact],
|
|
||||||
)->Option<(Ray,Conts<'a>)>{
|
|
||||||
match conts{
|
|
||||||
&[c0,c1,c2,c3]=>get_best_push_ray_and_conts_4(point,c0,c1,c2,c3),
|
|
||||||
&[c0,c1,c2]=>get_best_push_ray_and_conts_3(point,c0,c1,c2),
|
|
||||||
&[c0,c1]=>get_best_push_ray_and_conts_2(point,c0,c1),
|
|
||||||
&[c0]=>get_best_push_ray_and_conts_1(point,c0),
|
|
||||||
&[]=>Some(get_best_push_ray_and_conts_0(point)),
|
|
||||||
_=>unreachable!(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_first_touch<'a>(contacts:&'a [Contact],ray:&Ray,conts:&Conts)->Option<(Ratio<Fixed<2,64>,Fixed<2,64>>,&'a Contact)>{
|
|
||||||
contacts.iter()
|
|
||||||
.filter(|&contact|
|
|
||||||
!conts.iter().any(|&c|std::ptr::eq(c,contact))
|
|
||||||
&&contact.relative_dot(ray.direction).is_negative()
|
|
||||||
)
|
|
||||||
.map(|contact|(contact.solve(ray),contact))
|
|
||||||
.min_by_key(|&(t,_)|t)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn push_solve(contacts:&[Contact],point:Planar64Vec3)->Planar64Vec3{
|
|
||||||
let (mut ray,mut conts)=get_best_push_ray_and_conts_0(point);
|
|
||||||
loop{
|
|
||||||
let (next_t,next_cont)=match get_first_touch(contacts,&ray,&conts){
|
|
||||||
Some((t,cont))=>(t,cont),
|
|
||||||
None=>return ray.origin,
|
|
||||||
};
|
|
||||||
|
|
||||||
if RATIO_ZERO.le_ratio(next_t){
|
|
||||||
return ray.origin;
|
|
||||||
}
|
|
||||||
|
|
||||||
//push_front
|
|
||||||
if conts.len()==conts.capacity(){
|
|
||||||
//this is a dead case, new_conts never has more than 3 elements
|
|
||||||
conts.rotate_right(1);
|
|
||||||
conts[0]=next_cont;
|
|
||||||
}else{
|
|
||||||
conts.push(next_cont);
|
|
||||||
conts.rotate_right(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
let meet_point=ray.extrapolate(next_t);
|
|
||||||
match get_best_push_ray_and_conts(meet_point,conts.as_slice()){
|
|
||||||
Some((new_ray,new_conts))=>(ray,conts)=(new_ray,new_conts),
|
|
||||||
None=>return meet_point,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests{
|
|
||||||
use super::*;
|
|
||||||
#[test]
|
|
||||||
fn test_push_solve(){
|
|
||||||
let contacts=vec![
|
|
||||||
Contact{
|
|
||||||
position:vec3::ZERO,
|
|
||||||
velocity:vec3::Y,
|
|
||||||
normal:vec3::Y,
|
|
||||||
}
|
|
||||||
];
|
|
||||||
assert_eq!(
|
|
||||||
vec3::ZERO,
|
|
||||||
push_solve(&contacts,vec3::NEG_Y)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,12 +0,0 @@
|
|||||||
[package]
|
|
||||||
name = "strafesnet_session"
|
|
||||||
version = "0.1.0"
|
|
||||||
edition = "2021"
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
glam = "0.29.0"
|
|
||||||
replace_with = "0.1.7"
|
|
||||||
strafesnet_common = { path = "../../lib/common", registry = "strafesnet" }
|
|
||||||
strafesnet_physics = { path = "../physics", registry = "strafesnet" }
|
|
||||||
strafesnet_settings = { path = "../settings", registry = "strafesnet" }
|
|
||||||
strafesnet_snf = { path = "../../lib/snf", registry = "strafesnet" }
|
|
@ -1,8 +0,0 @@
|
|||||||
/*******************************************************
|
|
||||||
* Copyright (C) 2023-2024 Rhys Lloyd <krakow20@gmail.com>
|
|
||||||
*
|
|
||||||
* This file is part of the StrafesNET bhop/surf client.
|
|
||||||
*
|
|
||||||
* StrafesNET can not be copied and/or distributed
|
|
||||||
* without the express permission of Rhys Lloyd
|
|
||||||
*******************************************************/
|
|
@ -1,2 +0,0 @@
|
|||||||
mod mouse_interpolator;
|
|
||||||
pub mod session;
|
|
@ -1,281 +0,0 @@
|
|||||||
use strafesnet_common::mouse::MouseState;
|
|
||||||
use strafesnet_common::physics::{
|
|
||||||
MouseInstruction,SetControlInstruction,ModeInstruction,MiscInstruction,
|
|
||||||
Instruction as PhysicsInstruction,
|
|
||||||
TimeInner as PhysicsTimeInner,
|
|
||||||
Time as PhysicsTime,
|
|
||||||
};
|
|
||||||
use strafesnet_common::session::{Time as SessionTime,TimeInner as SessionTimeInner};
|
|
||||||
use strafesnet_common::instruction::{InstructionConsumer,InstructionEmitter,TimedInstruction};
|
|
||||||
|
|
||||||
type TimedSelfInstruction=TimedInstruction<Instruction,PhysicsTimeInner>;
|
|
||||||
type DoubleTimedSelfInstruction=TimedInstruction<TimedSelfInstruction,SessionTimeInner>;
|
|
||||||
|
|
||||||
type TimedPhysicsInstruction=TimedInstruction<PhysicsInstruction,PhysicsTimeInner>;
|
|
||||||
|
|
||||||
const MOUSE_TIMEOUT:SessionTime=SessionTime::from_millis(10);
|
|
||||||
|
|
||||||
/// To be fed into MouseInterpolator
|
|
||||||
#[derive(Clone,Debug)]
|
|
||||||
pub(crate) enum Instruction{
|
|
||||||
MoveMouse(glam::IVec2),
|
|
||||||
SetControl(SetControlInstruction),
|
|
||||||
Mode(ModeInstruction),
|
|
||||||
Misc(MiscInstruction),
|
|
||||||
Idle,
|
|
||||||
}
|
|
||||||
#[derive(Clone,Debug)]
|
|
||||||
enum UnbufferedInstruction{
|
|
||||||
MoveMouse(glam::IVec2),
|
|
||||||
NonMouse(NonMouseInstruction),
|
|
||||||
}
|
|
||||||
#[derive(Clone,Debug)]
|
|
||||||
enum BufferedInstruction{
|
|
||||||
Mouse(MouseInstruction),
|
|
||||||
NonMouse(NonMouseInstruction),
|
|
||||||
}
|
|
||||||
#[derive(Clone,Debug)]
|
|
||||||
pub(crate) enum NonMouseInstruction{
|
|
||||||
SetControl(SetControlInstruction),
|
|
||||||
Mode(ModeInstruction),
|
|
||||||
Misc(MiscInstruction),
|
|
||||||
Idle,
|
|
||||||
}
|
|
||||||
impl From<Instruction> for UnbufferedInstruction{
|
|
||||||
#[inline]
|
|
||||||
fn from(value:Instruction)->Self{
|
|
||||||
match value{
|
|
||||||
Instruction::MoveMouse(mouse_instruction)=>UnbufferedInstruction::MoveMouse(mouse_instruction),
|
|
||||||
Instruction::SetControl(set_control_instruction)=>UnbufferedInstruction::NonMouse(NonMouseInstruction::SetControl(set_control_instruction)),
|
|
||||||
Instruction::Mode(mode_instruction)=>UnbufferedInstruction::NonMouse(NonMouseInstruction::Mode(mode_instruction)),
|
|
||||||
Instruction::Misc(misc_instruction)=>UnbufferedInstruction::NonMouse(NonMouseInstruction::Misc(misc_instruction)),
|
|
||||||
Instruction::Idle=>UnbufferedInstruction::NonMouse(NonMouseInstruction::Idle),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl From<BufferedInstruction> for PhysicsInstruction{
|
|
||||||
#[inline]
|
|
||||||
fn from(value:BufferedInstruction)->Self{
|
|
||||||
match value{
|
|
||||||
BufferedInstruction::Mouse(mouse_instruction)=>PhysicsInstruction::Mouse(mouse_instruction),
|
|
||||||
BufferedInstruction::NonMouse(non_mouse_instruction)=>match non_mouse_instruction{
|
|
||||||
NonMouseInstruction::SetControl(set_control_instruction)=>PhysicsInstruction::SetControl(set_control_instruction),
|
|
||||||
NonMouseInstruction::Mode(mode_instruction)=>PhysicsInstruction::Mode(mode_instruction),
|
|
||||||
NonMouseInstruction::Misc(misc_instruction)=>PhysicsInstruction::Misc(misc_instruction),
|
|
||||||
NonMouseInstruction::Idle=>PhysicsInstruction::Idle,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) enum StepInstruction{
|
|
||||||
Pop,
|
|
||||||
Timeout,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone,Debug)]
|
|
||||||
enum BufferState{
|
|
||||||
Unbuffered,
|
|
||||||
Initializing(SessionTime,MouseState<PhysicsTimeInner>),
|
|
||||||
Buffered(SessionTime,MouseState<PhysicsTimeInner>),
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct MouseInterpolator{
|
|
||||||
buffer_state:BufferState,
|
|
||||||
// double timestamped timeline?
|
|
||||||
buffer:std::collections::VecDeque<TimedPhysicsInstruction>,
|
|
||||||
output:std::collections::VecDeque<TimedPhysicsInstruction>,
|
|
||||||
}
|
|
||||||
// Maybe MouseInterpolator manipulation is better expressed using impls
|
|
||||||
// and called from Instruction trait impls in session
|
|
||||||
impl InstructionConsumer<TimedSelfInstruction> for MouseInterpolator{
|
|
||||||
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 TimeInner=SessionTimeInner;
|
|
||||||
fn next_instruction(&self,time_limit:SessionTime)->Option<TimedInstruction<StepInstruction,Self::TimeInner>>{
|
|
||||||
self.buffered_instruction_with_timeout(time_limit)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl MouseInterpolator{
|
|
||||||
pub fn new()->MouseInterpolator{
|
|
||||||
MouseInterpolator{
|
|
||||||
buffer_state:BufferState::Unbuffered,
|
|
||||||
buffer:std::collections::VecDeque::new(),
|
|
||||||
output:std::collections::VecDeque::new(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
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(),
|
|
||||||
});
|
|
||||||
// flush buffer to output
|
|
||||||
if self.output.len()==0{
|
|
||||||
// swap buffers
|
|
||||||
core::mem::swap(&mut self.buffer,&mut self.output);
|
|
||||||
}else{
|
|
||||||
// append buffer contents to output
|
|
||||||
self.output.append(&mut self.buffer);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fn get_mouse_timedout_at(&self,time_limit:SessionTime)->Option<SessionTime>{
|
|
||||||
match &self.buffer_state{
|
|
||||||
BufferState::Unbuffered=>None,
|
|
||||||
BufferState::Initializing(time,_mouse_state)
|
|
||||||
|BufferState::Buffered(time,_mouse_state)=>{
|
|
||||||
let timeout=*time+MOUSE_TIMEOUT;
|
|
||||||
(timeout<time_limit).then_some(timeout)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fn timeout_mouse(&mut self,timeout_time:PhysicsTime){
|
|
||||||
// the state always changes to unbuffered
|
|
||||||
let buffer_state=core::mem::replace(&mut self.buffer_state,BufferState::Unbuffered);
|
|
||||||
match buffer_state{
|
|
||||||
BufferState::Unbuffered=>(),
|
|
||||||
BufferState::Initializing(_time,mouse_state)=>{
|
|
||||||
// only a single mouse move was sent in 10ms, this is very much an edge case!
|
|
||||||
self.push_mouse_and_flush_buffer(TimedInstruction{
|
|
||||||
time:mouse_state.time,
|
|
||||||
instruction:MouseInstruction::ReplaceMouse{
|
|
||||||
m1:MouseState{pos:mouse_state.pos,time:timeout_time},
|
|
||||||
m0:mouse_state,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
BufferState::Buffered(_time,mouse_state)=>{
|
|
||||||
// duplicate the currently buffered mouse state but at a later (future, from the physics perspective) time
|
|
||||||
self.push_mouse_and_flush_buffer(TimedInstruction{
|
|
||||||
time:mouse_state.time,
|
|
||||||
instruction:MouseInstruction::SetNextMouse(MouseState{pos:mouse_state.pos,time:timeout_time}),
|
|
||||||
});
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fn push_unbuffered_input(&mut self,session_time:SessionTime,physics_time:PhysicsTime,ins:UnbufferedInstruction){
|
|
||||||
// new input
|
|
||||||
// if there is zero instruction buffered, it means the mouse is not moving
|
|
||||||
// case 1: unbuffered
|
|
||||||
// no mouse event is buffered
|
|
||||||
// - ins is mouse event? change to buffered
|
|
||||||
// - ins other -> write to timeline
|
|
||||||
// case 2: buffered
|
|
||||||
// a mouse event is buffered, and exists within the last 10ms
|
|
||||||
// case 3: stop
|
|
||||||
// a mouse event is buffered, but no mouse events have transpired within 10ms
|
|
||||||
|
|
||||||
// replace_with allows the enum variant to safely be replaced
|
|
||||||
// from behind a mutable reference, but a panic in the closure means that
|
|
||||||
// the entire program terminates rather than completing an unwind.
|
|
||||||
let (ins_mouse,ins_other)=replace_with::replace_with_or_abort_and_return(&mut self.buffer_state,|buffer_state|{
|
|
||||||
match ins{
|
|
||||||
UnbufferedInstruction::MoveMouse(pos)=>{
|
|
||||||
let next_mouse_state=MouseState{pos,time:physics_time};
|
|
||||||
match buffer_state{
|
|
||||||
BufferState::Unbuffered=>{
|
|
||||||
((None,None),BufferState::Initializing(session_time,next_mouse_state))
|
|
||||||
},
|
|
||||||
BufferState::Initializing(_time,mouse_state)=>{
|
|
||||||
let ins_mouse=TimedInstruction{
|
|
||||||
time:mouse_state.time,
|
|
||||||
instruction:MouseInstruction::ReplaceMouse{
|
|
||||||
m0:mouse_state,
|
|
||||||
m1:next_mouse_state.clone(),
|
|
||||||
},
|
|
||||||
};
|
|
||||||
((Some(ins_mouse),None),BufferState::Buffered(session_time,next_mouse_state))
|
|
||||||
},
|
|
||||||
BufferState::Buffered(_time,mouse_state)=>{
|
|
||||||
let ins_mouse=TimedInstruction{
|
|
||||||
time:mouse_state.time,
|
|
||||||
instruction:MouseInstruction::SetNextMouse(next_mouse_state.clone()),
|
|
||||||
};
|
|
||||||
((Some(ins_mouse),None),BufferState::Buffered(session_time,next_mouse_state))
|
|
||||||
},
|
|
||||||
}
|
|
||||||
},
|
|
||||||
UnbufferedInstruction::NonMouse(other_instruction)=>((None,Some(TimedInstruction{
|
|
||||||
time:physics_time,
|
|
||||||
instruction:other_instruction,
|
|
||||||
})),buffer_state),
|
|
||||||
}
|
|
||||||
});
|
|
||||||
if let Some(ins)=ins_mouse{
|
|
||||||
self.push_mouse_and_flush_buffer(ins);
|
|
||||||
}
|
|
||||||
if let Some(ins)=ins_other{
|
|
||||||
let instruction=TimedInstruction{
|
|
||||||
time:ins.time,
|
|
||||||
instruction:BufferedInstruction::NonMouse(ins.instruction).into(),
|
|
||||||
};
|
|
||||||
if matches!(self.buffer_state,BufferState::Unbuffered){
|
|
||||||
self.output.push_back(instruction);
|
|
||||||
}else{
|
|
||||||
self.buffer.push_back(instruction);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
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,
|
|
||||||
instruction:StepInstruction::Timeout,
|
|
||||||
}),
|
|
||||||
None=>(self.output.len()!=0).then_some(TimedInstruction{
|
|
||||||
// this timestamp should not matter
|
|
||||||
time:time_limit,
|
|
||||||
instruction:StepInstruction::Pop,
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
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),
|
|
||||||
}
|
|
||||||
self.output.pop_front()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod test{
|
|
||||||
use super::*;
|
|
||||||
#[test]
|
|
||||||
fn test(){
|
|
||||||
let mut interpolator=MouseInterpolator::new();
|
|
||||||
|
|
||||||
let timer=strafesnet_common::timer::Timer::<strafesnet_common::timer::Scaled<SessionTimeInner,PhysicsTimeInner>>::unpaused(SessionTime::ZERO,PhysicsTime::from_secs(1000));
|
|
||||||
|
|
||||||
macro_rules! push{
|
|
||||||
($time:expr,$ins:expr)=>{
|
|
||||||
println!("in={:?}",$ins);
|
|
||||||
interpolator.push_unbuffered_input(
|
|
||||||
$time,
|
|
||||||
timer.time($time),
|
|
||||||
$ins,
|
|
||||||
);
|
|
||||||
while let Some(ins)=interpolator.buffered_instruction_with_timeout($time){
|
|
||||||
let ins_retimed=TimedInstruction{
|
|
||||||
time:timer.time(ins.time),
|
|
||||||
instruction:ins.instruction,
|
|
||||||
};
|
|
||||||
let out=interpolator.pop_buffered_instruction(ins_retimed);
|
|
||||||
println!("out={out:?}");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// test each buffer_state transition
|
|
||||||
let mut t=SessionTime::ZERO;
|
|
||||||
push!(t,UnbufferedInstruction::MoveMouse(glam::ivec2(0,0)));
|
|
||||||
t+=SessionTime::from_millis(5);
|
|
||||||
push!(t,UnbufferedInstruction::MoveMouse(glam::ivec2(0,0)));
|
|
||||||
t+=SessionTime::from_millis(5);
|
|
||||||
push!(t,UnbufferedInstruction::MoveMouse(glam::ivec2(0,0)));
|
|
||||||
t+=SessionTime::from_millis(1);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,443 +0,0 @@
|
|||||||
use std::collections::HashMap;
|
|
||||||
|
|
||||||
use strafesnet_common::gameplay_modes::{ModeId,StageId};
|
|
||||||
use strafesnet_common::instruction::{InstructionConsumer,InstructionEmitter,InstructionFeedback,TimedInstruction};
|
|
||||||
// session represents the non-hardware state of the client.
|
|
||||||
// Ideally it is a deterministic state which is atomically updated by instructions, same as the simulation state.
|
|
||||||
use strafesnet_common::physics::{
|
|
||||||
ModeInstruction,MiscInstruction,
|
|
||||||
Instruction as PhysicsInputInstruction,
|
|
||||||
TimeInner as PhysicsTimeInner,
|
|
||||||
Time as PhysicsTime
|
|
||||||
};
|
|
||||||
use strafesnet_common::timer::{Scaled,Timer};
|
|
||||||
use strafesnet_common::session::{TimeInner as SessionTimeInner,Time as SessionTime};
|
|
||||||
use strafesnet_settings::directories::Directories;
|
|
||||||
|
|
||||||
use crate::mouse_interpolator::{MouseInterpolator,StepInstruction,Instruction as MouseInterpolatorInstruction};
|
|
||||||
use strafesnet_physics::physics::{self,PhysicsContext,PhysicsData};
|
|
||||||
use strafesnet_settings::settings::UserSettings;
|
|
||||||
|
|
||||||
pub enum Instruction<'a>{
|
|
||||||
Input(SessionInputInstruction),
|
|
||||||
Control(SessionControlInstruction),
|
|
||||||
Playback(SessionPlaybackInstruction),
|
|
||||||
ChangeMap(&'a strafesnet_common::map::CompleteMap),
|
|
||||||
LoadReplay(strafesnet_snf::bot::Segment),
|
|
||||||
Idle,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub enum SessionInputInstruction{
|
|
||||||
Mouse(glam::IVec2),
|
|
||||||
SetControl(strafesnet_common::physics::SetControlInstruction),
|
|
||||||
Mode(ImplicitModeInstruction),
|
|
||||||
Misc(strafesnet_common::physics::MiscInstruction),
|
|
||||||
}
|
|
||||||
/// Implicit mode instruction are fed separately to session.
|
|
||||||
/// Session generates the explicit mode instructions interlaced with a SetSensitivity instruction
|
|
||||||
#[derive(Clone,Debug)]
|
|
||||||
pub enum ImplicitModeInstruction{
|
|
||||||
ResetAndRestart,
|
|
||||||
ResetAndSpawn(ModeId,StageId),
|
|
||||||
}
|
|
||||||
|
|
||||||
pub enum SessionControlInstruction{
|
|
||||||
SetPaused(bool),
|
|
||||||
// copy the current session simulation recording into a replay and view it
|
|
||||||
CopyRecordingIntoReplayAndSpectate,
|
|
||||||
StopSpectate,
|
|
||||||
SaveReplay,
|
|
||||||
LoadIntoReplayState,
|
|
||||||
}
|
|
||||||
pub enum SessionPlaybackInstruction{
|
|
||||||
SkipForward,
|
|
||||||
SkipBack,
|
|
||||||
TogglePaused,
|
|
||||||
DecreaseTimescale,
|
|
||||||
IncreaseTimescale,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct FrameState{
|
|
||||||
pub body:physics::Body,
|
|
||||||
pub camera:physics::PhysicsCamera,
|
|
||||||
pub time:PhysicsTime,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct Simulation{
|
|
||||||
timer:Timer<Scaled<SessionTimeInner,PhysicsTimeInner>>,
|
|
||||||
physics:physics::PhysicsState,
|
|
||||||
}
|
|
||||||
impl Simulation{
|
|
||||||
pub const fn new(
|
|
||||||
timer:Timer<Scaled<SessionTimeInner,PhysicsTimeInner>>,
|
|
||||||
physics:physics::PhysicsState,
|
|
||||||
)->Self{
|
|
||||||
Self{
|
|
||||||
timer,
|
|
||||||
physics,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub fn get_frame_state(&self,time:SessionTime)->FrameState{
|
|
||||||
FrameState{
|
|
||||||
body:self.physics.camera_body(),
|
|
||||||
camera:self.physics.camera(),
|
|
||||||
time:self.timer.time(time),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Default)]
|
|
||||||
pub struct Recording{
|
|
||||||
instructions:Vec<TimedInstruction<PhysicsInputInstruction,PhysicsTimeInner>>,
|
|
||||||
}
|
|
||||||
impl Recording{
|
|
||||||
pub fn new(
|
|
||||||
instructions:Vec<TimedInstruction<PhysicsInputInstruction,PhysicsTimeInner>>,
|
|
||||||
)->Self{
|
|
||||||
Self{instructions}
|
|
||||||
}
|
|
||||||
fn clear(&mut self){
|
|
||||||
self.instructions.clear();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub struct Replay{
|
|
||||||
next_instruction_id:usize,
|
|
||||||
recording:Recording,
|
|
||||||
simulation:Simulation,
|
|
||||||
}
|
|
||||||
impl Replay{
|
|
||||||
pub const fn new(
|
|
||||||
recording:Recording,
|
|
||||||
simulation:Simulation,
|
|
||||||
)->Self{
|
|
||||||
Self{
|
|
||||||
next_instruction_id:0,
|
|
||||||
recording,
|
|
||||||
simulation,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub fn advance(&mut self,physics_data:&PhysicsData,time_limit:SessionTime){
|
|
||||||
let mut time=self.simulation.timer.time(time_limit);
|
|
||||||
loop{
|
|
||||||
if let Some(ins)=self.recording.instructions.get(self.next_instruction_id){
|
|
||||||
if ins.time<time{
|
|
||||||
PhysicsContext::run_input_instruction(&mut self.simulation.physics,physics_data,ins.clone());
|
|
||||||
self.next_instruction_id+=1;
|
|
||||||
}else{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}else{
|
|
||||||
// loop playback
|
|
||||||
self.next_instruction_id=0;
|
|
||||||
// No need to reset physics because the very first instruction is 'Reset'
|
|
||||||
let new_time=self.recording.instructions.first().map_or(PhysicsTime::ZERO,|ins|ins.time);
|
|
||||||
self.simulation.timer.set_time(time_limit,new_time);
|
|
||||||
time=new_time;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone,Copy,Hash,PartialEq,Eq)]
|
|
||||||
struct BotId(u32);
|
|
||||||
//#[derive(Clone,Copy,Hash,PartialEq,Eq)]
|
|
||||||
//struct PlayerId(u32);
|
|
||||||
|
|
||||||
enum ViewState{
|
|
||||||
Play,
|
|
||||||
//Spectate(PlayerId),
|
|
||||||
Replay(BotId),
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct Session{
|
|
||||||
directories:Directories,
|
|
||||||
user_settings:UserSettings,
|
|
||||||
mouse_interpolator:crate::mouse_interpolator::MouseInterpolator,
|
|
||||||
view_state:ViewState,
|
|
||||||
//gui:GuiState
|
|
||||||
geometry_shared:physics::PhysicsData,
|
|
||||||
simulation:Simulation,
|
|
||||||
// below fields not included in lite session
|
|
||||||
recording:Recording,
|
|
||||||
//players:HashMap<PlayerId,Simulation>,
|
|
||||||
replays:HashMap<BotId,Replay>,
|
|
||||||
}
|
|
||||||
impl Session{
|
|
||||||
pub fn new(
|
|
||||||
user_settings:UserSettings,
|
|
||||||
directories:Directories,
|
|
||||||
simulation:Simulation,
|
|
||||||
)->Self{
|
|
||||||
Self{
|
|
||||||
user_settings,
|
|
||||||
directories,
|
|
||||||
mouse_interpolator:MouseInterpolator::new(),
|
|
||||||
geometry_shared:Default::default(),
|
|
||||||
simulation,
|
|
||||||
view_state:ViewState::Play,
|
|
||||||
recording:Default::default(),
|
|
||||||
replays:HashMap::new(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fn clear_recording(&mut self){
|
|
||||||
self.recording.clear();
|
|
||||||
}
|
|
||||||
fn change_map(&mut self,map:&strafesnet_common::map::CompleteMap){
|
|
||||||
self.simulation.physics.clear();
|
|
||||||
self.geometry_shared.generate_models(map);
|
|
||||||
}
|
|
||||||
pub fn get_frame_state(&self,time:SessionTime)->Option<FrameState>{
|
|
||||||
match &self.view_state{
|
|
||||||
ViewState::Play=>Some(self.simulation.get_frame_state(time)),
|
|
||||||
ViewState::Replay(bot_id)=>self.replays.get(bot_id).map(|replay|
|
|
||||||
replay.simulation.get_frame_state(time)
|
|
||||||
),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub fn user_settings(&self)->&UserSettings{
|
|
||||||
&self.user_settings
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// mouseinterpolator consumes RawInputInstruction
|
|
||||||
// mouseinterpolator emits PhysicsInputInstruction
|
|
||||||
// mouseinterpolator consumes DoStep to move on to the next emitted instruction
|
|
||||||
// Session comsumes SessionInstruction -> forwards RawInputInstruction to mouseinterpolator
|
|
||||||
// Session consumes DoStep -> forwards DoStep to mouseinterpolator
|
|
||||||
// Session emits DoStep
|
|
||||||
|
|
||||||
impl InstructionConsumer<Instruction<'_>> for Session{
|
|
||||||
type TimeInner=SessionTimeInner;
|
|
||||||
fn process_instruction(&mut self,ins:TimedInstruction<Instruction,Self::TimeInner>){
|
|
||||||
// repetitive procedure macro
|
|
||||||
macro_rules! run_mouse_interpolator_instruction{
|
|
||||||
($instruction:expr)=>{
|
|
||||||
self.mouse_interpolator.process_instruction(TimedInstruction{
|
|
||||||
time:ins.time,
|
|
||||||
instruction:TimedInstruction{
|
|
||||||
time:self.simulation.timer.time(ins.time),
|
|
||||||
instruction:$instruction,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// process any timeouts that occured since the last instruction
|
|
||||||
self.process_exhaustive(ins.time);
|
|
||||||
|
|
||||||
match ins.instruction{
|
|
||||||
// send it down to MouseInterpolator with two timestamps, SessionTime and PhysicsTime
|
|
||||||
Instruction::Input(SessionInputInstruction::Mouse(pos))=>{
|
|
||||||
run_mouse_interpolator_instruction!(MouseInterpolatorInstruction::MoveMouse(pos));
|
|
||||||
},
|
|
||||||
Instruction::Input(SessionInputInstruction::SetControl(set_control_instruction))=>{
|
|
||||||
run_mouse_interpolator_instruction!(MouseInterpolatorInstruction::SetControl(set_control_instruction));
|
|
||||||
},
|
|
||||||
Instruction::Input(SessionInputInstruction::Mode(ImplicitModeInstruction::ResetAndRestart))=>{
|
|
||||||
self.clear_recording();
|
|
||||||
let mode_id=self.simulation.physics.mode();
|
|
||||||
run_mouse_interpolator_instruction!(MouseInterpolatorInstruction::Mode(ModeInstruction::Reset));
|
|
||||||
run_mouse_interpolator_instruction!(MouseInterpolatorInstruction::Misc(MiscInstruction::SetSensitivity(self.user_settings().calculate_sensitivity())));
|
|
||||||
run_mouse_interpolator_instruction!(MouseInterpolatorInstruction::Mode(ModeInstruction::Restart(mode_id)));
|
|
||||||
},
|
|
||||||
Instruction::Input(SessionInputInstruction::Mode(ImplicitModeInstruction::ResetAndSpawn(mode_id,spawn_id)))=>{
|
|
||||||
self.clear_recording();
|
|
||||||
run_mouse_interpolator_instruction!(MouseInterpolatorInstruction::Mode(ModeInstruction::Reset));
|
|
||||||
run_mouse_interpolator_instruction!(MouseInterpolatorInstruction::Misc(MiscInstruction::SetSensitivity(self.user_settings().calculate_sensitivity())));
|
|
||||||
run_mouse_interpolator_instruction!(MouseInterpolatorInstruction::Mode(ModeInstruction::Spawn(mode_id,spawn_id)));
|
|
||||||
},
|
|
||||||
Instruction::Input(SessionInputInstruction::Misc(misc_instruction))=>{
|
|
||||||
run_mouse_interpolator_instruction!(MouseInterpolatorInstruction::Misc(misc_instruction));
|
|
||||||
},
|
|
||||||
Instruction::Control(SessionControlInstruction::SetPaused(paused))=>{
|
|
||||||
// don't flush the buffered instructions in the mouse interpolator
|
|
||||||
// until the mouse is confirmed to be not moving at a later time
|
|
||||||
// what if they pause for 5ms lmao
|
|
||||||
_=self.simulation.timer.set_paused(ins.time,paused);
|
|
||||||
},
|
|
||||||
Instruction::Control(SessionControlInstruction::CopyRecordingIntoReplayAndSpectate)=> if let ViewState::Play=self.view_state{
|
|
||||||
// Bind: B
|
|
||||||
|
|
||||||
// pause simulation
|
|
||||||
_=self.simulation.timer.set_paused(ins.time,true);
|
|
||||||
|
|
||||||
// create recording
|
|
||||||
let mut recording=Recording::default();
|
|
||||||
recording.instructions.extend(self.recording.instructions.iter().cloned());
|
|
||||||
|
|
||||||
// create timer starting at first instruction (or zero if the list is empty)
|
|
||||||
let new_time=recording.instructions.first().map_or(PhysicsTime::ZERO,|ins|ins.time);
|
|
||||||
let timer=Timer::unpaused(ins.time,new_time);
|
|
||||||
|
|
||||||
// create default physics state
|
|
||||||
let simulation=Simulation::new(timer,Default::default());
|
|
||||||
|
|
||||||
// invent a new bot id and insert the replay
|
|
||||||
let bot_id=BotId(self.replays.len() as u32);
|
|
||||||
self.replays.insert(bot_id,Replay::new(
|
|
||||||
recording,
|
|
||||||
simulation,
|
|
||||||
));
|
|
||||||
|
|
||||||
// begin spectate
|
|
||||||
self.view_state=ViewState::Replay(bot_id);
|
|
||||||
},
|
|
||||||
Instruction::Control(SessionControlInstruction::StopSpectate)=>{
|
|
||||||
let view_state=core::mem::replace(&mut self.view_state,ViewState::Play);
|
|
||||||
// delete the bot, otherwise it's inaccessible and wastes CPU
|
|
||||||
match view_state{
|
|
||||||
ViewState::Play=>(),
|
|
||||||
ViewState::Replay(bot_id)=>{
|
|
||||||
self.replays.remove(&bot_id);
|
|
||||||
},
|
|
||||||
}
|
|
||||||
_=self.simulation.timer.set_paused(ins.time,false);
|
|
||||||
},
|
|
||||||
Instruction::Control(SessionControlInstruction::SaveReplay)=>{
|
|
||||||
// Bind: N
|
|
||||||
let view_state=core::mem::replace(&mut self.view_state,ViewState::Play);
|
|
||||||
match view_state{
|
|
||||||
ViewState::Play=>(),
|
|
||||||
ViewState::Replay(bot_id)=>if let Some(replay)=self.replays.remove(&bot_id){
|
|
||||||
let mut replays_path=self.directories.replays.clone();
|
|
||||||
let file_name=format!("{}.snfb",ins.time);
|
|
||||||
std::thread::spawn(move ||{
|
|
||||||
std::fs::create_dir_all(replays_path.as_path()).unwrap();
|
|
||||||
replays_path.push(file_name);
|
|
||||||
let file=std::fs::File::create(replays_path).unwrap();
|
|
||||||
strafesnet_snf::bot::write_bot(
|
|
||||||
std::io::BufWriter::new(file),
|
|
||||||
strafesnet_physics::VERSION.get(),
|
|
||||||
replay.recording.instructions
|
|
||||||
).unwrap();
|
|
||||||
println!("Finished writing bot file!");
|
|
||||||
});
|
|
||||||
},
|
|
||||||
}
|
|
||||||
_=self.simulation.timer.set_paused(ins.time,false);
|
|
||||||
},
|
|
||||||
Instruction::Control(SessionControlInstruction::LoadIntoReplayState)=>{
|
|
||||||
// Bind: J
|
|
||||||
let view_state=core::mem::replace(&mut self.view_state,ViewState::Play);
|
|
||||||
match view_state{
|
|
||||||
ViewState::Play=>(),
|
|
||||||
ViewState::Replay(bot_id)=>if let Some(replay)=self.replays.remove(&bot_id){
|
|
||||||
self.recording.instructions=replay.recording.instructions.into_iter().take(replay.next_instruction_id).collect();
|
|
||||||
self.simulation=replay.simulation;
|
|
||||||
},
|
|
||||||
}
|
|
||||||
// don't unpause -- use the replay timer state whether it is pasued or unpaused
|
|
||||||
},
|
|
||||||
Instruction::Playback(SessionPlaybackInstruction::IncreaseTimescale)=>{
|
|
||||||
match &self.view_state{
|
|
||||||
ViewState::Play=>{
|
|
||||||
// allow simulation timescale for fun
|
|
||||||
let scale=self.simulation.timer.get_scale();
|
|
||||||
self.simulation.timer.set_scale(ins.time,strafesnet_common::integer::Ratio64::new(scale.num()*5,scale.den()*4).unwrap());
|
|
||||||
},
|
|
||||||
ViewState::Replay(bot_id)=>if let Some(replay)=self.replays.get_mut(bot_id){
|
|
||||||
let scale=replay.simulation.timer.get_scale();
|
|
||||||
replay.simulation.timer.set_scale(ins.time,strafesnet_common::integer::Ratio64::new(scale.num()*5,scale.den()*4).unwrap());
|
|
||||||
},
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Instruction::Playback(SessionPlaybackInstruction::DecreaseTimescale)=>{
|
|
||||||
match &self.view_state{
|
|
||||||
ViewState::Play=>{
|
|
||||||
// allow simulation timescale for fun
|
|
||||||
let scale=self.simulation.timer.get_scale();
|
|
||||||
self.simulation.timer.set_scale(ins.time,strafesnet_common::integer::Ratio64::new(scale.num()*4,scale.den()*5).unwrap());
|
|
||||||
},
|
|
||||||
ViewState::Replay(bot_id)=>if let Some(replay)=self.replays.get_mut(bot_id){
|
|
||||||
let scale=replay.simulation.timer.get_scale();
|
|
||||||
replay.simulation.timer.set_scale(ins.time,strafesnet_common::integer::Ratio64::new(scale.num()*4,scale.den()*5).unwrap());
|
|
||||||
},
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Instruction::Playback(SessionPlaybackInstruction::SkipForward)=>{
|
|
||||||
match &self.view_state{
|
|
||||||
ViewState::Play=>(),
|
|
||||||
ViewState::Replay(bot_id)=>if let Some(replay)=self.replays.get_mut(bot_id){
|
|
||||||
let time=replay.simulation.timer.time(ins.time+SessionTime::from_secs(5));
|
|
||||||
replay.simulation.timer.set_time(ins.time,time);
|
|
||||||
},
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Instruction::Playback(SessionPlaybackInstruction::SkipBack)=>{
|
|
||||||
match &self.view_state{
|
|
||||||
ViewState::Play=>(),
|
|
||||||
ViewState::Replay(bot_id)=>if let Some(replay)=self.replays.get_mut(bot_id){
|
|
||||||
let time=replay.simulation.timer.time(ins.time+SessionTime::from_secs(5));
|
|
||||||
replay.simulation.timer.set_time(ins.time,time);
|
|
||||||
// resimulate the entire playback lol
|
|
||||||
replay.next_instruction_id=0;
|
|
||||||
},
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Instruction::Playback(SessionPlaybackInstruction::TogglePaused)=>{
|
|
||||||
match &self.view_state{
|
|
||||||
ViewState::Play=>(),
|
|
||||||
ViewState::Replay(bot_id)=>if let Some(replay)=self.replays.get_mut(bot_id){
|
|
||||||
_=replay.simulation.timer.set_paused(ins.time,!replay.simulation.timer.is_paused());
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Instruction::ChangeMap(complete_map)=>{
|
|
||||||
self.clear_recording();
|
|
||||||
self.change_map(complete_map);
|
|
||||||
},
|
|
||||||
Instruction::LoadReplay(bot)=>{
|
|
||||||
// pause simulation
|
|
||||||
_=self.simulation.timer.set_paused(ins.time,true);
|
|
||||||
|
|
||||||
// create recording
|
|
||||||
let recording=Recording::new(bot.instructions);
|
|
||||||
|
|
||||||
// create timer starting at first instruction (or zero if the list is empty)
|
|
||||||
let new_time=recording.instructions.first().map_or(PhysicsTime::ZERO,|ins|ins.time);
|
|
||||||
let timer=Timer::unpaused(ins.time,new_time);
|
|
||||||
|
|
||||||
// create default physics state
|
|
||||||
let simulation=Simulation::new(timer,Default::default());
|
|
||||||
|
|
||||||
// invent a new bot id and insert the replay
|
|
||||||
let bot_id=BotId(self.replays.len() as u32);
|
|
||||||
self.replays.insert(bot_id,Replay::new(
|
|
||||||
recording,
|
|
||||||
simulation,
|
|
||||||
));
|
|
||||||
|
|
||||||
// begin spectate
|
|
||||||
self.view_state=ViewState::Replay(bot_id);
|
|
||||||
},
|
|
||||||
Instruction::Idle=>{
|
|
||||||
run_mouse_interpolator_instruction!(MouseInterpolatorInstruction::Idle);
|
|
||||||
// this just refreshes the replays
|
|
||||||
for replay in self.replays.values_mut(){
|
|
||||||
// TODO: filter idles from recording, inject new idles in real time
|
|
||||||
replay.advance(&self.geometry_shared,ins.time);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// process all emitted output instructions
|
|
||||||
self.process_exhaustive(ins.time);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl InstructionConsumer<StepInstruction> for Session{
|
|
||||||
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
|
|
||||||
self.recording.instructions.push(instruction.clone());
|
|
||||||
PhysicsContext::run_input_instruction(&mut self.simulation.physics,&self.geometry_shared,instruction);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl InstructionEmitter<StepInstruction> for Session{
|
|
||||||
type TimeInner=SessionTimeInner;
|
|
||||||
fn next_instruction(&self,time_limit:SessionTime)->Option<TimedInstruction<StepInstruction,Self::TimeInner>>{
|
|
||||||
self.mouse_interpolator.next_instruction(time_limit)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,10 +0,0 @@
|
|||||||
[package]
|
|
||||||
name = "strafesnet_settings"
|
|
||||||
version = "0.1.0"
|
|
||||||
edition = "2021"
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
configparser = "3.0.2"
|
|
||||||
directories = "6.0.0"
|
|
||||||
glam = "0.29.0"
|
|
||||||
strafesnet_common = { path = "../../lib/common", registry = "strafesnet" }
|
|
@ -1,8 +0,0 @@
|
|||||||
/*******************************************************
|
|
||||||
* Copyright (C) 2023-2024 Rhys Lloyd <krakow20@gmail.com>
|
|
||||||
*
|
|
||||||
* This file is part of the StrafesNET bhop/surf client.
|
|
||||||
*
|
|
||||||
* StrafesNET can not be copied and/or distributed
|
|
||||||
* without the express permission of Rhys Lloyd
|
|
||||||
*******************************************************/
|
|
@ -1,32 +0,0 @@
|
|||||||
use std::path::PathBuf;
|
|
||||||
|
|
||||||
use crate::settings::{UserSettings,load_user_settings};
|
|
||||||
|
|
||||||
pub struct Directories{
|
|
||||||
pub settings:PathBuf,
|
|
||||||
pub maps:PathBuf,
|
|
||||||
pub replays:PathBuf,
|
|
||||||
}
|
|
||||||
impl Directories{
|
|
||||||
pub fn settings(&self)->UserSettings{
|
|
||||||
load_user_settings(&self.settings)
|
|
||||||
}
|
|
||||||
pub fn user()->Option<Self>{
|
|
||||||
let dirs=directories::ProjectDirs::from("net.strafes","StrafesNET","Strafe Client")?;
|
|
||||||
Some(Self{
|
|
||||||
settings:dirs.config_dir().join("settings.conf"),
|
|
||||||
maps:dirs.cache_dir().join("maps"),
|
|
||||||
// separate directory for remote downloaded replays (cache)
|
|
||||||
// bots:dirs.cache_dir().join("bots"),
|
|
||||||
replays:dirs.data_local_dir().join("replays"),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
pub fn portable()->Result<Self,std::io::Error>{
|
|
||||||
let current_dir=std::env::current_dir()?;
|
|
||||||
Ok(Self{
|
|
||||||
settings:current_dir.join("settings.conf"),
|
|
||||||
maps:current_dir.join("maps"),
|
|
||||||
replays:current_dir.join("replays"),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,2 +0,0 @@
|
|||||||
pub mod settings;
|
|
||||||
pub mod directories;
|
|
@ -1,9 +0,0 @@
|
|||||||
[package]
|
|
||||||
name = "integration-testing"
|
|
||||||
version = "0.1.0"
|
|
||||||
edition = "2021"
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
strafesnet_common = { path = "../lib/common", registry = "strafesnet" }
|
|
||||||
strafesnet_physics = { path = "../engine/physics", registry = "strafesnet" }
|
|
||||||
strafesnet_snf = { path = "../lib/snf", registry = "strafesnet" }
|
|
@ -1,221 +0,0 @@
|
|||||||
|
|
||||||
use std::{io::{Cursor,Read},path::Path};
|
|
||||||
|
|
||||||
use strafesnet_physics::physics::{PhysicsData,PhysicsState,PhysicsContext};
|
|
||||||
|
|
||||||
fn main(){
|
|
||||||
test_determinism().unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(unused)]
|
|
||||||
#[derive(Debug)]
|
|
||||||
enum ReplayError{
|
|
||||||
IO(std::io::Error),
|
|
||||||
SNF(strafesnet_snf::Error),
|
|
||||||
SNFM(strafesnet_snf::map::Error),
|
|
||||||
SNFB(strafesnet_snf::bot::Error),
|
|
||||||
}
|
|
||||||
impl From<std::io::Error> for ReplayError{
|
|
||||||
fn from(value:std::io::Error)->Self{
|
|
||||||
Self::IO(value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl From<strafesnet_snf::Error> for ReplayError{
|
|
||||||
fn from(value:strafesnet_snf::Error)->Self{
|
|
||||||
Self::SNF(value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl From<strafesnet_snf::map::Error> for ReplayError{
|
|
||||||
fn from(value:strafesnet_snf::map::Error)->Self{
|
|
||||||
Self::SNFM(value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl From<strafesnet_snf::bot::Error> for ReplayError{
|
|
||||||
fn from(value:strafesnet_snf::bot::Error)->Self{
|
|
||||||
Self::SNFB(value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn read_entire_file(path:impl AsRef<Path>)->Result<Cursor<Vec<u8>>,std::io::Error>{
|
|
||||||
let mut file=std::fs::File::open(path)?;
|
|
||||||
let mut data=Vec::new();
|
|
||||||
file.read_to_end(&mut data)?;
|
|
||||||
Ok(Cursor::new(data))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn run_replay()->Result<(),ReplayError>{
|
|
||||||
println!("loading map file..");
|
|
||||||
let data=read_entire_file("../tools/bhop_maps/5692113331.snfm")?;
|
|
||||||
let map=strafesnet_snf::read_map(data)?.into_complete_map()?;
|
|
||||||
|
|
||||||
println!("loading bot file..");
|
|
||||||
let data=read_entire_file("../tools/replays/535s+159764769ns.snfb")?;
|
|
||||||
let bot=strafesnet_snf::read_bot(data)?.read_all()?;
|
|
||||||
|
|
||||||
// create recording
|
|
||||||
let mut physics_data=PhysicsData::default();
|
|
||||||
println!("generating models..");
|
|
||||||
physics_data.generate_models(&map);
|
|
||||||
println!("simulating...");
|
|
||||||
let mut physics=PhysicsState::default();
|
|
||||||
for ins in bot.instructions{
|
|
||||||
PhysicsContext::run_input_instruction(&mut physics,&physics_data,ins);
|
|
||||||
}
|
|
||||||
match physics.get_finish_time(){
|
|
||||||
Some(time)=>println!("finish time:{}",time),
|
|
||||||
None=>println!("simulation did not end in finished state"),
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
enum DeterminismResult{
|
|
||||||
Deterministic,
|
|
||||||
NonDeterministic,
|
|
||||||
}
|
|
||||||
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
|
|
||||||
let mut physics_filtered=PhysicsState::default();
|
|
||||||
|
|
||||||
// invent a new bot id and insert the replay
|
|
||||||
println!("simulating...");
|
|
||||||
|
|
||||||
let mut non_idle_count=0;
|
|
||||||
|
|
||||||
for (i,ins) in bot.instructions.into_iter().enumerate(){
|
|
||||||
let state_deterministic=physics_deterministic.clone();
|
|
||||||
let state_filtered=physics_filtered.clone();
|
|
||||||
PhysicsContext::run_input_instruction(&mut physics_deterministic,&physics_data,ins.clone());
|
|
||||||
match ins{
|
|
||||||
strafesnet_common::instruction::TimedInstruction{instruction:strafesnet_common::physics::Instruction::Idle,..}=>(),
|
|
||||||
other=>{
|
|
||||||
non_idle_count+=1;
|
|
||||||
// run
|
|
||||||
PhysicsContext::run_input_instruction(&mut physics_filtered,&physics_data,other.clone());
|
|
||||||
// check if position matches
|
|
||||||
let b0=physics_deterministic.camera_body();
|
|
||||||
let b1=physics_filtered.camera_body();
|
|
||||||
if b0.position!=b1.position{
|
|
||||||
println!("desync at instruction #{}",i);
|
|
||||||
println!("non idle instructions completed={non_idle_count}");
|
|
||||||
println!("instruction #{i}={:?}",other);
|
|
||||||
println!("deterministic state0:\n{state_deterministic:?}");
|
|
||||||
println!("filtered state0:\n{state_filtered:?}");
|
|
||||||
println!("deterministic state1:\n{:?}",physics_deterministic);
|
|
||||||
println!("filtered state1:\n{:?}",physics_filtered);
|
|
||||||
return DeterminismResult::NonDeterministic;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
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"),
|
|
||||||
}
|
|
||||||
match physics_filtered.get_finish_time(){
|
|
||||||
Some(time)=>println!("[filtered] finish time:{}",time),
|
|
||||||
None=>println!("[filtered] simulation did not end in finished state"),
|
|
||||||
}
|
|
||||||
DeterminismResult::Deterministic
|
|
||||||
}
|
|
||||||
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()?;
|
|
||||||
println!("Running {:?}",file_path.file_stem());
|
|
||||||
Ok(Some(segment_determinism(bot,physics_data)))
|
|
||||||
}
|
|
||||||
fn do_thread<'a>(s:&'a std::thread::Scope<'a,'_>,file_path:std::path::PathBuf,send:std::sync::mpsc::Sender<ThreadResult>,physics_data:&'a PhysicsData){
|
|
||||||
s.spawn(move ||{
|
|
||||||
let result=read_and_run(file_path,physics_data);
|
|
||||||
// send when thread is complete
|
|
||||||
send.send(result).unwrap();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
fn get_file_path(dir_entry:std::fs::DirEntry)->Result<Option<std::path::PathBuf>,std::io::Error>{
|
|
||||||
Ok(dir_entry.file_type()?.is_file().then_some(
|
|
||||||
dir_entry.path()
|
|
||||||
))
|
|
||||||
}
|
|
||||||
fn test_determinism()->Result<(),ReplayError>{
|
|
||||||
let thread_limit=std::thread::available_parallelism()?.get();
|
|
||||||
println!("loading map file..");
|
|
||||||
let data=read_entire_file("../tools/bhop_maps/5692113331.snfm")?;
|
|
||||||
let map=strafesnet_snf::read_map(data)?.into_complete_map()?;
|
|
||||||
|
|
||||||
let mut physics_data=PhysicsData::default();
|
|
||||||
println!("generating models..");
|
|
||||||
physics_data.generate_models(&map);
|
|
||||||
|
|
||||||
let (send,recv)=std::sync::mpsc::channel();
|
|
||||||
|
|
||||||
let mut read_dir=std::fs::read_dir("../tools/replays")?;
|
|
||||||
|
|
||||||
// promise that &physics_data will outlive the spawned threads
|
|
||||||
let thread_results=std::thread::scope(|s|{
|
|
||||||
let mut thread_results=Vec::new();
|
|
||||||
|
|
||||||
// spawn threads
|
|
||||||
println!("spawning up to {thread_limit} threads...");
|
|
||||||
let mut active_thread_count=0;
|
|
||||||
while active_thread_count<thread_limit{
|
|
||||||
if let Some(dir_entry_result)=read_dir.next(){
|
|
||||||
if let Some(file_path)=get_file_path(dir_entry_result?)?{
|
|
||||||
active_thread_count+=1;
|
|
||||||
do_thread(s,file_path,send.clone(),&physics_data);
|
|
||||||
}
|
|
||||||
}else{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// spawn another thread every time a message is received from the channel
|
|
||||||
println!("riding parallelism wave...");
|
|
||||||
while let Some(dir_entry_result)=read_dir.next(){
|
|
||||||
if let Some(file_path)=get_file_path(dir_entry_result?)?{
|
|
||||||
// wait for a thread to complete
|
|
||||||
thread_results.push(recv.recv().unwrap());
|
|
||||||
do_thread(s,file_path,send.clone(),&physics_data);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// wait for remaining threads to complete
|
|
||||||
println!("waiting for all threads to complete...");
|
|
||||||
for _ in 0..active_thread_count{
|
|
||||||
thread_results.push(recv.recv().unwrap());
|
|
||||||
}
|
|
||||||
|
|
||||||
println!("done.");
|
|
||||||
Ok::<_,ReplayError>(thread_results)
|
|
||||||
})?;
|
|
||||||
|
|
||||||
// tally results
|
|
||||||
#[derive(Default)]
|
|
||||||
struct Totals{
|
|
||||||
deterministic:u32,
|
|
||||||
nondeterministic:u32,
|
|
||||||
invalid:u32,
|
|
||||||
error:u32,
|
|
||||||
}
|
|
||||||
let Totals{deterministic,nondeterministic,invalid,error}=thread_results.into_iter().fold(Totals::default(),|mut totals,result|{
|
|
||||||
match result{
|
|
||||||
Ok(Some(DeterminismResult::Deterministic))=>totals.deterministic+=1,
|
|
||||||
Ok(Some(DeterminismResult::NonDeterministic))=>totals.nondeterministic+=1,
|
|
||||||
Ok(None)=>totals.invalid+=1,
|
|
||||||
Err(_)=>totals.error+=1,
|
|
||||||
}
|
|
||||||
totals
|
|
||||||
});
|
|
||||||
|
|
||||||
println!("deterministic={deterministic}");
|
|
||||||
println!("nondeterministic={nondeterministic}");
|
|
||||||
println!("invalid={invalid}");
|
|
||||||
println!("error={error}");
|
|
||||||
|
|
||||||
assert!(nondeterministic==0);
|
|
||||||
assert!(invalid==0);
|
|
||||||
assert!(error==0);
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "strafesnet_bsp_loader"
|
name = "strafesnet_bsp_loader"
|
||||||
version = "0.3.0"
|
version = "0.2.2"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
repository = "https://git.itzana.me/StrafesNET/strafe-project"
|
repository = "https://git.itzana.me/StrafesNET/strafe-project"
|
||||||
license = "MIT OR Apache-2.0"
|
license = "MIT OR Apache-2.0"
|
||||||
@ -11,8 +11,6 @@ authors = ["Rhys Lloyd <krakow20@gmail.com>"]
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
glam = "0.29.0"
|
glam = "0.29.0"
|
||||||
strafesnet_common = { version = "0.6.0", path = "../common", registry = "strafesnet" }
|
strafesnet_common = { path = "../common", registry = "strafesnet" }
|
||||||
strafesnet_deferred_loader = { version = "0.5.0", path = "../deferred_loader", registry = "strafesnet" }
|
|
||||||
vbsp = "0.6.0"
|
vbsp = "0.6.0"
|
||||||
vmdl = "0.2.0"
|
vmdl = "0.2.0"
|
||||||
vpk = "0.2.0"
|
|
||||||
|
@ -1,52 +1,32 @@
|
|||||||
use std::borrow::Cow;
|
|
||||||
|
|
||||||
use strafesnet_common::{map,model,integer,gameplay_attributes};
|
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 crate::valve_transform;
|
const VALVE_SCALE:f32=1.0/16.0;
|
||||||
|
fn valve_transform([x,y,z]:[f32;3])->integer::Planar64Vec3{
|
||||||
fn ingest_vertex(
|
integer::vec3::try_from_f32_array([x*VALVE_SCALE,z*VALVE_SCALE,-y*VALVE_SCALE]).unwrap()
|
||||||
mb:&mut model::MeshBuilder,
|
|
||||||
world_position:vbsp::Vector,
|
|
||||||
texture_transform_u:glam::Vec4,
|
|
||||||
texture_transform_v:glam::Vec4,
|
|
||||||
normal:model::NormalId,
|
|
||||||
color:model::ColorId,
|
|
||||||
)->model::VertexId{
|
|
||||||
//world_model.origin seems to always be 0,0,0
|
|
||||||
let vertex_xyz=world_position.into();
|
|
||||||
let pos=mb.acquire_pos_id(valve_transform(vertex_xyz));
|
|
||||||
|
|
||||||
//calculate texture coordinates
|
|
||||||
let pos_4d=glam::Vec3::from_array(vertex_xyz).extend(1.0);
|
|
||||||
let tex=glam::vec2(texture_transform_u.dot(pos_4d),texture_transform_v.dot(pos_4d));
|
|
||||||
let tex=mb.acquire_tex_id(tex);
|
|
||||||
|
|
||||||
mb.acquire_vertex_id(model::IndexedVertex{
|
|
||||||
pos,
|
|
||||||
tex,
|
|
||||||
normal,
|
|
||||||
color,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
pub fn convert_bsp<AcquireRenderConfigId,AcquireMeshId>(
|
||||||
pub fn convert<'a>(
|
bsp:&vbsp::Bsp,
|
||||||
bsp:&'a crate::Bsp,
|
mut acquire_render_config_id:AcquireRenderConfigId,
|
||||||
render_config_deferred_loader:&mut RenderConfigDeferredLoader<Cow<'a,str>>,
|
mut acquire_mesh_id:AcquireMeshId
|
||||||
mesh_deferred_loader:&mut MeshDeferredLoader<&'a str>,
|
)->PartialMap1
|
||||||
)->PartialMap1{
|
where
|
||||||
let bsp=bsp.as_ref();
|
AcquireRenderConfigId:FnMut(Option<&str>)->model::RenderConfigId,
|
||||||
|
AcquireMeshId:FnMut(&str)->model::MeshId,
|
||||||
|
{
|
||||||
//figure out real attributes later
|
//figure out real attributes later
|
||||||
let mut unique_attributes=Vec::new();
|
let mut unique_attributes=Vec::new();
|
||||||
unique_attributes.push(gameplay_attributes::CollisionAttributes::Decoration);
|
unique_attributes.push(gameplay_attributes::CollisionAttributes::Decoration);
|
||||||
const TEMP_TOUCH_ME_ATTRIBUTE:gameplay_attributes::CollisionAttributesId=gameplay_attributes::CollisionAttributesId::new(0);
|
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
|
//declare all prop models to Loader
|
||||||
let prop_models=bsp.static_props().map(|prop|{
|
let prop_models=bsp.static_props().map(|prop|{
|
||||||
//get or create mesh_id
|
//get or create mesh_id
|
||||||
let mesh_id=mesh_deferred_loader.acquire_mesh_id(prop.model());
|
let mesh_id=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();
|
let placement=prop.as_prop_placement();
|
||||||
model::Model{
|
model::Model{
|
||||||
mesh:mesh_id,
|
mesh:mesh_id,
|
||||||
@ -68,11 +48,13 @@ pub fn convert<'a>(
|
|||||||
//the generated MeshIds in here will collide with the Loader Mesh Ids
|
//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.
|
//but I can't think of a good workaround other than just remapping one later.
|
||||||
let 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();
|
//non-deduplicated
|
||||||
|
let mut spam_pos=Vec::new();
|
||||||
let color=mb.acquire_color_id(glam::Vec4::ONE);
|
let mut spam_tex=Vec::new();
|
||||||
|
let mut spam_normal=Vec::new();
|
||||||
|
let mut spam_vertices=Vec::new();
|
||||||
let mut graphics_groups=Vec::new();
|
let mut graphics_groups=Vec::new();
|
||||||
let mut render_id_to_graphics_group_id=std::collections::HashMap::new();
|
let mut physics_group=model::IndexedPhysicsGroup::default();
|
||||||
let polygon_groups=world_model.faces().enumerate().map(|(polygon_group_id,face)|{
|
let polygon_groups=world_model.faces().enumerate().map(|(polygon_group_id,face)|{
|
||||||
let polygon_group_id=model::PolygonGroupId::new(polygon_group_id as u32);
|
let polygon_group_id=model::PolygonGroupId::new(polygon_group_id as u32);
|
||||||
let face_texture=face.texture();
|
let face_texture=face.texture();
|
||||||
@ -81,41 +63,62 @@ 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_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);
|
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=acquire_render_config_id(Some(face_texture_data.name()));
|
||||||
|
|
||||||
//normal
|
//normal
|
||||||
let normal=mb.acquire_normal_id(valve_transform(face.normal().into()));
|
let normal=face.normal();
|
||||||
let mut polygon_iter=face.vertex_positions().map(|vertex_position|
|
let normal_idx=spam_normal.len() as u32;
|
||||||
world_model.origin+vertex_position
|
spam_normal.push(valve_transform(normal.into()));
|
||||||
);
|
let mut polygon_iter=face.vertex_positions().map(|vertex_position|{
|
||||||
|
//world_model.origin seems to always be 0,0,0
|
||||||
|
let vertex_xyz=(world_model.origin+vertex_position).into();
|
||||||
|
let pos_idx=spam_pos.len();
|
||||||
|
spam_pos.push(valve_transform(vertex_xyz));
|
||||||
|
|
||||||
|
//calculate texture coordinates
|
||||||
|
let pos=glam::Vec3::from_array(vertex_xyz).extend(1.0);
|
||||||
|
let tex=glam::vec2(texture_transform_u.dot(pos),texture_transform_v.dot(pos));
|
||||||
|
let tex_idx=spam_tex.len() as u32;
|
||||||
|
spam_tex.push(tex);
|
||||||
|
|
||||||
|
let vertex_id=model::VertexId::new(spam_vertices.len() as u32);
|
||||||
|
spam_vertices.push(model::IndexedVertex{
|
||||||
|
pos:model::PositionId::new(pos_idx as u32),
|
||||||
|
tex:model::TextureCoordinateId::new(tex_idx as u32),
|
||||||
|
normal:model::NormalId::new(normal_idx),
|
||||||
|
color:model::ColorId::new(0),
|
||||||
|
});
|
||||||
|
vertex_id
|
||||||
|
});
|
||||||
let polygon_list=std::iter::from_fn(move||{
|
let polygon_list=std::iter::from_fn(move||{
|
||||||
match (polygon_iter.next(),polygon_iter.next(),polygon_iter.next()){
|
match (polygon_iter.next(),polygon_iter.next(),polygon_iter.next()){
|
||||||
(Some(v1),Some(v2),Some(v3))=>Some([v1,v2,v3]),
|
(Some(v1),Some(v2),Some(v3))=>Some(vec![v1,v2,v3]),
|
||||||
//ignore extra vertices, not sure what to do in this case, failing the whole conversion could be appropriate
|
//ignore extra vertices, not sure what to do in this case, failing the whole conversion could be appropriate
|
||||||
_=>None,
|
_=>None,
|
||||||
}
|
}
|
||||||
}).map(|triplet|{
|
|
||||||
triplet.map(|world_position|
|
|
||||||
ingest_vertex(&mut mb,world_position,texture_transform_u,texture_transform_v,normal,color)
|
|
||||||
).to_vec()
|
|
||||||
}).collect();
|
}).collect();
|
||||||
if face.is_visible(){
|
if face.is_visible(){
|
||||||
//this automatically figures out what the texture is trying to do and creates
|
//TODO: deduplicate graphics groups by render id
|
||||||
//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{
|
graphics_groups.push(model::IndexedGraphicsGroup{
|
||||||
render:render_id,
|
render:render_id,
|
||||||
groups:vec![],
|
groups:vec![polygon_group_id],
|
||||||
});
|
})
|
||||||
graphics_group_id
|
|
||||||
});
|
|
||||||
graphics_groups[graphics_group_id].groups.push(polygon_group_id);
|
|
||||||
}
|
}
|
||||||
|
physics_group.groups.push(polygon_group_id);
|
||||||
model::PolygonGroup::PolygonList(model::PolygonList::new(polygon_list))
|
model::PolygonGroup::PolygonList(model::PolygonList::new(polygon_list))
|
||||||
}).collect();
|
}).collect();
|
||||||
|
model::Mesh{
|
||||||
mb.build(polygon_groups,graphics_groups,vec![])
|
unique_pos:spam_pos,
|
||||||
|
unique_tex:spam_tex,
|
||||||
|
unique_normal:spam_normal,
|
||||||
|
unique_color:vec![glam::Vec4::ONE],
|
||||||
|
unique_vertices:spam_vertices,
|
||||||
|
polygon_groups,
|
||||||
|
graphics_groups,
|
||||||
|
physics_groups:vec![physics_group],
|
||||||
|
}
|
||||||
}).collect();
|
}).collect();
|
||||||
|
|
||||||
let world_models:Vec<model::Model>=
|
let world_models:Vec<model::Model>=
|
||||||
@ -173,13 +176,26 @@ pub struct PartialMap1{
|
|||||||
modes:strafesnet_common::gameplay_modes::Modes,
|
modes:strafesnet_common::gameplay_modes::Modes,
|
||||||
}
|
}
|
||||||
impl PartialMap1{
|
impl PartialMap1{
|
||||||
pub fn add_prop_meshes<'a>(
|
pub fn add_prop_meshes<AcquireRenderConfigId>(
|
||||||
self,
|
self,
|
||||||
prop_meshes:Meshes,
|
prop_meshes:impl IntoIterator<Item=(model::MeshId,crate::data::ModelData)>,
|
||||||
)->PartialMap2{
|
mut acquire_render_config_id:AcquireRenderConfigId,
|
||||||
|
)->PartialMap2
|
||||||
|
where
|
||||||
|
AcquireRenderConfigId:FnMut(Option<&str>)->model::RenderConfigId,
|
||||||
|
{
|
||||||
PartialMap2{
|
PartialMap2{
|
||||||
attributes:self.attributes,
|
attributes:self.attributes,
|
||||||
prop_meshes:prop_meshes.consume().collect(),
|
prop_meshes:prop_meshes.into_iter().filter_map(|(mesh_id,model_data)|
|
||||||
|
//this will generate new render ids and texture ids
|
||||||
|
match convert_mesh(model_data,&mut acquire_render_config_id){
|
||||||
|
Ok(mesh)=>Some((mesh_id,mesh)),
|
||||||
|
Err(e)=>{
|
||||||
|
println!("error converting mesh: {e}");
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
).collect(),
|
||||||
prop_models:self.prop_models,
|
prop_models:self.prop_models,
|
||||||
world_meshes:self.world_meshes,
|
world_meshes:self.world_meshes,
|
||||||
world_models:self.world_models,
|
world_models:self.world_models,
|
||||||
@ -198,7 +214,8 @@ pub struct PartialMap2{
|
|||||||
impl PartialMap2{
|
impl PartialMap2{
|
||||||
pub fn add_render_configs_and_textures(
|
pub fn add_render_configs_and_textures(
|
||||||
mut self,
|
mut self,
|
||||||
render_configs:RenderConfigs,
|
render_configs:impl IntoIterator<Item=(model::RenderConfigId,model::RenderConfig)>,
|
||||||
|
textures:impl IntoIterator<Item=(model::TextureId,Vec<u8>)>,
|
||||||
)->map::CompleteMap{
|
)->map::CompleteMap{
|
||||||
//merge mesh and model lists, flatten and remap all ids
|
//merge mesh and model lists, flatten and remap all ids
|
||||||
let mesh_id_offset=self.world_meshes.len();
|
let mesh_id_offset=self.world_meshes.len();
|
||||||
@ -217,14 +234,13 @@ impl PartialMap2{
|
|||||||
})
|
})
|
||||||
));
|
));
|
||||||
//let mut models=Vec::new();
|
//let mut models=Vec::new();
|
||||||
let (textures,render_configs)=render_configs.consume();
|
|
||||||
let (textures,texture_id_map):(Vec<Vec<u8>>,std::collections::HashMap<model::TextureId,model::TextureId>)
|
let (textures,texture_id_map):(Vec<Vec<u8>>,std::collections::HashMap<model::TextureId,model::TextureId>)
|
||||||
=textures.into_iter()
|
=textures.into_iter()
|
||||||
//.filter_map(f) cull unused textures
|
//.filter_map(f) cull unused textures
|
||||||
.enumerate().map(|(new_texture_id,(old_texture_id,Texture::ImageDDS(texture)))|{
|
.enumerate().map(|(new_texture_id,(old_texture_id,texture))|{
|
||||||
(texture,(old_texture_id,model::TextureId::new(new_texture_id as u32)))
|
(texture,(old_texture_id,model::TextureId::new(new_texture_id as u32)))
|
||||||
}).unzip();
|
}).unzip();
|
||||||
let render_configs=render_configs.into_iter().map(|(_render_config_id,mut render_config)|{
|
let render_configs=render_configs.into_iter().map(|(render_config_id,mut render_config)|{
|
||||||
//this may generate duplicate no-texture render configs but idc
|
//this may generate duplicate no-texture render configs but idc
|
||||||
render_config.texture=render_config.texture.and_then(|texture_id|
|
render_config.texture=render_config.texture.and_then(|texture_id|
|
||||||
texture_id_map.get(&texture_id).copied()
|
texture_id_map.get(&texture_id).copied()
|
||||||
@ -241,3 +257,77 @@ impl PartialMap2{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn convert_mesh<AcquireRenderConfigId>(
|
||||||
|
model_data:crate::data::ModelData,
|
||||||
|
acquire_render_config_id:&mut AcquireRenderConfigId,
|
||||||
|
)->Result<model::Mesh,vmdl::ModelError>
|
||||||
|
where
|
||||||
|
AcquireRenderConfigId:FnMut(Option<&str>)->model::RenderConfigId,
|
||||||
|
{
|
||||||
|
let model=model_data.read_model()?;
|
||||||
|
let texture_paths=model.texture_directories();
|
||||||
|
if texture_paths.len()!=1{
|
||||||
|
println!("WARNING: multiple texture paths");
|
||||||
|
}
|
||||||
|
let skin=model.skin_tables().nth(0).unwrap();
|
||||||
|
|
||||||
|
let mut spam_pos=Vec::with_capacity(model.vertices().len());
|
||||||
|
let mut spam_normal=Vec::with_capacity(model.vertices().len());
|
||||||
|
let mut spam_tex=Vec::with_capacity(model.vertices().len());
|
||||||
|
let mut spam_vertices=Vec::with_capacity(model.vertices().len());
|
||||||
|
for (i,vertex) in model.vertices().iter().enumerate(){
|
||||||
|
spam_pos.push(valve_transform(vertex.position.into()));
|
||||||
|
spam_normal.push(valve_transform(vertex.normal.into()));
|
||||||
|
spam_tex.push(glam::Vec2::from_array(vertex.texture_coordinates));
|
||||||
|
spam_vertices.push(model::IndexedVertex{
|
||||||
|
pos:model::PositionId::new(i as u32),
|
||||||
|
tex:model::TextureCoordinateId::new(i as u32),
|
||||||
|
normal:model::NormalId::new(i as u32),
|
||||||
|
color:model::ColorId::new(0),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
let mut graphics_groups=Vec::new();
|
||||||
|
let mut physics_groups=Vec::new();
|
||||||
|
let polygon_groups=model.meshes().enumerate().map(|(polygon_group_id,mesh)|{
|
||||||
|
let polygon_group_id=model::PolygonGroupId::new(polygon_group_id as u32);
|
||||||
|
|
||||||
|
let render_id=if let (Some(texture_path),Some(texture_name))=(texture_paths.get(0),skin.texture(mesh.material_index())){
|
||||||
|
let mut path=std::path::PathBuf::from(texture_path.as_str());
|
||||||
|
path.push(texture_name);
|
||||||
|
acquire_render_config_id(path.as_os_str().to_str())
|
||||||
|
}else{
|
||||||
|
acquire_render_config_id(None)
|
||||||
|
};
|
||||||
|
|
||||||
|
graphics_groups.push(model::IndexedGraphicsGroup{
|
||||||
|
render:render_id,
|
||||||
|
groups:vec![polygon_group_id],
|
||||||
|
});
|
||||||
|
physics_groups.push(model::IndexedPhysicsGroup{
|
||||||
|
groups:vec![polygon_group_id],
|
||||||
|
});
|
||||||
|
model::PolygonGroup::PolygonList(model::PolygonList::new(
|
||||||
|
//looking at the code, it would seem that the strips are pre-deindexed into triangle lists when calling this function
|
||||||
|
mesh.vertex_strip_indices().flat_map(|mut strip|
|
||||||
|
std::iter::from_fn(move||{
|
||||||
|
match (strip.next(),strip.next(),strip.next()){
|
||||||
|
(Some(v1),Some(v2),Some(v3))=>Some([v1,v2,v3].map(|vertex_id|model::VertexId::new(vertex_id as u32)).to_vec()),
|
||||||
|
//ignore extra vertices, not sure what to do in this case, failing the whole conversion could be appropriate
|
||||||
|
_=>None,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
).collect()
|
||||||
|
))
|
||||||
|
}).collect();
|
||||||
|
Ok(model::Mesh{
|
||||||
|
unique_pos:spam_pos,
|
||||||
|
unique_normal:spam_normal,
|
||||||
|
unique_tex:spam_tex,
|
||||||
|
unique_color:vec![glam::Vec4::ONE],
|
||||||
|
unique_vertices:spam_vertices,
|
||||||
|
polygon_groups,
|
||||||
|
graphics_groups,
|
||||||
|
physics_groups,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
60
lib/bsp_loader/src/data.rs
Normal file
60
lib/bsp_loader/src/data.rs
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
pub struct Bsp(vbsp::Bsp);
|
||||||
|
impl Bsp{
|
||||||
|
pub const fn new(value:vbsp::Bsp)->Self{
|
||||||
|
Self(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl AsRef<vbsp::Bsp> for Bsp{
|
||||||
|
fn as_ref(&self)->&vbsp::Bsp{
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct MdlData(Vec<u8>);
|
||||||
|
impl MdlData{
|
||||||
|
pub const fn new(value:Vec<u8>)->Self{
|
||||||
|
Self(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl AsRef<[u8]> for MdlData{
|
||||||
|
fn as_ref(&self)->&[u8]{
|
||||||
|
self.0.as_ref()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub struct VtxData(Vec<u8>);
|
||||||
|
impl VtxData{
|
||||||
|
pub const fn new(value:Vec<u8>)->Self{
|
||||||
|
Self(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl AsRef<[u8]> for VtxData{
|
||||||
|
fn as_ref(&self)->&[u8]{
|
||||||
|
self.0.as_ref()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub struct VvdData(Vec<u8>);
|
||||||
|
impl VvdData{
|
||||||
|
pub const fn new(value:Vec<u8>)->Self{
|
||||||
|
Self(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl AsRef<[u8]> for VvdData{
|
||||||
|
fn as_ref(&self)->&[u8]{
|
||||||
|
self.0.as_ref()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct ModelData{
|
||||||
|
pub mdl:MdlData,
|
||||||
|
pub vtx:VtxData,
|
||||||
|
pub vvd:VvdData,
|
||||||
|
}
|
||||||
|
impl ModelData{
|
||||||
|
pub fn read_model(&self)->Result<vmdl::Model,vmdl::ModelError>{
|
||||||
|
Ok(vmdl::Model::from_parts(
|
||||||
|
vmdl::mdl::Mdl::read(self.mdl.as_ref())?,
|
||||||
|
vmdl::vtx::Vtx::read(self.vtx.as_ref())?,
|
||||||
|
vmdl::vvd::Vvd::read(self.vvd.as_ref())?,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
@ -1,13 +1,7 @@
|
|||||||
use strafesnet_deferred_loader::deferred_loader::{LoadFailureMode,MeshDeferredLoader,RenderConfigDeferredLoader};
|
|
||||||
|
|
||||||
mod bsp;
|
mod bsp;
|
||||||
mod mesh;
|
pub mod data;
|
||||||
pub mod loader;
|
|
||||||
|
|
||||||
const VALVE_SCALE:f32=1.0/16.0;
|
pub use data::Bsp;
|
||||||
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()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum ReadError{
|
pub enum ReadError{
|
||||||
@ -21,38 +15,6 @@ impl std::fmt::Display for ReadError{
|
|||||||
}
|
}
|
||||||
impl std::error::Error for ReadError{}
|
impl std::error::Error for ReadError{}
|
||||||
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub enum LoadError{
|
|
||||||
Texture(loader::TextureError),
|
|
||||||
Mesh(loader::MeshError),
|
|
||||||
}
|
|
||||||
impl std::fmt::Display for LoadError{
|
|
||||||
fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{
|
|
||||||
write!(f,"{self:?}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl std::error::Error for LoadError{}
|
|
||||||
impl From<loader::TextureError> for LoadError{
|
|
||||||
fn from(value:loader::TextureError)->Self{
|
|
||||||
Self::Texture(value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl From<loader::MeshError> for LoadError{
|
|
||||||
fn from(value:loader::MeshError)->Self{
|
|
||||||
Self::Mesh(value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub struct Bsp{
|
|
||||||
bsp:vbsp::Bsp,
|
|
||||||
case_folded_file_names:std::collections::HashMap<String,String>,
|
|
||||||
}
|
|
||||||
impl AsRef<vbsp::Bsp> for Bsp{
|
|
||||||
fn as_ref(&self)->&vbsp::Bsp{
|
|
||||||
&self.bsp
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn read<R:std::io::Read>(mut input:R)->Result<Bsp,ReadError>{
|
pub fn read<R:std::io::Read>(mut input:R)->Result<Bsp,ReadError>{
|
||||||
let mut s=Vec::new();
|
let mut s=Vec::new();
|
||||||
|
|
||||||
@ -61,66 +23,15 @@ 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)
|
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 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>{
|
|
||||||
let mut texture_deferred_loader=RenderConfigDeferredLoader::new();
|
|
||||||
let mut mesh_deferred_loader=MeshDeferredLoader::new();
|
|
||||||
|
|
||||||
let map_step1=bsp::convert(
|
pub fn convert<AcquireRenderConfigId,AcquireMeshId>(
|
||||||
self,
|
bsp:&Bsp,
|
||||||
&mut texture_deferred_loader,
|
acquire_render_config_id:AcquireRenderConfigId,
|
||||||
&mut mesh_deferred_loader,
|
acquire_mesh_id:AcquireMeshId
|
||||||
);
|
)->bsp::PartialMap1
|
||||||
|
where
|
||||||
let mut mesh_loader=loader::MeshLoader::new(loader::BspFinder{bsp:self,vpks:vpk_list},&mut texture_deferred_loader);
|
AcquireRenderConfigId:FnMut(Option<&str>)->strafesnet_common::model::RenderConfigId,
|
||||||
let prop_meshes=mesh_deferred_loader.into_meshes(&mut mesh_loader,failure_mode).map_err(LoadError::Mesh)?;
|
AcquireMeshId:FnMut(&str)->strafesnet_common::model::MeshId,
|
||||||
|
{
|
||||||
let map_step2=map_step1.add_prop_meshes(prop_meshes);
|
bsp::convert_bsp(bsp.as_ref(),acquire_render_config_id,acquire_mesh_id)
|
||||||
|
|
||||||
let mut texture_loader=loader::TextureLoader::new();
|
|
||||||
let render_configs=texture_deferred_loader.into_render_configs(&mut texture_loader,failure_mode).map_err(LoadError::Texture)?;
|
|
||||||
|
|
||||||
let map=map_step2.add_render_configs_and_textures(render_configs);
|
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
}
|
@ -1,175 +0,0 @@
|
|||||||
use std::{borrow::Cow, io::Read};
|
|
||||||
|
|
||||||
use strafesnet_common::model::Mesh;
|
|
||||||
use strafesnet_deferred_loader::{loader::Loader,texture::Texture};
|
|
||||||
|
|
||||||
use crate::{Bsp,Vpk};
|
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub enum TextureError{
|
|
||||||
Io(std::io::Error),
|
|
||||||
}
|
|
||||||
impl std::fmt::Display for TextureError{
|
|
||||||
fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{
|
|
||||||
write!(f,"{self:?}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl std::error::Error for TextureError{}
|
|
||||||
impl From<std::io::Error> for TextureError{
|
|
||||||
fn from(value:std::io::Error)->Self{
|
|
||||||
Self::Io(value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct TextureLoader<'a>(std::marker::PhantomData<&'a ()>);
|
|
||||||
impl TextureLoader<'_>{
|
|
||||||
pub fn new()->Self{
|
|
||||||
Self(std::marker::PhantomData)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl<'a> Loader for TextureLoader<'a>{
|
|
||||||
type Error=TextureError;
|
|
||||||
type Index=Cow<'a,str>;
|
|
||||||
type Resource=Texture;
|
|
||||||
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();
|
|
||||||
file.read_to_end(&mut data)?;
|
|
||||||
Ok(Texture::ImageDDS(data))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub enum MeshError{
|
|
||||||
Io(std::io::Error),
|
|
||||||
VMDL(vmdl::ModelError),
|
|
||||||
VBSP(vbsp::BspError),
|
|
||||||
MissingMdl,
|
|
||||||
MissingVtx,
|
|
||||||
MissingVvd,
|
|
||||||
}
|
|
||||||
impl std::fmt::Display for MeshError{
|
|
||||||
fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{
|
|
||||||
write!(f,"{self:?}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl std::error::Error for MeshError{}
|
|
||||||
impl From<std::io::Error> for MeshError{
|
|
||||||
fn from(value:std::io::Error)->Self{
|
|
||||||
Self::Io(value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl From<vmdl::ModelError> for MeshError{
|
|
||||||
fn from(value:vmdl::ModelError)->Self{
|
|
||||||
Self::VMDL(value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl From<vbsp::BspError> for MeshError{
|
|
||||||
fn from(value:vbsp::BspError)->Self{
|
|
||||||
Self::VBSP(value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone,Copy)]
|
|
||||||
pub struct BspFinder<'bsp,'vpk>{
|
|
||||||
pub bsp:&'bsp Bsp,
|
|
||||||
pub vpks:&'vpk [Vpk],
|
|
||||||
}
|
|
||||||
impl<'bsp,'vpk> BspFinder<'bsp,'vpk>{
|
|
||||||
pub fn find<'a>(&self,path:&str)->Result<Option<Cow<'a,[u8]>>,vbsp::BspError>
|
|
||||||
where
|
|
||||||
'bsp:'a,
|
|
||||||
'vpk:'a,
|
|
||||||
{
|
|
||||||
// search bsp
|
|
||||||
if let Some(data)=self.bsp.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){
|
|
||||||
return Ok(Some(vpk_entry.get()?));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(None)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct ModelLoader<'bsp,'vpk,'a>{
|
|
||||||
finder:BspFinder<'bsp,'vpk>,
|
|
||||||
life:core::marker::PhantomData<&'a ()>,
|
|
||||||
}
|
|
||||||
impl ModelLoader<'_,'_,'_>{
|
|
||||||
#[inline]
|
|
||||||
pub const fn new<'bsp,'vpk,'a>(
|
|
||||||
finder:BspFinder<'bsp,'vpk>,
|
|
||||||
)->ModelLoader<'bsp,'vpk,'a>{
|
|
||||||
ModelLoader{
|
|
||||||
finder,
|
|
||||||
life:core::marker::PhantomData,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl<'bsp,'vpk,'a> Loader for ModelLoader<'bsp,'vpk,'a>
|
|
||||||
where
|
|
||||||
'bsp:'a,
|
|
||||||
'vpk:'a,
|
|
||||||
{
|
|
||||||
type Error=MeshError;
|
|
||||||
type Index=&'a str;
|
|
||||||
type Resource=vmdl::Model;
|
|
||||||
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());
|
|
||||||
let mut vvd_path=path.clone();
|
|
||||||
let mut vtx_path=path;
|
|
||||||
vvd_path.set_extension("vvd");
|
|
||||||
vtx_path.set_extension("dx90.vtx");
|
|
||||||
// TODO: search more packs, possibly using an index of multiple packs
|
|
||||||
let mdl=self.finder.find(mdl_path_lower.as_str())?.ok_or(MeshError::MissingMdl)?;
|
|
||||||
let 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(
|
|
||||||
vmdl::mdl::Mdl::read(mdl.as_ref())?,
|
|
||||||
vmdl::vtx::Vtx::read(vtx.as_ref())?,
|
|
||||||
vmdl::vvd::Vvd::read(vvd.as_ref())?,
|
|
||||||
))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct MeshLoader<'bsp,'vpk,'load,'a>{
|
|
||||||
finder:BspFinder<'bsp,'vpk>,
|
|
||||||
deferred_loader:&'load mut strafesnet_deferred_loader::deferred_loader::RenderConfigDeferredLoader<Cow<'a,str>>,
|
|
||||||
}
|
|
||||||
impl MeshLoader<'_,'_,'_,'_>{
|
|
||||||
#[inline]
|
|
||||||
pub const fn new<'bsp,'vpk,'load,'a>(
|
|
||||||
finder:BspFinder<'bsp,'vpk>,
|
|
||||||
deferred_loader:&'load mut strafesnet_deferred_loader::deferred_loader::RenderConfigDeferredLoader<Cow<'a,str>>,
|
|
||||||
)->MeshLoader<'bsp,'vpk,'load,'a>{
|
|
||||||
MeshLoader{
|
|
||||||
finder,
|
|
||||||
deferred_loader
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl<'bsp,'vpk,'load,'a> Loader for MeshLoader<'bsp,'vpk,'load,'a>
|
|
||||||
where
|
|
||||||
'bsp:'a,
|
|
||||||
'vpk:'a,
|
|
||||||
{
|
|
||||||
type Error=MeshError;
|
|
||||||
type Index=&'a str;
|
|
||||||
type Resource=Mesh;
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,78 +0,0 @@
|
|||||||
use std::borrow::Cow;
|
|
||||||
|
|
||||||
use strafesnet_common::model;
|
|
||||||
use strafesnet_deferred_loader::deferred_loader::RenderConfigDeferredLoader;
|
|
||||||
|
|
||||||
use crate::valve_transform;
|
|
||||||
|
|
||||||
fn ingest_vertex(mb:&mut model::MeshBuilder,vertex:&vmdl::vvd::Vertex,color:model::ColorId)->model::VertexId{
|
|
||||||
let pos=mb.acquire_pos_id(valve_transform(vertex.position.into()));
|
|
||||||
let normal=mb.acquire_normal_id(valve_transform(vertex.normal.into()));
|
|
||||||
let tex=mb.acquire_tex_id(glam::Vec2::from_array(vertex.texture_coordinates));
|
|
||||||
mb.acquire_vertex_id(model::IndexedVertex{
|
|
||||||
pos,
|
|
||||||
tex,
|
|
||||||
normal,
|
|
||||||
color,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn convert_mesh(model:vmdl::Model,deferred_loader:&mut RenderConfigDeferredLoader<Cow<str>>)->model::Mesh{
|
|
||||||
let texture_paths=model.texture_directories();
|
|
||||||
if texture_paths.len()!=1{
|
|
||||||
println!("WARNING: multiple texture paths");
|
|
||||||
}
|
|
||||||
let skin=model.skin_tables().nth(0).unwrap();
|
|
||||||
|
|
||||||
let mut mb=model::MeshBuilder::new();
|
|
||||||
|
|
||||||
let color=mb.acquire_color_id(glam::Vec4::ONE);
|
|
||||||
|
|
||||||
let model_vertices=model.vertices();
|
|
||||||
|
|
||||||
let mut graphics_groups=Vec::new();
|
|
||||||
let mut physics_groups=Vec::new();
|
|
||||||
let polygon_groups=model.meshes().enumerate().map(|(polygon_group_id,mesh)|{
|
|
||||||
let polygon_group_id=model::PolygonGroupId::new(polygon_group_id as u32);
|
|
||||||
|
|
||||||
let render_id=if let (Some(texture_path),Some(texture_name))=(texture_paths.get(0),skin.texture(mesh.material_index())){
|
|
||||||
let mut path=std::path::PathBuf::from(texture_path.as_str());
|
|
||||||
path.push(texture_name);
|
|
||||||
let index=path.as_os_str().to_str().map(|s|Cow::Owned(s.to_owned()));
|
|
||||||
deferred_loader.acquire_render_config_id(index)
|
|
||||||
}else{
|
|
||||||
deferred_loader.acquire_render_config_id(None)
|
|
||||||
};
|
|
||||||
|
|
||||||
graphics_groups.push(model::IndexedGraphicsGroup{
|
|
||||||
render:render_id,
|
|
||||||
groups:vec![polygon_group_id],
|
|
||||||
});
|
|
||||||
physics_groups.push(model::IndexedPhysicsGroup{
|
|
||||||
groups:vec![polygon_group_id],
|
|
||||||
});
|
|
||||||
model::PolygonGroup::PolygonList(model::PolygonList::new(
|
|
||||||
//looking at the code, it would seem that the strips are pre-deindexed into triangle lists when calling this function
|
|
||||||
mesh.vertex_strip_indices().flat_map(|mut strip|{
|
|
||||||
std::iter::from_fn(move ||{
|
|
||||||
match (strip.next(),strip.next(),strip.next()){
|
|
||||||
(Some(v1),Some(v2),Some(v3))=>Some([v1,v2,v3]),
|
|
||||||
//ignore extra vertices, not sure what to do in this case, failing the whole conversion could be appropriate
|
|
||||||
_=>None,
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}).flat_map(|[v1,v2,v3]|{
|
|
||||||
// this should probably be a fatal error :D
|
|
||||||
let v1=model_vertices.get(v1)?;
|
|
||||||
let v2=model_vertices.get(v2)?;
|
|
||||||
let v3=model_vertices.get(v3)?;
|
|
||||||
Some(vec![
|
|
||||||
ingest_vertex(&mut mb,v1,color),
|
|
||||||
ingest_vertex(&mut mb,v2,color),
|
|
||||||
ingest_vertex(&mut mb,v3,color),
|
|
||||||
])
|
|
||||||
}).collect()
|
|
||||||
))
|
|
||||||
}).collect();
|
|
||||||
mb.build(polygon_groups,graphics_groups,physics_groups)
|
|
||||||
}
|
|
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "strafesnet_common"
|
name = "strafesnet_common"
|
||||||
version = "0.6.0"
|
version = "0.5.2"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
repository = "https://git.itzana.me/StrafesNET/strafe-project"
|
repository = "https://git.itzana.me/StrafesNET/strafe-project"
|
||||||
license = "MIT OR Apache-2.0"
|
license = "MIT OR Apache-2.0"
|
||||||
@ -12,8 +12,8 @@ authors = ["Rhys Lloyd <krakow20@gmail.com>"]
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
arrayvec = "0.7.4"
|
arrayvec = "0.7.4"
|
||||||
bitflags = "2.6.0"
|
bitflags = "2.6.0"
|
||||||
fixed_wide = { version = "0.1.2", path = "../fixed_wide", registry = "strafesnet", features = ["deferred-division","zeroes","wide-mul"] }
|
fixed_wide = { path = "../fixed_wide", registry = "strafesnet", features = ["deferred-division","zeroes","wide-mul"] }
|
||||||
linear_ops = { version = "0.1.0", path = "../linear_ops", registry = "strafesnet", features = ["deferred-division","named-fields"] }
|
linear_ops = { path = "../linear_ops", registry = "strafesnet", features = ["deferred-division","named-fields"] }
|
||||||
ratio_ops = { version = "0.1.0", path = "../ratio_ops", registry = "strafesnet" }
|
ratio_ops = { path = "../ratio_ops", registry = "strafesnet" }
|
||||||
glam = "0.29.0"
|
glam = "0.29.0"
|
||||||
id = { version = "0.1.0", registry = "strafesnet" }
|
id = { version = "0.1.0", registry = "strafesnet" }
|
||||||
|
@ -18,8 +18,13 @@ bitflags::bitflags!{
|
|||||||
const Use=1<<14;//Interact with object
|
const Use=1<<14;//Interact with object
|
||||||
const PrimaryAction=1<<15;//LBM/Shoot/Melee
|
const PrimaryAction=1<<15;//LBM/Shoot/Melee
|
||||||
const SecondaryAction=1<<16;//RMB/ADS/Block
|
const SecondaryAction=1<<16;//RMB/ADS/Block
|
||||||
|
}
|
||||||
const WASD=Self::MoveForward.union(Self::MoveLeft).union(Self::MoveBackward).union(Self::MoveRight).bits();
|
}
|
||||||
const WASDQE=Self::MoveForward.union(Self::MoveLeft).union(Self::MoveBackward).union(Self::MoveRight).union(Self::MoveUp).union(Self::MoveDown).bits();
|
impl Controls{
|
||||||
|
pub const fn wasd()->Self{
|
||||||
|
Self::MoveForward.union(Self::MoveLeft).union(Self::MoveBackward).union(Self::MoveRight)
|
||||||
|
}
|
||||||
|
pub const fn wasdqe()->Self{
|
||||||
|
Self::MoveForward.union(Self::MoveLeft).union(Self::MoveBackward).union(Self::MoveRight).union(Self::MoveUp).union(Self::MoveDown)
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,5 +1,5 @@
|
|||||||
use crate::model;
|
use crate::model;
|
||||||
use crate::integer::{AbsoluteTime,Planar64,Planar64Vec3};
|
use crate::integer::{Time,Planar64,Planar64Vec3};
|
||||||
|
|
||||||
//you have this effect while in contact
|
//you have this effect while in contact
|
||||||
#[derive(Clone,Hash,Eq,PartialEq)]
|
#[derive(Clone,Hash,Eq,PartialEq)]
|
||||||
@ -31,7 +31,7 @@ pub enum Booster{
|
|||||||
//Affine(crate::integer::Planar64Affine3),//capable of SetVelocity,DotVelocity,normal booster,bouncy part,redirect velocity, and much more
|
//Affine(crate::integer::Planar64Affine3),//capable of SetVelocity,DotVelocity,normal booster,bouncy part,redirect velocity, and much more
|
||||||
Velocity(Planar64Vec3),//straight up boost velocity adds to your current velocity
|
Velocity(Planar64Vec3),//straight up boost velocity adds to your current velocity
|
||||||
Energy{direction:Planar64Vec3,energy:Planar64},//increase energy in direction
|
Energy{direction:Planar64Vec3,energy:Planar64},//increase energy in direction
|
||||||
AirTime(AbsoluteTime),//increase airtime, invariant across mass and gravity changes
|
AirTime(Time),//increase airtime, invariant across mass and gravity changes
|
||||||
Height(Planar64),//increase height, invariant across mass and gravity changes
|
Height(Planar64),//increase height, invariant across mass and gravity changes
|
||||||
}
|
}
|
||||||
impl Booster{
|
impl Booster{
|
||||||
@ -57,13 +57,13 @@ pub enum TrajectoryChoice{
|
|||||||
#[derive(Clone,Hash,Eq,PartialEq)]
|
#[derive(Clone,Hash,Eq,PartialEq)]
|
||||||
pub enum SetTrajectory{
|
pub enum SetTrajectory{
|
||||||
//Speed-type SetTrajectory
|
//Speed-type SetTrajectory
|
||||||
AirTime(AbsoluteTime),//air time (relative to gravity direction) is invariant across mass and gravity changes
|
AirTime(Time),//air time (relative to gravity direction) is invariant across mass and gravity changes
|
||||||
Height(Planar64),//boost height (relative to gravity direction) is invariant across mass and gravity changes
|
Height(Planar64),//boost height (relative to gravity direction) is invariant across mass and gravity changes
|
||||||
DotVelocity{direction:Planar64Vec3,dot:Planar64},//set your velocity in a specific direction without touching other directions
|
DotVelocity{direction:Planar64Vec3,dot:Planar64},//set your velocity in a specific direction without touching other directions
|
||||||
//Velocity-type SetTrajectory
|
//Velocity-type SetTrajectory
|
||||||
TargetPointTime{//launch on a trajectory that will land at a target point in a set amount of time
|
TargetPointTime{//launch on a trajectory that will land at a target point in a set amount of time
|
||||||
target_point:Planar64Vec3,
|
target_point:Planar64Vec3,
|
||||||
time:AbsoluteTime,//short time = fast and direct, long time = launch high in the air, negative time = wrong way
|
time:Time,//short time = fast and direct, long time = launch high in the air, negative time = wrong way
|
||||||
},
|
},
|
||||||
TargetPointSpeed{//launch at a fixed speed and land at a target point
|
TargetPointSpeed{//launch at a fixed speed and land at a target point
|
||||||
target_point:Planar64Vec3,
|
target_point:Planar64Vec3,
|
||||||
|
@ -110,7 +110,6 @@ impl Stage{
|
|||||||
pub fn into_inner(self)->(HashMap<CheckpointId,ModelId>,HashSet<ModelId>){
|
pub fn into_inner(self)->(HashMap<CheckpointId,ModelId>,HashSet<ModelId>){
|
||||||
(self.ordered_checkpoints,self.unordered_checkpoints)
|
(self.ordered_checkpoints,self.unordered_checkpoints)
|
||||||
}
|
}
|
||||||
/// Returns true if the stage has no checkpoints.
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub const fn is_empty(&self)->bool{
|
pub const fn is_empty(&self)->bool{
|
||||||
self.is_complete(0,0)
|
self.is_complete(0,0)
|
||||||
|
@ -1,8 +1,7 @@
|
|||||||
const VALVE_SCALE:Planar64=Planar64::raw(1<<28);// 1/16
|
const VALVE_SCALE:Planar64=Planar64::raw(1<<28);// 1/16
|
||||||
|
|
||||||
use crate::integer::{int,vec3::int as int3,AbsoluteTime,Ratio64,Planar64,Planar64Vec3};
|
use crate::integer::{int,vec3::int as int3,Time,Ratio64,Planar64,Planar64Vec3};
|
||||||
use crate::controls_bitflag::Controls;
|
use crate::controls_bitflag::Controls;
|
||||||
use crate::physics::Time as PhysicsTime;
|
|
||||||
|
|
||||||
#[derive(Clone,Debug)]
|
#[derive(Clone,Debug)]
|
||||||
pub struct StyleModifiers{
|
pub struct StyleModifiers{
|
||||||
@ -49,7 +48,7 @@ pub enum JumpCalculation{
|
|||||||
|
|
||||||
#[derive(Clone,Debug)]
|
#[derive(Clone,Debug)]
|
||||||
pub enum JumpImpulse{
|
pub enum JumpImpulse{
|
||||||
Time(AbsoluteTime),//jump time is invariant across mass and gravity changes
|
Time(Time),//jump time is invariant across mass and gravity changes
|
||||||
Height(Planar64),//jump height is invariant across mass and gravity changes
|
Height(Planar64),//jump height is invariant across mass and gravity changes
|
||||||
Linear(Planar64),//jump velocity is invariant across mass and gravity changes
|
Linear(Planar64),//jump velocity is invariant across mass and gravity changes
|
||||||
Energy(Planar64),// :)
|
Energy(Planar64),// :)
|
||||||
@ -200,8 +199,8 @@ impl ControlsActivation{
|
|||||||
}
|
}
|
||||||
pub const fn full_3d()->Self{
|
pub const fn full_3d()->Self{
|
||||||
Self{
|
Self{
|
||||||
controls_mask:Controls::WASDQE,
|
controls_mask:Controls::wasdqe(),
|
||||||
controls_intersects:Controls::WASDQE,
|
controls_intersects:Controls::wasdqe(),
|
||||||
controls_contains:Controls::empty(),
|
controls_contains:Controls::empty(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -209,8 +208,8 @@ impl ControlsActivation{
|
|||||||
//Normal
|
//Normal
|
||||||
pub const fn full_2d()->Self{
|
pub const fn full_2d()->Self{
|
||||||
Self{
|
Self{
|
||||||
controls_mask:Controls::WASD,
|
controls_mask:Controls::wasd(),
|
||||||
controls_intersects:Controls::WASD,
|
controls_intersects:Controls::wasd(),
|
||||||
controls_contains:Controls::empty(),
|
controls_contains:Controls::empty(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -273,8 +272,8 @@ impl StrafeSettings{
|
|||||||
false=>None,
|
false=>None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn next_tick(&self,time:PhysicsTime)->PhysicsTime{
|
pub fn next_tick(&self,time:Time)->Time{
|
||||||
PhysicsTime::from_nanos(self.tick_rate.rhs_div_int(self.tick_rate.mul_int(time.nanos())+1))
|
Time::from_nanos(self.tick_rate.rhs_div_int(self.tick_rate.mul_int(time.nanos())+1))
|
||||||
}
|
}
|
||||||
pub const fn activates(&self,controls:Controls)->bool{
|
pub const fn activates(&self,controls:Controls)->bool{
|
||||||
self.enable.activates(controls)
|
self.enable.activates(controls)
|
||||||
@ -436,7 +435,7 @@ impl StyleModifiers{
|
|||||||
enable:ControlsActivation::full_2d(),
|
enable:ControlsActivation::full_2d(),
|
||||||
air_accel_limit:None,
|
air_accel_limit:None,
|
||||||
mv:int(3),
|
mv:int(3),
|
||||||
tick_rate:Ratio64::new(64,AbsoluteTime::ONE_SECOND.get() as u64).unwrap(),
|
tick_rate:Ratio64::new(64,Time::ONE_SECOND.nanos() as u64).unwrap(),
|
||||||
}),
|
}),
|
||||||
jump:Some(JumpSettings{
|
jump:Some(JumpSettings{
|
||||||
impulse:JumpImpulse::Energy(int(512)),
|
impulse:JumpImpulse::Energy(int(512)),
|
||||||
@ -478,10 +477,10 @@ impl StyleModifiers{
|
|||||||
enable:ControlsActivation::full_2d(),
|
enable:ControlsActivation::full_2d(),
|
||||||
air_accel_limit:None,
|
air_accel_limit:None,
|
||||||
mv:int(27)/10,
|
mv:int(27)/10,
|
||||||
tick_rate:Ratio64::new(100,AbsoluteTime::ONE_SECOND.get() as u64).unwrap(),
|
tick_rate:Ratio64::new(100,Time::ONE_SECOND.nanos() as u64).unwrap(),
|
||||||
}),
|
}),
|
||||||
jump:Some(JumpSettings{
|
jump:Some(JumpSettings{
|
||||||
impulse:JumpImpulse::Time(AbsoluteTime::from_micros(715_588)),
|
impulse:JumpImpulse::Time(Time::from_micros(715_588)),
|
||||||
calculation:JumpCalculation::Max,
|
calculation:JumpCalculation::Max,
|
||||||
limit_minimum:true,
|
limit_minimum:true,
|
||||||
}),
|
}),
|
||||||
@ -535,7 +534,7 @@ impl StyleModifiers{
|
|||||||
enable:ControlsActivation::full_2d(),
|
enable:ControlsActivation::full_2d(),
|
||||||
air_accel_limit:Some(Planar64::raw(150<<28)*100),
|
air_accel_limit:Some(Planar64::raw(150<<28)*100),
|
||||||
mv:(Planar64::raw(30)*VALVE_SCALE).fix_1(),
|
mv:(Planar64::raw(30)*VALVE_SCALE).fix_1(),
|
||||||
tick_rate:Ratio64::new(100,AbsoluteTime::ONE_SECOND.get() as u64).unwrap(),
|
tick_rate:Ratio64::new(100,Time::ONE_SECOND.nanos() as u64).unwrap(),
|
||||||
}),
|
}),
|
||||||
jump:Some(JumpSettings{
|
jump:Some(JumpSettings{
|
||||||
impulse:JumpImpulse::Height((int(52)*VALVE_SCALE).fix_1()),
|
impulse:JumpImpulse::Height((int(52)*VALVE_SCALE).fix_1()),
|
||||||
@ -576,7 +575,7 @@ impl StyleModifiers{
|
|||||||
enable:ControlsActivation::full_2d(),
|
enable:ControlsActivation::full_2d(),
|
||||||
air_accel_limit:Some((int(150)*66*VALVE_SCALE).fix_1()),
|
air_accel_limit:Some((int(150)*66*VALVE_SCALE).fix_1()),
|
||||||
mv:(int(30)*VALVE_SCALE).fix_1(),
|
mv:(int(30)*VALVE_SCALE).fix_1(),
|
||||||
tick_rate:Ratio64::new(66,AbsoluteTime::ONE_SECOND.get() as u64).unwrap(),
|
tick_rate:Ratio64::new(66,Time::ONE_SECOND.nanos() as u64).unwrap(),
|
||||||
}),
|
}),
|
||||||
jump:Some(JumpSettings{
|
jump:Some(JumpSettings{
|
||||||
impulse:JumpImpulse::Height((int(52)*VALVE_SCALE).fix_1()),
|
impulse:JumpImpulse::Height((int(52)*VALVE_SCALE).fix_1()),
|
||||||
|
@ -1,82 +1,53 @@
|
|||||||
use crate::integer::Time;
|
use crate::integer::Time;
|
||||||
|
|
||||||
#[derive(Clone,Debug)]
|
#[derive(Debug)]
|
||||||
pub struct TimedInstruction<I,T>{
|
pub struct TimedInstruction<I>{
|
||||||
pub time:Time<T>,
|
pub time:Time,
|
||||||
pub instruction:I,
|
pub instruction:I,
|
||||||
}
|
}
|
||||||
impl<I,T> TimedInstruction<I,T>{
|
|
||||||
#[inline]
|
|
||||||
pub fn set_time<TimeInner>(self,new_time:Time<TimeInner>)->TimedInstruction<I,TimeInner>{
|
|
||||||
TimedInstruction{
|
|
||||||
time:new_time,
|
|
||||||
instruction:self.instruction,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Ensure all emitted instructions are processed before consuming external instructions
|
|
||||||
pub trait InstructionEmitter<I>{
|
pub trait InstructionEmitter<I>{
|
||||||
type TimeInner;
|
fn next_instruction(&self,time_limit:Time)->Option<TimedInstruction<I>>;
|
||||||
fn next_instruction(&self,time_limit:Time<Self::TimeInner>)->Option<TimedInstruction<I,Self::TimeInner>>;
|
|
||||||
}
|
}
|
||||||
/// Apply an atomic state update
|
|
||||||
pub trait InstructionConsumer<I>{
|
pub trait InstructionConsumer<I>{
|
||||||
type TimeInner;
|
fn process_instruction(&mut self, instruction:TimedInstruction<I>);
|
||||||
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,TimeInner=T>+InstructionConsumer<I,TimeInner=T>
|
|
||||||
where
|
|
||||||
Time<T>:Copy,
|
|
||||||
{
|
|
||||||
#[inline]
|
|
||||||
fn process_exhaustive(&mut self,time_limit:Time<T>){
|
|
||||||
while let Some(instruction)=self.next_instruction(time_limit){
|
|
||||||
self.process_instruction(instruction);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl<I,T,X> InstructionFeedback<I,T> for X
|
|
||||||
where
|
|
||||||
Time<T>:Copy,
|
|
||||||
X:InstructionEmitter<I,TimeInner=T>+InstructionConsumer<I,TimeInner=T>,
|
|
||||||
{}
|
|
||||||
|
|
||||||
//PROPER PRIVATE FIELDS!!!
|
//PROPER PRIVATE FIELDS!!!
|
||||||
pub struct InstructionCollector<I,T>{
|
pub struct InstructionCollector<I>{
|
||||||
time:Time<T>,
|
time:Time,
|
||||||
instruction:Option<I>,
|
instruction:Option<I>,
|
||||||
}
|
}
|
||||||
impl<I,T> InstructionCollector<I,T>
|
impl<I> InstructionCollector<I>{
|
||||||
where Time<T>:Copy+PartialOrd,
|
pub const fn new(time:Time)->Self{
|
||||||
{
|
|
||||||
#[inline]
|
|
||||||
pub const fn new(time:Time<T>)->Self{
|
|
||||||
Self{
|
Self{
|
||||||
time,
|
time,
|
||||||
instruction:None
|
instruction:None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#[inline]
|
#[inline]
|
||||||
pub const fn time(&self)->Time<T>{
|
pub const fn time(&self)->Time{
|
||||||
self.time
|
self.time
|
||||||
}
|
}
|
||||||
#[inline]
|
pub fn collect(&mut self,instruction:Option<TimedInstruction<I>>){
|
||||||
pub fn collect(&mut self,instruction:Option<TimedInstruction<I,T>>){
|
match instruction{
|
||||||
if let Some(ins)=instruction{
|
Some(unwrap_instruction)=>{
|
||||||
if ins.time<self.time{
|
if unwrap_instruction.time<self.time {
|
||||||
self.time=ins.time;
|
self.time=unwrap_instruction.time;
|
||||||
self.instruction=Some(ins.instruction);
|
self.instruction=Some(unwrap_instruction.instruction);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
None=>(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
pub fn instruction(self)->Option<TimedInstruction<I>>{
|
||||||
#[inline]
|
|
||||||
pub fn take(self)->Option<TimedInstruction<I,T>>{
|
|
||||||
//STEAL INSTRUCTION AND DESTROY INSTRUCTIONCOLLECTOR
|
//STEAL INSTRUCTION AND DESTROY INSTRUCTIONCOLLECTOR
|
||||||
self.instruction.map(|instruction|TimedInstruction{
|
match self.instruction{
|
||||||
|
Some(instruction)=>Some(TimedInstruction{
|
||||||
time:self.time,
|
time:self.time,
|
||||||
instruction
|
instruction
|
||||||
})
|
}),
|
||||||
|
None=>None,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,26 +2,19 @@ pub use fixed_wide::fixed::{Fixed,Fix};
|
|||||||
pub use ratio_ops::ratio::{Ratio,Divide};
|
pub use ratio_ops::ratio::{Ratio,Divide};
|
||||||
|
|
||||||
//integer units
|
//integer units
|
||||||
|
|
||||||
/// specific example of a "default" time type
|
|
||||||
#[derive(Clone,Copy,Hash,Eq,PartialEq,PartialOrd,Debug)]
|
#[derive(Clone,Copy,Hash,Eq,PartialEq,PartialOrd,Debug)]
|
||||||
pub enum TimeInner{}
|
pub struct Time(i64);
|
||||||
pub type AbsoluteTime=Time<TimeInner>;
|
impl Time{
|
||||||
|
pub const MIN:Self=Self(i64::MIN);
|
||||||
#[derive(Clone,Copy,Hash,Eq,PartialEq,PartialOrd,Debug)]
|
pub const MAX:Self=Self(i64::MAX);
|
||||||
pub struct Time<T>(i64,core::marker::PhantomData<T>);
|
pub const ZERO:Self=Self(0);
|
||||||
impl<T> Time<T>{
|
pub const ONE_SECOND:Self=Self(1_000_000_000);
|
||||||
pub const MIN:Self=Self::raw(i64::MIN);
|
pub const ONE_MILLISECOND:Self=Self(1_000_000);
|
||||||
pub const MAX:Self=Self::raw(i64::MAX);
|
pub const ONE_MICROSECOND:Self=Self(1_000);
|
||||||
pub const ZERO:Self=Self::raw(0);
|
pub const ONE_NANOSECOND:Self=Self(1);
|
||||||
pub const EPSILON:Self=Self::raw(1);
|
|
||||||
pub const ONE_SECOND:Self=Self::raw(1_000_000_000);
|
|
||||||
pub const ONE_MILLISECOND:Self=Self::raw(1_000_000);
|
|
||||||
pub const ONE_MICROSECOND:Self=Self::raw(1_000);
|
|
||||||
pub const ONE_NANOSECOND:Self=Self::raw(1);
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub const fn raw(num:i64)->Self{
|
pub const fn raw(num:i64)->Self{
|
||||||
Self(num,core::marker::PhantomData)
|
Self(num)
|
||||||
}
|
}
|
||||||
#[inline]
|
#[inline]
|
||||||
pub const fn get(self)->i64{
|
pub const fn get(self)->i64{
|
||||||
@ -29,19 +22,19 @@ impl<T> Time<T>{
|
|||||||
}
|
}
|
||||||
#[inline]
|
#[inline]
|
||||||
pub const fn from_secs(num:i64)->Self{
|
pub const fn from_secs(num:i64)->Self{
|
||||||
Self::raw(Self::ONE_SECOND.0*num)
|
Self(Self::ONE_SECOND.0*num)
|
||||||
}
|
}
|
||||||
#[inline]
|
#[inline]
|
||||||
pub const fn from_millis(num:i64)->Self{
|
pub const fn from_millis(num:i64)->Self{
|
||||||
Self::raw(Self::ONE_MILLISECOND.0*num)
|
Self(Self::ONE_MILLISECOND.0*num)
|
||||||
}
|
}
|
||||||
#[inline]
|
#[inline]
|
||||||
pub const fn from_micros(num:i64)->Self{
|
pub const fn from_micros(num:i64)->Self{
|
||||||
Self::raw(Self::ONE_MICROSECOND.0*num)
|
Self(Self::ONE_MICROSECOND.0*num)
|
||||||
}
|
}
|
||||||
#[inline]
|
#[inline]
|
||||||
pub const fn from_nanos(num:i64)->Self{
|
pub const fn from_nanos(num:i64)->Self{
|
||||||
Self::raw(Self::ONE_NANOSECOND.0*num)
|
Self(Self::ONE_NANOSECOND.0*num)
|
||||||
}
|
}
|
||||||
//should I have checked subtraction? force all time variables to be positive?
|
//should I have checked subtraction? force all time variables to be positive?
|
||||||
#[inline]
|
#[inline]
|
||||||
@ -52,18 +45,14 @@ impl<T> Time<T>{
|
|||||||
pub const fn to_ratio(self)->Ratio<Planar64,Planar64>{
|
pub const fn to_ratio(self)->Ratio<Planar64,Planar64>{
|
||||||
Ratio::new(Planar64::raw(self.0),Planar64::raw(1_000_000_000))
|
Ratio::new(Planar64::raw(self.0),Planar64::raw(1_000_000_000))
|
||||||
}
|
}
|
||||||
#[inline]
|
|
||||||
pub const fn coerce<U>(self)->Time<U>{
|
|
||||||
Time::raw(self.0)
|
|
||||||
}
|
}
|
||||||
}
|
impl From<Planar64> for Time{
|
||||||
impl<T> From<Planar64> for Time<T>{
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn from(value:Planar64)->Self{
|
fn from(value:Planar64)->Self{
|
||||||
Self::raw((value*Planar64::raw(1_000_000_000)).fix_1().to_raw())
|
Time((value*Planar64::raw(1_000_000_000)).fix_1().to_raw())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl<T,Num,Den,N1,T1> From<Ratio<Num,Den>> for Time<T>
|
impl<Num,Den,N1,T1> From<Ratio<Num,Den>> for Time
|
||||||
where
|
where
|
||||||
Num:core::ops::Mul<Planar64,Output=N1>,
|
Num:core::ops::Mul<Planar64,Output=N1>,
|
||||||
N1:Divide<Den,Output=T1>,
|
N1:Divide<Den,Output=T1>,
|
||||||
@ -71,34 +60,34 @@ impl<T,Num,Den,N1,T1> From<Ratio<Num,Den>> for Time<T>
|
|||||||
{
|
{
|
||||||
#[inline]
|
#[inline]
|
||||||
fn from(value:Ratio<Num,Den>)->Self{
|
fn from(value:Ratio<Num,Den>)->Self{
|
||||||
Self::raw((value*Planar64::raw(1_000_000_000)).divide().fix().to_raw())
|
Time((value*Planar64::raw(1_000_000_000)).divide().fix().to_raw())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl<T> std::fmt::Display for Time<T>{
|
impl std::fmt::Display for Time{
|
||||||
#[inline]
|
#[inline]
|
||||||
fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{
|
fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{
|
||||||
write!(f,"{}s+{:09}ns",self.0/Self::ONE_SECOND.0,self.0%Self::ONE_SECOND.0)
|
write!(f,"{}s+{:09}ns",self.0/Self::ONE_SECOND.0,self.0%Self::ONE_SECOND.0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl<T> std::default::Default for Time<T>{
|
impl std::default::Default for Time{
|
||||||
fn default()->Self{
|
fn default()->Self{
|
||||||
Self::raw(0)
|
Self(0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl<T> std::ops::Neg for Time<T>{
|
impl std::ops::Neg for Time{
|
||||||
type Output=Self;
|
type Output=Time;
|
||||||
#[inline]
|
#[inline]
|
||||||
fn neg(self)->Self::Output {
|
fn neg(self)->Self::Output {
|
||||||
Self::raw(-self.0)
|
Time(-self.0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
macro_rules! impl_time_additive_operator {
|
macro_rules! impl_time_additive_operator {
|
||||||
($trait:ty, $method:ident) => {
|
($trait:ty, $method:ident) => {
|
||||||
impl<T> $trait for Time<T>{
|
impl $trait for Time{
|
||||||
type Output=Self;
|
type Output=Time;
|
||||||
#[inline]
|
#[inline]
|
||||||
fn $method(self,rhs:Self)->Self::Output {
|
fn $method(self,rhs:Self)->Self::Output {
|
||||||
Self::raw(self.0.$method(rhs.0))
|
Time(self.0.$method(rhs.0))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -108,7 +97,7 @@ impl_time_additive_operator!(core::ops::Sub,sub);
|
|||||||
impl_time_additive_operator!(core::ops::Rem,rem);
|
impl_time_additive_operator!(core::ops::Rem,rem);
|
||||||
macro_rules! impl_time_additive_assign_operator {
|
macro_rules! impl_time_additive_assign_operator {
|
||||||
($trait:ty, $method:ident) => {
|
($trait:ty, $method:ident) => {
|
||||||
impl<T> $trait for Time<T>{
|
impl $trait for Time{
|
||||||
#[inline]
|
#[inline]
|
||||||
fn $method(&mut self,rhs:Self){
|
fn $method(&mut self,rhs:Self){
|
||||||
self.0.$method(rhs.0)
|
self.0.$method(rhs.0)
|
||||||
@ -119,37 +108,33 @@ macro_rules! impl_time_additive_assign_operator {
|
|||||||
impl_time_additive_assign_operator!(core::ops::AddAssign,add_assign);
|
impl_time_additive_assign_operator!(core::ops::AddAssign,add_assign);
|
||||||
impl_time_additive_assign_operator!(core::ops::SubAssign,sub_assign);
|
impl_time_additive_assign_operator!(core::ops::SubAssign,sub_assign);
|
||||||
impl_time_additive_assign_operator!(core::ops::RemAssign,rem_assign);
|
impl_time_additive_assign_operator!(core::ops::RemAssign,rem_assign);
|
||||||
impl<T> std::ops::Mul for Time<T>{
|
impl std::ops::Mul for Time{
|
||||||
type Output=Ratio<fixed_wide::fixed::Fixed<2,64>,fixed_wide::fixed::Fixed<2,64>>;
|
type Output=Ratio<fixed_wide::fixed::Fixed<2,64>,fixed_wide::fixed::Fixed<2,64>>;
|
||||||
#[inline]
|
#[inline]
|
||||||
fn mul(self,rhs:Self)->Self::Output{
|
fn mul(self,rhs:Self)->Self::Output{
|
||||||
Ratio::new(Fixed::raw(self.0)*Fixed::raw(rhs.0),Fixed::raw_digit(1_000_000_000i64.pow(2)))
|
Ratio::new(Fixed::raw(self.0)*Fixed::raw(rhs.0),Fixed::raw_digit(1_000_000_000i64.pow(2)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl<T> std::ops::Div<i64> for Time<T>{
|
impl std::ops::Div<i64> for Time{
|
||||||
type Output=Self;
|
type Output=Time;
|
||||||
#[inline]
|
#[inline]
|
||||||
fn div(self,rhs:i64)->Self::Output{
|
fn div(self,rhs:i64)->Self::Output{
|
||||||
Self::raw(self.0/rhs)
|
Time(self.0/rhs)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl<T> std::ops::Mul<i64> for Time<T>{
|
impl std::ops::Mul<i64> for Time{
|
||||||
type Output=Self;
|
type Output=Time;
|
||||||
#[inline]
|
#[inline]
|
||||||
fn mul(self,rhs:i64)->Self::Output{
|
fn mul(self,rhs:i64)->Self::Output{
|
||||||
Self::raw(self.0*rhs)
|
Time(self.0*rhs)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl<T> core::ops::Mul<Time<T>> for Planar64{
|
impl core::ops::Mul<Time> for Planar64{
|
||||||
type Output=Ratio<Fixed<2,64>,Planar64>;
|
type Output=Ratio<Fixed<2,64>,Planar64>;
|
||||||
fn mul(self,rhs:Time<T>)->Self::Output{
|
fn mul(self,rhs:Time)->Self::Output{
|
||||||
Ratio::new(self*Fixed::raw(rhs.0),Planar64::raw(1_000_000_000))
|
Ratio::new(self*Fixed::raw(rhs.0),Planar64::raw(1_000_000_000))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#[cfg(test)]
|
|
||||||
mod test_time{
|
|
||||||
use super::*;
|
|
||||||
type Time=super::AbsoluteTime;
|
|
||||||
#[test]
|
#[test]
|
||||||
fn time_from_planar64(){
|
fn time_from_planar64(){
|
||||||
let a:Time=Planar64::from(1).into();
|
let a:Time=Planar64::from(1).into();
|
||||||
@ -171,7 +156,6 @@ mod test_time{
|
|||||||
let b=Planar64::from(2);
|
let b=Planar64::from(2);
|
||||||
assert_eq!(b*a,Ratio::new(Fixed::<2,64>::raw_digit(1_000_000_000*(1<<32))<<2,Fixed::<1,32>::raw_digit(1_000_000_000)));
|
assert_eq!(b*a,Ratio::new(Fixed::<2,64>::raw_digit(1_000_000_000*(1<<32))<<2,Fixed::<1,32>::raw_digit(1_000_000_000)));
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
const fn gcd(mut a:u64,mut b:u64)->u64{
|
const fn gcd(mut a:u64,mut b:u64)->u64{
|
||||||
|
@ -7,7 +7,6 @@ pub mod mouse;
|
|||||||
pub mod timer;
|
pub mod timer;
|
||||||
pub mod integer;
|
pub mod integer;
|
||||||
pub mod physics;
|
pub mod physics;
|
||||||
pub mod session;
|
|
||||||
pub mod updatable;
|
pub mod updatable;
|
||||||
pub mod instruction;
|
pub mod instruction;
|
||||||
pub mod gameplay_attributes;
|
pub mod gameplay_attributes;
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
use std::collections::HashMap;
|
|
||||||
|
|
||||||
use crate::integer::{Planar64Vec3,Planar64Affine3};
|
use crate::integer::{Planar64Vec3,Planar64Affine3};
|
||||||
use crate::gameplay_attributes;
|
use crate::gameplay_attributes;
|
||||||
|
|
||||||
@ -125,87 +123,6 @@ pub struct Mesh{
|
|||||||
pub physics_groups:Vec<IndexedPhysicsGroup>,
|
pub physics_groups:Vec<IndexedPhysicsGroup>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
|
||||||
pub struct MeshBuilder{
|
|
||||||
unique_pos:Vec<Planar64Vec3>,//Unit32Vec3
|
|
||||||
unique_normal:Vec<Planar64Vec3>,//Unit32Vec3
|
|
||||||
unique_tex:Vec<TextureCoordinate>,
|
|
||||||
unique_color:Vec<Color4>,
|
|
||||||
unique_vertices:Vec<IndexedVertex>,
|
|
||||||
pos_id_from:HashMap<Planar64Vec3,PositionId>,//Unit32Vec3
|
|
||||||
normal_id_from:HashMap<Planar64Vec3,NormalId>,//Unit32Vec3
|
|
||||||
tex_id_from:HashMap<[u32;2],TextureCoordinateId>,
|
|
||||||
color_id_from:HashMap<[u32;4],ColorId>,
|
|
||||||
vertex_id_from:HashMap<IndexedVertex,VertexId>,
|
|
||||||
}
|
|
||||||
impl MeshBuilder{
|
|
||||||
pub fn new()->Self{
|
|
||||||
Self::default()
|
|
||||||
}
|
|
||||||
pub fn build(
|
|
||||||
self,
|
|
||||||
polygon_groups:Vec<PolygonGroup>,
|
|
||||||
graphics_groups:Vec<IndexedGraphicsGroup>,
|
|
||||||
physics_groups:Vec<IndexedPhysicsGroup>,
|
|
||||||
)->Mesh{
|
|
||||||
let MeshBuilder{
|
|
||||||
unique_pos,
|
|
||||||
unique_normal,
|
|
||||||
unique_tex,
|
|
||||||
unique_color,
|
|
||||||
unique_vertices,
|
|
||||||
..
|
|
||||||
}=self;
|
|
||||||
Mesh{
|
|
||||||
unique_pos,
|
|
||||||
unique_normal,
|
|
||||||
unique_tex,
|
|
||||||
unique_color,
|
|
||||||
unique_vertices,
|
|
||||||
polygon_groups,
|
|
||||||
graphics_groups,
|
|
||||||
physics_groups,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub fn acquire_pos_id(&mut self,pos:Planar64Vec3)->PositionId{
|
|
||||||
*self.pos_id_from.entry(pos).or_insert_with(||{
|
|
||||||
let pos_id=PositionId::new(self.unique_pos.len() as u32);
|
|
||||||
self.unique_pos.push(pos);
|
|
||||||
pos_id
|
|
||||||
})
|
|
||||||
}
|
|
||||||
pub fn acquire_normal_id(&mut self,normal:Planar64Vec3)->NormalId{
|
|
||||||
*self.normal_id_from.entry(normal).or_insert_with(||{
|
|
||||||
let normal_id=NormalId::new(self.unique_normal.len() as u32);
|
|
||||||
self.unique_normal.push(normal);
|
|
||||||
normal_id
|
|
||||||
})
|
|
||||||
}
|
|
||||||
pub fn acquire_tex_id(&mut self,tex:TextureCoordinate)->TextureCoordinateId{
|
|
||||||
let h=tex.to_array().map(f32::to_bits);
|
|
||||||
*self.tex_id_from.entry(h).or_insert_with(||{
|
|
||||||
let tex_id=TextureCoordinateId::new(self.unique_tex.len() as u32);
|
|
||||||
self.unique_tex.push(tex);
|
|
||||||
tex_id
|
|
||||||
})
|
|
||||||
}
|
|
||||||
pub fn acquire_color_id(&mut self,color:Color4)->ColorId{
|
|
||||||
let h=color.to_array().map(f32::to_bits);
|
|
||||||
*self.color_id_from.entry(h).or_insert_with(||{
|
|
||||||
let color_id=ColorId::new(self.unique_color.len() as u32);
|
|
||||||
self.unique_color.push(color);
|
|
||||||
color_id
|
|
||||||
})
|
|
||||||
}
|
|
||||||
pub fn acquire_vertex_id(&mut self,vertex:IndexedVertex)->VertexId{
|
|
||||||
*self.vertex_id_from.entry(vertex.clone()).or_insert_with(||{
|
|
||||||
let vertex_id=VertexId::new(self.unique_vertices.len() as u32);
|
|
||||||
self.unique_vertices.push(vertex);
|
|
||||||
vertex_id
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug,Clone,Copy,Hash,id::Id,Eq,PartialEq)]
|
#[derive(Debug,Clone,Copy,Hash,id::Id,Eq,PartialEq)]
|
||||||
pub struct ModelId(u32);
|
pub struct ModelId(u32);
|
||||||
pub struct Model{
|
pub struct Model{
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
use crate::integer::Time;
|
use crate::integer::Time;
|
||||||
|
|
||||||
#[derive(Clone,Debug)]
|
#[derive(Clone,Debug)]
|
||||||
pub struct MouseState<T>{
|
pub struct MouseState{
|
||||||
pub pos:glam::IVec2,
|
pub pos:glam::IVec2,
|
||||||
pub time:Time<T>,
|
pub time:Time,
|
||||||
}
|
}
|
||||||
impl<T> Default for MouseState<T>{
|
impl Default for MouseState{
|
||||||
fn default()->Self{
|
fn default()->Self{
|
||||||
Self{
|
Self{
|
||||||
time:Time::ZERO,
|
time:Time::ZERO,
|
||||||
@ -13,10 +13,8 @@ impl<T> Default for MouseState<T>{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl<T> MouseState<T>
|
impl MouseState{
|
||||||
where Time<T>:Copy,
|
pub fn lerp(&self,target:&MouseState,time:Time)->glam::IVec2{
|
||||||
{
|
|
||||||
pub fn lerp(&self,target:&MouseState<T>,time:Time<T>)->glam::IVec2{
|
|
||||||
let m0=self.pos.as_i64vec2();
|
let m0=self.pos.as_i64vec2();
|
||||||
let m1=target.pos.as_i64vec2();
|
let m1=target.pos.as_i64vec2();
|
||||||
//these are deltas
|
//these are deltas
|
||||||
|
@ -1,33 +1,7 @@
|
|||||||
use crate::mouse::MouseState;
|
|
||||||
use crate::gameplay_modes::{ModeId,StageId};
|
|
||||||
|
|
||||||
#[derive(Clone,Copy,Hash,Eq,PartialEq,PartialOrd,Debug)]
|
|
||||||
pub enum TimeInner{}
|
|
||||||
pub type Time=crate::integer::Time<TimeInner>;
|
|
||||||
|
|
||||||
#[derive(Clone,Debug)]
|
#[derive(Clone,Debug)]
|
||||||
pub enum Instruction{
|
pub enum Instruction{
|
||||||
Mouse(MouseInstruction),
|
ReplaceMouse(crate::mouse::MouseState,crate::mouse::MouseState),
|
||||||
SetControl(SetControlInstruction),
|
SetNextMouse(crate::mouse::MouseState),
|
||||||
Mode(ModeInstruction),
|
|
||||||
Misc(MiscInstruction),
|
|
||||||
/// Idle: there were no input events, but the simulation is safe to advance to this timestep
|
|
||||||
Idle,
|
|
||||||
}
|
|
||||||
impl Instruction{
|
|
||||||
pub const IDLE:Self=Self::Idle;
|
|
||||||
}
|
|
||||||
#[derive(Clone,Debug)]
|
|
||||||
pub enum MouseInstruction{
|
|
||||||
/// Replace the entire interpolation state to avoid dividing by zero when replacing twice
|
|
||||||
ReplaceMouse{
|
|
||||||
m0:MouseState<TimeInner>,
|
|
||||||
m1:MouseState<TimeInner>,
|
|
||||||
},
|
|
||||||
SetNextMouse(MouseState<TimeInner>),
|
|
||||||
}
|
|
||||||
#[derive(Clone,Debug)]
|
|
||||||
pub enum SetControlInstruction{
|
|
||||||
SetMoveRight(bool),
|
SetMoveRight(bool),
|
||||||
SetMoveUp(bool),
|
SetMoveUp(bool),
|
||||||
SetMoveBack(bool),
|
SetMoveBack(bool),
|
||||||
@ -36,21 +10,18 @@ pub enum SetControlInstruction{
|
|||||||
SetMoveForward(bool),
|
SetMoveForward(bool),
|
||||||
SetJump(bool),
|
SetJump(bool),
|
||||||
SetZoom(bool),
|
SetZoom(bool),
|
||||||
}
|
|
||||||
#[derive(Clone,Debug)]
|
|
||||||
pub enum ModeInstruction{
|
|
||||||
/// Reset: fully replace the physics state.
|
/// Reset: fully replace the physics state.
|
||||||
/// This forgets all inputs and settings which need to be reapplied.
|
/// This forgets all inputs and settings which need to be reapplied.
|
||||||
Reset,
|
Reset,
|
||||||
/// Restart: Teleport to the start zone.
|
/// Restart: Teleport to the start zone.
|
||||||
/// This runs when you press R or teleport to a bonus
|
Restart,
|
||||||
Restart(ModeId),
|
|
||||||
/// Spawn: Teleport to a specific mode's spawn
|
/// Spawn: Teleport to a specific mode's spawn
|
||||||
/// This runs when the map loads to put you at the map lobby
|
/// Sets current mode & spawn
|
||||||
Spawn(ModeId,StageId),
|
Spawn(crate::gameplay_modes::ModeId,crate::gameplay_modes::StageId),
|
||||||
}
|
Idle,
|
||||||
#[derive(Clone,Debug)]
|
//Idle: there were no input events, but the simulation is safe to advance to this timestep
|
||||||
pub enum MiscInstruction{
|
//for interpolation / networking / playback reasons, most playback heads will always want
|
||||||
|
//to be 1 instruction ahead to generate the next state for interpolation.
|
||||||
PracticeFly,
|
PracticeFly,
|
||||||
SetSensitivity(crate::integer::Ratio64Vec2),
|
SetSensitivity(crate::integer::Ratio64Vec2),
|
||||||
}
|
}
|
||||||
|
@ -1,10 +1,5 @@
|
|||||||
use crate::timer::{TimerFixed,Realtime,Paused,Unpaused};
|
use crate::timer::{TimerFixed,Realtime,Paused,Unpaused};
|
||||||
|
use crate::integer::Time;
|
||||||
use crate::physics::{TimeInner as PhysicsTimeInner,Time as PhysicsTime};
|
|
||||||
|
|
||||||
#[derive(Clone,Copy,Hash,Eq,PartialEq,PartialOrd,Debug)]
|
|
||||||
pub enum TimeInner{}
|
|
||||||
pub type Time=crate::integer::Time<TimeInner>;
|
|
||||||
|
|
||||||
#[derive(Clone,Copy,Debug)]
|
#[derive(Clone,Copy,Debug)]
|
||||||
pub enum FlagReason{
|
pub enum FlagReason{
|
||||||
@ -20,11 +15,6 @@ pub enum FlagReason{
|
|||||||
}
|
}
|
||||||
impl ToString for FlagReason{
|
impl ToString for FlagReason{
|
||||||
fn to_string(&self)->String{
|
fn to_string(&self)->String{
|
||||||
self.as_ref().to_owned()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl AsRef<str> for FlagReason{
|
|
||||||
fn as_ref(&self)->&str{
|
|
||||||
match self{
|
match self{
|
||||||
FlagReason::Anticheat=>"Passed through anticheat zone.",
|
FlagReason::Anticheat=>"Passed through anticheat zone.",
|
||||||
FlagReason::StyleChange=>"Changed style.",
|
FlagReason::StyleChange=>"Changed style.",
|
||||||
@ -35,7 +25,7 @@ impl AsRef<str> for FlagReason{
|
|||||||
FlagReason::Timescale=>"Timescale is not allowed in this style.",
|
FlagReason::Timescale=>"Timescale is not allowed in this style.",
|
||||||
FlagReason::TimeTravel=>"Time travel is not allowed in this style.",
|
FlagReason::TimeTravel=>"Time travel is not allowed in this style.",
|
||||||
FlagReason::Teleport=>"Illegal teleport.",
|
FlagReason::Teleport=>"Illegal teleport.",
|
||||||
}
|
}.to_owned()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -55,8 +45,8 @@ impl std::error::Error for Error{}
|
|||||||
#[derive(Clone,Copy,Debug)]
|
#[derive(Clone,Copy,Debug)]
|
||||||
enum RunState{
|
enum RunState{
|
||||||
Created,
|
Created,
|
||||||
Started{timer:TimerFixed<Realtime<PhysicsTimeInner,TimeInner>,Unpaused>},
|
Started{timer:TimerFixed<Realtime,Unpaused>},
|
||||||
Finished{timer:TimerFixed<Realtime<PhysicsTimeInner,TimeInner>,Paused>},
|
Finished{timer:TimerFixed<Realtime,Paused>},
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone,Copy,Debug)]
|
#[derive(Clone,Copy,Debug)]
|
||||||
@ -72,14 +62,14 @@ impl Run{
|
|||||||
flagged:None,
|
flagged:None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn time(&self,time:PhysicsTime)->Time{
|
pub fn time(&self,time:Time)->Time{
|
||||||
match &self.state{
|
match &self.state{
|
||||||
RunState::Created=>Time::ZERO,
|
RunState::Created=>Time::ZERO,
|
||||||
RunState::Started{timer}=>timer.time(time),
|
RunState::Started{timer}=>timer.time(time),
|
||||||
RunState::Finished{timer}=>timer.time(),
|
RunState::Finished{timer}=>timer.time(time),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn start(&mut self,time:PhysicsTime)->Result<(),Error>{
|
pub fn start(&mut self,time:Time)->Result<(),Error>{
|
||||||
match &self.state{
|
match &self.state{
|
||||||
RunState::Created=>{
|
RunState::Created=>{
|
||||||
self.state=RunState::Started{
|
self.state=RunState::Started{
|
||||||
@ -91,7 +81,7 @@ impl Run{
|
|||||||
RunState::Finished{..}=>Err(Error::AlreadyFinished),
|
RunState::Finished{..}=>Err(Error::AlreadyFinished),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn finish(&mut self,time:PhysicsTime)->Result<(),Error>{
|
pub fn finish(&mut self,time:Time)->Result<(),Error>{
|
||||||
//this uses Copy
|
//this uses Copy
|
||||||
match &self.state{
|
match &self.state{
|
||||||
RunState::Created=>Err(Error::NotStarted),
|
RunState::Created=>Err(Error::NotStarted),
|
||||||
@ -110,10 +100,4 @@ impl Run{
|
|||||||
self.flagged=Some(flag_reason);
|
self.flagged=Some(flag_reason);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn get_finish_time(&self)->Option<Time>{
|
|
||||||
match &self.state{
|
|
||||||
RunState::Finished{timer}=>Some(timer.time()),
|
|
||||||
_=>None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,3 +0,0 @@
|
|||||||
#[derive(Clone,Copy,Hash,Eq,PartialEq,PartialOrd,Debug)]
|
|
||||||
pub enum TimeInner{}
|
|
||||||
pub type Time=crate::integer::Time<TimeInner>;
|
|
@ -22,106 +22,79 @@ impl PauseState for Unpaused{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone,Copy,Hash,Eq,PartialEq,PartialOrd,Debug)]
|
|
||||||
pub enum Inner{}
|
|
||||||
type InnerTime=Time<Inner>;
|
|
||||||
|
|
||||||
#[derive(Clone,Copy,Debug)]
|
#[derive(Clone,Copy,Debug)]
|
||||||
pub struct Realtime<In,Out>{
|
pub struct Realtime{
|
||||||
offset:InnerTime,
|
offset:Time,
|
||||||
_in:core::marker::PhantomData<In>,
|
|
||||||
_out:core::marker::PhantomData<Out>,
|
|
||||||
}
|
|
||||||
impl<In,Out> Realtime<In,Out>{
|
|
||||||
pub const fn new(offset:InnerTime)->Self{
|
|
||||||
Self{
|
|
||||||
offset,
|
|
||||||
_in:core::marker::PhantomData,
|
|
||||||
_out:core::marker::PhantomData,
|
|
||||||
}
|
}
|
||||||
|
impl Realtime{
|
||||||
|
pub const fn new(offset:Time)->Self{
|
||||||
|
Self{offset}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone,Copy,Debug)]
|
#[derive(Clone,Copy,Debug)]
|
||||||
pub struct Scaled<In,Out>{
|
pub struct Scaled{
|
||||||
scale:Ratio64,
|
scale:Ratio64,
|
||||||
offset:InnerTime,
|
offset:Time,
|
||||||
_in:core::marker::PhantomData<In>,
|
|
||||||
_out:core::marker::PhantomData<Out>,
|
|
||||||
}
|
|
||||||
impl<In,Out> Scaled<In,Out>
|
|
||||||
where Time<In>:Copy,
|
|
||||||
{
|
|
||||||
pub const fn new(scale:Ratio64,offset:InnerTime)->Self{
|
|
||||||
Self{
|
|
||||||
scale,
|
|
||||||
offset,
|
|
||||||
_in:core::marker::PhantomData,
|
|
||||||
_out:core::marker::PhantomData,
|
|
||||||
}
|
}
|
||||||
|
impl Scaled{
|
||||||
|
pub const fn new(scale:Ratio64,offset:Time)->Self{
|
||||||
|
Self{scale,offset}
|
||||||
}
|
}
|
||||||
const fn with_scale(scale:Ratio64)->Self{
|
const fn with_scale(scale:Ratio64)->Self{
|
||||||
Self::new(scale,InnerTime::ZERO)
|
Self{scale,offset:Time::ZERO}
|
||||||
}
|
}
|
||||||
const fn scale(&self,time:Time<In>)->InnerTime{
|
const fn scale(&self,time:Time)->Time{
|
||||||
InnerTime::raw(self.scale.mul_int(time.get()))
|
Time::raw(self.scale.mul_int(time.get()))
|
||||||
}
|
}
|
||||||
const fn get_scale(&self)->Ratio64{
|
const fn get_scale(&self)->Ratio64{
|
||||||
self.scale
|
self.scale
|
||||||
}
|
}
|
||||||
fn set_scale(&mut self,time:Time<In>,new_scale:Ratio64){
|
fn set_scale(&mut self,time:Time,new_scale:Ratio64){
|
||||||
let new_time=self.get_time(time);
|
let new_time=self.get_time(time);
|
||||||
self.scale=new_scale;
|
self.scale=new_scale;
|
||||||
self.set_time(time,new_time);
|
self.set_time(time,new_time);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait TimerState{
|
pub trait TimerState:Copy+std::fmt::Debug{
|
||||||
type In;
|
|
||||||
type Out;
|
|
||||||
fn identity()->Self;
|
fn identity()->Self;
|
||||||
fn get_time(&self,time:Time<Self::In>)->Time<Self::Out>;
|
fn get_time(&self,time:Time)->Time;
|
||||||
fn set_time(&mut self,time:Time<Self::In>,new_time:Time<Self::Out>);
|
fn set_time(&mut self,time:Time,new_time:Time);
|
||||||
fn get_offset(&self)->InnerTime;
|
fn get_offset(&self)->Time;
|
||||||
fn set_offset(&mut self,offset:InnerTime);
|
fn set_offset(&mut self,offset:Time);
|
||||||
}
|
}
|
||||||
impl<In,Out> TimerState for Realtime<In,Out>{
|
impl TimerState for Realtime{
|
||||||
type In=In;
|
|
||||||
type Out=Out;
|
|
||||||
fn identity()->Self{
|
fn identity()->Self{
|
||||||
Self::new(InnerTime::ZERO)
|
Self{offset:Time::ZERO}
|
||||||
}
|
}
|
||||||
fn get_time(&self,time:Time<In>)->Time<Out>{
|
fn get_time(&self,time:Time)->Time{
|
||||||
time.coerce()+self.offset.coerce()
|
time+self.offset
|
||||||
}
|
}
|
||||||
fn set_time(&mut self,time:Time<In>,new_time:Time<Out>){
|
fn set_time(&mut self,time:Time,new_time:Time){
|
||||||
self.offset=new_time.coerce()-time.coerce();
|
self.offset=new_time-time;
|
||||||
}
|
}
|
||||||
fn get_offset(&self)->InnerTime{
|
fn get_offset(&self)->Time{
|
||||||
self.offset
|
self.offset
|
||||||
}
|
}
|
||||||
fn set_offset(&mut self,offset:InnerTime){
|
fn set_offset(&mut self,offset:Time){
|
||||||
self.offset=offset;
|
self.offset=offset;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl<In,Out> TimerState for Scaled<In,Out>
|
impl TimerState for Scaled{
|
||||||
where Time<In>:Copy,
|
|
||||||
{
|
|
||||||
type In=In;
|
|
||||||
type Out=Out;
|
|
||||||
fn identity()->Self{
|
fn identity()->Self{
|
||||||
Self::new(Ratio64::ONE,InnerTime::ZERO)
|
Self{scale:Ratio64::ONE,offset:Time::ZERO}
|
||||||
}
|
}
|
||||||
fn get_time(&self,time:Time<In>)->Time<Out>{
|
fn get_time(&self,time:Time)->Time{
|
||||||
(self.scale(time)+self.offset).coerce()
|
self.scale(time)+self.offset
|
||||||
}
|
}
|
||||||
fn set_time(&mut self,time:Time<In>,new_time:Time<Out>){
|
fn set_time(&mut self,time:Time,new_time:Time){
|
||||||
self.offset=new_time.coerce()-self.scale(time);
|
self.offset=new_time-self.scale(time);
|
||||||
}
|
}
|
||||||
fn get_offset(&self)->InnerTime{
|
fn get_offset(&self)->Time{
|
||||||
self.offset
|
self.offset
|
||||||
}
|
}
|
||||||
fn set_offset(&mut self,offset:InnerTime){
|
fn set_offset(&mut self,offset:Time){
|
||||||
self.offset=offset;
|
self.offset=offset;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -133,10 +106,8 @@ pub struct TimerFixed<T:TimerState,P:PauseState>{
|
|||||||
}
|
}
|
||||||
|
|
||||||
//scaled timer methods are generic across PauseState
|
//scaled timer methods are generic across PauseState
|
||||||
impl<P:PauseState,In,Out> TimerFixed<Scaled<In,Out>,P>
|
impl<P:PauseState> TimerFixed<Scaled,P>{
|
||||||
where Time<In>:Copy,
|
pub fn scaled(time:Time,new_time:Time,scale:Ratio64)->Self{
|
||||||
{
|
|
||||||
pub fn scaled(time:Time<In>,new_time:Time<Out>,scale:Ratio64)->Self{
|
|
||||||
let mut timer=Self{
|
let mut timer=Self{
|
||||||
state:Scaled::with_scale(scale),
|
state:Scaled::with_scale(scale),
|
||||||
_paused:P::new(),
|
_paused:P::new(),
|
||||||
@ -147,17 +118,15 @@ impl<P:PauseState,In,Out> TimerFixed<Scaled<In,Out>,P>
|
|||||||
pub const fn get_scale(&self)->Ratio64{
|
pub const fn get_scale(&self)->Ratio64{
|
||||||
self.state.get_scale()
|
self.state.get_scale()
|
||||||
}
|
}
|
||||||
pub fn set_scale(&mut self,time:Time<In>,new_scale:Ratio64){
|
pub fn set_scale(&mut self,time:Time,new_scale:Ratio64){
|
||||||
self.state.set_scale(time,new_scale)
|
self.state.set_scale(time,new_scale)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//pause and unpause is generic across TimerState
|
//pause and unpause is generic across TimerState
|
||||||
impl<T:TimerState> TimerFixed<T,Paused>
|
impl<T:TimerState> TimerFixed<T,Paused>{
|
||||||
where Time<T::In>:Copy,
|
pub fn into_unpaused(self,time:Time)->TimerFixed<T,Unpaused>{
|
||||||
{
|
let new_time=self.time(time);
|
||||||
pub fn into_unpaused(self,time:Time<T::In>)->TimerFixed<T,Unpaused>{
|
|
||||||
let new_time=self.time();
|
|
||||||
let mut timer=TimerFixed{
|
let mut timer=TimerFixed{
|
||||||
state:self.state,
|
state:self.state,
|
||||||
_paused:Unpaused,
|
_paused:Unpaused,
|
||||||
@ -165,14 +134,9 @@ impl<T:TimerState> TimerFixed<T,Paused>
|
|||||||
timer.set_time(time,new_time);
|
timer.set_time(time,new_time);
|
||||||
timer
|
timer
|
||||||
}
|
}
|
||||||
pub fn time(&self)->Time<T::Out>{
|
|
||||||
self.state.get_offset().coerce()
|
|
||||||
}
|
}
|
||||||
}
|
impl<T:TimerState> TimerFixed<T,Unpaused>{
|
||||||
impl<T:TimerState> TimerFixed<T,Unpaused>
|
pub fn into_paused(self,time:Time)->TimerFixed<T,Paused>{
|
||||||
where Time<T::In>:Copy,
|
|
||||||
{
|
|
||||||
pub fn into_paused(self,time:Time<T::In>)->TimerFixed<T,Paused>{
|
|
||||||
let new_time=self.time(time);
|
let new_time=self.time(time);
|
||||||
let mut timer=TimerFixed{
|
let mut timer=TimerFixed{
|
||||||
state:self.state,
|
state:self.state,
|
||||||
@ -181,14 +145,11 @@ impl<T:TimerState> TimerFixed<T,Unpaused>
|
|||||||
timer.set_time(time,new_time);
|
timer.set_time(time,new_time);
|
||||||
timer
|
timer
|
||||||
}
|
}
|
||||||
pub fn time(&self,time:Time<T::In>)->Time<T::Out>{
|
|
||||||
self.state.get_time(time)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//the new constructor and time queries are generic across both
|
//the new constructor and time queries are generic across both
|
||||||
impl<T:TimerState,P:PauseState> TimerFixed<T,P>{
|
impl<T:TimerState,P:PauseState> TimerFixed<T,P>{
|
||||||
pub fn new(time:Time<T::In>,new_time:Time<T::Out>)->Self{
|
pub fn new(time:Time,new_time:Time)->Self{
|
||||||
let mut timer=Self{
|
let mut timer=Self{
|
||||||
state:T::identity(),
|
state:T::identity(),
|
||||||
_paused:P::new(),
|
_paused:P::new(),
|
||||||
@ -205,9 +166,15 @@ impl<T:TimerState,P:PauseState> TimerFixed<T,P>{
|
|||||||
pub fn into_state(self)->T{
|
pub fn into_state(self)->T{
|
||||||
self.state
|
self.state
|
||||||
}
|
}
|
||||||
pub fn set_time(&mut self,time:Time<T::In>,new_time:Time<T::Out>){
|
pub fn time(&self,time:Time)->Time{
|
||||||
match P::IS_PAUSED{
|
match P::IS_PAUSED{
|
||||||
true=>self.state.set_offset(new_time.coerce()),
|
true=>self.state.get_offset(),
|
||||||
|
false=>self.state.get_time(time),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn set_time(&mut self,time:Time,new_time:Time){
|
||||||
|
match P::IS_PAUSED{
|
||||||
|
true=>self.state.set_offset(new_time),
|
||||||
false=>self.state.set_time(time,new_time),
|
false=>self.state.set_time(time,new_time),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -231,11 +198,7 @@ pub enum Timer<T:TimerState>{
|
|||||||
Paused(TimerFixed<T,Paused>),
|
Paused(TimerFixed<T,Paused>),
|
||||||
Unpaused(TimerFixed<T,Unpaused>),
|
Unpaused(TimerFixed<T,Unpaused>),
|
||||||
}
|
}
|
||||||
impl<T:TimerState> Timer<T>
|
impl<T:TimerState> Timer<T>{
|
||||||
where
|
|
||||||
T:Copy,
|
|
||||||
Time<T::In>:Copy,
|
|
||||||
{
|
|
||||||
pub fn from_state(state:T,paused:bool)->Self{
|
pub fn from_state(state:T,paused:bool)->Self{
|
||||||
match paused{
|
match paused{
|
||||||
true=>Self::Paused(TimerFixed::from_state(state)),
|
true=>Self::Paused(TimerFixed::from_state(state)),
|
||||||
@ -248,32 +211,32 @@ impl<T:TimerState> Timer<T>
|
|||||||
Self::Unpaused(timer)=>(timer.into_state(),false),
|
Self::Unpaused(timer)=>(timer.into_state(),false),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn paused(time:Time<T::In>,new_time:Time<T::Out>)->Self{
|
pub fn paused(time:Time,new_time:Time)->Self{
|
||||||
Self::Paused(TimerFixed::new(time,new_time))
|
Self::Paused(TimerFixed::new(time,new_time))
|
||||||
}
|
}
|
||||||
pub fn unpaused(time:Time<T::In>,new_time:Time<T::Out>)->Self{
|
pub fn unpaused(time:Time,new_time:Time)->Self{
|
||||||
Self::Unpaused(TimerFixed::new(time,new_time))
|
Self::Unpaused(TimerFixed::new(time,new_time))
|
||||||
}
|
}
|
||||||
pub fn time(&self,time:Time<T::In>)->Time<T::Out>{
|
pub fn time(&self,time:Time)->Time{
|
||||||
match self{
|
match self{
|
||||||
Self::Paused(timer)=>timer.time(),
|
Self::Paused(timer)=>timer.time(time),
|
||||||
Self::Unpaused(timer)=>timer.time(time),
|
Self::Unpaused(timer)=>timer.time(time),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn set_time(&mut self,time:Time<T::In>,new_time:Time<T::Out>){
|
pub fn set_time(&mut self,time:Time,new_time:Time){
|
||||||
match self{
|
match self{
|
||||||
Self::Paused(timer)=>timer.set_time(time,new_time),
|
Self::Paused(timer)=>timer.set_time(time,new_time),
|
||||||
Self::Unpaused(timer)=>timer.set_time(time,new_time),
|
Self::Unpaused(timer)=>timer.set_time(time,new_time),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn pause(&mut self,time:Time<T::In>)->Result<(),Error>{
|
pub fn pause(&mut self,time:Time)->Result<(),Error>{
|
||||||
*self=match *self{
|
*self=match *self{
|
||||||
Self::Paused(_)=>return Err(Error::AlreadyPaused),
|
Self::Paused(_)=>return Err(Error::AlreadyPaused),
|
||||||
Self::Unpaused(timer)=>Self::Paused(timer.into_paused(time)),
|
Self::Unpaused(timer)=>Self::Paused(timer.into_paused(time)),
|
||||||
};
|
};
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
pub fn unpause(&mut self,time:Time<T::In>)->Result<(),Error>{
|
pub fn unpause(&mut self,time:Time)->Result<(),Error>{
|
||||||
*self=match *self{
|
*self=match *self{
|
||||||
Self::Paused(timer)=>Self::Unpaused(timer.into_unpaused(time)),
|
Self::Paused(timer)=>Self::Unpaused(timer.into_unpaused(time)),
|
||||||
Self::Unpaused(_)=>return Err(Error::AlreadyUnpaused),
|
Self::Unpaused(_)=>return Err(Error::AlreadyUnpaused),
|
||||||
@ -286,7 +249,7 @@ impl<T:TimerState> Timer<T>
|
|||||||
Self::Unpaused(_)=>false,
|
Self::Unpaused(_)=>false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn set_paused(&mut self,time:Time<T::In>,paused:bool)->Result<(),Error>{
|
pub fn set_paused(&mut self,time:Time,paused:bool)->Result<(),Error>{
|
||||||
match paused{
|
match paused{
|
||||||
true=>self.pause(time),
|
true=>self.pause(time),
|
||||||
false=>self.unpause(time),
|
false=>self.unpause(time),
|
||||||
@ -294,16 +257,14 @@ impl<T:TimerState> Timer<T>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
//scaled timer methods are generic across PauseState
|
//scaled timer methods are generic across PauseState
|
||||||
impl<In,Out> Timer<Scaled<In,Out>>
|
impl Timer<Scaled>{
|
||||||
where Time<In>:Copy,
|
|
||||||
{
|
|
||||||
pub const fn get_scale(&self)->Ratio64{
|
pub const fn get_scale(&self)->Ratio64{
|
||||||
match self{
|
match self{
|
||||||
Self::Paused(timer)=>timer.get_scale(),
|
Self::Paused(timer)=>timer.get_scale(),
|
||||||
Self::Unpaused(timer)=>timer.get_scale(),
|
Self::Unpaused(timer)=>timer.get_scale(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn set_scale(&mut self,time:Time<In>,new_scale:Ratio64){
|
pub fn set_scale(&mut self,time:Time,new_scale:Ratio64){
|
||||||
match self{
|
match self{
|
||||||
Self::Paused(timer)=>timer.set_scale(time,new_scale),
|
Self::Paused(timer)=>timer.set_scale(time,new_scale),
|
||||||
Self::Unpaused(timer)=>timer.set_scale(time,new_scale),
|
Self::Unpaused(timer)=>timer.set_scale(time,new_scale),
|
||||||
@ -319,17 +280,12 @@ mod test{
|
|||||||
Time::from_secs($s)
|
Time::from_secs($s)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone,Copy,Hash,Eq,PartialEq,PartialOrd,Debug)]
|
|
||||||
enum Parent{}
|
|
||||||
#[derive(Clone,Copy,Hash,Eq,PartialEq,PartialOrd,Debug)]
|
|
||||||
enum Calculated{}
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_timerfixed_scaled(){
|
fn test_timerfixed_scaled(){
|
||||||
//create a paused timer that reads 0s
|
//create a paused timer that reads 0s
|
||||||
let timer=TimerFixed::<Scaled<Parent,Calculated>,Paused>::from_state(Scaled::new(0.5f32.try_into().unwrap(),sec!(0)));
|
let timer=TimerFixed::<Scaled,Paused>::from_state(Scaled{scale:0.5f32.try_into().unwrap(),offset:sec!(0)});
|
||||||
//the paused timer at 1 second should read 0s
|
//the paused timer at 1 second should read 0s
|
||||||
assert_eq!(timer.time(),sec!(0));
|
assert_eq!(timer.time(sec!(1)),sec!(0));
|
||||||
|
|
||||||
//unpause it after one second
|
//unpause it after one second
|
||||||
let timer=timer.into_unpaused(sec!(1));
|
let timer=timer.into_unpaused(sec!(1));
|
||||||
@ -339,12 +295,12 @@ mod test{
|
|||||||
//pause the timer after 11 seconds
|
//pause the timer after 11 seconds
|
||||||
let timer=timer.into_paused(sec!(11));
|
let timer=timer.into_paused(sec!(11));
|
||||||
//the paused timer at 20 seconds should read 5s
|
//the paused timer at 20 seconds should read 5s
|
||||||
assert_eq!(timer.time(),sec!(5));
|
assert_eq!(timer.time(sec!(20)),sec!(5));
|
||||||
}
|
}
|
||||||
#[test]
|
#[test]
|
||||||
fn test_timer()->Result<(),Error>{
|
fn test_timer()->Result<(),Error>{
|
||||||
//create a paused timer that reads 0s
|
//create a paused timer that reads 0s
|
||||||
let mut timer=Timer::<Realtime<Parent,Calculated>>::paused(sec!(0),sec!(0));
|
let mut timer=Timer::<Realtime>::paused(sec!(0),sec!(0));
|
||||||
//the paused timer at 1 second should read 0s
|
//the paused timer at 1 second should read 0s
|
||||||
assert_eq!(timer.time(sec!(1)),sec!(0));
|
assert_eq!(timer.time(sec!(1)),sec!(0));
|
||||||
|
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
// This whole thing should be a drive macro
|
|
||||||
|
|
||||||
pub trait Updatable<Updater>{
|
pub trait Updatable<Updater>{
|
||||||
fn update(&mut self,update:Updater);
|
fn update(&mut self,update:Updater);
|
||||||
}
|
}
|
||||||
@ -55,3 +53,4 @@ impl Updatable<OuterUpdate> for Outer{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
//*/
|
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "strafesnet_deferred_loader"
|
name = "strafesnet_deferred_loader"
|
||||||
version = "0.5.0"
|
version = "0.4.1"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
repository = "https://git.itzana.me/StrafesNET/strafe-project"
|
repository = "https://git.itzana.me/StrafesNET/strafe-project"
|
||||||
license = "MIT OR Apache-2.0"
|
license = "MIT OR Apache-2.0"
|
||||||
@ -9,5 +9,13 @@ authors = ["Rhys Lloyd <krakow20@gmail.com>"]
|
|||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[features]
|
||||||
|
default = ["legacy"]
|
||||||
|
legacy = ["dep:url","dep:vbsp"]
|
||||||
|
#roblox = ["dep:lazy-regex"]
|
||||||
|
#source = ["dep:vbsp"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
strafesnet_common = { version = "0.6.0", path = "../common", registry = "strafesnet" }
|
strafesnet_common = { path = "../common", registry = "strafesnet" }
|
||||||
|
url = { version = "2.5.2", optional = true }
|
||||||
|
vbsp = { version = "0.6.0", optional = true }
|
||||||
|
@ -1,116 +0,0 @@
|
|||||||
use std::collections::HashMap;
|
|
||||||
use crate::loader::Loader;
|
|
||||||
use crate::mesh::Meshes;
|
|
||||||
use crate::texture::{RenderConfigs,Texture};
|
|
||||||
use strafesnet_common::model::{Mesh,MeshId,RenderConfig,RenderConfigId,TextureId};
|
|
||||||
|
|
||||||
#[derive(Clone,Copy,Debug)]
|
|
||||||
pub enum LoadFailureMode{
|
|
||||||
DefaultToNone,
|
|
||||||
Fatal,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct RenderConfigDeferredLoader<H>{
|
|
||||||
texture_count:u32,
|
|
||||||
render_configs:Vec<RenderConfig>,
|
|
||||||
render_config_id_from_asset_id:HashMap<Option<H>,RenderConfigId>,
|
|
||||||
}
|
|
||||||
impl<H> RenderConfigDeferredLoader<H>{
|
|
||||||
pub fn new()->Self{
|
|
||||||
Self{
|
|
||||||
texture_count:0,
|
|
||||||
render_configs:Vec::new(),
|
|
||||||
render_config_id_from_asset_id:HashMap::new(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<H:core::hash::Hash+Eq> RenderConfigDeferredLoader<H>{
|
|
||||||
pub fn acquire_render_config_id(&mut self,index:Option<H>)->RenderConfigId{
|
|
||||||
let some_texture=index.is_some();
|
|
||||||
*self.render_config_id_from_asset_id.entry(index).or_insert_with(||{
|
|
||||||
//create the render config.
|
|
||||||
let render_config=if some_texture{
|
|
||||||
let render_config=RenderConfig::texture(TextureId::new(self.texture_count));
|
|
||||||
self.texture_count+=1;
|
|
||||||
render_config
|
|
||||||
}else{
|
|
||||||
RenderConfig::default()
|
|
||||||
};
|
|
||||||
let render_id=RenderConfigId::new(self.render_configs.len() as u32);
|
|
||||||
self.render_configs.push(render_config);
|
|
||||||
render_id
|
|
||||||
})
|
|
||||||
}
|
|
||||||
pub fn into_indices(self)->impl Iterator<Item=H>{
|
|
||||||
self.render_config_id_from_asset_id.into_keys().flatten()
|
|
||||||
}
|
|
||||||
pub fn into_render_configs<L:Loader<Resource=Texture,Index=H>>(mut self,loader:&mut L,failure_mode:LoadFailureMode)->Result<RenderConfigs,L::Error>{
|
|
||||||
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];
|
|
||||||
if let (Some(index),Some(texture_id))=(index_option,render_config.texture){
|
|
||||||
let resource_result=loader.load(index);
|
|
||||||
let texture=match failure_mode{
|
|
||||||
// if texture fails to load, use no texture
|
|
||||||
LoadFailureMode::DefaultToNone=>match resource_result{
|
|
||||||
Ok(texture)=>Some(texture),
|
|
||||||
Err(e)=>{
|
|
||||||
render_config.texture=None;
|
|
||||||
println!("Error loading texture: {e}");
|
|
||||||
None
|
|
||||||
},
|
|
||||||
},
|
|
||||||
// loading failure is fatal
|
|
||||||
LoadFailureMode::Fatal=>Some(resource_result?)
|
|
||||||
};
|
|
||||||
sorted_textures[texture_id.get() as usize]=texture;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(RenderConfigs::new(
|
|
||||||
sorted_textures,
|
|
||||||
self.render_configs,
|
|
||||||
))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct MeshDeferredLoader<H>{
|
|
||||||
mesh_id_from_asset_id:HashMap<H,MeshId>,
|
|
||||||
}
|
|
||||||
impl<H> MeshDeferredLoader<H>{
|
|
||||||
pub fn new()->Self{
|
|
||||||
Self{
|
|
||||||
mesh_id_from_asset_id:HashMap::new(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<H:core::hash::Hash+Eq> MeshDeferredLoader<H>{
|
|
||||||
pub fn acquire_mesh_id(&mut self,index:H)->MeshId{
|
|
||||||
let mesh_id=MeshId::new(self.mesh_id_from_asset_id.len() as u32);
|
|
||||||
*self.mesh_id_from_asset_id.entry(index).or_insert(mesh_id)
|
|
||||||
}
|
|
||||||
pub fn into_indices(self)->impl Iterator<Item=H>{
|
|
||||||
self.mesh_id_from_asset_id.into_keys()
|
|
||||||
}
|
|
||||||
pub fn into_meshes<L:Loader<Resource=Mesh,Index=H>>(self,loader:&mut L,failure_mode:LoadFailureMode)->Result<Meshes,L::Error>{
|
|
||||||
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);
|
|
||||||
let mesh=match failure_mode{
|
|
||||||
// if mesh fails to load, use no mesh
|
|
||||||
LoadFailureMode::DefaultToNone=>match resource_result{
|
|
||||||
Ok(mesh)=>Some(mesh),
|
|
||||||
Err(e)=>{
|
|
||||||
println!("Error loading mesh: {e}");
|
|
||||||
None
|
|
||||||
},
|
|
||||||
},
|
|
||||||
// loading failure is fatal
|
|
||||||
LoadFailureMode::Fatal=>Some(resource_result?)
|
|
||||||
};
|
|
||||||
mesh_list[mesh_id.get() as usize]=mesh;
|
|
||||||
}
|
|
||||||
Ok(Meshes::new(mesh_list))
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,5 +1,34 @@
|
|||||||
|
#[cfg(feature="legacy")]
|
||||||
|
mod roblox_legacy;
|
||||||
|
#[cfg(feature="legacy")]
|
||||||
|
mod source_legacy;
|
||||||
|
#[cfg(feature="roblox")]
|
||||||
|
mod roblox;
|
||||||
|
#[cfg(feature="source")]
|
||||||
|
mod source;
|
||||||
|
|
||||||
|
#[cfg(any(feature="roblox",feature="legacy"))]
|
||||||
|
pub mod rbxassetid;
|
||||||
|
|
||||||
pub mod mesh;
|
|
||||||
pub mod loader;
|
|
||||||
pub mod texture;
|
pub mod texture;
|
||||||
pub mod deferred_loader;
|
#[cfg(any(feature="source",feature="legacy"))]
|
||||||
|
pub mod valve_mesh;
|
||||||
|
#[cfg(any(feature="roblox",feature="legacy"))]
|
||||||
|
pub mod roblox_mesh;
|
||||||
|
|
||||||
|
#[cfg(feature="legacy")]
|
||||||
|
pub fn roblox_legacy()->roblox_legacy::Loader{
|
||||||
|
roblox_legacy::Loader::new()
|
||||||
|
}
|
||||||
|
#[cfg(feature="legacy")]
|
||||||
|
pub fn source_legacy()->source_legacy::Loader{
|
||||||
|
source_legacy::Loader::new()
|
||||||
|
}
|
||||||
|
#[cfg(feature="roblox")]
|
||||||
|
pub fn roblox()->roblox::Loader{
|
||||||
|
roblox::Loader::new()
|
||||||
|
}
|
||||||
|
#[cfg(feature="source")]
|
||||||
|
pub fn source()->source::Loader{
|
||||||
|
source::Loader::new()
|
||||||
|
}
|
||||||
|
@ -1,8 +0,0 @@
|
|||||||
use std::error::Error;
|
|
||||||
|
|
||||||
pub trait Loader{
|
|
||||||
type Error:Error;
|
|
||||||
type Index;
|
|
||||||
type Resource;
|
|
||||||
fn load(&mut self,index:Self::Index)->Result<Self::Resource,Self::Error>;
|
|
||||||
}
|
|
@ -1,17 +0,0 @@
|
|||||||
use strafesnet_common::model::{Mesh,MeshId};
|
|
||||||
|
|
||||||
pub struct Meshes{
|
|
||||||
meshes:Vec<Option<Mesh>>,
|
|
||||||
}
|
|
||||||
impl Meshes{
|
|
||||||
pub(crate) const fn new(meshes:Vec<Option<Mesh>>)->Self{
|
|
||||||
Self{
|
|
||||||
meshes,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub fn consume(self)->impl Iterator<Item=(MeshId,Mesh)>{
|
|
||||||
self.meshes.into_iter().enumerate().filter_map(|(mesh_id,maybe_mesh)|
|
|
||||||
maybe_mesh.map(|mesh|(MeshId::new(mesh_id as u32),mesh))
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
48
lib/deferred_loader/src/rbxassetid.rs
Normal file
48
lib/deferred_loader/src/rbxassetid.rs
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
#[derive(Hash,Eq,PartialEq)]
|
||||||
|
pub struct RobloxAssetId(pub u64);
|
||||||
|
#[derive(Debug)]
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub struct StringWithError{
|
||||||
|
string:String,
|
||||||
|
error:RobloxAssetIdParseErr,
|
||||||
|
}
|
||||||
|
impl std::fmt::Display for StringWithError{
|
||||||
|
fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{
|
||||||
|
write!(f,"{self:?}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl std::error::Error for StringWithError{}
|
||||||
|
impl StringWithError{
|
||||||
|
const fn new(
|
||||||
|
string:String,
|
||||||
|
error:RobloxAssetIdParseErr,
|
||||||
|
)->Self{
|
||||||
|
Self{string,error}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum RobloxAssetIdParseErr{
|
||||||
|
Url(url::ParseError),
|
||||||
|
UnknownScheme,
|
||||||
|
ParseInt(std::num::ParseIntError),
|
||||||
|
MissingAssetId,
|
||||||
|
}
|
||||||
|
impl std::str::FromStr for RobloxAssetId{
|
||||||
|
type Err=StringWithError;
|
||||||
|
fn from_str(s:&str)->Result<Self,Self::Err>{
|
||||||
|
let url=url::Url::parse(s).map_err(|e|StringWithError::new(s.to_owned(),RobloxAssetIdParseErr::Url(e)))?;
|
||||||
|
let parsed_asset_id=match url.scheme(){
|
||||||
|
"rbxassetid"=>url.domain().ok_or_else(||StringWithError::new(s.to_owned(),RobloxAssetIdParseErr::MissingAssetId))?.parse(),
|
||||||
|
"http"|"https"=>{
|
||||||
|
let (_,asset_id)=url.query_pairs()
|
||||||
|
.find(|(id,_)|match id.as_ref(){
|
||||||
|
"ID"|"id"|"Id"|"iD"=>true,
|
||||||
|
_=>false,
|
||||||
|
}).ok_or_else(||StringWithError::new(s.to_owned(),RobloxAssetIdParseErr::MissingAssetId))?;
|
||||||
|
asset_id.parse()
|
||||||
|
},
|
||||||
|
_=>Err(StringWithError::new(s.to_owned(),RobloxAssetIdParseErr::UnknownScheme))?,
|
||||||
|
};
|
||||||
|
Ok(Self(parsed_asset_id.map_err(|e|StringWithError::new(s.to_owned(),RobloxAssetIdParseErr::ParseInt(e)))?))
|
||||||
|
}
|
||||||
|
}
|
0
lib/deferred_loader/src/roblox.rs
Normal file
0
lib/deferred_loader/src/roblox.rs
Normal file
112
lib/deferred_loader/src/roblox_legacy.rs
Normal file
112
lib/deferred_loader/src/roblox_legacy.rs
Normal file
@ -0,0 +1,112 @@
|
|||||||
|
use std::io::Read;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use crate::roblox_mesh;
|
||||||
|
use crate::texture::{RenderConfigs,Texture};
|
||||||
|
use strafesnet_common::model::{MeshId,RenderConfig,RenderConfigId,TextureId};
|
||||||
|
use crate::rbxassetid::RobloxAssetId;
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct RenderConfigLoader{
|
||||||
|
texture_count:u32,
|
||||||
|
render_configs:Vec<RenderConfig>,
|
||||||
|
render_config_id_from_asset_id:HashMap<Option<RobloxAssetId>,RenderConfigId>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RenderConfigLoader{
|
||||||
|
pub fn acquire_render_config_id(&mut self,name:Option<&str>)->RenderConfigId{
|
||||||
|
let render_id=RenderConfigId::new(self.render_config_id_from_asset_id.len() as u32);
|
||||||
|
let index=name.and_then(|name|{
|
||||||
|
match name.parse::<RobloxAssetId>(){
|
||||||
|
Ok(asset_id)=>Some(asset_id),
|
||||||
|
Err(e)=>{
|
||||||
|
println!("Failed to parse AssetId: {e}");
|
||||||
|
None
|
||||||
|
},
|
||||||
|
}
|
||||||
|
});
|
||||||
|
*self.render_config_id_from_asset_id.entry(index).or_insert_with(||{
|
||||||
|
//create the render config.
|
||||||
|
let render_config=if name.is_some(){
|
||||||
|
let render_config=RenderConfig::texture(TextureId::new(self.texture_count));
|
||||||
|
self.texture_count+=1;
|
||||||
|
render_config
|
||||||
|
}else{
|
||||||
|
RenderConfig::default()
|
||||||
|
};
|
||||||
|
self.render_configs.push(render_config);
|
||||||
|
render_id
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct MeshLoader{
|
||||||
|
mesh_id_from_asset_id:HashMap<Option<RobloxAssetId>,MeshId>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MeshLoader{
|
||||||
|
pub fn acquire_mesh_id(&mut self,name:&str)->MeshId{
|
||||||
|
let mesh_id=MeshId::new(self.mesh_id_from_asset_id.len() as u32);
|
||||||
|
let index=match name.parse::<RobloxAssetId>(){
|
||||||
|
Ok(asset_id)=>Some(asset_id),
|
||||||
|
Err(e)=>{
|
||||||
|
println!("Failed to parse AssetId: {e}");
|
||||||
|
None
|
||||||
|
},
|
||||||
|
};
|
||||||
|
*self.mesh_id_from_asset_id.entry(index).or_insert(mesh_id)
|
||||||
|
}
|
||||||
|
pub fn load_meshes(&mut self)->Result<roblox_mesh::Meshes,std::io::Error>{
|
||||||
|
let mut mesh_data=vec![None;self.mesh_id_from_asset_id.len()];
|
||||||
|
for (asset_id_option,mesh_id) in &self.mesh_id_from_asset_id{
|
||||||
|
if let Some(asset_id)=asset_id_option{
|
||||||
|
if let Ok(mut file)=std::fs::File::open(format!("meshes/{}",asset_id.0)){
|
||||||
|
//TODO: parallel
|
||||||
|
let mut data=Vec::<u8>::new();
|
||||||
|
file.read_to_end(&mut data)?;
|
||||||
|
mesh_data[mesh_id.get() as usize]=Some(roblox_mesh::RobloxMeshData::new(data));
|
||||||
|
}else{
|
||||||
|
println!("[roblox_legacy] no mesh name={}",asset_id.0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(roblox_mesh::Meshes::new(mesh_data))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Loader{
|
||||||
|
render_config_loader:RenderConfigLoader,
|
||||||
|
mesh_loader:MeshLoader,
|
||||||
|
}
|
||||||
|
impl Loader{
|
||||||
|
pub fn new()->Self{
|
||||||
|
Self{
|
||||||
|
render_config_loader:RenderConfigLoader::default(),
|
||||||
|
mesh_loader:MeshLoader::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn get_inner_mut(&mut self)->(&mut RenderConfigLoader,&mut MeshLoader){
|
||||||
|
(&mut self.render_config_loader,&mut self.mesh_loader)
|
||||||
|
}
|
||||||
|
pub fn into_render_configs(mut self)->Result<RenderConfigs,std::io::Error>{
|
||||||
|
let mut sorted_textures=vec![None;self.render_config_loader.texture_count as usize];
|
||||||
|
for (asset_id_option,render_config_id) in self.render_config_loader.render_config_id_from_asset_id{
|
||||||
|
let render_config=self.render_config_loader.render_configs.get_mut(render_config_id.get() as usize).unwrap();
|
||||||
|
if let (Some(asset_id),Some(texture_id))=(asset_id_option,render_config.texture){
|
||||||
|
if let Ok(mut file)=std::fs::File::open(format!("textures/{}.dds",asset_id.0)){
|
||||||
|
//TODO: parallel
|
||||||
|
let mut data=Vec::<u8>::new();
|
||||||
|
file.read_to_end(&mut data)?;
|
||||||
|
sorted_textures[texture_id.get() as usize]=Some(Texture::ImageDDS(data));
|
||||||
|
}else{
|
||||||
|
//texture failed to load
|
||||||
|
render_config.texture=None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(RenderConfigs::new(
|
||||||
|
sorted_textures,
|
||||||
|
self.render_config_loader.render_configs,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
30
lib/deferred_loader/src/roblox_mesh.rs
Normal file
30
lib/deferred_loader/src/roblox_mesh.rs
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
use strafesnet_common::model::MeshId;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct RobloxMeshData(Vec<u8>);
|
||||||
|
impl RobloxMeshData{
|
||||||
|
pub(crate) fn new(data:Vec<u8>)->Self{
|
||||||
|
Self(data)
|
||||||
|
}
|
||||||
|
pub fn get(self)->Vec<u8>{
|
||||||
|
self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub struct Meshes{
|
||||||
|
meshes:Vec<Option<RobloxMeshData>>,
|
||||||
|
}
|
||||||
|
impl Meshes{
|
||||||
|
pub(crate) const fn new(meshes:Vec<Option<RobloxMeshData>>)->Self{
|
||||||
|
Self{
|
||||||
|
meshes,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn get_texture(&self,texture_id:MeshId)->Option<&RobloxMeshData>{
|
||||||
|
self.meshes.get(texture_id.get() as usize)?.as_ref()
|
||||||
|
}
|
||||||
|
pub fn into_iter(self)->impl Iterator<Item=(MeshId,RobloxMeshData)>{
|
||||||
|
self.meshes.into_iter().enumerate().filter_map(|(mesh_id,maybe_mesh)|
|
||||||
|
maybe_mesh.map(|mesh|(MeshId::new(mesh_id as u32),mesh))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
102
lib/deferred_loader/src/source_legacy.rs
Normal file
102
lib/deferred_loader/src/source_legacy.rs
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
use std::io::Read;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use crate::valve_mesh;
|
||||||
|
use crate::texture::{Texture,RenderConfigs};
|
||||||
|
use strafesnet_common::model::{MeshId,TextureId,RenderConfig,RenderConfigId};
|
||||||
|
|
||||||
|
pub struct RenderConfigLoader{
|
||||||
|
texture_count:u32,
|
||||||
|
render_configs:Vec<RenderConfig>,
|
||||||
|
texture_paths:HashMap<Option<Box<str>>,RenderConfigId>,
|
||||||
|
}
|
||||||
|
impl RenderConfigLoader{
|
||||||
|
pub fn acquire_render_config_id(&mut self,name:Option<&str>)->RenderConfigId{
|
||||||
|
let render_id=RenderConfigId::new(self.texture_paths.len() as u32);
|
||||||
|
*self.texture_paths.entry(name.map(Into::into)).or_insert_with(||{
|
||||||
|
//create the render config.
|
||||||
|
let render_config=if name.is_some(){
|
||||||
|
let render_config=RenderConfig::texture(TextureId::new(self.texture_count));
|
||||||
|
self.texture_count+=1;
|
||||||
|
render_config
|
||||||
|
}else{
|
||||||
|
RenderConfig::default()
|
||||||
|
};
|
||||||
|
self.render_configs.push(render_config);
|
||||||
|
render_id
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub struct MeshLoader{
|
||||||
|
mesh_paths:HashMap<Box<str>,MeshId>,
|
||||||
|
}
|
||||||
|
impl MeshLoader{
|
||||||
|
pub fn acquire_mesh_id(&mut self,name:&str)->MeshId{
|
||||||
|
let mesh_id=MeshId::new(self.mesh_paths.len() as u32);
|
||||||
|
*self.mesh_paths.entry(name.into()).or_insert(mesh_id)
|
||||||
|
}
|
||||||
|
//load_meshes should look like load_textures
|
||||||
|
pub fn load_meshes(&mut self,bsp:&vbsp::Bsp)->valve_mesh::Meshes{
|
||||||
|
let mut mesh_data=vec![None;self.mesh_paths.len()];
|
||||||
|
for (mesh_path,mesh_id) in &self.mesh_paths{
|
||||||
|
let mesh_path_lower=mesh_path.to_lowercase();
|
||||||
|
//.mdl, .vvd, .dx90.vtx
|
||||||
|
let path=std::path::PathBuf::from(mesh_path_lower.as_str());
|
||||||
|
let mut vvd_path=path.clone();
|
||||||
|
let mut vtx_path=path.clone();
|
||||||
|
vvd_path.set_extension("vvd");
|
||||||
|
vtx_path.set_extension("dx90.vtx");
|
||||||
|
match (bsp.pack.get(mesh_path_lower.as_str()),bsp.pack.get(vvd_path.as_os_str().to_str().unwrap()),bsp.pack.get(vtx_path.as_os_str().to_str().unwrap())){
|
||||||
|
(Ok(Some(mdl_file)),Ok(Some(vvd_file)),Ok(Some(vtx_file)))=>{
|
||||||
|
mesh_data[mesh_id.get() as usize]=Some(valve_mesh::ModelData{
|
||||||
|
mdl:valve_mesh::MdlData::new(mdl_file),
|
||||||
|
vtx:valve_mesh::VtxData::new(vtx_file),
|
||||||
|
vvd:valve_mesh::VvdData::new(vvd_file),
|
||||||
|
});
|
||||||
|
},
|
||||||
|
_=>println!("no model name={}",mesh_path),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
valve_mesh::Meshes::new(mesh_data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Loader{
|
||||||
|
render_config_loader:RenderConfigLoader,
|
||||||
|
mesh_loader:MeshLoader,
|
||||||
|
}
|
||||||
|
impl Loader{
|
||||||
|
pub fn new()->Self{
|
||||||
|
Self{
|
||||||
|
render_config_loader:RenderConfigLoader{
|
||||||
|
texture_count:0,
|
||||||
|
texture_paths:HashMap::new(),
|
||||||
|
render_configs:Vec::new(),
|
||||||
|
},
|
||||||
|
mesh_loader:MeshLoader{mesh_paths:HashMap::new()},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn get_inner_mut(&mut self)->(&mut RenderConfigLoader,&mut MeshLoader){
|
||||||
|
(&mut self.render_config_loader,&mut self.mesh_loader)
|
||||||
|
}
|
||||||
|
pub fn into_render_configs(mut self)->Result<RenderConfigs,std::io::Error>{
|
||||||
|
let mut sorted_textures=vec![None;self.render_config_loader.texture_count as usize];
|
||||||
|
for (texture_path,render_config_id) in self.render_config_loader.texture_paths{
|
||||||
|
let render_config=self.render_config_loader.render_configs.get_mut(render_config_id.get() as usize).unwrap();
|
||||||
|
if let (Some(texture_path),Some(texture_id))=(texture_path,render_config.texture){
|
||||||
|
if let Ok(mut file)=std::fs::File::open(format!("textures/{}.dds",texture_path)){
|
||||||
|
//TODO: parallel
|
||||||
|
let mut data=Vec::<u8>::new();
|
||||||
|
file.read_to_end(&mut data)?;
|
||||||
|
sorted_textures[texture_id.get() as usize]=Some(Texture::ImageDDS(data));
|
||||||
|
}else{
|
||||||
|
//texture failed to load
|
||||||
|
render_config.texture=None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(RenderConfigs::new(
|
||||||
|
sorted_textures,
|
||||||
|
self.render_config_loader.render_configs,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
60
lib/deferred_loader/src/valve_mesh.rs
Normal file
60
lib/deferred_loader/src/valve_mesh.rs
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
use strafesnet_common::model::MeshId;
|
||||||
|
|
||||||
|
//duplicate this code for now
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct MdlData(Vec<u8>);
|
||||||
|
impl MdlData{
|
||||||
|
pub const fn new(value:Vec<u8>)->Self{
|
||||||
|
Self(value)
|
||||||
|
}
|
||||||
|
pub fn get(self)->Vec<u8>{
|
||||||
|
self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct VtxData(Vec<u8>);
|
||||||
|
impl VtxData{
|
||||||
|
pub const fn new(value:Vec<u8>)->Self{
|
||||||
|
Self(value)
|
||||||
|
}
|
||||||
|
pub fn get(self)->Vec<u8>{
|
||||||
|
self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct VvdData(Vec<u8>);
|
||||||
|
impl VvdData{
|
||||||
|
pub const fn new(value:Vec<u8>)->Self{
|
||||||
|
Self(value)
|
||||||
|
}
|
||||||
|
pub fn get(self)->Vec<u8>{
|
||||||
|
self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct ModelData{
|
||||||
|
pub mdl:MdlData,
|
||||||
|
pub vtx:VtxData,
|
||||||
|
pub vvd:VvdData,
|
||||||
|
}
|
||||||
|
|
||||||
|
//meshes is more prone to failure
|
||||||
|
pub struct Meshes{
|
||||||
|
meshes:Vec<Option<ModelData>>,
|
||||||
|
}
|
||||||
|
impl Meshes{
|
||||||
|
pub(crate) const fn new(meshes:Vec<Option<ModelData>>)->Self{
|
||||||
|
Self{
|
||||||
|
meshes,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn get_texture(&self,texture_id:MeshId)->Option<&ModelData>{
|
||||||
|
self.meshes.get(texture_id.get() as usize)?.as_ref()
|
||||||
|
}
|
||||||
|
pub fn into_iter(self)->impl Iterator<Item=(MeshId,ModelData)>{
|
||||||
|
self.meshes.into_iter().enumerate().filter_map(|(mesh_id,maybe_mesh)|
|
||||||
|
maybe_mesh.map(|mesh|(MeshId::new(mesh_id as u32),mesh))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "fixed_wide"
|
name = "fixed_wide"
|
||||||
version = "0.1.2"
|
version = "0.1.1"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
repository = "https://git.itzana.me/StrafesNET/strafe-project"
|
repository = "https://git.itzana.me/StrafesNET/strafe-project"
|
||||||
license = "MIT OR Apache-2.0"
|
license = "MIT OR Apache-2.0"
|
||||||
@ -17,4 +17,4 @@ zeroes=["dep:arrayvec"]
|
|||||||
bnum = "0.12.0"
|
bnum = "0.12.0"
|
||||||
arrayvec = { version = "0.7.6", optional = true }
|
arrayvec = { version = "0.7.6", optional = true }
|
||||||
paste = "1.0.15"
|
paste = "1.0.15"
|
||||||
ratio_ops = { version = "0.1.0", path = "../ratio_ops", registry = "strafesnet", optional = true }
|
ratio_ops = { path = "../ratio_ops", registry = "strafesnet", optional = true }
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
use bnum::{BInt,cast::As};
|
use bnum::{BInt,cast::As};
|
||||||
|
|
||||||
#[derive(Clone,Copy,Debug,Default,Hash,PartialEq,Eq,PartialOrd,Ord)]
|
#[derive(Clone,Copy,Debug,Default,Hash)]
|
||||||
/// A Fixed point number for which multiply operations widen the bits in the output. (when the wide-mul feature is enabled)
|
/// A Fixed point number for which multiply operations widen the bits in the output. (when the wide-mul feature is enabled)
|
||||||
/// N is the number of u64s to use
|
/// N is the number of u64s to use
|
||||||
/// F is the number of fractional bits (always N*32 lol)
|
/// F is the number of fractional bits (always N*32 lol)
|
||||||
@ -87,6 +87,12 @@ impl_from!(
|
|||||||
i8,i16,i32,i64,i128,isize
|
i8,i16,i32,i64,i128,isize
|
||||||
);
|
);
|
||||||
|
|
||||||
|
impl<const N:usize,const F:usize> PartialEq for Fixed<N,F>{
|
||||||
|
#[inline]
|
||||||
|
fn eq(&self,other:&Self)->bool{
|
||||||
|
self.bits.eq(&other.bits)
|
||||||
|
}
|
||||||
|
}
|
||||||
impl<const N:usize,const F:usize,T> PartialEq<T> for Fixed<N,F>
|
impl<const N:usize,const F:usize,T> PartialEq<T> for Fixed<N,F>
|
||||||
where
|
where
|
||||||
T:Copy,
|
T:Copy,
|
||||||
@ -97,7 +103,14 @@ where
|
|||||||
self.bits.eq(&other.into())
|
self.bits.eq(&other.into())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
impl<const N:usize,const F:usize> Eq for Fixed<N,F>{}
|
||||||
|
|
||||||
|
impl<const N:usize,const F:usize> PartialOrd for Fixed<N,F>{
|
||||||
|
#[inline]
|
||||||
|
fn partial_cmp(&self,other:&Self)->Option<std::cmp::Ordering>{
|
||||||
|
self.bits.partial_cmp(&other.bits)
|
||||||
|
}
|
||||||
|
}
|
||||||
impl<const N:usize,const F:usize,T> PartialOrd<T> for Fixed<N,F>
|
impl<const N:usize,const F:usize,T> PartialOrd<T> for Fixed<N,F>
|
||||||
where
|
where
|
||||||
T:Copy,
|
T:Copy,
|
||||||
@ -108,6 +121,12 @@ impl<const N:usize,const F:usize,T> PartialOrd<T> for Fixed<N,F>
|
|||||||
self.bits.partial_cmp(&other.into())
|
self.bits.partial_cmp(&other.into())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
impl<const N:usize,const F:usize> Ord for Fixed<N,F>{
|
||||||
|
#[inline]
|
||||||
|
fn cmp(&self,other:&Self)->std::cmp::Ordering{
|
||||||
|
self.bits.cmp(&other.bits)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<const N:usize,const F:usize> std::ops::Neg for Fixed<N,F>{
|
impl<const N:usize,const F:usize> std::ops::Neg for Fixed<N,F>{
|
||||||
type Output=Self;
|
type Output=Self;
|
||||||
|
@ -57,7 +57,7 @@ fn from_f32(){
|
|||||||
assert_eq!(b,Ok(a));
|
assert_eq!(b,Ok(a));
|
||||||
//I32F32::MIN hits a special case since it's not representable as a positive signed integer
|
//I32F32::MIN hits a special case since it's not representable as a positive signed integer
|
||||||
//TODO: don't return an overflow because this is technically possible
|
//TODO: don't return an overflow because this is technically possible
|
||||||
let _a=I32F32::MIN;
|
let a=I32F32::MIN;
|
||||||
let b:Result<I32F32,_>=Into::<f32>::into(I32F32::MIN).try_into();
|
let b:Result<I32F32,_>=Into::<f32>::into(I32F32::MIN).try_into();
|
||||||
assert_eq!(b,Err(crate::fixed::FixedFromFloatError::Overflow));
|
assert_eq!(b,Err(crate::fixed::FixedFromFloatError::Overflow));
|
||||||
//16 is within the 24 bits of float precision
|
//16 is within the 24 bits of float precision
|
||||||
|
@ -14,8 +14,8 @@ fixed-wide=["dep:fixed_wide","dep:paste"]
|
|||||||
deferred-division=["dep:ratio_ops"]
|
deferred-division=["dep:ratio_ops"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
ratio_ops = { version = "0.1.0", path = "../ratio_ops", registry = "strafesnet", optional = true }
|
ratio_ops = { path = "../ratio_ops", registry = "strafesnet", optional = true }
|
||||||
fixed_wide = { version = "0.1.2", path = "../fixed_wide", registry = "strafesnet", optional = true }
|
fixed_wide = { path = "../fixed_wide", registry = "strafesnet", optional = true }
|
||||||
paste = { version = "1.0.15", optional = true }
|
paste = { version = "1.0.15", optional = true }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "strafesnet_rbx_loader"
|
name = "strafesnet_rbx_loader"
|
||||||
version = "0.6.0"
|
version = "0.5.2"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
repository = "https://git.itzana.me/StrafesNET/strafe-project"
|
repository = "https://git.itzana.me/StrafesNET/strafe-project"
|
||||||
license = "MIT OR Apache-2.0"
|
license = "MIT OR Apache-2.0"
|
||||||
@ -15,10 +15,8 @@ glam = "0.29.0"
|
|||||||
lazy-regex = "3.1.0"
|
lazy-regex = "3.1.0"
|
||||||
rbx_binary = { version = "0.7.4", registry = "strafesnet" }
|
rbx_binary = { version = "0.7.4", registry = "strafesnet" }
|
||||||
rbx_dom_weak = { version = "2.7.0", registry = "strafesnet" }
|
rbx_dom_weak = { version = "2.7.0", registry = "strafesnet" }
|
||||||
rbx_mesh = "0.3.1"
|
rbx_mesh = "0.1.2"
|
||||||
rbx_reflection_database = { version = "0.2.10", registry = "strafesnet" }
|
rbx_reflection_database = { version = "0.2.10", registry = "strafesnet" }
|
||||||
rbx_xml = { version = "0.13.3", registry = "strafesnet" }
|
rbx_xml = { version = "0.13.3", registry = "strafesnet" }
|
||||||
rbxassetid = { version = "0.1.0", path = "../rbxassetid", registry = "strafesnet" }
|
roblox_emulator = { path = "../roblox_emulator", registry = "strafesnet" }
|
||||||
roblox_emulator = { version = "0.4.7", path = "../roblox_emulator", registry = "strafesnet" }
|
strafesnet_common = { path = "../common", registry = "strafesnet" }
|
||||||
strafesnet_common = { version = "0.6.0", path = "../common", registry = "strafesnet" }
|
|
||||||
strafesnet_deferred_loader = { version = "0.5.0", path = "../deferred_loader", registry = "strafesnet" }
|
|
||||||
|
@ -1,11 +1,8 @@
|
|||||||
use std::io::Read;
|
use std::io::Read;
|
||||||
use rbx_dom_weak::WeakDom;
|
use rbx_dom_weak::WeakDom;
|
||||||
use strafesnet_deferred_loader::deferred_loader::{LoadFailureMode,MeshDeferredLoader,RenderConfigDeferredLoader};
|
|
||||||
|
|
||||||
mod rbx;
|
mod rbx;
|
||||||
mod mesh;
|
mod mesh;
|
||||||
mod union;
|
|
||||||
pub mod loader;
|
|
||||||
mod primitives;
|
mod primitives;
|
||||||
|
|
||||||
pub mod data{
|
pub mod data{
|
||||||
@ -33,9 +30,6 @@ impl Model{
|
|||||||
let services=context.convert_into_place();
|
let services=context.convert_into_place();
|
||||||
Place{dom,services}
|
Place{dom,services}
|
||||||
}
|
}
|
||||||
pub fn to_snf(&self,failure_mode:LoadFailureMode)->Result<strafesnet_common::map::CompleteMap,LoadError>{
|
|
||||||
to_snf(self,failure_mode)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
impl AsRef<WeakDom> for Model{
|
impl AsRef<WeakDom> for Model{
|
||||||
fn as_ref(&self)->&WeakDom{
|
fn as_ref(&self)->&WeakDom{
|
||||||
@ -48,7 +42,7 @@ pub struct Place{
|
|||||||
services:roblox_emulator::context::Services,
|
services:roblox_emulator::context::Services,
|
||||||
}
|
}
|
||||||
impl Place{
|
impl Place{
|
||||||
pub fn new(dom:WeakDom)->Option<Self>{
|
fn new(dom:WeakDom)->Option<Self>{
|
||||||
let context=roblox_emulator::context::Context::from_ref(&dom);
|
let context=roblox_emulator::context::Context::from_ref(&dom);
|
||||||
Some(Self{
|
Some(Self{
|
||||||
services:context.find_services()?,
|
services:context.find_services()?,
|
||||||
@ -67,9 +61,6 @@ impl Place{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn to_snf(&self,failure_mode:LoadFailureMode)->Result<strafesnet_common::map::CompleteMap,LoadError>{
|
|
||||||
to_snf(self,failure_mode)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
impl AsRef<WeakDom> for Place{
|
impl AsRef<WeakDom> for Place{
|
||||||
fn as_ref(&self)->&WeakDom{
|
fn as_ref(&self)->&WeakDom{
|
||||||
@ -101,49 +92,16 @@ pub fn read<R:Read>(input:R)->Result<Model,ReadError>{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
//ConvertError
|
||||||
pub enum LoadError{
|
|
||||||
Texture(loader::TextureError),
|
|
||||||
Mesh(loader::MeshError),
|
|
||||||
}
|
|
||||||
impl std::fmt::Display for LoadError{
|
|
||||||
fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{
|
|
||||||
write!(f,"{self:?}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl std::error::Error for LoadError{}
|
|
||||||
impl From<loader::TextureError> for LoadError{
|
|
||||||
fn from(value:loader::TextureError)->Self{
|
|
||||||
Self::Texture(value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl From<loader::MeshError> for LoadError{
|
|
||||||
fn from(value:loader::MeshError)->Self{
|
|
||||||
Self::Mesh(value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn to_snf(dom:impl AsRef<WeakDom>,failure_mode:LoadFailureMode)->Result<strafesnet_common::map::CompleteMap,LoadError>{
|
pub fn convert<AcquireRenderConfigId,AcquireMeshId>(
|
||||||
let dom=dom.as_ref();
|
dom:impl AsRef<WeakDom>,
|
||||||
|
acquire_render_config_id:AcquireRenderConfigId,
|
||||||
let mut texture_deferred_loader=RenderConfigDeferredLoader::new();
|
acquire_mesh_id:AcquireMeshId
|
||||||
let mut mesh_deferred_loader=MeshDeferredLoader::new();
|
)->rbx::PartialMap1
|
||||||
|
where
|
||||||
let map_step1=rbx::convert(
|
AcquireRenderConfigId:FnMut(Option<&str>)->strafesnet_common::model::RenderConfigId,
|
||||||
dom,
|
AcquireMeshId:FnMut(&str)->strafesnet_common::model::MeshId,
|
||||||
&mut texture_deferred_loader,
|
{
|
||||||
&mut mesh_deferred_loader,
|
rbx::convert(&dom.as_ref(),acquire_render_config_id,acquire_mesh_id)
|
||||||
);
|
|
||||||
|
|
||||||
let mut mesh_loader=loader::MeshLoader::new();
|
|
||||||
let meshpart_meshes=mesh_deferred_loader.into_meshes(&mut mesh_loader,failure_mode).map_err(LoadError::Mesh)?;
|
|
||||||
|
|
||||||
let map_step2=map_step1.add_meshpart_meshes_and_calculate_attributes(meshpart_meshes);
|
|
||||||
|
|
||||||
let mut texture_loader=loader::TextureLoader::new();
|
|
||||||
let render_configs=texture_deferred_loader.into_render_configs(&mut texture_loader,failure_mode).map_err(LoadError::Texture)?;
|
|
||||||
|
|
||||||
let map=map_step2.add_render_configs_and_textures(render_configs);
|
|
||||||
|
|
||||||
Ok(map)
|
|
||||||
}
|
}
|
||||||
|
@ -1,191 +0,0 @@
|
|||||||
use std::io::Read;
|
|
||||||
use rbxassetid::{RobloxAssetId,RobloxAssetIdParseErr};
|
|
||||||
use strafesnet_common::model::Mesh;
|
|
||||||
use strafesnet_deferred_loader::{loader::Loader,texture::Texture};
|
|
||||||
|
|
||||||
use crate::data::RobloxMeshBytes;
|
|
||||||
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)?;
|
|
||||||
let mut data=Vec::new();
|
|
||||||
file.read_to_end(&mut data)?;
|
|
||||||
Ok(data)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub enum TextureError{
|
|
||||||
Io(std::io::Error),
|
|
||||||
RobloxAssetIdParse(RobloxAssetIdParseErr),
|
|
||||||
}
|
|
||||||
impl std::fmt::Display for TextureError{
|
|
||||||
fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{
|
|
||||||
write!(f,"{self:?}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl std::error::Error for TextureError{}
|
|
||||||
impl From<std::io::Error> for TextureError{
|
|
||||||
fn from(value:std::io::Error)->Self{
|
|
||||||
Self::Io(value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl From<RobloxAssetIdParseErr> for TextureError{
|
|
||||||
fn from(value:RobloxAssetIdParseErr)->Self{
|
|
||||||
Self::RobloxAssetIdParse(value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct TextureLoader<'a>(std::marker::PhantomData<&'a ()>);
|
|
||||||
impl TextureLoader<'_>{
|
|
||||||
pub fn new()->Self{
|
|
||||||
Self(std::marker::PhantomData)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl<'a> Loader for TextureLoader<'a>{
|
|
||||||
type Error=TextureError;
|
|
||||||
type Index=&'a str;
|
|
||||||
type Resource=Texture;
|
|
||||||
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)?;
|
|
||||||
Ok(Texture::ImageDDS(data))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub enum MeshError{
|
|
||||||
Io(std::io::Error),
|
|
||||||
RobloxAssetIdParse(RobloxAssetIdParseErr),
|
|
||||||
Mesh(crate::mesh::Error),
|
|
||||||
Union(crate::union::Error),
|
|
||||||
DecodeBinary(rbx_binary::DecodeError),
|
|
||||||
OneChildPolicy,
|
|
||||||
MissingInstance,
|
|
||||||
}
|
|
||||||
impl std::fmt::Display for MeshError{
|
|
||||||
fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{
|
|
||||||
write!(f,"{self:?}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl std::error::Error for MeshError{}
|
|
||||||
impl From<std::io::Error> for MeshError{
|
|
||||||
fn from(value:std::io::Error)->Self{
|
|
||||||
Self::Io(value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl From<RobloxAssetIdParseErr> for MeshError{
|
|
||||||
fn from(value:RobloxAssetIdParseErr)->Self{
|
|
||||||
Self::RobloxAssetIdParse(value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl From<crate::mesh::Error> for MeshError{
|
|
||||||
fn from(value:crate::mesh::Error)->Self{
|
|
||||||
Self::Mesh(value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl From<crate::union::Error> for MeshError{
|
|
||||||
fn from(value:crate::union::Error)->Self{
|
|
||||||
Self::Union(value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl From<rbx_binary::DecodeError> for MeshError{
|
|
||||||
fn from(value:rbx_binary::DecodeError)->Self{
|
|
||||||
Self::DecodeBinary(value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Hash,Eq,PartialEq)]
|
|
||||||
pub enum MeshType<'a>{
|
|
||||||
FileMesh,
|
|
||||||
Union{
|
|
||||||
mesh_data:&'a [u8],
|
|
||||||
physics_data:&'a [u8],
|
|
||||||
size_float_bits:[u32;3],
|
|
||||||
part_texture_description:[Option<RobloxFaceTextureDescription>;6],
|
|
||||||
},
|
|
||||||
}
|
|
||||||
#[derive(Hash,Eq,PartialEq)]
|
|
||||||
pub struct MeshIndex<'a>{
|
|
||||||
mesh_type:MeshType<'a>,
|
|
||||||
content:&'a str,
|
|
||||||
}
|
|
||||||
impl MeshIndex<'_>{
|
|
||||||
pub fn file_mesh(content:&str)->MeshIndex{
|
|
||||||
MeshIndex{
|
|
||||||
mesh_type:MeshType::FileMesh,
|
|
||||||
content,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub fn union<'a>(
|
|
||||||
content:&'a str,
|
|
||||||
mesh_data:&'a [u8],
|
|
||||||
physics_data:&'a [u8],
|
|
||||||
size:&rbx_dom_weak::types::Vector3,
|
|
||||||
part_texture_description:crate::rbx::RobloxPartDescription,
|
|
||||||
)->MeshIndex<'a>{
|
|
||||||
MeshIndex{
|
|
||||||
mesh_type:MeshType::Union{
|
|
||||||
mesh_data,
|
|
||||||
physics_data,
|
|
||||||
size_float_bits:[size.x.to_bits(),size.y.to_bits(),size.z.to_bits()],
|
|
||||||
part_texture_description,
|
|
||||||
},
|
|
||||||
content,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct MeshLoader<'a>(std::marker::PhantomData<&'a ()>);
|
|
||||||
impl MeshLoader<'_>{
|
|
||||||
pub fn new()->Self{
|
|
||||||
Self(std::marker::PhantomData)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl<'a> Loader for MeshLoader<'a>{
|
|
||||||
type Error=MeshError;
|
|
||||||
type Index=MeshIndex<'a>;
|
|
||||||
type Resource=Mesh;
|
|
||||||
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()?;
|
|
||||||
let file_name=format!("meshes/{}",asset_id);
|
|
||||||
let data=read_entire_file(file_name)?;
|
|
||||||
crate::mesh::convert(RobloxMeshBytes::new(data))?
|
|
||||||
},
|
|
||||||
MeshType::Union{mut physics_data,mut mesh_data,size_float_bits,part_texture_description}=>{
|
|
||||||
// decode asset
|
|
||||||
let size=glam::Vec3::from_array(size_float_bits.map(f32::from_bits));
|
|
||||||
if !index.content.is_empty()&&(physics_data.is_empty()||mesh_data.is_empty()){
|
|
||||||
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(std::io::Cursor::new(data))?;
|
|
||||||
let &[referent]=dom.root().children()else{
|
|
||||||
return Err(MeshError::OneChildPolicy);
|
|
||||||
};
|
|
||||||
let Some(instance)=dom.get_by_ref(referent)else{
|
|
||||||
return Err(MeshError::MissingInstance);
|
|
||||||
};
|
|
||||||
if physics_data.is_empty(){
|
|
||||||
if let Some(rbx_dom_weak::types::Variant::BinaryString(data))=instance.properties.get("PhysicsData"){
|
|
||||||
physics_data=data.as_ref();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if mesh_data.is_empty(){
|
|
||||||
if let Some(rbx_dom_weak::types::Variant::BinaryString(data))=instance.properties.get("MeshData"){
|
|
||||||
mesh_data=data.as_ref();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
crate::union::convert(physics_data,mesh_data,size,part_texture_description)?
|
|
||||||
}else{
|
|
||||||
crate::union::convert(physics_data,mesh_data,size,part_texture_description)?
|
|
||||||
}
|
|
||||||
},
|
|
||||||
};
|
|
||||||
Ok(mesh)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,9 +1,8 @@
|
|||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use rbx_mesh::mesh::{Vertex2, Vertex2Truncated};
|
use rbx_mesh::mesh::{Vertex2, Vertex2Truncated};
|
||||||
use strafesnet_common::{integer::vec3,model::{self,ColorId,IndexedVertex,NormalId,PolygonGroup,PolygonList,PositionId,RenderConfigId,TextureCoordinateId,VertexId}};
|
use strafesnet_common::{integer::vec3,model::{self, ColorId, IndexedVertex, NormalId, PolygonGroup, PolygonList, PositionId, TextureCoordinateId, VertexId}};
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum Error{
|
pub enum Error{
|
||||||
Planar64Vec3(strafesnet_common::integer::Planar64TryFromFloatError),
|
Planar64Vec3(strafesnet_common::integer::Planar64TryFromFloatError),
|
||||||
@ -205,13 +204,7 @@ pub fn convert(roblox_mesh_bytes:crate::data::RobloxMeshBytes)->Result<model::Me
|
|||||||
unique_vertices,
|
unique_vertices,
|
||||||
polygon_groups,
|
polygon_groups,
|
||||||
//these should probably be moved to the model...
|
//these should probably be moved to the model...
|
||||||
//but what if models want to use the same texture
|
graphics_groups:Vec::new(),
|
||||||
graphics_groups:vec![model::IndexedGraphicsGroup{
|
|
||||||
render:RenderConfigId::new(0),
|
|
||||||
//the lowest lod is highest quality
|
|
||||||
groups:vec![model::PolygonGroupId::new(0)]
|
|
||||||
}],
|
|
||||||
//disable physics
|
|
||||||
physics_groups:Vec::new(),
|
physics_groups:Vec::new(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
use strafesnet_common::model::{Color4,TextureCoordinate,Mesh,IndexedGraphicsGroup,IndexedPhysicsGroup,IndexedVertex,PolygonGroupId,PolygonGroup,PolygonList,PositionId,TextureCoordinateId,NormalId,ColorId,VertexId,RenderConfigId};
|
use strafesnet_common::model::{Color4,TextureCoordinate,Mesh,IndexedGraphicsGroup,IndexedPhysicsGroup,IndexedVertex,PolygonGroupId,PolygonGroup,PolygonList,IndexedVertexList,PositionId,TextureCoordinateId,NormalId,ColorId,VertexId,RenderConfigId};
|
||||||
use strafesnet_common::integer::{vec3,Planar64Vec3};
|
use strafesnet_common::integer::{vec3,Planar64Vec3};
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
@ -42,121 +42,6 @@ const CUBE_DEFAULT_NORMALS:[Planar64Vec3;6]=[
|
|||||||
vec3::int( 0,-1, 0),//CubeFace::Bottom
|
vec3::int( 0,-1, 0),//CubeFace::Bottom
|
||||||
vec3::int( 0, 0,-1),//CubeFace::Front
|
vec3::int( 0, 0,-1),//CubeFace::Front
|
||||||
];
|
];
|
||||||
|
|
||||||
#[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 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 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 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 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 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{
|
|
||||||
pub render:RenderConfigId,
|
|
||||||
pub transform:glam::Affine2,
|
|
||||||
pub color:Color4,
|
|
||||||
}
|
|
||||||
impl FaceDescription{
|
|
||||||
pub fn new_with_render_id(render:RenderConfigId)->Self {
|
|
||||||
Self{
|
|
||||||
render,
|
|
||||||
transform:glam::Affine2::IDENTITY,
|
|
||||||
color:Color4::new(1.0,1.0,1.0,0.0),//zero alpha to hide the default texture
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub fn generate_partial_unit_cube(face_descriptions:CubeFaceDescription)->Mesh{
|
|
||||||
const CUBE_DEFAULT_POLYS:[[[u32;3];4];6]=[
|
const CUBE_DEFAULT_POLYS:[[[u32;3];4];6]=[
|
||||||
// right (1, 0, 0)
|
// right (1, 0, 0)
|
||||||
[
|
[
|
||||||
@ -201,6 +86,128 @@ pub fn generate_partial_unit_cube(face_descriptions:CubeFaceDescription)->Mesh{
|
|||||||
[7,2,5],
|
[7,2,5],
|
||||||
],
|
],
|
||||||
];
|
];
|
||||||
|
|
||||||
|
#[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
|
||||||
|
];
|
||||||
|
pub fn unit_sphere(render:RenderConfigId)->Mesh{
|
||||||
|
unit_cube(render)
|
||||||
|
}
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct CubeFaceDescription([Option<FaceDescription>;6]);
|
||||||
|
impl CubeFaceDescription{
|
||||||
|
pub fn insert(&mut self,index:CubeFace,value:FaceDescription){
|
||||||
|
self.0[index as usize]=Some(value);
|
||||||
|
}
|
||||||
|
pub fn pairs(self)->std::iter::FilterMap<std::iter::Enumerate<std::array::IntoIter<Option<FaceDescription>,6>>,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_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)
|
||||||
|
}
|
||||||
|
pub fn unit_cylinder(render:RenderConfigId)->Mesh{
|
||||||
|
//lmao
|
||||||
|
unit_cube(render)
|
||||||
|
}
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct WedgeFaceDescription([Option<FaceDescription>;5]);
|
||||||
|
impl WedgeFaceDescription{
|
||||||
|
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 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 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{
|
||||||
|
pub render:RenderConfigId,
|
||||||
|
pub transform:glam::Affine2,
|
||||||
|
pub color:Color4,
|
||||||
|
}
|
||||||
|
impl FaceDescription{
|
||||||
|
pub fn new_with_render_id(render:RenderConfigId)->Self {
|
||||||
|
Self{
|
||||||
|
render,
|
||||||
|
transform:glam::Affine2::IDENTITY,
|
||||||
|
color:Color4::new(1.0,1.0,1.0,0.0),//zero alpha to hide the default texture
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn generate_partial_unit_cube(face_descriptions:CubeFaceDescription)->Mesh{
|
||||||
let mut generated_pos=Vec::new();
|
let mut generated_pos=Vec::new();
|
||||||
let mut generated_tex=Vec::new();
|
let mut generated_tex=Vec::new();
|
||||||
let mut generated_normal=Vec::new();
|
let mut generated_normal=Vec::new();
|
||||||
@ -279,35 +286,35 @@ pub fn generate_partial_unit_cube(face_descriptions:CubeFaceDescription)->Mesh{
|
|||||||
}
|
}
|
||||||
//don't think too hard about the copy paste because this is all going into the map tool eventually...
|
//don't think too hard about the copy paste because this is all going into the map tool eventually...
|
||||||
pub fn generate_partial_unit_wedge(face_descriptions:WedgeFaceDescription)->Mesh{
|
pub fn generate_partial_unit_wedge(face_descriptions:WedgeFaceDescription)->Mesh{
|
||||||
const WEDGE_DEFAULT_POLYS:[&[[u32;3]];5]=[
|
let wedge_default_polys=[
|
||||||
// right (1, 0, 0)
|
// right (1, 0, 0)
|
||||||
&[
|
vec![
|
||||||
[6,2,0],//[vertex,tex,norm]
|
[6,2,0],//[vertex,tex,norm]
|
||||||
[2,0,0],
|
[2,0,0],
|
||||||
[1,3,0],
|
[1,3,0],
|
||||||
],
|
],
|
||||||
// FrontTop (0, 1, -1)
|
// FrontTop (0, 1, -1)
|
||||||
&[
|
vec![
|
||||||
[3,1,1],
|
[3,1,1],
|
||||||
[2,0,1],
|
[2,0,1],
|
||||||
[6,3,1],
|
[6,3,1],
|
||||||
[7,2,1],
|
[7,2,1],
|
||||||
],
|
],
|
||||||
// back (0, 0, 1)
|
// back (0, 0, 1)
|
||||||
&[
|
vec![
|
||||||
[0,3,2],
|
[0,3,2],
|
||||||
[1,2,2],
|
[1,2,2],
|
||||||
[2,1,2],
|
[2,1,2],
|
||||||
[3,0,2],
|
[3,0,2],
|
||||||
],
|
],
|
||||||
// left (-1, 0, 0)
|
// left (-1, 0, 0)
|
||||||
&[
|
vec![
|
||||||
[0,2,3],
|
[0,2,3],
|
||||||
[3,1,3],
|
[3,1,3],
|
||||||
[7,3,3],
|
[7,3,3],
|
||||||
],
|
],
|
||||||
// bottom (0,-1, 0)
|
// bottom (0,-1, 0)
|
||||||
&[
|
vec![
|
||||||
[1,1,4],
|
[1,1,4],
|
||||||
[0,0,4],
|
[0,0,4],
|
||||||
[7,3,4],
|
[7,3,4],
|
||||||
@ -351,7 +358,7 @@ pub fn generate_partial_unit_wedge(face_descriptions:WedgeFaceDescription)->Mesh
|
|||||||
//push vertices as they are needed
|
//push vertices as they are needed
|
||||||
let group_id=PolygonGroupId::new(polygon_groups.len() as u32);
|
let group_id=PolygonGroupId::new(polygon_groups.len() as u32);
|
||||||
polygon_groups.push(PolygonGroup::PolygonList(PolygonList::new(vec![
|
polygon_groups.push(PolygonGroup::PolygonList(PolygonList::new(vec![
|
||||||
WEDGE_DEFAULT_POLYS[face_id].iter().map(|tup|{
|
wedge_default_polys[face_id].iter().map(|tup|{
|
||||||
let pos=CUBE_DEFAULT_VERTICES[tup[0] as usize];
|
let pos=CUBE_DEFAULT_VERTICES[tup[0] as usize];
|
||||||
let pos_index=if let Some(pos_index)=generated_pos.iter().position(|&p|p==pos){
|
let pos_index=if let Some(pos_index)=generated_pos.iter().position(|&p|p==pos){
|
||||||
pos_index
|
pos_index
|
||||||
@ -392,34 +399,34 @@ pub fn generate_partial_unit_wedge(face_descriptions:WedgeFaceDescription)->Mesh
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn generate_partial_unit_cornerwedge(face_descriptions:CornerWedgeFaceDescription)->Mesh{
|
pub fn generate_partial_unit_cornerwedge(face_descriptions:CornerWedgeFaceDescription)->Mesh{
|
||||||
const CORNERWEDGE_DEFAULT_POLYS:[&[[u32;3]];5]=[
|
let cornerwedge_default_polys=[
|
||||||
// right (1, 0, 0)
|
// right (1, 0, 0)
|
||||||
&[
|
vec![
|
||||||
[6,2,0],//[vertex,tex,norm]
|
[6,2,0],//[vertex,tex,norm]
|
||||||
[5,1,0],
|
[5,1,0],
|
||||||
[1,3,0],
|
[1,3,0],
|
||||||
],
|
],
|
||||||
// BackTop (0, 1, 1)
|
// BackTop (0, 1, 1)
|
||||||
&[
|
vec![
|
||||||
[5,3,1],
|
[5,3,1],
|
||||||
[0,1,1],
|
[0,1,1],
|
||||||
[1,0,1],
|
[1,0,1],
|
||||||
],
|
],
|
||||||
// LeftTop (-1, 1, 0)
|
// LeftTop (-1, 1, 0)
|
||||||
&[
|
vec![
|
||||||
[5,3,2],
|
[5,3,2],
|
||||||
[7,2,2],
|
[7,2,2],
|
||||||
[0,1,2],
|
[0,1,2],
|
||||||
],
|
],
|
||||||
// bottom (0,-1, 0)
|
// bottom (0,-1, 0)
|
||||||
&[
|
vec![
|
||||||
[1,1,3],
|
[1,1,3],
|
||||||
[0,0,3],
|
[0,0,3],
|
||||||
[7,3,3],
|
[7,3,3],
|
||||||
[6,2,3],
|
[6,2,3],
|
||||||
],
|
],
|
||||||
// front (0, 0,-1)
|
// front (0, 0,-1)
|
||||||
&[
|
vec![
|
||||||
[5,0,4],
|
[5,0,4],
|
||||||
[6,3,4],
|
[6,3,4],
|
||||||
[7,2,4],
|
[7,2,4],
|
||||||
@ -462,7 +469,7 @@ pub fn generate_partial_unit_cornerwedge(face_descriptions:CornerWedgeFaceDescri
|
|||||||
//push vertices as they are needed
|
//push vertices as they are needed
|
||||||
let group_id=PolygonGroupId::new(polygon_groups.len() as u32);
|
let group_id=PolygonGroupId::new(polygon_groups.len() as u32);
|
||||||
polygon_groups.push(PolygonGroup::PolygonList(PolygonList::new(vec![
|
polygon_groups.push(PolygonGroup::PolygonList(PolygonList::new(vec![
|
||||||
CORNERWEDGE_DEFAULT_POLYS[face_id].iter().map(|tup|{
|
cornerwedge_default_polys[face_id].iter().map(|tup|{
|
||||||
let pos=CUBE_DEFAULT_VERTICES[tup[0] as usize];
|
let pos=CUBE_DEFAULT_VERTICES[tup[0] as usize];
|
||||||
let pos_index=if let Some(pos_index)=generated_pos.iter().position(|&p|p==pos){
|
let pos_index=if let Some(pos_index)=generated_pos.iter().position(|&p|p==pos){
|
||||||
pos_index
|
pos_index
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use crate::loader::MeshIndex;
|
|
||||||
use crate::primitives;
|
use crate::primitives;
|
||||||
use strafesnet_common::aabb::Aabb;
|
|
||||||
use strafesnet_common::map;
|
use strafesnet_common::map;
|
||||||
use strafesnet_common::model;
|
use strafesnet_common::model;
|
||||||
use strafesnet_common::gameplay_modes;
|
use strafesnet_common::gameplay_modes;
|
||||||
@ -10,9 +8,6 @@ use strafesnet_common::gameplay_attributes as attr;
|
|||||||
use strafesnet_common::integer::{self,vec3,Planar64,Planar64Vec3,Planar64Mat3,Planar64Affine3};
|
use strafesnet_common::integer::{self,vec3,Planar64,Planar64Vec3,Planar64Mat3,Planar64Affine3};
|
||||||
use strafesnet_common::model::RenderConfigId;
|
use strafesnet_common::model::RenderConfigId;
|
||||||
use strafesnet_common::updatable::Updatable;
|
use strafesnet_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 class_is_a(class: &str, superclass: &str) -> bool {
|
fn class_is_a(class: &str, superclass: &str) -> bool {
|
||||||
if class==superclass {
|
if class==superclass {
|
||||||
@ -135,9 +130,9 @@ impl ModesBuilder{
|
|||||||
fn push_mode_update(&mut self,mode_id:gameplay_modes::ModeId,mode_update:gameplay_modes::ModeUpdate){
|
fn push_mode_update(&mut self,mode_id:gameplay_modes::ModeId,mode_update:gameplay_modes::ModeUpdate){
|
||||||
self.mode_updates.push((mode_id,mode_update));
|
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){
|
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));
|
self.stage_updates.push((mode_id,stage_id,stage_update));
|
||||||
// }
|
}
|
||||||
}
|
}
|
||||||
fn get_attributes(name:&str,can_collide:bool,velocity:Planar64Vec3,model_id:model::ModelId,modes_builder:&mut ModesBuilder,wormhole_in_model_to_id:&mut HashMap<model::ModelId,u32>,wormhole_id_to_out_model:&mut HashMap<u32,model::ModelId>)->attr::CollisionAttributes{
|
fn get_attributes(name:&str,can_collide:bool,velocity:Planar64Vec3,model_id:model::ModelId,modes_builder:&mut ModesBuilder,wormhole_in_model_to_id:&mut HashMap<model::ModelId,u32>,wormhole_id_to_out_model:&mut HashMap<u32,model::ModelId>)->attr::CollisionAttributes{
|
||||||
let mut general=attr::GeneralAttributes::default();
|
let mut general=attr::GeneralAttributes::default();
|
||||||
@ -346,103 +341,58 @@ fn get_attributes(name:&str,can_collide:bool,velocity:Planar64Vec3,model_id:mode
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone,Copy)]
|
#[derive(Clone,Copy,PartialEq)]
|
||||||
pub struct RobloxTextureTransform{
|
struct RobloxTextureTransform{
|
||||||
offset_studs_u:f32,
|
offset_u:f32,
|
||||||
offset_studs_v:f32,
|
offset_v:f32,
|
||||||
studs_per_tile_u:f32,
|
scale_u:f32,
|
||||||
studs_per_tile_v:f32,
|
scale_v:f32,
|
||||||
size_u:f32,
|
|
||||||
size_v:f32,
|
|
||||||
}
|
}
|
||||||
#[derive(Clone,Copy,Hash,Eq,PartialEq)]
|
impl std::cmp::Eq for RobloxTextureTransform{}//????
|
||||||
pub struct RobloxTextureTransformBits{
|
impl std::default::Default for RobloxTextureTransform{
|
||||||
offset_studs_u:u32,
|
fn default()->Self{
|
||||||
offset_studs_v:u32,
|
Self{offset_u:0.0,offset_v:0.0,scale_u:1.0,scale_v:1.0}
|
||||||
studs_per_tile_u:u32,
|
|
||||||
studs_per_tile_v:u32,
|
|
||||||
size_u:u32,
|
|
||||||
size_v:u32,
|
|
||||||
}
|
|
||||||
impl RobloxTextureTransform{
|
|
||||||
fn identity()->Self{
|
|
||||||
Self{
|
|
||||||
offset_studs_u:0.0,
|
|
||||||
offset_studs_v:0.0,
|
|
||||||
studs_per_tile_u:1.0,
|
|
||||||
studs_per_tile_v:1.0,
|
|
||||||
size_u:1.0,
|
|
||||||
size_v:1.0,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn to_bits(self)->RobloxTextureTransformBits{
|
impl std::hash::Hash for RobloxTextureTransform{
|
||||||
RobloxTextureTransformBits{
|
fn hash<H:std::hash::Hasher>(&self,state:&mut H) {
|
||||||
offset_studs_u:self.offset_studs_u.to_bits(),
|
self.offset_u.to_ne_bytes().hash(state);
|
||||||
offset_studs_v:self.offset_studs_v.to_bits(),
|
self.offset_v.to_ne_bytes().hash(state);
|
||||||
studs_per_tile_u:self.studs_per_tile_u.to_bits(),
|
self.scale_u.to_ne_bytes().hash(state);
|
||||||
studs_per_tile_v:self.studs_per_tile_v.to_bits(),
|
self.scale_v.to_ne_bytes().hash(state);
|
||||||
size_u:self.size_u.to_bits(),
|
|
||||||
size_v:self.size_v.to_bits(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn affine(&self)->glam::Affine2{
|
#[derive(Clone,PartialEq)]
|
||||||
glam::Affine2::from_translation(
|
struct RobloxFaceTextureDescription{
|
||||||
glam::vec2(self.offset_studs_u/self.studs_per_tile_u,self.offset_studs_v/self.studs_per_tile_v)
|
|
||||||
)
|
|
||||||
*glam::Affine2::from_scale(
|
|
||||||
glam::vec2(self.size_u/self.studs_per_tile_u,self.size_v/self.studs_per_tile_v)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
pub fn set_size(&mut self,size_u:f32,size_v:f32){
|
|
||||||
self.size_u=size_u;
|
|
||||||
self.size_v=size_v;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl core::hash::Hash for RobloxTextureTransform{
|
|
||||||
fn hash<H:core::hash::Hasher>(&self,state:&mut H){
|
|
||||||
self.to_bits().hash(state);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#[derive(Clone,Copy,Hash,Eq,PartialEq)]
|
|
||||||
pub struct RobloxFaceTextureDescriptionBits{
|
|
||||||
render:RenderConfigId,
|
render:RenderConfigId,
|
||||||
color:[u32;4],
|
color:glam::Vec4,
|
||||||
transform:RobloxTextureTransformBits,
|
transform:RobloxTextureTransform,
|
||||||
}
|
}
|
||||||
#[derive(Clone,Copy)]
|
impl std::cmp::Eq for RobloxFaceTextureDescription{}//????
|
||||||
pub struct RobloxFaceTextureDescription{
|
impl std::hash::Hash for RobloxFaceTextureDescription{
|
||||||
pub render:RenderConfigId,
|
fn hash<H:std::hash::Hasher>(&self,state:&mut H){
|
||||||
pub color:glam::Vec4,
|
self.render.hash(state);
|
||||||
pub transform:RobloxTextureTransform,
|
self.transform.hash(state);
|
||||||
|
for &el in self.color.as_ref().iter(){
|
||||||
|
el.to_ne_bytes().hash(state);
|
||||||
}
|
}
|
||||||
impl core::cmp::PartialEq for RobloxFaceTextureDescription{
|
|
||||||
fn eq(&self,other:&Self)->bool{
|
|
||||||
self.to_bits().eq(&other.to_bits())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl core::cmp::Eq for RobloxFaceTextureDescription{}
|
|
||||||
impl core::hash::Hash for RobloxFaceTextureDescription{
|
|
||||||
fn hash<H:core::hash::Hasher>(&self,state:&mut H){
|
|
||||||
self.to_bits().hash(state);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl RobloxFaceTextureDescription{
|
impl RobloxFaceTextureDescription{
|
||||||
pub fn to_bits(self)->RobloxFaceTextureDescriptionBits{
|
fn to_face_description(&self)->primitives::FaceDescription{
|
||||||
RobloxFaceTextureDescriptionBits{
|
|
||||||
render:self.render,
|
|
||||||
color:self.color.to_array().map(f32::to_bits),
|
|
||||||
transform:self.transform.to_bits(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub fn to_face_description(&self)->primitives::FaceDescription{
|
|
||||||
primitives::FaceDescription{
|
primitives::FaceDescription{
|
||||||
render:self.render,
|
render:self.render,
|
||||||
transform:self.transform.affine(),
|
transform:glam::Affine2::from_translation(
|
||||||
|
glam::vec2(self.transform.offset_u,self.transform.offset_v)
|
||||||
|
)
|
||||||
|
*glam::Affine2::from_scale(
|
||||||
|
glam::vec2(self.transform.scale_u,self.transform.scale_v)
|
||||||
|
),
|
||||||
color:self.color,
|
color:self.color,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub type RobloxPartDescription=[Option<RobloxFaceTextureDescription>;6];
|
type RobloxPartDescription=[Option<RobloxFaceTextureDescription>;6];
|
||||||
type RobloxWedgeDescription=[Option<RobloxFaceTextureDescription>;5];
|
type RobloxWedgeDescription=[Option<RobloxFaceTextureDescription>;5];
|
||||||
type RobloxCornerWedgeDescription=[Option<RobloxFaceTextureDescription>;5];
|
type RobloxCornerWedgeDescription=[Option<RobloxFaceTextureDescription>;5];
|
||||||
#[derive(Clone,Eq,Hash,PartialEq)]
|
#[derive(Clone,Eq,Hash,PartialEq)]
|
||||||
@ -453,134 +403,52 @@ enum RobloxBasePartDescription{
|
|||||||
Wedge(RobloxWedgeDescription),
|
Wedge(RobloxWedgeDescription),
|
||||||
CornerWedge(RobloxCornerWedgeDescription),
|
CornerWedge(RobloxCornerWedgeDescription),
|
||||||
}
|
}
|
||||||
fn get_texture_description<'a>(
|
|
||||||
temp_objects:&mut Vec<rbx_dom_weak::types::Ref>,
|
|
||||||
render_config_deferred_loader:&mut RenderConfigDeferredLoader<&'a str>,
|
|
||||||
dom:&'a rbx_dom_weak::WeakDom,
|
|
||||||
object:&rbx_dom_weak::Instance,
|
|
||||||
size:&rbx_dom_weak::types::Vector3,
|
|
||||||
)->RobloxPartDescription{
|
|
||||||
//use the biggest one and cut it down later...
|
|
||||||
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{
|
|
||||||
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("Texture"),
|
|
||||||
decal.properties.get("Face"),
|
|
||||||
decal.properties.get("Color3"),
|
|
||||||
decal.properties.get("Transparency"),
|
|
||||||
) {
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
part_texture_description
|
|
||||||
}
|
|
||||||
enum Shape{
|
enum Shape{
|
||||||
Primitive(primitives::Primitives),
|
Primitive(primitives::Primitives),
|
||||||
MeshPart,
|
MeshPart,
|
||||||
PhysicsData,
|
|
||||||
}
|
}
|
||||||
enum MeshAvailability{
|
enum MeshAvailability{
|
||||||
Immediate,
|
Immediate,
|
||||||
DeferredMesh(RenderConfigId),
|
Deferred(RenderConfigId),
|
||||||
DeferredUnion(RobloxPartDescription),
|
|
||||||
}
|
}
|
||||||
struct DeferredModelDeferredAttributes<'a>{
|
struct DeferredModelDeferredAttributes{
|
||||||
render:RenderConfigId,
|
render:RenderConfigId,
|
||||||
model:ModelDeferredAttributes<'a>,
|
model:ModelDeferredAttributes,
|
||||||
}
|
}
|
||||||
struct ModelDeferredAttributes<'a>{
|
struct ModelDeferredAttributes{
|
||||||
mesh:model::MeshId,
|
mesh:model::MeshId,
|
||||||
deferred_attributes:GetAttributesArgs<'a>,
|
deferred_attributes:GetAttributesArgs,
|
||||||
color:model::Color4,//transparency is in here
|
color:model::Color4,//transparency is in here
|
||||||
transform:Planar64Affine3,
|
transform:Planar64Affine3,
|
||||||
}
|
}
|
||||||
struct DeferredUnionDeferredAttributes<'a>{
|
|
||||||
render:RobloxPartDescription,
|
|
||||||
model:ModelDeferredAttributes<'a>,
|
|
||||||
}
|
|
||||||
struct ModelOwnedAttributes{
|
struct ModelOwnedAttributes{
|
||||||
mesh:model::MeshId,
|
mesh:model::MeshId,
|
||||||
attributes:attr::CollisionAttributes,
|
attributes:attr::CollisionAttributes,
|
||||||
color:model::Color4,//transparency is in here
|
color:model::Color4,//transparency is in here
|
||||||
transform:Planar64Affine3,
|
transform:Planar64Affine3,
|
||||||
}
|
}
|
||||||
struct GetAttributesArgs<'a>{
|
struct GetAttributesArgs{
|
||||||
name:&'a str,
|
name:Box<str>,
|
||||||
can_collide:bool,
|
can_collide:bool,
|
||||||
velocity:Planar64Vec3,
|
velocity:Planar64Vec3,
|
||||||
}
|
}
|
||||||
pub fn convert<'a>(
|
pub fn convert<AcquireRenderConfigId,AcquireMeshId>(
|
||||||
dom:&'a rbx_dom_weak::WeakDom,
|
dom:&rbx_dom_weak::WeakDom,
|
||||||
render_config_deferred_loader:&mut RenderConfigDeferredLoader<&'a str>,
|
mut acquire_render_config_id:AcquireRenderConfigId,
|
||||||
mesh_deferred_loader:&mut MeshDeferredLoader<MeshIndex<'a>>,
|
mut acquire_mesh_id:AcquireMeshId,
|
||||||
)->PartialMap1<'a>{
|
)->PartialMap1
|
||||||
|
where
|
||||||
|
AcquireRenderConfigId:FnMut(Option<&str>)->model::RenderConfigId,
|
||||||
|
AcquireMeshId:FnMut(&str)->model::MeshId,
|
||||||
|
{
|
||||||
|
|
||||||
let mut deferred_models_deferred_attributes=Vec::new();
|
let mut deferred_models_deferred_attributes=Vec::new();
|
||||||
let mut deferred_unions_deferred_attributes=Vec::new();
|
|
||||||
let mut primitive_models_deferred_attributes=Vec::new();
|
let mut primitive_models_deferred_attributes=Vec::new();
|
||||||
let mut primitive_meshes=Vec::new();
|
let mut primitive_meshes=Vec::new();
|
||||||
let mut mesh_id_from_description=HashMap::new();
|
let mut mesh_id_from_description=HashMap::new();
|
||||||
|
|
||||||
//just going to leave it like this for now instead of reworking the data structures for this whole thing
|
//just going to leave it like this for now instead of reworking the data structures for this whole thing
|
||||||
let textureless_render_group=render_config_deferred_loader.acquire_render_config_id(None);
|
let textureless_render_group=acquire_render_config_id(None);
|
||||||
|
|
||||||
let mut object_refs=Vec::new();
|
let mut object_refs=Vec::new();
|
||||||
let mut temp_objects=Vec::new();
|
let mut temp_objects=Vec::new();
|
||||||
@ -617,6 +485,9 @@ pub fn convert<'a>(
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//at this point a new model is going to be generated for sure.
|
||||||
|
let model_id=model::ModelId::new(primitive_models_deferred_attributes.len() as u32);
|
||||||
|
|
||||||
//TODO: also detect "CylinderMesh" etc here
|
//TODO: also detect "CylinderMesh" etc here
|
||||||
let shape=match object.class.as_str(){
|
let shape=match object.class.as_str(){
|
||||||
"Part"=>if let Some(rbx_dom_weak::types::Variant::Enum(shape))=object.properties.get("Shape"){
|
"Part"=>if let Some(rbx_dom_weak::types::Variant::Enum(shape))=object.properties.get("Shape"){
|
||||||
@ -635,7 +506,6 @@ pub fn convert<'a>(
|
|||||||
"WedgePart"=>Shape::Primitive(primitives::Primitives::Wedge),
|
"WedgePart"=>Shape::Primitive(primitives::Primitives::Wedge),
|
||||||
"CornerWedgePart"=>Shape::Primitive(primitives::Primitives::CornerWedge),
|
"CornerWedgePart"=>Shape::Primitive(primitives::Primitives::CornerWedge),
|
||||||
"MeshPart"=>Shape::MeshPart,
|
"MeshPart"=>Shape::MeshPart,
|
||||||
"UnionOperation"=>Shape::PhysicsData,
|
|
||||||
_=>{
|
_=>{
|
||||||
println!("Unsupported BasePart ClassName={}; defaulting to cube",object.class);
|
println!("Unsupported BasePart ClassName={}; defaulting to cube",object.class);
|
||||||
Shape::Primitive(primitives::Primitives::Cube)
|
Shape::Primitive(primitives::Primitives::Cube)
|
||||||
@ -645,7 +515,73 @@ pub fn convert<'a>(
|
|||||||
let (availability,mesh_id)=match shape{
|
let (availability,mesh_id)=match shape{
|
||||||
Shape::Primitive(primitive_shape)=>{
|
Shape::Primitive(primitive_shape)=>{
|
||||||
//TODO: TAB TAB
|
//TODO: TAB TAB
|
||||||
let part_texture_description=get_texture_description(&mut temp_objects,render_config_deferred_loader,dom,object,size);
|
//use the biggest one and cut it down later...
|
||||||
|
let mut part_texture_description:RobloxPartDescription=[None,None,None,None,None,None];
|
||||||
|
temp_objects.clear();
|
||||||
|
recursive_collect_superclass(&mut temp_objects, &dom, object,"Decal");
|
||||||
|
for &decal_ref in &temp_objects{
|
||||||
|
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("Texture"),
|
||||||
|
decal.properties.get("Face"),
|
||||||
|
decal.properties.get("Color3"),
|
||||||
|
decal.properties.get("Transparency"),
|
||||||
|
) {
|
||||||
|
let render_id=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(ox)),
|
||||||
|
Some(rbx_dom_weak::types::Variant::Float32(oy)),
|
||||||
|
Some(rbx_dom_weak::types::Variant::Float32(sx)),
|
||||||
|
Some(rbx_dom_weak::types::Variant::Float32(sy)),
|
||||||
|
) = (
|
||||||
|
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_u:*ox/(*sx),offset_v:*oy/(*sy),
|
||||||
|
scale_u:size_u/(*sx),scale_v:size_v/(*sy),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}else{
|
||||||
|
(glam::Vec4::ONE,RobloxTextureTransform::default())
|
||||||
|
}
|
||||||
|
}else{
|
||||||
|
(glam::Vec4::ONE,RobloxTextureTransform::default())
|
||||||
|
};
|
||||||
|
part_texture_description[normal_id as usize]=Some(RobloxFaceTextureDescription{
|
||||||
|
render:render_id,
|
||||||
|
color:roblox_texture_color,
|
||||||
|
transform:roblox_texture_transform,
|
||||||
|
});
|
||||||
|
}else{
|
||||||
|
println!("NormalId={} unsupported for shape={:?}",normal_id,primitive_shape);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
//obscure rust syntax "slice pattern"
|
//obscure rust syntax "slice pattern"
|
||||||
let [
|
let [
|
||||||
f0,//Cube::Right
|
f0,//Cube::Right
|
||||||
@ -662,7 +598,7 @@ pub fn convert<'a>(
|
|||||||
//use front face texture first and use top face texture as a fallback
|
//use front face texture first and use top face texture as a fallback
|
||||||
primitives::Primitives::Wedge=>RobloxBasePartDescription::Wedge([
|
primitives::Primitives::Wedge=>RobloxBasePartDescription::Wedge([
|
||||||
f0,//Cube::Right->Wedge::Right
|
f0,//Cube::Right->Wedge::Right
|
||||||
f5.or(f1),//Cube::Front|Cube::Top->Wedge::TopFront
|
if f5.is_some(){f5}else{f1},//Cube::Front|Cube::Top->Wedge::TopFront
|
||||||
f2,//Cube::Back->Wedge::Back
|
f2,//Cube::Back->Wedge::Back
|
||||||
f3,//Cube::Left->Wedge::Left
|
f3,//Cube::Left->Wedge::Left
|
||||||
f4,//Cube::Bottom->Wedge::Bottom
|
f4,//Cube::Bottom->Wedge::Bottom
|
||||||
@ -670,8 +606,8 @@ pub fn convert<'a>(
|
|||||||
//TODO: fix Left+Back texture coordinates to match roblox when not overwridden by Top
|
//TODO: fix Left+Back texture coordinates to match roblox when not overwridden by Top
|
||||||
primitives::Primitives::CornerWedge=>RobloxBasePartDescription::CornerWedge([
|
primitives::Primitives::CornerWedge=>RobloxBasePartDescription::CornerWedge([
|
||||||
f0,//Cube::Right->CornerWedge::Right
|
f0,//Cube::Right->CornerWedge::Right
|
||||||
f2.or(f1.clone()),//Cube::Back|Cube::Top->CornerWedge::TopBack
|
if f2.is_some(){f2}else{f1.clone()},//Cube::Back|Cube::Top->CornerWedge::TopBack
|
||||||
f3.or(f1),//Cube::Left|Cube::Top->CornerWedge::TopLeft
|
if f3.is_some(){f3}else{f1},//Cube::Left|Cube::Top->CornerWedge::TopLeft
|
||||||
f4,//Cube::Bottom->CornerWedge::Bottom
|
f4,//Cube::Bottom->CornerWedge::Bottom
|
||||||
f5,//Cube::Front->CornerWedge::Front
|
f5,//Cube::Front->CornerWedge::Front
|
||||||
]),
|
]),
|
||||||
@ -758,51 +694,29 @@ pub fn convert<'a>(
|
|||||||
object.properties.get("TextureID"),
|
object.properties.get("TextureID"),
|
||||||
){
|
){
|
||||||
(
|
(
|
||||||
MeshAvailability::DeferredMesh(render_config_deferred_loader.acquire_render_config_id(Some(texture_asset_id.as_ref()))),
|
MeshAvailability::Deferred(acquire_render_config_id(Some(texture_asset_id.as_ref()))),
|
||||||
mesh_deferred_loader.acquire_mesh_id(MeshIndex::file_mesh(mesh_asset_id.as_ref())),
|
acquire_mesh_id(mesh_asset_id.as_ref()),
|
||||||
)
|
)
|
||||||
}else{
|
}else{
|
||||||
panic!("Mesh has no Mesh or Texture");
|
panic!("Mesh has no Mesh or Texture");
|
||||||
},
|
},
|
||||||
Shape::PhysicsData=>{
|
|
||||||
let mut content="";
|
|
||||||
let mut mesh_data:&[u8]=&[];
|
|
||||||
let mut physics_data:&[u8]=&[];
|
|
||||||
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"){
|
|
||||||
mesh_data=data.as_ref();
|
|
||||||
}
|
|
||||||
if let Some(rbx_dom_weak::types::Variant::BinaryString(data))=object.properties.get("PhysicsData"){
|
|
||||||
physics_data=data.as_ref();
|
|
||||||
}
|
|
||||||
let part_texture_description=get_texture_description(&mut temp_objects,render_config_deferred_loader,dom,object,size);
|
|
||||||
let mesh_index=MeshIndex::union(content,mesh_data,physics_data,size,part_texture_description.clone());
|
|
||||||
let mesh_id=mesh_deferred_loader.acquire_mesh_id(mesh_index);
|
|
||||||
(MeshAvailability::DeferredUnion(part_texture_description),mesh_id)
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
let model_deferred_attributes=ModelDeferredAttributes{
|
let model_deferred_attributes=ModelDeferredAttributes{
|
||||||
mesh:mesh_id,
|
mesh:mesh_id,
|
||||||
transform:model_transform,
|
transform:model_transform,
|
||||||
color:glam::vec4(color3.r as f32/255f32, color3.g as f32/255f32, color3.b as f32/255f32, 1.0-*transparency),
|
color:glam::vec4(color3.r as f32/255f32, color3.g as f32/255f32, color3.b as f32/255f32, 1.0-*transparency),
|
||||||
deferred_attributes:GetAttributesArgs{
|
deferred_attributes:GetAttributesArgs{
|
||||||
name:object.name.as_str(),
|
name:object.name.as_str().into(),
|
||||||
can_collide:*can_collide,
|
can_collide:*can_collide,
|
||||||
velocity:vec3::try_from_f32_array([velocity.x,velocity.y,velocity.z]).unwrap(),
|
velocity:vec3::try_from_f32_array([velocity.x,velocity.y,velocity.z]).unwrap(),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
match availability{
|
match availability{
|
||||||
MeshAvailability::Immediate=>primitive_models_deferred_attributes.push(model_deferred_attributes),
|
MeshAvailability::Immediate=>primitive_models_deferred_attributes.push(model_deferred_attributes),
|
||||||
MeshAvailability::DeferredMesh(render)=>deferred_models_deferred_attributes.push(DeferredModelDeferredAttributes{
|
MeshAvailability::Deferred(render)=>deferred_models_deferred_attributes.push(DeferredModelDeferredAttributes{
|
||||||
render,
|
render,
|
||||||
model:model_deferred_attributes
|
model:model_deferred_attributes
|
||||||
}),
|
}),
|
||||||
MeshAvailability::DeferredUnion(part_texture_description)=>deferred_unions_deferred_attributes.push(DeferredUnionDeferredAttributes{
|
|
||||||
render:part_texture_description,
|
|
||||||
model:model_deferred_attributes,
|
|
||||||
}),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -811,71 +725,21 @@ pub fn convert<'a>(
|
|||||||
primitive_meshes,
|
primitive_meshes,
|
||||||
primitive_models_deferred_attributes,
|
primitive_models_deferred_attributes,
|
||||||
deferred_models_deferred_attributes,
|
deferred_models_deferred_attributes,
|
||||||
deferred_unions_deferred_attributes,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
struct MeshWithAabb{
|
struct MeshWithAabb{
|
||||||
mesh:model::Mesh,
|
mesh:model::Mesh,
|
||||||
aabb:Aabb,
|
aabb:strafesnet_common::aabb::Aabb,
|
||||||
}
|
}
|
||||||
fn acquire_mesh_id_from_render_config_id<'a>(
|
pub struct PartialMap1{
|
||||||
primitive_meshes:&mut Vec<model::Mesh>,
|
|
||||||
mesh_id_from_render_config_id:&mut HashMap<model::MeshId,HashMap<RenderConfigId,model::MeshId>>,
|
|
||||||
loaded_meshes:&'a HashMap<model::MeshId,MeshWithAabb>,
|
|
||||||
old_mesh_id:model::MeshId,
|
|
||||||
render:RenderConfigId,
|
|
||||||
)->Option<(model::MeshId,&'a Aabb)>{
|
|
||||||
//ignore meshes that fail to load completely for now
|
|
||||||
loaded_meshes.get(&old_mesh_id).map(|mesh_with_aabb|(
|
|
||||||
*mesh_id_from_render_config_id.entry(old_mesh_id).or_insert_with(||HashMap::new())
|
|
||||||
.entry(render).or_insert_with(||{
|
|
||||||
let mesh_id=model::MeshId::new(primitive_meshes.len() as u32);
|
|
||||||
let mut mesh_clone=mesh_with_aabb.mesh.clone();
|
|
||||||
//set the render group lool
|
|
||||||
if let Some(graphics_group)=mesh_clone.graphics_groups.first_mut(){
|
|
||||||
graphics_group.render=render;
|
|
||||||
}
|
|
||||||
primitive_meshes.push(mesh_clone);
|
|
||||||
mesh_id
|
|
||||||
}),
|
|
||||||
&mesh_with_aabb.aabb,
|
|
||||||
))
|
|
||||||
}
|
|
||||||
fn acquire_union_id_from_render_config_id<'a>(
|
|
||||||
primitive_meshes:&mut Vec<model::Mesh>,
|
|
||||||
union_id_from_render_config_id:&mut HashMap<model::MeshId,HashMap<RobloxPartDescription,model::MeshId>>,
|
|
||||||
loaded_meshes:&'a HashMap<model::MeshId,MeshWithAabb>,
|
|
||||||
old_union_id:model::MeshId,
|
|
||||||
part_texture_description:RobloxPartDescription,
|
|
||||||
)->Option<(model::MeshId,&'a Aabb)>{
|
|
||||||
//ignore uniones that fail to load completely for now
|
|
||||||
loaded_meshes.get(&old_union_id).map(|union_with_aabb|(
|
|
||||||
*union_id_from_render_config_id.entry(old_union_id).or_insert_with(||HashMap::new())
|
|
||||||
.entry(part_texture_description.clone()).or_insert_with(||{
|
|
||||||
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){
|
|
||||||
if let Some(face_texture_description)=maybe_face_texture_description{
|
|
||||||
graphics_group.render=face_texture_description.render;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
primitive_meshes.push(union_clone);
|
|
||||||
union_id
|
|
||||||
}),
|
|
||||||
&union_with_aabb.aabb,
|
|
||||||
))
|
|
||||||
}
|
|
||||||
pub struct PartialMap1<'a>{
|
|
||||||
primitive_meshes:Vec<model::Mesh>,
|
primitive_meshes:Vec<model::Mesh>,
|
||||||
primitive_models_deferred_attributes:Vec<ModelDeferredAttributes<'a>>,
|
primitive_models_deferred_attributes:Vec<ModelDeferredAttributes>,
|
||||||
deferred_models_deferred_attributes:Vec<DeferredModelDeferredAttributes<'a>>,
|
deferred_models_deferred_attributes:Vec<DeferredModelDeferredAttributes>,
|
||||||
deferred_unions_deferred_attributes:Vec<DeferredUnionDeferredAttributes<'a>>,
|
|
||||||
}
|
}
|
||||||
impl PartialMap1<'_>{
|
impl PartialMap1{
|
||||||
pub fn add_meshpart_meshes_and_calculate_attributes(
|
pub fn add_meshpart_meshes_and_calculate_attributes(
|
||||||
mut self,
|
mut self,
|
||||||
meshpart_meshes:Meshes,
|
meshpart_meshes:impl IntoIterator<Item=(model::MeshId,crate::data::RobloxMeshBytes)>,
|
||||||
)->PartialMap2{
|
)->PartialMap2{
|
||||||
//calculate attributes
|
//calculate attributes
|
||||||
let mut modes_builder=ModesBuilder::default();
|
let mut modes_builder=ModesBuilder::default();
|
||||||
@ -888,32 +752,51 @@ impl PartialMap1<'_>{
|
|||||||
//decode roblox meshes
|
//decode roblox meshes
|
||||||
//generate mesh_id_map based on meshes that failed to load
|
//generate mesh_id_map based on meshes that failed to load
|
||||||
let loaded_meshes:HashMap<model::MeshId,MeshWithAabb>=
|
let loaded_meshes:HashMap<model::MeshId,MeshWithAabb>=
|
||||||
meshpart_meshes.consume().map(|(old_mesh_id,mesh)|{
|
meshpart_meshes.into_iter().flat_map(|(old_mesh_id,roblox_mesh_bytes)|
|
||||||
|
match crate::mesh::convert(roblox_mesh_bytes){
|
||||||
|
Ok(mesh)=>{
|
||||||
let mut aabb=strafesnet_common::aabb::Aabb::default();
|
let mut aabb=strafesnet_common::aabb::Aabb::default();
|
||||||
for &pos in &mesh.unique_pos{
|
for &pos in &mesh.unique_pos{
|
||||||
aabb.grow(pos);
|
aabb.grow(pos);
|
||||||
}
|
}
|
||||||
(old_mesh_id,MeshWithAabb{
|
Some((old_mesh_id,MeshWithAabb{
|
||||||
mesh,
|
mesh,
|
||||||
aabb,
|
aabb,
|
||||||
})
|
}))
|
||||||
}).collect();
|
},
|
||||||
|
Err(e)=>{
|
||||||
// SAFETY: I have no idea what I'm doing and this is definitely unsound in some subtle way
|
println!("Error converting mesh: {e:?}");
|
||||||
// I just want to chain iterators together man
|
None
|
||||||
let aint_no_way=core::cell::UnsafeCell::new(&mut self.primitive_meshes);
|
},
|
||||||
|
}
|
||||||
|
).collect();
|
||||||
|
|
||||||
let mut mesh_id_from_render_config_id=HashMap::new();
|
let mut mesh_id_from_render_config_id=HashMap::new();
|
||||||
let mut union_id_from_render_config_id=HashMap::new();
|
//ignore meshes that fail to load completely for now
|
||||||
|
let mut acquire_mesh_id_from_render_config_id=|old_mesh_id,render|{
|
||||||
|
loaded_meshes.get(&old_mesh_id).map(|mesh_with_aabb|(
|
||||||
|
*mesh_id_from_render_config_id.entry(old_mesh_id).or_insert_with(||HashMap::new())
|
||||||
|
.entry(render).or_insert_with(||{
|
||||||
|
let mesh_id=model::MeshId::new(self.primitive_meshes.len() as u32);
|
||||||
|
let mut mesh_clone=mesh_with_aabb.mesh.clone();
|
||||||
|
//add a render group lool
|
||||||
|
mesh_clone.graphics_groups.push(model::IndexedGraphicsGroup{
|
||||||
|
render,
|
||||||
|
//the lowest lod is highest quality
|
||||||
|
groups:vec![model::PolygonGroupId::new(0)]
|
||||||
|
});
|
||||||
|
self.primitive_meshes.push(mesh_clone);
|
||||||
|
mesh_id
|
||||||
|
}),
|
||||||
|
&mesh_with_aabb.aabb,
|
||||||
|
))
|
||||||
|
};
|
||||||
//now that the meshes are loaded, these models can be generated
|
//now that the meshes are loaded, these models can be generated
|
||||||
let models_owned_attributes:Vec<ModelOwnedAttributes>=
|
let models_owned_attributes:Vec<ModelOwnedAttributes>=
|
||||||
self.deferred_models_deferred_attributes.into_iter().flat_map(|deferred_model_deferred_attributes|{
|
self.deferred_models_deferred_attributes.into_iter().flat_map(|deferred_model_deferred_attributes|{
|
||||||
//meshes need to be cloned from loaded_meshes with a new id when they are used with a new render_id
|
//meshes need to be cloned from loaded_meshes with a new id when they are used with a new render_id
|
||||||
//insert into primitive_meshes
|
//insert into primitive_meshes
|
||||||
let (mesh,aabb)=acquire_mesh_id_from_render_config_id(
|
let (mesh,aabb)=acquire_mesh_id_from_render_config_id(
|
||||||
unsafe{*aint_no_way.get()},
|
|
||||||
&mut mesh_id_from_render_config_id,
|
|
||||||
&loaded_meshes,
|
|
||||||
deferred_model_deferred_attributes.model.mesh,
|
deferred_model_deferred_attributes.model.mesh,
|
||||||
deferred_model_deferred_attributes.render
|
deferred_model_deferred_attributes.render
|
||||||
)?;
|
)?;
|
||||||
@ -931,32 +814,7 @@ impl PartialMap1<'_>{
|
|||||||
deferred_model_deferred_attributes.model.transform.translation
|
deferred_model_deferred_attributes.model.transform.translation
|
||||||
),
|
),
|
||||||
})
|
})
|
||||||
}).chain(self.deferred_unions_deferred_attributes.into_iter().flat_map(|deferred_union_deferred_attributes|{
|
}).chain(self.primitive_models_deferred_attributes.into_iter())
|
||||||
//meshes need to be cloned from loaded_meshes with a new id when they are used with a new render_id
|
|
||||||
//insert into primitive_meshes
|
|
||||||
let (mesh,aabb)=acquire_union_id_from_render_config_id(
|
|
||||||
unsafe{*aint_no_way.get()},
|
|
||||||
&mut union_id_from_render_config_id,
|
|
||||||
&loaded_meshes,
|
|
||||||
deferred_union_deferred_attributes.model.mesh,
|
|
||||||
deferred_union_deferred_attributes.render
|
|
||||||
)?;
|
|
||||||
let size=aabb.size();
|
|
||||||
Some(ModelDeferredAttributes{
|
|
||||||
mesh,
|
|
||||||
deferred_attributes:deferred_union_deferred_attributes.model.deferred_attributes,
|
|
||||||
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().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
|
|
||||||
),
|
|
||||||
})
|
|
||||||
}))
|
|
||||||
.chain(self.primitive_models_deferred_attributes.into_iter())
|
|
||||||
.enumerate().map(|(model_id,model_deferred_attributes)|{
|
.enumerate().map(|(model_id,model_deferred_attributes)|{
|
||||||
let model_id=model::ModelId::new(model_id as u32);
|
let model_id=model::ModelId::new(model_id as u32);
|
||||||
ModelOwnedAttributes{
|
ModelOwnedAttributes{
|
||||||
@ -1024,21 +882,15 @@ pub struct PartialMap2{
|
|||||||
impl PartialMap2{
|
impl PartialMap2{
|
||||||
pub fn add_render_configs_and_textures(
|
pub fn add_render_configs_and_textures(
|
||||||
self,
|
self,
|
||||||
render_configs:RenderConfigs,
|
render_configs:impl IntoIterator<Item=(model::RenderConfigId,model::RenderConfig)>,
|
||||||
|
textures:impl IntoIterator<Item=(model::TextureId,Vec<u8>)>,
|
||||||
)->map::CompleteMap{
|
)->map::CompleteMap{
|
||||||
let (textures,render_configs)=render_configs.consume();
|
|
||||||
let (textures,texture_id_map):(Vec<Vec<u8>>,HashMap<model::TextureId,model::TextureId>)
|
let (textures,texture_id_map):(Vec<Vec<u8>>,HashMap<model::TextureId,model::TextureId>)
|
||||||
=textures.into_iter().enumerate().map(|(new_texture_id,(old_texture_id,Texture::ImageDDS(texture)))|{
|
=textures.into_iter().enumerate().map(|(new_texture_id,(old_texture_id,texture))|{
|
||||||
(texture,(old_texture_id,model::TextureId::new(new_texture_id as u32)))
|
(texture,(old_texture_id,model::TextureId::new(new_texture_id as u32)))
|
||||||
}).unzip();
|
}).unzip();
|
||||||
let render_configs=render_configs.into_iter().map(|(_render_config_id,mut render_config)|{
|
let render_configs=render_configs.into_iter().map(|(render_config_id,mut render_config)|{
|
||||||
// This may generate duplicate no-texture render configs but idc
|
//this may generate duplicate no-texture render configs but idc
|
||||||
//
|
|
||||||
// This is because some textures may not exist, so the render config
|
|
||||||
// that it points to is unique but is texture.
|
|
||||||
//
|
|
||||||
// I don't think this needs to be fixed because missing textures
|
|
||||||
// should be a conversion error anyways.
|
|
||||||
render_config.texture=render_config.texture.and_then(|texture_id|
|
render_config.texture=render_config.texture.and_then(|texture_id|
|
||||||
texture_id_map.get(&texture_id).copied()
|
texture_id_map.get(&texture_id).copied()
|
||||||
);
|
);
|
||||||
|
@ -1,177 +0,0 @@
|
|||||||
use rbx_mesh::mesh_data::NormalId2 as MeshDataNormalId2;
|
|
||||||
use strafesnet_common::model::{self,IndexedVertex,PolygonGroup,PolygonGroupId,PolygonList,RenderConfigId};
|
|
||||||
use strafesnet_common::integer::vec3;
|
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub enum Error{
|
|
||||||
Block,
|
|
||||||
MissingVertexId(u32),
|
|
||||||
Planar64Vec3(strafesnet_common::integer::Planar64TryFromFloatError),
|
|
||||||
RobloxPhysicsData(rbx_mesh::physics_data::Error),
|
|
||||||
RobloxMeshData(rbx_mesh::mesh_data::Error),
|
|
||||||
}
|
|
||||||
impl std::fmt::Display for Error{
|
|
||||||
fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{
|
|
||||||
write!(f,"{self:?}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// wacky state machine to make sure all vertices in a face agree upon what NormalId to use.
|
|
||||||
// Roblox duplicates this information per vertex when it should only exist per-face.
|
|
||||||
enum MeshDataNormalStatus{
|
|
||||||
Agree(MeshDataNormalId2),
|
|
||||||
Conflicting,
|
|
||||||
}
|
|
||||||
struct MeshDataNormalChecker{
|
|
||||||
status:Option<MeshDataNormalStatus>,
|
|
||||||
}
|
|
||||||
impl MeshDataNormalChecker{
|
|
||||||
fn new()->Self{
|
|
||||||
Self{status:None}
|
|
||||||
}
|
|
||||||
fn check(&mut self,normal:MeshDataNormalId2){
|
|
||||||
self.status=match self.status.take(){
|
|
||||||
None=>Some(MeshDataNormalStatus::Agree(normal)),
|
|
||||||
Some(MeshDataNormalStatus::Agree(old_normal))=>{
|
|
||||||
if old_normal==normal{
|
|
||||||
Some(MeshDataNormalStatus::Agree(old_normal))
|
|
||||||
}else{
|
|
||||||
Some(MeshDataNormalStatus::Conflicting)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Some(MeshDataNormalStatus::Conflicting)=>Some(MeshDataNormalStatus::Conflicting),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
fn into_agreed_normal(self)->Option<MeshDataNormalId2>{
|
|
||||||
self.status.and_then(|status|match status{
|
|
||||||
MeshDataNormalStatus::Agree(normal)=>Some(normal),
|
|
||||||
MeshDataNormalStatus::Conflicting=>None,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl std::error::Error for Error{}
|
|
||||||
pub fn convert(
|
|
||||||
roblox_physics_data:&[u8],
|
|
||||||
roblox_mesh_data:&[u8],
|
|
||||||
size:glam::Vec3,
|
|
||||||
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];
|
|
||||||
|
|
||||||
// build graphics and physics meshes
|
|
||||||
let mut mb=strafesnet_common::model::MeshBuilder::new();
|
|
||||||
// graphics
|
|
||||||
let graphics_groups=if !roblox_mesh_data.is_empty(){
|
|
||||||
// create per-face texture coordinate affine transforms
|
|
||||||
let cube_face_description=part_texture_description.map(|opt|opt.map(|mut t|{
|
|
||||||
t.transform.set_size(1.0,1.0);
|
|
||||||
t.to_face_description()
|
|
||||||
}));
|
|
||||||
|
|
||||||
let mesh_data=rbx_mesh::read_mesh_data_versioned(
|
|
||||||
std::io::Cursor::new(roblox_mesh_data)
|
|
||||||
).map_err(Error::RobloxMeshData)?;
|
|
||||||
let graphics_mesh=match mesh_data{
|
|
||||||
rbx_mesh::mesh_data::MeshData::CSGK(_)=>return Err(Error::Block),
|
|
||||||
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 [vertex_id0,vertex_id1,vertex_id2] in graphics_mesh.faces{
|
|
||||||
let face=[
|
|
||||||
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|{
|
|
||||||
normal_agreement_checker.check(vertex.normal_id);
|
|
||||||
let pos=mb.acquire_pos_id(vec3::try_from_f32_array(vertex.pos)?);
|
|
||||||
let normal=mb.acquire_normal_id(vec3::try_from_f32_array(vertex.norm)?);
|
|
||||||
let tex_coord=glam::Vec2::from_array(vertex.tex);
|
|
||||||
let maybe_face_description=&cube_face_description[vertex.normal_id as usize-1];
|
|
||||||
let (tex,color)=match maybe_face_description{
|
|
||||||
Some(face_description)=>{
|
|
||||||
// transform texture coordinates and set decal color
|
|
||||||
let tex=mb.acquire_tex_id(face_description.transform.transform_point2(tex_coord));
|
|
||||||
let color=mb.acquire_color_id(face_description.color);
|
|
||||||
(tex,color)
|
|
||||||
},
|
|
||||||
None=>{
|
|
||||||
// texture coordinates don't matter and pass through mesh vertex color
|
|
||||||
let tex=mb.acquire_tex_id(tex_coord);
|
|
||||||
let color=mb.acquire_color_id(glam::Vec4::from_array(vertex.color.map(|f|f as f32/255.0f32)));
|
|
||||||
(tex,color)
|
|
||||||
},
|
|
||||||
};
|
|
||||||
Ok(mb.acquire_vertex_id(IndexedVertex{pos,tex,normal,color}))
|
|
||||||
}).collect::<Result<Vec<_>,_>>().map_err(Error::Planar64Vec3)?;
|
|
||||||
if let Some(normal_id)=normal_agreement_checker.into_agreed_normal(){
|
|
||||||
polygon_groups_normal_id[normal_id as usize-1].push(face);
|
|
||||||
}else{
|
|
||||||
panic!("Empty face!");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
(0..NORMAL_FACES).map(|polygon_group_id|{
|
|
||||||
model::IndexedGraphicsGroup{
|
|
||||||
render:cube_face_description[polygon_group_id].as_ref().map_or(RenderConfigId::new(0),|face_description|face_description.render),
|
|
||||||
groups:vec![PolygonGroupId::new(polygon_group_id as u32)]
|
|
||||||
}
|
|
||||||
}).collect()
|
|
||||||
}else{
|
|
||||||
Vec::new()
|
|
||||||
};
|
|
||||||
|
|
||||||
//physics
|
|
||||||
let physics_convex_meshes=if !roblox_physics_data.is_empty(){
|
|
||||||
let physics_data=rbx_mesh::read_physics_data_versioned(
|
|
||||||
std::io::Cursor::new(roblox_physics_data)
|
|
||||||
).map_err(Error::RobloxPhysicsData)?;
|
|
||||||
let physics_convex_meshes=match physics_data{
|
|
||||||
rbx_mesh::physics_data::PhysicsData::CSGK(_)
|
|
||||||
// have not seen this format in practice
|
|
||||||
|rbx_mesh::physics_data::PhysicsData::CSGPHS(rbx_mesh::physics_data::CSGPHS::Block)
|
|
||||||
=>return Err(Error::Block),
|
|
||||||
rbx_mesh::physics_data::PhysicsData::CSGPHS(rbx_mesh::physics_data::CSGPHS::Meshes(meshes))
|
|
||||||
=>meshes.meshes,
|
|
||||||
rbx_mesh::physics_data::PhysicsData::CSGPHS(rbx_mesh::physics_data::CSGPHS::PhysicsInfoMesh(pim))
|
|
||||||
=>vec![pim.mesh],
|
|
||||||
};
|
|
||||||
physics_convex_meshes
|
|
||||||
}else{
|
|
||||||
Vec::new()
|
|
||||||
};
|
|
||||||
let polygon_groups:Vec<PolygonGroup>=polygon_groups_normal_id.into_iter().map(|faces|
|
|
||||||
// graphics polygon groups (to be rendered)
|
|
||||||
Ok(PolygonGroup::PolygonList(PolygonList::new(faces)))
|
|
||||||
).chain(physics_convex_meshes.into_iter().map(|mesh|{
|
|
||||||
// this can be factored out of the loop but I am lazy
|
|
||||||
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(|[vertex_id0,vertex_id1,vertex_id2]|{
|
|
||||||
let face=[
|
|
||||||
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]);
|
|
||||||
let normal=mb.acquire_normal_id(vec3::try_from_f32_array(vertex_norm.to_array()).map_err(Error::Planar64Vec3)?);
|
|
||||||
face.into_iter().map(|vertex_pos|{
|
|
||||||
let pos=mb.acquire_pos_id(vec3::try_from_f32_array(vertex_pos.to_array()).map_err(Error::Planar64Vec3)?);
|
|
||||||
Ok(mb.acquire_vertex_id(IndexedVertex{pos,tex,normal,color}))
|
|
||||||
}).collect()
|
|
||||||
}).collect::<Result<_,_>>()?)))
|
|
||||||
})).collect::<Result<_,_>>()?;
|
|
||||||
let physics_groups=(NORMAL_FACES..polygon_groups.len()).map(|id|model::IndexedPhysicsGroup{
|
|
||||||
groups:vec![PolygonGroupId::new(id as u32)]
|
|
||||||
}).collect();
|
|
||||||
Ok(mb.build(
|
|
||||||
polygon_groups,
|
|
||||||
graphics_groups,
|
|
||||||
physics_groups,
|
|
||||||
))
|
|
||||||
}
|
|
@ -1,11 +0,0 @@
|
|||||||
[package]
|
|
||||||
name = "rbxassetid"
|
|
||||||
version = "0.1.0"
|
|
||||||
edition = "2021"
|
|
||||||
repository = "https://git.itzana.me/StrafesNET/strafe-project"
|
|
||||||
license = "MIT OR Apache-2.0"
|
|
||||||
description = "Parse Roblox asset id from 'Content' urls."
|
|
||||||
authors = ["Rhys Lloyd <krakow20@gmail.com>"]
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
url = "2.5.4"
|
|
@ -1,176 +0,0 @@
|
|||||||
Apache License
|
|
||||||
Version 2.0, January 2004
|
|
||||||
http://www.apache.org/licenses/
|
|
||||||
|
|
||||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
|
||||||
|
|
||||||
1. Definitions.
|
|
||||||
|
|
||||||
"License" shall mean the terms and conditions for use, reproduction,
|
|
||||||
and distribution as defined by Sections 1 through 9 of this document.
|
|
||||||
|
|
||||||
"Licensor" shall mean the copyright owner or entity authorized by
|
|
||||||
the copyright owner that is granting the License.
|
|
||||||
|
|
||||||
"Legal Entity" shall mean the union of the acting entity and all
|
|
||||||
other entities that control, are controlled by, or are under common
|
|
||||||
control with that entity. For the purposes of this definition,
|
|
||||||
"control" means (i) the power, direct or indirect, to cause the
|
|
||||||
direction or management of such entity, whether by contract or
|
|
||||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
|
||||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
|
||||||
|
|
||||||
"You" (or "Your") shall mean an individual or Legal Entity
|
|
||||||
exercising permissions granted by this License.
|
|
||||||
|
|
||||||
"Source" form shall mean the preferred form for making modifications,
|
|
||||||
including but not limited to software source code, documentation
|
|
||||||
source, and configuration files.
|
|
||||||
|
|
||||||
"Object" form shall mean any form resulting from mechanical
|
|
||||||
transformation or translation of a Source form, including but
|
|
||||||
not limited to compiled object code, generated documentation,
|
|
||||||
and conversions to other media types.
|
|
||||||
|
|
||||||
"Work" shall mean the work of authorship, whether in Source or
|
|
||||||
Object form, made available under the License, as indicated by a
|
|
||||||
copyright notice that is included in or attached to the work
|
|
||||||
(an example is provided in the Appendix below).
|
|
||||||
|
|
||||||
"Derivative Works" shall mean any work, whether in Source or Object
|
|
||||||
form, that is based on (or derived from) the Work and for which the
|
|
||||||
editorial revisions, annotations, elaborations, or other modifications
|
|
||||||
represent, as a whole, an original work of authorship. For the purposes
|
|
||||||
of this License, Derivative Works shall not include works that remain
|
|
||||||
separable from, or merely link (or bind by name) to the interfaces of,
|
|
||||||
the Work and Derivative Works thereof.
|
|
||||||
|
|
||||||
"Contribution" shall mean any work of authorship, including
|
|
||||||
the original version of the Work and any modifications or additions
|
|
||||||
to that Work or Derivative Works thereof, that is intentionally
|
|
||||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
|
||||||
or by an individual or Legal Entity authorized to submit on behalf of
|
|
||||||
the copyright owner. For the purposes of this definition, "submitted"
|
|
||||||
means any form of electronic, verbal, or written communication sent
|
|
||||||
to the Licensor or its representatives, including but not limited to
|
|
||||||
communication on electronic mailing lists, source code control systems,
|
|
||||||
and issue tracking systems that are managed by, or on behalf of, the
|
|
||||||
Licensor for the purpose of discussing and improving the Work, but
|
|
||||||
excluding communication that is conspicuously marked or otherwise
|
|
||||||
designated in writing by the copyright owner as "Not a Contribution."
|
|
||||||
|
|
||||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
|
||||||
on behalf of whom a Contribution has been received by Licensor and
|
|
||||||
subsequently incorporated within the Work.
|
|
||||||
|
|
||||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
|
||||||
this License, each Contributor hereby grants to You a perpetual,
|
|
||||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
||||||
copyright license to reproduce, prepare Derivative Works of,
|
|
||||||
publicly display, publicly perform, sublicense, and distribute the
|
|
||||||
Work and such Derivative Works in Source or Object form.
|
|
||||||
|
|
||||||
3. Grant of Patent License. Subject to the terms and conditions of
|
|
||||||
this License, each Contributor hereby grants to You a perpetual,
|
|
||||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
||||||
(except as stated in this section) patent license to make, have made,
|
|
||||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
|
||||||
where such license applies only to those patent claims licensable
|
|
||||||
by such Contributor that are necessarily infringed by their
|
|
||||||
Contribution(s) alone or by combination of their Contribution(s)
|
|
||||||
with the Work to which such Contribution(s) was submitted. If You
|
|
||||||
institute patent litigation against any entity (including a
|
|
||||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
|
||||||
or a Contribution incorporated within the Work constitutes direct
|
|
||||||
or contributory patent infringement, then any patent licenses
|
|
||||||
granted to You under this License for that Work shall terminate
|
|
||||||
as of the date such litigation is filed.
|
|
||||||
|
|
||||||
4. Redistribution. You may reproduce and distribute copies of the
|
|
||||||
Work or Derivative Works thereof in any medium, with or without
|
|
||||||
modifications, and in Source or Object form, provided that You
|
|
||||||
meet the following conditions:
|
|
||||||
|
|
||||||
(a) You must give any other recipients of the Work or
|
|
||||||
Derivative Works a copy of this License; and
|
|
||||||
|
|
||||||
(b) You must cause any modified files to carry prominent notices
|
|
||||||
stating that You changed the files; and
|
|
||||||
|
|
||||||
(c) You must retain, in the Source form of any Derivative Works
|
|
||||||
that You distribute, all copyright, patent, trademark, and
|
|
||||||
attribution notices from the Source form of the Work,
|
|
||||||
excluding those notices that do not pertain to any part of
|
|
||||||
the Derivative Works; and
|
|
||||||
|
|
||||||
(d) If the Work includes a "NOTICE" text file as part of its
|
|
||||||
distribution, then any Derivative Works that You distribute must
|
|
||||||
include a readable copy of the attribution notices contained
|
|
||||||
within such NOTICE file, excluding those notices that do not
|
|
||||||
pertain to any part of the Derivative Works, in at least one
|
|
||||||
of the following places: within a NOTICE text file distributed
|
|
||||||
as part of the Derivative Works; within the Source form or
|
|
||||||
documentation, if provided along with the Derivative Works; or,
|
|
||||||
within a display generated by the Derivative Works, if and
|
|
||||||
wherever such third-party notices normally appear. The contents
|
|
||||||
of the NOTICE file are for informational purposes only and
|
|
||||||
do not modify the License. You may add Your own attribution
|
|
||||||
notices within Derivative Works that You distribute, alongside
|
|
||||||
or as an addendum to the NOTICE text from the Work, provided
|
|
||||||
that such additional attribution notices cannot be construed
|
|
||||||
as modifying the License.
|
|
||||||
|
|
||||||
You may add Your own copyright statement to Your modifications and
|
|
||||||
may provide additional or different license terms and conditions
|
|
||||||
for use, reproduction, or distribution of Your modifications, or
|
|
||||||
for any such Derivative Works as a whole, provided Your use,
|
|
||||||
reproduction, and distribution of the Work otherwise complies with
|
|
||||||
the conditions stated in this License.
|
|
||||||
|
|
||||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
|
||||||
any Contribution intentionally submitted for inclusion in the Work
|
|
||||||
by You to the Licensor shall be under the terms and conditions of
|
|
||||||
this License, without any additional terms or conditions.
|
|
||||||
Notwithstanding the above, nothing herein shall supersede or modify
|
|
||||||
the terms of any separate license agreement you may have executed
|
|
||||||
with Licensor regarding such Contributions.
|
|
||||||
|
|
||||||
6. Trademarks. This License does not grant permission to use the trade
|
|
||||||
names, trademarks, service marks, or product names of the Licensor,
|
|
||||||
except as required for reasonable and customary use in describing the
|
|
||||||
origin of the Work and reproducing the content of the NOTICE file.
|
|
||||||
|
|
||||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
|
||||||
agreed to in writing, Licensor provides the Work (and each
|
|
||||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
||||||
implied, including, without limitation, any warranties or conditions
|
|
||||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
|
||||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
|
||||||
appropriateness of using or redistributing the Work and assume any
|
|
||||||
risks associated with Your exercise of permissions under this License.
|
|
||||||
|
|
||||||
8. Limitation of Liability. In no event and under no legal theory,
|
|
||||||
whether in tort (including negligence), contract, or otherwise,
|
|
||||||
unless required by applicable law (such as deliberate and grossly
|
|
||||||
negligent acts) or agreed to in writing, shall any Contributor be
|
|
||||||
liable to You for damages, including any direct, indirect, special,
|
|
||||||
incidental, or consequential damages of any character arising as a
|
|
||||||
result of this License or out of the use or inability to use the
|
|
||||||
Work (including but not limited to damages for loss of goodwill,
|
|
||||||
work stoppage, computer failure or malfunction, or any and all
|
|
||||||
other commercial damages or losses), even if such Contributor
|
|
||||||
has been advised of the possibility of such damages.
|
|
||||||
|
|
||||||
9. Accepting Warranty or Additional Liability. While redistributing
|
|
||||||
the Work or Derivative Works thereof, You may choose to offer,
|
|
||||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
|
||||||
or other liability obligations and/or rights consistent with this
|
|
||||||
License. However, in accepting such obligations, You may act only
|
|
||||||
on Your own behalf and on Your sole responsibility, not on behalf
|
|
||||||
of any other Contributor, and only if You agree to indemnify,
|
|
||||||
defend, and hold each Contributor harmless for any liability
|
|
||||||
incurred by, or claims asserted against, such Contributor by reason
|
|
||||||
of your accepting any such warranty or additional liability.
|
|
||||||
|
|
||||||
END OF TERMS AND CONDITIONS
|
|
@ -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.
|
|
@ -1,26 +0,0 @@
|
|||||||
Roblox Asset Id
|
|
||||||
===============
|
|
||||||
|
|
||||||
## Example
|
|
||||||
|
|
||||||
```rust
|
|
||||||
use rbxassetid::RobloxAssetId;
|
|
||||||
|
|
||||||
let content="rbxassetid://255299419";
|
|
||||||
let RobloxAssetId(asset_id)=content.parse()?;
|
|
||||||
```
|
|
||||||
|
|
||||||
#### License
|
|
||||||
|
|
||||||
<sup>
|
|
||||||
Licensed under either of <a href="LICENSE-APACHE">Apache License, Version
|
|
||||||
2.0</a> or <a href="LICENSE-MIT">MIT license</a> at your option.
|
|
||||||
</sup>
|
|
||||||
|
|
||||||
<br>
|
|
||||||
|
|
||||||
<sub>
|
|
||||||
Unless you explicitly state otherwise, any contribution intentionally submitted
|
|
||||||
for inclusion in this crate by you, as defined in the Apache-2.0 license, shall
|
|
||||||
be dual licensed as above, without any additional terms or conditions.
|
|
||||||
</sub>
|
|
@ -1,41 +0,0 @@
|
|||||||
#[derive(Clone,Copy,Debug,Hash,Eq,PartialEq)]
|
|
||||||
pub struct RobloxAssetId(pub u64);
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub enum RobloxAssetIdParseErr{
|
|
||||||
Url(url::ParseError),
|
|
||||||
UnknownScheme,
|
|
||||||
ParseInt(std::num::ParseIntError),
|
|
||||||
MissingAssetId,
|
|
||||||
MissingIDQueryParam,
|
|
||||||
}
|
|
||||||
impl std::fmt::Display for RobloxAssetIdParseErr{
|
|
||||||
fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{
|
|
||||||
write!(f,"{self:?}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl std::error::Error for RobloxAssetIdParseErr{}
|
|
||||||
impl std::str::FromStr for RobloxAssetId{
|
|
||||||
type Err=RobloxAssetIdParseErr;
|
|
||||||
fn from_str(s:&str)->Result<Self,Self::Err>{
|
|
||||||
let url=url::Url::parse(s).map_err(RobloxAssetIdParseErr::Url)?;
|
|
||||||
let parsed_asset_id=match url.scheme(){
|
|
||||||
"rbxassetid"=>url.domain().ok_or(RobloxAssetIdParseErr::MissingAssetId)?.parse(),
|
|
||||||
"http"|"https"=>{
|
|
||||||
let (_,asset_id)=url.query_pairs()
|
|
||||||
.find(|(id,_)|match id.as_ref(){
|
|
||||||
"ID"|"id"|"Id"|"iD"=>true,
|
|
||||||
_=>false,
|
|
||||||
}).ok_or(RobloxAssetIdParseErr::MissingIDQueryParam)?;
|
|
||||||
asset_id.parse()
|
|
||||||
},
|
|
||||||
_=>Err(RobloxAssetIdParseErr::UnknownScheme)?,
|
|
||||||
};
|
|
||||||
Ok(Self(parsed_asset_id.map_err(RobloxAssetIdParseErr::ParseInt)?))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_rbxassetid(){
|
|
||||||
let content="rbxassetid://255299419";
|
|
||||||
let RobloxAssetId(_asset_id)=content.parse().unwrap();
|
|
||||||
}
|
|
@ -33,7 +33,7 @@ pub fn set_globals(lua:&mlua::Lua,globals:&mlua::Table)->Result<(),mlua::Error>{
|
|||||||
// LMAO look at this function!
|
// 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>{
|
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::<&'static mut WeakDom>().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)
|
f(&mut *dom)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn coerce_float32(value:&mlua::Value)->Option<f32>{
|
fn coerce_float32(value:&mlua::Value)->Option<f32>{
|
||||||
|
@ -1,22 +1,22 @@
|
|||||||
macro_rules! type_from_lua_userdata{
|
macro_rules! type_from_lua_userdata{
|
||||||
($ty:ident)=>{
|
($asd:ident)=>{
|
||||||
impl mlua::FromLua for $ty{
|
impl mlua::FromLua for $asd{
|
||||||
fn from_lua(value:mlua::Value,_lua:&mlua::Lua)->Result<Self,mlua::Error>{
|
fn from_lua(value:mlua::Value,_lua:&mlua::Lua)->Result<Self,mlua::Error>{
|
||||||
match value{
|
match value{
|
||||||
mlua::Value::UserData(ud)=>Ok(*ud.borrow::<Self>()?),
|
mlua::Value::UserData(ud)=>Ok(*ud.borrow::<Self>()?),
|
||||||
other=>Err(mlua::Error::runtime(format!("Expected {} got {:?}",stringify!($ty),other))),
|
other=>Err(mlua::Error::runtime(format!("Expected {} got {:?}",stringify!($asd),other))),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
macro_rules! type_from_lua_userdata_lua_lifetime{
|
macro_rules! type_from_lua_userdata_lua_lifetime{
|
||||||
($ty:ident)=>{
|
($asd:ident)=>{
|
||||||
impl mlua::FromLua for $ty<'static>{
|
impl mlua::FromLua for $asd<'static>{
|
||||||
fn from_lua(value:mlua::Value,_lua:&mlua::Lua)->Result<Self,mlua::Error>{
|
fn from_lua(value:mlua::Value,_lua:&mlua::Lua)->Result<Self,mlua::Error>{
|
||||||
match value{
|
match value{
|
||||||
mlua::Value::UserData(ud)=>Ok(*ud.borrow::<Self>()?),
|
mlua::Value::UserData(ud)=>Ok(*ud.borrow::<Self>()?),
|
||||||
other=>Err(mlua::Error::runtime(format!("Expected {} got {:?}",stringify!($ty),other))),
|
other=>Err(mlua::Error::runtime(format!("Expected {} got {:?}",stringify!($asd),other))),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -59,8 +59,8 @@ fn schedule_thread(lua:&mlua::Lua,dt:mlua::Value)->Result<(),mlua::Error>{
|
|||||||
match delay.classify(){
|
match delay.classify(){
|
||||||
std::num::FpCategory::Nan=>Err(mlua::Error::runtime("NaN"))?,
|
std::num::FpCategory::Nan=>Err(mlua::Error::runtime("NaN"))?,
|
||||||
// cases where the number is too large to schedule
|
// cases where the number is too large to schedule
|
||||||
std::num::FpCategory::Infinite
|
std::num::FpCategory::Infinite=>return Ok(()),
|
||||||
|std::num::FpCategory::Normal if (u64::MAX as f64)<delay=>{
|
std::num::FpCategory::Normal=>if (u64::MAX as f64)<delay{
|
||||||
return Ok(());
|
return Ok(());
|
||||||
},
|
},
|
||||||
_=>(),
|
_=>(),
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "strafesnet_snf"
|
name = "strafesnet_snf"
|
||||||
version = "0.3.0"
|
version = "0.2.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
@ -8,4 +8,4 @@ edition = "2021"
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
binrw = "0.14.0"
|
binrw = "0.14.0"
|
||||||
id = { version = "0.1.0", registry = "strafesnet" }
|
id = { version = "0.1.0", registry = "strafesnet" }
|
||||||
strafesnet_common = { version = "0.6.0", path = "../common", registry = "strafesnet" }
|
strafesnet_common = { path = "../common", registry = "strafesnet" }
|
||||||
|
@ -1,347 +1,98 @@
|
|||||||
use binrw::{binrw,BinReaderExt,BinWrite,BinWriterExt};
|
use binrw::{BinReaderExt, binrw};
|
||||||
|
|
||||||
use crate::newtypes;
|
|
||||||
use crate::file::BlockId;
|
|
||||||
use strafesnet_common::physics::Time;
|
|
||||||
|
|
||||||
const VERSION:u32=0;
|
|
||||||
|
|
||||||
type TimedPhysicsInstruction=strafesnet_common::instruction::TimedInstruction<strafesnet_common::physics::Instruction,strafesnet_common::physics::TimeInner>;
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum Error{
|
pub enum Error{
|
||||||
InvalidHeader(binrw::Error),
|
InvalidHeader,
|
||||||
InvalidSegment(binrw::Error),
|
InvalidSegment(binrw::Error),
|
||||||
SegmentConvert(newtypes::integer::RatioError),
|
|
||||||
InstructionConvert(newtypes::physics::InstructionConvert),
|
|
||||||
InstructionWrite(binrw::Error),
|
|
||||||
InvalidSegmentId(SegmentId),
|
InvalidSegmentId(SegmentId),
|
||||||
InvalidData(binrw::Error),
|
|
||||||
IO(std::io::Error),
|
|
||||||
File(crate::file::Error),
|
File(crate::file::Error),
|
||||||
}
|
}
|
||||||
|
|
||||||
// Bot files are simply the sequence of instructions that the physics received during the run.
|
|
||||||
// The instructions are partitioned into timestamped blocks for ease of streaming.
|
|
||||||
//
|
|
||||||
// Keyframe information required for efficient seeking
|
|
||||||
// is part of a different file, and is generated from this file.
|
|
||||||
|
|
||||||
/* block types
|
/* block types
|
||||||
|
|
||||||
BLOCK_BOT_HEADER:
|
BLOCK_BOT_HEADER:
|
||||||
// Segments are laid out in chronological order,
|
u128 map_resource_uuid //which map is this bot running
|
||||||
// but block_id is not necessarily in ascending order.
|
//don't include style info in bot header because it's in the simulation state
|
||||||
//
|
//blocks are laid out in chronological order, but indices may jump around.
|
||||||
// This is to place the final segment close to the start of the file,
|
u64 num_segments
|
||||||
// which allows the duration of the bot to be conveniently calculated
|
|
||||||
// from the first and last instruction timestamps.
|
|
||||||
//
|
|
||||||
// Use exact physics version for replay playback
|
|
||||||
// Use highest compatible physics version for verification
|
|
||||||
u32 physics_version
|
|
||||||
u32 num_segments
|
|
||||||
for _ in 0..num_segments{
|
for _ in 0..num_segments{
|
||||||
i64 time
|
i64 time //simulation_state timestamp
|
||||||
u32 instruction_count
|
u64 block_id
|
||||||
u32 block_id
|
|
||||||
}
|
}
|
||||||
|
|
||||||
BLOCK_BOT_SEGMENT:
|
BLOCK_BOT_SEGMENT:
|
||||||
// segments can potentially be losslessly compressed!
|
//format version indicates what version of these structures to use
|
||||||
for _ in 0..instruction_count{
|
SimulationState simulation_state //SimulationState is just under ClientState which includes Play/Pause events that the simulation doesn't know about.
|
||||||
// TODO: delta encode as much as possible (time,mousepos)
|
//to read, greedily decode instructions until eof
|
||||||
i64 time
|
loop{
|
||||||
physics::Instruction instruction
|
//delta encode as much as possible (time,mousepos)
|
||||||
|
//strafe ticks are implied
|
||||||
|
//physics can be implied in an input-only bot file
|
||||||
|
TimedInstruction<SimulationInstruction> instruction
|
||||||
}
|
}
|
||||||
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#[binrw]
|
//error hiding mock code
|
||||||
|
mod simulation{
|
||||||
|
#[super::binrw]
|
||||||
#[brw(little)]
|
#[brw(little)]
|
||||||
struct SegmentHeader{
|
pub struct State{}
|
||||||
time:i64,
|
#[super::binrw]
|
||||||
instruction_count:u32,
|
|
||||||
block_id:BlockId,
|
|
||||||
}
|
|
||||||
#[binrw]
|
|
||||||
#[brw(little)]
|
#[brw(little)]
|
||||||
struct Header{
|
pub struct Instruction{}
|
||||||
physics_version:u32,
|
|
||||||
num_segments:u32,
|
|
||||||
#[br(count=num_segments)]
|
|
||||||
segments:Vec<SegmentHeader>,
|
|
||||||
}
|
}
|
||||||
|
// mod instruction{
|
||||||
|
// #[super::binrw]
|
||||||
|
// #[brw(little)]
|
||||||
|
// pub struct TimedInstruction<Instruction:binrw::BinRead+binrw::BinWrite>{
|
||||||
|
// time:u64,
|
||||||
|
// instruction:Instruction
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// mod timeline{
|
||||||
|
// #[super::binrw]
|
||||||
|
// #[brw(little)]
|
||||||
|
// pub struct Timeline<Instruction:binrw::BinRead+binrw::BinWrite>{
|
||||||
|
// #[bw(try_calc(u32::try_from(instructions.len())))]
|
||||||
|
// instruction_count:u32,
|
||||||
|
// #[br(count=instruction_count)]
|
||||||
|
// instructions:Vec<super::instruction::TimedInstruction<Instruction>>
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
//serious code
|
||||||
|
|
||||||
#[binrw]
|
#[binrw]
|
||||||
#[brw(little)]
|
#[brw(little)]
|
||||||
#[derive(Clone,Copy,Debug,id::Id)]
|
#[derive(Clone,Copy,Debug,id::Id)]
|
||||||
pub struct SegmentId(u32);
|
pub struct SegmentId(u32);
|
||||||
|
|
||||||
|
#[binrw]
|
||||||
|
#[brw(little)]
|
||||||
pub struct Segment{
|
pub struct Segment{
|
||||||
pub instructions:Vec<TimedPhysicsInstruction>
|
state:simulation::State,
|
||||||
}
|
//#[bw(try_calc(u32::try_from(instructions.len())))]
|
||||||
|
//instruction_count:u32,
|
||||||
|
//#[br(count=instruction_count)]
|
||||||
|
//instructions:Vec<instruction::TimedInstruction<simulation::Instruction>>
|
||||||
|
|
||||||
#[derive(Clone,Copy,Debug)]
|
//please remember that strafe ticks are implicit! 33% smaller bot files
|
||||||
pub struct SegmentInfo{
|
|
||||||
/// time of the first instruction in this segment.
|
|
||||||
time:Time,
|
|
||||||
instruction_count:u32,
|
|
||||||
/// How many total instructions in segments up to and including this segment
|
|
||||||
/// Alternatively, the id of the first instruction be in the _next_ segment
|
|
||||||
instructions_subtotal:u64,
|
|
||||||
block_id:BlockId,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct StreamableBot<R:BinReaderExt>{
|
pub struct StreamableBot<R:BinReaderExt>{
|
||||||
file:crate::file::File<R>,
|
file:crate::file::File<R>,
|
||||||
segment_map:Vec<SegmentInfo>,
|
//timeline:timeline::Timeline<SegmentId>,
|
||||||
|
segment_id_to_block_id:Vec<crate::file::BlockId>,
|
||||||
}
|
}
|
||||||
impl<R:BinReaderExt> StreamableBot<R>{
|
impl<R:BinReaderExt> StreamableBot<R>{
|
||||||
pub(crate) fn new(mut file:crate::file::File<R>)->Result<Self,Error>{
|
pub(crate) fn new(file:crate::file::File<R>)->Result<Self,Error>{
|
||||||
//assume the file seek is in the right place to start reading header
|
Err(Error::InvalidHeader)
|
||||||
let header:Header=file.data_mut().read_le().map_err(Error::InvalidHeader)?;
|
|
||||||
let mut instructions_subtotal=0;
|
|
||||||
let segment_map=header.segments.into_iter().map(|SegmentHeader{time,instruction_count,block_id}|{
|
|
||||||
instructions_subtotal+=instruction_count as u64;
|
|
||||||
SegmentInfo{
|
|
||||||
time:Time::raw(time),
|
|
||||||
instruction_count,
|
|
||||||
instructions_subtotal,
|
|
||||||
block_id,
|
|
||||||
}
|
|
||||||
}).collect();
|
|
||||||
Ok(Self{
|
|
||||||
file,
|
|
||||||
segment_map,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
fn get_segment_info(&self,segment_id:SegmentId)->Result<SegmentInfo,Error>{
|
|
||||||
Ok(*self.segment_map.get(segment_id.get() as usize).ok_or(Error::InvalidSegmentId(segment_id))?)
|
|
||||||
}
|
|
||||||
pub fn find_segments_instruction_range(&self,start_instruction:u64,end_instruction:u64)->&[SegmentInfo]{
|
|
||||||
let start=self.segment_map.partition_point(|segment_info|segment_info.instructions_subtotal<start_instruction);
|
|
||||||
let end=self.segment_map.partition_point(|segment_info|segment_info.instructions_subtotal<end_instruction);
|
|
||||||
&self.segment_map[start..=end]
|
|
||||||
}
|
|
||||||
// pub fn find_segments_time_range(&self,start_time:Time,end_time:Time)->&[SegmentInfo]{
|
|
||||||
// // TODO: This is off by one, both should be one less
|
|
||||||
// let start=self.segment_map.partition_point(|segment_info|segment_info.time<start_time);
|
|
||||||
// let end=self.segment_map.partition_point(|segment_info|segment_info.time<end_time);
|
|
||||||
// &self.segment_map[start..=end]
|
|
||||||
// }
|
|
||||||
fn append_to_segment(&mut self,segment_info:SegmentInfo,segment:&mut Segment)->Result<(),Error>{
|
|
||||||
let mut block=self.file.block_reader(segment_info.block_id).map_err(Error::File)?;
|
|
||||||
for _ in 0..segment_info.instruction_count{
|
|
||||||
let instruction:newtypes::physics::TimedInstruction=block.read_le().map_err(Error::InvalidSegment)?;
|
|
||||||
segment.instructions.push(instruction.try_into().map_err(Error::SegmentConvert)?);
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
pub fn load_segment(&mut self,segment_info:SegmentInfo)->Result<Segment,Error>{
|
|
||||||
let mut segment=Segment{
|
|
||||||
instructions:Vec::with_capacity(segment_info.instruction_count as usize),
|
|
||||||
};
|
|
||||||
self.append_to_segment(segment_info,&mut segment)?;
|
|
||||||
Ok(segment)
|
|
||||||
}
|
|
||||||
pub fn read_all(&mut self)->Result<Segment,Error>{
|
|
||||||
let mut segment=Segment{
|
|
||||||
instructions:Vec::new(),
|
|
||||||
};
|
|
||||||
for i in 0..self.segment_map.len(){
|
|
||||||
let segment_info=self.segment_map[i];
|
|
||||||
self.append_to_segment(segment_info,&mut segment)?;
|
|
||||||
}
|
}
|
||||||
|
pub fn load_segment(&mut self,segment_id:SegmentId)->Result<Segment,Error>{
|
||||||
|
let block_id=*self.segment_id_to_block_id.get(segment_id.get() as usize).ok_or(Error::InvalidSegmentId(segment_id))?;
|
||||||
|
let mut block=self.file.block_reader(block_id).map_err(Error::File)?;
|
||||||
|
let segment=block.read_le().map_err(Error::InvalidSegment)?;
|
||||||
Ok(segment)
|
Ok(segment)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const MAX_BLOCK_SIZE:usize=64*1024;//64 kB
|
|
||||||
pub fn write_bot<W:BinWriterExt>(mut writer:W,physics_version:u32,instructions:impl IntoIterator<Item=TimedPhysicsInstruction>)->Result<(),Error>{
|
|
||||||
// decide which instructions to put in which segment
|
|
||||||
// write segment 1 to block 1
|
|
||||||
// write segment N to block 2
|
|
||||||
// write rest of segments
|
|
||||||
// 1 2 3 4 5
|
|
||||||
// becomes
|
|
||||||
// [1 5] 2 3 4
|
|
||||||
struct SegmentHeaderInfo{
|
|
||||||
time:Time,
|
|
||||||
instruction_count:u32,
|
|
||||||
range:core::ops::Range<usize>
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut segment_header_infos=Vec::new();
|
|
||||||
let mut raw_segments=std::io::Cursor::new(Vec::new());
|
|
||||||
|
|
||||||
// block info
|
|
||||||
let mut start_time=Time::ZERO;
|
|
||||||
let mut start_position=raw_segments.position() as usize;
|
|
||||||
let mut instruction_count=0;
|
|
||||||
|
|
||||||
let mut last_position=start_position;
|
|
||||||
|
|
||||||
let mut iter=instructions.into_iter();
|
|
||||||
|
|
||||||
macro_rules! collect_instruction{
|
|
||||||
($instruction:expr)=>{
|
|
||||||
let time=$instruction.time;
|
|
||||||
let instruction_writable:newtypes::physics::TimedInstruction=$instruction.try_into().map_err(Error::InstructionConvert)?;
|
|
||||||
instruction_writable.write_le(&mut raw_segments).map_err(Error::InstructionWrite)?;
|
|
||||||
instruction_count+=1;
|
|
||||||
let position=raw_segments.position() as usize;
|
|
||||||
// exceeds max block size
|
|
||||||
if MAX_BLOCK_SIZE<position-last_position{
|
|
||||||
segment_header_infos.push(SegmentHeaderInfo{
|
|
||||||
time:start_time,
|
|
||||||
instruction_count,
|
|
||||||
range:start_position..last_position,
|
|
||||||
});
|
|
||||||
start_position=last_position;
|
|
||||||
instruction_count=0;
|
|
||||||
start_time=time;
|
|
||||||
}
|
|
||||||
last_position=position;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// unroll one loop iteration to grab the starting time
|
|
||||||
if let Some(instruction)=iter.next(){
|
|
||||||
start_time=instruction.time;
|
|
||||||
collect_instruction!(instruction);
|
|
||||||
}
|
|
||||||
|
|
||||||
for instruction in iter{
|
|
||||||
collect_instruction!(instruction);
|
|
||||||
}
|
|
||||||
//last block, whatever size it happens to be
|
|
||||||
{
|
|
||||||
let final_position=raw_segments.position() as usize;
|
|
||||||
segment_header_infos.push(SegmentHeaderInfo{
|
|
||||||
time:start_time,
|
|
||||||
instruction_count,
|
|
||||||
range:start_position..final_position,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
// drop cursor
|
|
||||||
let raw_segments=raw_segments.into_inner();
|
|
||||||
|
|
||||||
let num_segments=segment_header_infos.len();
|
|
||||||
|
|
||||||
// segments list is in chronological order
|
|
||||||
let make_segment_header=|block_id,&SegmentHeaderInfo{time,instruction_count,range:ref _range}|SegmentHeader{
|
|
||||||
time:time.get(),
|
|
||||||
instruction_count,
|
|
||||||
block_id,
|
|
||||||
};
|
|
||||||
let segments=if 2<num_segments{
|
|
||||||
let mut segments=Vec::with_capacity(num_segments);
|
|
||||||
// segment 1 is second block
|
|
||||||
if let Some(seg)=segment_header_infos.first(){
|
|
||||||
segments.push(make_segment_header(BlockId::new(1),seg));
|
|
||||||
}
|
|
||||||
// rest of segments start at fourth block
|
|
||||||
for (i,seg) in segment_header_infos[1..num_segments-1].iter().enumerate(){
|
|
||||||
make_segment_header(BlockId::new(3+i as u32),seg);
|
|
||||||
}
|
|
||||||
// last segment is third block
|
|
||||||
if let Some(seg)=segment_header_infos.last(){
|
|
||||||
segments.push(make_segment_header(BlockId::new(2),seg));
|
|
||||||
}
|
|
||||||
segments
|
|
||||||
}else{
|
|
||||||
// all segments in order
|
|
||||||
segment_header_infos.iter().enumerate().map(|(i,seg)|
|
|
||||||
make_segment_header(BlockId::new(1+i as u32),seg)
|
|
||||||
).collect()
|
|
||||||
};
|
|
||||||
|
|
||||||
let header=Header{
|
|
||||||
physics_version,
|
|
||||||
num_segments:num_segments as u32,
|
|
||||||
segments,
|
|
||||||
};
|
|
||||||
|
|
||||||
// map header is +1
|
|
||||||
let block_count=1+num_segments as u32;
|
|
||||||
|
|
||||||
let mut offset=crate::file::Header::calculate_size(block_count) as u64;
|
|
||||||
// block_location is one longer than block_count
|
|
||||||
let mut block_location=Vec::with_capacity(1+block_count as usize);
|
|
||||||
|
|
||||||
//probe header length
|
|
||||||
let mut bot_header_data=Vec::new();
|
|
||||||
binrw::BinWrite::write_le(&header,&mut std::io::Cursor::new(&mut bot_header_data)).map_err(Error::InvalidData)?;
|
|
||||||
|
|
||||||
// the first block location is the map header
|
|
||||||
block_location.push(offset);
|
|
||||||
offset+=bot_header_data.len() as u64;
|
|
||||||
block_location.push(offset);
|
|
||||||
|
|
||||||
// priming includes file header + first 3 blocks [bot header, first segment, last segment]
|
|
||||||
let priming=if 2<num_segments{
|
|
||||||
// segment 1 is block 2
|
|
||||||
if let Some(seg)=segment_header_infos.first(){
|
|
||||||
offset+=seg.range.len() as u64;
|
|
||||||
block_location.push(offset);
|
|
||||||
}
|
|
||||||
// last segment is block 3
|
|
||||||
if let Some(seg)=segment_header_infos.last(){
|
|
||||||
offset+=seg.range.len() as u64;
|
|
||||||
block_location.push(offset);
|
|
||||||
}
|
|
||||||
|
|
||||||
let priming=offset;
|
|
||||||
|
|
||||||
// rest of segments
|
|
||||||
for seg in &segment_header_infos[1..num_segments-1]{
|
|
||||||
offset+=seg.range.len() as u64;
|
|
||||||
block_location.push(offset);
|
|
||||||
}
|
|
||||||
priming
|
|
||||||
}else{
|
|
||||||
// all segments in order
|
|
||||||
for seg in &segment_header_infos{
|
|
||||||
offset+=seg.range.len() as u64;
|
|
||||||
block_location.push(offset);
|
|
||||||
}
|
|
||||||
offset
|
|
||||||
};
|
|
||||||
|
|
||||||
let file_header=crate::file::Header{
|
|
||||||
fourcc:crate::file::FourCC::Bot,
|
|
||||||
version:VERSION,
|
|
||||||
priming,
|
|
||||||
resource:0,
|
|
||||||
block_count,
|
|
||||||
block_location,
|
|
||||||
};
|
|
||||||
|
|
||||||
// write file header
|
|
||||||
writer.write_le(&file_header).map_err(Error::InvalidData)?;
|
|
||||||
// write bot header
|
|
||||||
writer.write(&bot_header_data).map_err(Error::IO)?;
|
|
||||||
|
|
||||||
// write blocks
|
|
||||||
if 2<num_segments{
|
|
||||||
// segment 1 is block 2
|
|
||||||
if let Some(seg)=segment_header_infos.first(){
|
|
||||||
writer.write(&raw_segments[seg.range.clone()]).map_err(Error::IO)?;
|
|
||||||
}
|
|
||||||
// last segment is block 3
|
|
||||||
if let Some(seg)=segment_header_infos.last(){
|
|
||||||
writer.write(&raw_segments[seg.range.clone()]).map_err(Error::IO)?;
|
|
||||||
}
|
|
||||||
// rest of segments
|
|
||||||
for seg in &segment_header_infos[1..num_segments-1]{
|
|
||||||
writer.write(&raw_segments[seg.range.clone()]).map_err(Error::IO)?;
|
|
||||||
}
|
|
||||||
}else{
|
|
||||||
// all segments in order
|
|
||||||
for seg in segment_header_infos{
|
|
||||||
writer.write(&raw_segments[seg.range]).map_err(Error::IO)?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
@ -7,29 +7,21 @@ pub enum Error{
|
|||||||
|
|
||||||
/*
|
/*
|
||||||
BLOCK_DEMO_HEADER:
|
BLOCK_DEMO_HEADER:
|
||||||
u32 num_maps
|
|
||||||
for map_id in 0..num_maps{
|
|
||||||
i64 simulation_time
|
|
||||||
u128 map_resource_id
|
u128 map_resource_id
|
||||||
u64 map_header_block_id
|
u64 map_header_block_id
|
||||||
}
|
|
||||||
u32 num_bots
|
u32 num_bots
|
||||||
for bot_id in 0..num_bots{
|
for bot_id in 0..num_bots{
|
||||||
i64 simulation_time
|
|
||||||
u128 bot_resource_id
|
u128 bot_resource_id
|
||||||
u64 bot_header_block_id
|
u64 bot_header_block_id
|
||||||
}
|
}
|
||||||
|
|
||||||
//map loading timeline
|
|
||||||
|
|
||||||
//bot loading timeline
|
//bot loading timeline
|
||||||
how to do worldstate for deathrun!?
|
how to do worldstate for deathrun!?
|
||||||
- this is done in the client, there is no worldstate in the demo file
|
|
||||||
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
pub struct StreamableDemo<R:BinReaderExt>{
|
pub struct StreamableDemo<R:BinReaderExt>{
|
||||||
map:Vec<crate::map::StreamableMap<R>>,
|
map:Box<crate::map::StreamableMap<R>>,
|
||||||
bots:Vec<crate::bot::StreamableBot<R>>,
|
bots:Vec<crate::bot::StreamableBot<R>>,
|
||||||
}
|
}
|
||||||
impl<R:BinReaderExt> StreamableDemo<R>{
|
impl<R:BinReaderExt> StreamableDemo<R>{
|
||||||
|
@ -73,16 +73,6 @@ pub struct Header{
|
|||||||
#[br(count=block_count+1)]
|
#[br(count=block_count+1)]
|
||||||
pub block_location:Vec<u64>,
|
pub block_location:Vec<u64>,
|
||||||
}
|
}
|
||||||
impl Header{
|
|
||||||
pub const fn calculate_size(block_count:u32)->usize{
|
|
||||||
4 // fourcc
|
|
||||||
+4 // version
|
|
||||||
+8 // priming
|
|
||||||
+16 // resource
|
|
||||||
+4 // block_count
|
|
||||||
+(block_count as usize+1)*8 // block_location
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[binrw]
|
#[binrw]
|
||||||
#[brw(little)]
|
#[brw(little)]
|
||||||
|
@ -86,7 +86,6 @@ for model_id in 0..num_models{
|
|||||||
//if you hash the resource itself and set the first 8 bits to this, that's the resource uuid
|
//if you hash the resource itself and set the first 8 bits to this, that's the resource uuid
|
||||||
#[binrw]
|
#[binrw]
|
||||||
#[brw(little,repr=u8)]
|
#[brw(little,repr=u8)]
|
||||||
#[repr(u8)]
|
|
||||||
enum ResourceType{
|
enum ResourceType{
|
||||||
Mesh,
|
Mesh,
|
||||||
Texture,
|
Texture,
|
||||||
@ -95,6 +94,21 @@ enum ResourceType{
|
|||||||
//Video,
|
//Video,
|
||||||
//Animation,
|
//Animation,
|
||||||
}
|
}
|
||||||
|
const RESOURCE_TYPE_VARIANT_COUNT:u8=2;
|
||||||
|
#[binrw]
|
||||||
|
#[brw(little)]
|
||||||
|
struct ResourceId(u128);
|
||||||
|
impl ResourceId{
|
||||||
|
fn resource_type(&self)->Option<ResourceType>{
|
||||||
|
let discriminant=self.0 as u8;
|
||||||
|
//TODO: use this when it is stabilized https://github.com/rust-lang/rust/issues/73662
|
||||||
|
//if (discriminant as usize)<std::mem::variant_count::<ResourceType>(){
|
||||||
|
match discriminant<RESOURCE_TYPE_VARIANT_COUNT{
|
||||||
|
true=>Some(unsafe{std::mem::transmute::<u8,ResourceType>(discriminant)}),
|
||||||
|
false=>None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
struct ResourceMap<T>{
|
struct ResourceMap<T>{
|
||||||
meshes:HashMap<strafesnet_common::model::MeshId,T>,
|
meshes:HashMap<strafesnet_common::model::MeshId,T>,
|
||||||
@ -121,6 +135,11 @@ struct ResourceBlockHeader{
|
|||||||
resource:ResourceType,
|
resource:ResourceType,
|
||||||
id:BlockId,
|
id:BlockId,
|
||||||
}
|
}
|
||||||
|
#[binrw]
|
||||||
|
#[brw(little)]
|
||||||
|
struct ResourceExternalHeader{
|
||||||
|
resource_uuid:ResourceId,
|
||||||
|
}
|
||||||
|
|
||||||
#[binrw]
|
#[binrw]
|
||||||
#[brw(little)]
|
#[brw(little)]
|
||||||
@ -392,26 +411,28 @@ pub fn write_map<W:BinWriterExt>(mut writer:W,map:strafesnet_common::map::Comple
|
|||||||
attributes:map.attributes.into_iter().map(Into::into).collect(),
|
attributes:map.attributes.into_iter().map(Into::into).collect(),
|
||||||
render_configs:map.render_configs.into_iter().map(Into::into).collect(),
|
render_configs:map.render_configs.into_iter().map(Into::into).collect(),
|
||||||
};
|
};
|
||||||
//probe header length
|
let mut file_header=crate::file::Header{
|
||||||
let mut map_header_data=Vec::new();
|
|
||||||
binrw::BinWrite::write_le(&map_header,&mut std::io::Cursor::new(&mut map_header_data)).map_err(Error::InvalidData)?;
|
|
||||||
|
|
||||||
// calculate final file header
|
|
||||||
let mut offset=crate::file::Header::calculate_size(block_count) as u64;
|
|
||||||
offset+=map_header_data.len() as u64;
|
|
||||||
// priming includes map header
|
|
||||||
let priming=offset;
|
|
||||||
for position in &mut block_location{
|
|
||||||
*position+=offset;
|
|
||||||
}
|
|
||||||
let file_header=crate::file::Header{
|
|
||||||
fourcc:crate::file::FourCC::Map,
|
fourcc:crate::file::FourCC::Map,
|
||||||
version:0,
|
version:0,
|
||||||
priming,
|
priming:0,
|
||||||
resource:0,
|
resource:0,
|
||||||
block_count,
|
block_count,
|
||||||
block_location,
|
block_location,
|
||||||
};
|
};
|
||||||
|
//probe header length
|
||||||
|
let mut file_header_data=Vec::new();
|
||||||
|
binrw::BinWrite::write_le(&file_header,&mut std::io::Cursor::new(&mut file_header_data)).map_err(Error::InvalidData)?;
|
||||||
|
let mut map_header_data=Vec::new();
|
||||||
|
binrw::BinWrite::write_le(&map_header,&mut std::io::Cursor::new(&mut map_header_data)).map_err(Error::InvalidData)?;
|
||||||
|
|
||||||
|
//update file header according to probe data
|
||||||
|
let mut offset=file_header_data.len() as u64;
|
||||||
|
file_header.priming=offset;
|
||||||
|
file_header.block_location[0]=offset;
|
||||||
|
offset+=map_header_data.len() as u64;
|
||||||
|
for position in &mut file_header.block_location[1..]{
|
||||||
|
*position+=offset;
|
||||||
|
}
|
||||||
|
|
||||||
//write (updated) file header
|
//write (updated) file header
|
||||||
writer.write_le(&file_header).map_err(Error::InvalidData)?;
|
writer.write_le(&file_header).map_err(Error::InvalidData)?;
|
||||||
|
@ -1,9 +1,7 @@
|
|||||||
mod common;
|
mod common;
|
||||||
pub mod aabb;
|
pub mod aabb;
|
||||||
pub mod model;
|
pub mod model;
|
||||||
pub mod mouse;
|
|
||||||
pub mod integer;
|
pub mod integer;
|
||||||
pub mod physics;
|
|
||||||
pub mod gameplay_modes;
|
pub mod gameplay_modes;
|
||||||
pub mod gameplay_style;
|
pub mod gameplay_style;
|
||||||
pub mod gameplay_attributes;
|
pub mod gameplay_attributes;
|
@ -1,9 +1,3 @@
|
|||||||
pub const fn flag(b:bool,mask:u8)->u8{
|
pub const fn flag(b:bool,mask:u8)->u8{
|
||||||
(-(b as i8) as u8)&mask
|
(-(b as i8) as u8)&mask
|
||||||
}
|
}
|
||||||
pub fn bool_from_u8(value:u8)->bool{
|
|
||||||
value!=0
|
|
||||||
}
|
|
||||||
pub fn bool_into_u8(value:&bool)->u8{
|
|
||||||
*value as u8
|
|
||||||
}
|
|
||||||
|
@ -38,23 +38,6 @@ pub struct Ratio64Vec2{
|
|||||||
pub x:Ratio64,
|
pub x:Ratio64,
|
||||||
pub y:Ratio64,
|
pub y:Ratio64,
|
||||||
}
|
}
|
||||||
impl TryInto<strafesnet_common::integer::Ratio64Vec2> for Ratio64Vec2{
|
|
||||||
type Error=RatioError;
|
|
||||||
fn try_into(self)->Result<strafesnet_common::integer::Ratio64Vec2,Self::Error>{
|
|
||||||
Ok(strafesnet_common::integer::Ratio64Vec2{
|
|
||||||
x:self.x.try_into()?,
|
|
||||||
y:self.y.try_into()?,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl From<strafesnet_common::integer::Ratio64Vec2> for Ratio64Vec2{
|
|
||||||
fn from(value:strafesnet_common::integer::Ratio64Vec2)->Self{
|
|
||||||
Self{
|
|
||||||
x:value.x.into(),
|
|
||||||
y:value.y.into(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub type Angle32=i32;
|
pub type Angle32=i32;
|
||||||
pub type Planar64=i64;
|
pub type Planar64=i64;
|
||||||
|
@ -1,25 +0,0 @@
|
|||||||
use super::integer::Time;
|
|
||||||
|
|
||||||
#[binrw::binrw]
|
|
||||||
#[brw(little)]
|
|
||||||
pub struct MouseState{
|
|
||||||
pub pos:[i32;2],
|
|
||||||
pub time:Time,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> Into<strafesnet_common::mouse::MouseState<T>> for MouseState{
|
|
||||||
fn into(self)->strafesnet_common::mouse::MouseState<T>{
|
|
||||||
strafesnet_common::mouse::MouseState{
|
|
||||||
pos:self.pos.into(),
|
|
||||||
time:strafesnet_common::integer::Time::raw(self.time),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl<T> From<strafesnet_common::mouse::MouseState<T>> for MouseState{
|
|
||||||
fn from(value:strafesnet_common::mouse::MouseState<T>)->Self{
|
|
||||||
Self{
|
|
||||||
pos:value.pos.to_array(),
|
|
||||||
time:value.time.get(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,156 +0,0 @@
|
|||||||
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::TimeInner>;
|
|
||||||
|
|
||||||
#[binrw::binrw]
|
|
||||||
#[brw(little)]
|
|
||||||
pub struct TimedInstruction{
|
|
||||||
pub time:Time,
|
|
||||||
pub instruction:Instruction,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TryInto<TimedPhysicsInstruction> for TimedInstruction{
|
|
||||||
type Error=super::integer::RatioError;
|
|
||||||
fn try_into(self)->Result<TimedPhysicsInstruction,Self::Error>{
|
|
||||||
Ok(strafesnet_common::instruction::TimedInstruction{
|
|
||||||
time:strafesnet_common::integer::Time::raw(self.time),
|
|
||||||
instruction:self.instruction.try_into()?,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl TryFrom<TimedPhysicsInstruction> for TimedInstruction{
|
|
||||||
type Error=super::physics::InstructionConvert;
|
|
||||||
fn try_from(value:TimedPhysicsInstruction)->Result<Self,Self::Error>{
|
|
||||||
Ok(Self{
|
|
||||||
time:value.time.get(),
|
|
||||||
instruction:value.instruction.try_into()?,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#[binrw::binrw]
|
|
||||||
#[brw(little)]
|
|
||||||
pub enum Instruction{
|
|
||||||
#[brw(magic=0u8)]
|
|
||||||
ReplaceMouse{
|
|
||||||
m0:super::mouse::MouseState,
|
|
||||||
m1:super::mouse::MouseState
|
|
||||||
},
|
|
||||||
#[brw(magic=1u8)]
|
|
||||||
SetNextMouse(super::mouse::MouseState),
|
|
||||||
#[brw(magic=2u8)]
|
|
||||||
SetMoveRight(
|
|
||||||
#[br(map=bool_from_u8)]
|
|
||||||
#[bw(map=bool_into_u8)]
|
|
||||||
bool),
|
|
||||||
#[brw(magic=3u8)]
|
|
||||||
SetMoveUp(
|
|
||||||
#[br(map=bool_from_u8)]
|
|
||||||
#[bw(map=bool_into_u8)]
|
|
||||||
bool),
|
|
||||||
#[brw(magic=4u8)]
|
|
||||||
SetMoveBack(
|
|
||||||
#[br(map=bool_from_u8)]
|
|
||||||
#[bw(map=bool_into_u8)]
|
|
||||||
bool),
|
|
||||||
#[brw(magic=5u8)]
|
|
||||||
SetMoveLeft(
|
|
||||||
#[br(map=bool_from_u8)]
|
|
||||||
#[bw(map=bool_into_u8)]
|
|
||||||
bool),
|
|
||||||
#[brw(magic=6u8)]
|
|
||||||
SetMoveDown(
|
|
||||||
#[br(map=bool_from_u8)]
|
|
||||||
#[bw(map=bool_into_u8)]
|
|
||||||
bool),
|
|
||||||
#[brw(magic=7u8)]
|
|
||||||
SetMoveForward(
|
|
||||||
#[br(map=bool_from_u8)]
|
|
||||||
#[bw(map=bool_into_u8)]
|
|
||||||
bool),
|
|
||||||
#[brw(magic=8u8)]
|
|
||||||
SetJump(
|
|
||||||
#[br(map=bool_from_u8)]
|
|
||||||
#[bw(map=bool_into_u8)]
|
|
||||||
bool),
|
|
||||||
#[brw(magic=9u8)]
|
|
||||||
SetZoom(
|
|
||||||
#[br(map=bool_from_u8)]
|
|
||||||
#[bw(map=bool_into_u8)]
|
|
||||||
bool),
|
|
||||||
#[brw(magic=10u8)]
|
|
||||||
Reset,
|
|
||||||
#[brw(magic=11u8)]
|
|
||||||
Restart(super::gameplay_modes::ModeId),
|
|
||||||
#[brw(magic=12u8)]
|
|
||||||
Spawn(super::gameplay_modes::ModeId,super::gameplay_modes::StageId),
|
|
||||||
#[brw(magic=13u8)]
|
|
||||||
PracticeFly,
|
|
||||||
#[brw(magic=14u8)]
|
|
||||||
SetSensitivity(super::integer::Ratio64Vec2),
|
|
||||||
#[brw(magic=255u8)]
|
|
||||||
Idle,
|
|
||||||
}
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub enum InstructionConvert{
|
|
||||||
/// This is an instruction that can be dropped when serializing
|
|
||||||
DropInstruction,
|
|
||||||
}
|
|
||||||
impl std::fmt::Display for InstructionConvert{
|
|
||||||
fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{
|
|
||||||
write!(f,"{self:?}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl std::error::Error for InstructionConvert{}
|
|
||||||
impl TryInto<strafesnet_common::physics::Instruction> for Instruction{
|
|
||||||
type Error=super::integer::RatioError;
|
|
||||||
fn try_into(self)->Result<strafesnet_common::physics::Instruction,Self::Error>{
|
|
||||||
Ok(match self{
|
|
||||||
Instruction::ReplaceMouse{m0,m1}=>strafesnet_common::physics::Instruction::Mouse(strafesnet_common::physics::MouseInstruction::ReplaceMouse{m0:m0.into(),m1:m1.into()}),
|
|
||||||
Instruction::SetNextMouse(m)=>strafesnet_common::physics::Instruction::Mouse(strafesnet_common::physics::MouseInstruction::SetNextMouse(m.into())),
|
|
||||||
Instruction::SetMoveRight(state)=>strafesnet_common::physics::Instruction::SetControl(strafesnet_common::physics::SetControlInstruction::SetMoveRight(state.into())),
|
|
||||||
Instruction::SetMoveUp(state)=>strafesnet_common::physics::Instruction::SetControl(strafesnet_common::physics::SetControlInstruction::SetMoveUp(state.into())),
|
|
||||||
Instruction::SetMoveBack(state)=>strafesnet_common::physics::Instruction::SetControl(strafesnet_common::physics::SetControlInstruction::SetMoveBack(state.into())),
|
|
||||||
Instruction::SetMoveLeft(state)=>strafesnet_common::physics::Instruction::SetControl(strafesnet_common::physics::SetControlInstruction::SetMoveLeft(state.into())),
|
|
||||||
Instruction::SetMoveDown(state)=>strafesnet_common::physics::Instruction::SetControl(strafesnet_common::physics::SetControlInstruction::SetMoveDown(state.into())),
|
|
||||||
Instruction::SetMoveForward(state)=>strafesnet_common::physics::Instruction::SetControl(strafesnet_common::physics::SetControlInstruction::SetMoveForward(state.into())),
|
|
||||||
Instruction::SetJump(state)=>strafesnet_common::physics::Instruction::SetControl(strafesnet_common::physics::SetControlInstruction::SetJump(state.into())),
|
|
||||||
Instruction::SetZoom(state)=>strafesnet_common::physics::Instruction::SetControl(strafesnet_common::physics::SetControlInstruction::SetZoom(state.into())),
|
|
||||||
Instruction::Reset=>strafesnet_common::physics::Instruction::Mode(strafesnet_common::physics::ModeInstruction::Reset),
|
|
||||||
Instruction::Restart(mode_id)=>strafesnet_common::physics::Instruction::Mode(strafesnet_common::physics::ModeInstruction::Restart(strafesnet_common::gameplay_modes::ModeId::new(mode_id))),
|
|
||||||
Instruction::Spawn(mode_id,stage_id)=>strafesnet_common::physics::Instruction::Mode(strafesnet_common::physics::ModeInstruction::Spawn(
|
|
||||||
strafesnet_common::gameplay_modes::ModeId::new(mode_id),
|
|
||||||
strafesnet_common::gameplay_modes::StageId::new(stage_id),
|
|
||||||
)),
|
|
||||||
Instruction::PracticeFly=>strafesnet_common::physics::Instruction::Misc(strafesnet_common::physics::MiscInstruction::PracticeFly),
|
|
||||||
Instruction::SetSensitivity(sensitivity)=>strafesnet_common::physics::Instruction::Misc(strafesnet_common::physics::MiscInstruction::SetSensitivity(sensitivity.try_into()?)),
|
|
||||||
Instruction::Idle=>strafesnet_common::physics::Instruction::Idle,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl TryFrom<strafesnet_common::physics::Instruction> for Instruction{
|
|
||||||
type Error=InstructionConvert;
|
|
||||||
fn try_from(value:strafesnet_common::physics::Instruction)->Result<Self,Self::Error>{
|
|
||||||
match value{
|
|
||||||
strafesnet_common::physics::Instruction::Mouse(strafesnet_common::physics::MouseInstruction::ReplaceMouse{m0,m1})=>Ok(Instruction::ReplaceMouse{m0:m0.into(),m1:m1.into()}),
|
|
||||||
strafesnet_common::physics::Instruction::Mouse(strafesnet_common::physics::MouseInstruction::SetNextMouse(m))=>Ok(Instruction::SetNextMouse(m.into())),
|
|
||||||
strafesnet_common::physics::Instruction::SetControl(strafesnet_common::physics::SetControlInstruction::SetMoveRight(state))=>Ok(Instruction::SetMoveRight(state.into())),
|
|
||||||
strafesnet_common::physics::Instruction::SetControl(strafesnet_common::physics::SetControlInstruction::SetMoveUp(state))=>Ok(Instruction::SetMoveUp(state.into())),
|
|
||||||
strafesnet_common::physics::Instruction::SetControl(strafesnet_common::physics::SetControlInstruction::SetMoveBack(state))=>Ok(Instruction::SetMoveBack(state.into())),
|
|
||||||
strafesnet_common::physics::Instruction::SetControl(strafesnet_common::physics::SetControlInstruction::SetMoveLeft(state))=>Ok(Instruction::SetMoveLeft(state.into())),
|
|
||||||
strafesnet_common::physics::Instruction::SetControl(strafesnet_common::physics::SetControlInstruction::SetMoveDown(state))=>Ok(Instruction::SetMoveDown(state.into())),
|
|
||||||
strafesnet_common::physics::Instruction::SetControl(strafesnet_common::physics::SetControlInstruction::SetMoveForward(state))=>Ok(Instruction::SetMoveForward(state.into())),
|
|
||||||
strafesnet_common::physics::Instruction::SetControl(strafesnet_common::physics::SetControlInstruction::SetJump(state))=>Ok(Instruction::SetJump(state.into())),
|
|
||||||
strafesnet_common::physics::Instruction::SetControl(strafesnet_common::physics::SetControlInstruction::SetZoom(state))=>Ok(Instruction::SetZoom(state.into())),
|
|
||||||
strafesnet_common::physics::Instruction::Mode(strafesnet_common::physics::ModeInstruction::Reset)=>Ok(Instruction::Reset),
|
|
||||||
strafesnet_common::physics::Instruction::Mode(strafesnet_common::physics::ModeInstruction::Restart(mode_id))=>Ok(Instruction::Restart(mode_id.get())),
|
|
||||||
strafesnet_common::physics::Instruction::Mode(strafesnet_common::physics::ModeInstruction::Spawn(mode_id,stage_id))=>Ok(Instruction::Spawn(
|
|
||||||
mode_id.get(),
|
|
||||||
stage_id.get(),
|
|
||||||
)),
|
|
||||||
strafesnet_common::physics::Instruction::Misc(strafesnet_common::physics::MiscInstruction::PracticeFly)=>Ok(Instruction::PracticeFly),
|
|
||||||
strafesnet_common::physics::Instruction::Misc(strafesnet_common::physics::MiscInstruction::SetSensitivity(sensitivity))=>Ok(Instruction::SetSensitivity(sensitivity.into())),
|
|
||||||
strafesnet_common::physics::Instruction::Idle=>Ok(Instruction::Idle),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,2 +0,0 @@
|
|||||||
// a session is a recording of the client's inputs
|
|
||||||
// which should deterministically recreate a bot or whatever the client did
|
|
@ -1,37 +0,0 @@
|
|||||||
[package]
|
|
||||||
name = "map-tool"
|
|
||||||
version = "1.7.0"
|
|
||||||
edition = "2021"
|
|
||||||
|
|
||||||
# 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.2.5", registry = "strafesnet" }
|
|
||||||
rbx_binary = { version = "0.7.4", registry = "strafesnet" }
|
|
||||||
rbx_dom_weak = { version = "2.7.0", registry = "strafesnet" }
|
|
||||||
rbx_reflection_database = { version = "0.2.10", registry = "strafesnet" }
|
|
||||||
rbx_xml = { version = "0.13.3", 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.6.0"
|
|
||||||
vmdl = "0.2.0"
|
|
||||||
vmt-parser = "0.2.0"
|
|
||||||
vpk = "0.2.0"
|
|
||||||
vtf = "0.3.0"
|
|
||||||
|
|
||||||
#[profile.release]
|
|
||||||
#lto = true
|
|
||||||
#strip = true
|
|
||||||
#codegen-units = 1
|
|
@ -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.
|
|
@ -1,2 +0,0 @@
|
|||||||
# map-tool
|
|
||||||
|
|
@ -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,
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,431 +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,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(input).map_err(LoadDomError::Binary),
|
|
||||||
b"<roblox "=>rbx_xml::from_reader(input,rbx_xml::DecodeOptions::default()).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
|
|
||||||
*/
|
|
||||||
fn accumulate_content_id(content_list:&mut HashSet<RobloxAssetId>,object:&Instance,property:&str){
|
|
||||||
if let Some(rbx_dom_weak::types::Variant::Content(content))=object.properties.get(property){
|
|
||||||
let url:&str=content.as_ref();
|
|
||||||
if let Ok(asset_id)=url.parse(){
|
|
||||||
content_list.insert(asset_id);
|
|
||||||
}else{
|
|
||||||
println!("Content failed to parse into AssetID: {:?}",content);
|
|
||||||
}
|
|
||||||
}else{
|
|
||||||
println!("property={} does not exist for class={}",property,object.class.as_str());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
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.as_str(){
|
|
||||||
"Beam"=>accumulate_content_id(&mut self.textures,object,"Texture"),
|
|
||||||
"Decal"=>accumulate_content_id(&mut self.textures,object,"Texture"),
|
|
||||||
"Texture"=>accumulate_content_id(&mut self.textures,object,"Texture"),
|
|
||||||
"FileMesh"=>accumulate_content_id(&mut self.textures,object,"TextureId"),
|
|
||||||
"MeshPart"=>{
|
|
||||||
accumulate_content_id(&mut self.textures,object,"TextureID");
|
|
||||||
accumulate_content_id(&mut self.meshes,object,"MeshId");
|
|
||||||
},
|
|
||||||
"SpecialMesh"=>accumulate_content_id(&mut self.meshes,object,"MeshId"),
|
|
||||||
"ParticleEmitter"=>accumulate_content_id(&mut self.textures,object,"Texture"),
|
|
||||||
"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(asset_id)=>format!("downloaded_textures/{}",asset_id.0.to_string()).into(),
|
|
||||||
DownloadType::Mesh(asset_id)=>format!("meshes/{}",asset_id.0.to_string()).into(),
|
|
||||||
DownloadType::Union(asset_id)=>format!("unions/{}",asset_id.0.to_string()).into(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fn asset_id(&self)->u64{
|
|
||||||
match self{
|
|
||||||
DownloadType::Texture(asset_id)=>asset_id.0,
|
|
||||||
DownloadType::Mesh(asset_id)=>asset_id.0,
|
|
||||||
DownloadType::Union(asset_id)=>asset_id.0,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
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::CookieContext,download_instruction:DownloadType)->Result<DownloadResult,std::io::Error>{
|
|
||||||
stats.total_assets+=1;
|
|
||||||
let download_instruction=download_instruction;
|
|
||||||
// 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;
|
|
||||||
tokio::fs::write(path,&asset_result).await?;
|
|
||||||
break Ok(DownloadResult::Data(asset_result));
|
|
||||||
},
|
|
||||||
Err(rbx_asset::cookie::GetError::Response(rbx_asset::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(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/{}.dds",asset_id.0);
|
|
||||||
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::CookieContext::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=model.into_place();
|
|
||||||
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(())
|
|
||||||
}
|
|
@ -1,464 +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};
|
|
||||||
|
|
||||||
#[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 prop in bsp.static_props(){
|
|
||||||
mesh_deferred_loader.acquire_mesh_id(prop.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(())
|
|
||||||
}
|
|
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "strafe-client"
|
name = "strafe-client"
|
||||||
version = "0.11.0"
|
version = "0.10.5"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
repository = "https://git.itzana.me/StrafesNET/strafe-project"
|
repository = "https://git.itzana.me/StrafesNET/strafe-project"
|
||||||
license = "Custom"
|
license = "Custom"
|
||||||
@ -9,24 +9,24 @@ authors = ["Rhys Lloyd <krakow20@gmail.com>"]
|
|||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
[features]
|
[features]
|
||||||
user-install=[] # as opposed to portable install
|
|
||||||
default = ["snf"]
|
default = ["snf"]
|
||||||
snf = ["dep:strafesnet_snf"]
|
snf = ["dep:strafesnet_snf"]
|
||||||
source = ["dep:strafesnet_deferred_loader", "dep:strafesnet_bsp_loader"]
|
source = ["dep:strafesnet_deferred_loader", "dep:strafesnet_bsp_loader"]
|
||||||
roblox = ["dep:strafesnet_deferred_loader", "dep:strafesnet_rbx_loader"]
|
roblox = ["dep:strafesnet_deferred_loader", "dep:strafesnet_rbx_loader"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
bytemuck = { version = "1.13.1", features = ["derive"] }
|
||||||
|
configparser = "3.0.2"
|
||||||
|
ddsfile = "0.5.1"
|
||||||
glam = "0.29.0"
|
glam = "0.29.0"
|
||||||
|
id = { version = "0.1.0", registry = "strafesnet" }
|
||||||
parking_lot = "0.12.1"
|
parking_lot = "0.12.1"
|
||||||
pollster = "0.4.0"
|
pollster = "0.4.0"
|
||||||
strafesnet_bsp_loader = { path = "../lib/bsp_loader", registry = "strafesnet", optional = true }
|
strafesnet_bsp_loader = { path = "../lib/bsp_loader", registry = "strafesnet", optional = true }
|
||||||
strafesnet_common = { path = "../lib/common", registry = "strafesnet" }
|
strafesnet_common = { path = "../lib/common", registry = "strafesnet" }
|
||||||
strafesnet_deferred_loader = { path = "../lib/deferred_loader", registry = "strafesnet", optional = true }
|
strafesnet_deferred_loader = { path = "../lib/deferred_loader", features = ["legacy"], registry = "strafesnet", optional = true }
|
||||||
strafesnet_graphics = { path = "../engine/graphics", registry = "strafesnet" }
|
|
||||||
strafesnet_physics = { path = "../engine/physics", registry = "strafesnet" }
|
|
||||||
strafesnet_rbx_loader = { path = "../lib/rbx_loader", registry = "strafesnet", optional = true }
|
strafesnet_rbx_loader = { path = "../lib/rbx_loader", registry = "strafesnet", optional = true }
|
||||||
strafesnet_session = { path = "../engine/session", registry = "strafesnet" }
|
strafesnet_roblox_bot_file = { version = "0.2.0", registry = "strafesnet" }
|
||||||
strafesnet_settings = { path = "../engine/settings", registry = "strafesnet" }
|
|
||||||
strafesnet_snf = { path = "../lib/snf", registry = "strafesnet", optional = true }
|
strafesnet_snf = { path = "../lib/snf", registry = "strafesnet", optional = true }
|
||||||
wgpu = "24.0.0"
|
wgpu = "23.0.1"
|
||||||
winit = "0.30.7"
|
winit = "0.30.7"
|
||||||
|
@ -1,68 +0,0 @@
|
|||||||
use crate::window::Instruction;
|
|
||||||
use strafesnet_common::integer;
|
|
||||||
use strafesnet_common::instruction::TimedInstruction;
|
|
||||||
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,SessionTimeInner>>,
|
|
||||||
}
|
|
||||||
impl<'a> App<'a>{
|
|
||||||
pub fn new(
|
|
||||||
root_time:std::time::Instant,
|
|
||||||
window_thread:crate::compat_worker::QNWorker<'a,TimedInstruction<Instruction,SessionTimeInner>>,
|
|
||||||
)->App<'a>{
|
|
||||||
Self{
|
|
||||||
root_time,
|
|
||||||
window_thread,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fn send_timed_instruction(&mut self,instruction:Instruction){
|
|
||||||
let time=integer::Time::from_nanos(self.root_time.elapsed().as_nanos() as i64);
|
|
||||||
self.window_thread.send(TimedInstruction{time,instruction}).unwrap();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl winit::application::ApplicationHandler for App<'_>{
|
|
||||||
fn resumed(&mut self,_event_loop:&winit::event_loop::ActiveEventLoop){
|
|
||||||
//
|
|
||||||
}
|
|
||||||
|
|
||||||
fn window_event(
|
|
||||||
&mut self,
|
|
||||||
event_loop:&winit::event_loop::ActiveEventLoop,
|
|
||||||
_window_id:winit::window::WindowId,
|
|
||||||
event:winit::event::WindowEvent,
|
|
||||||
){
|
|
||||||
match event{
|
|
||||||
winit::event::WindowEvent::KeyboardInput{
|
|
||||||
event:winit::event::KeyEvent{
|
|
||||||
logical_key:winit::keyboard::Key::Named(winit::keyboard::NamedKey::Escape),
|
|
||||||
state:winit::event::ElementState::Pressed,
|
|
||||||
..
|
|
||||||
},
|
|
||||||
..
|
|
||||||
}
|
|
||||||
|winit::event::WindowEvent::CloseRequested=>{
|
|
||||||
event_loop.exit();
|
|
||||||
},
|
|
||||||
_=>(),
|
|
||||||
}
|
|
||||||
self.send_timed_instruction(Instruction::WindowEvent(event));
|
|
||||||
}
|
|
||||||
|
|
||||||
fn device_event(
|
|
||||||
&mut self,
|
|
||||||
_event_loop:&winit::event_loop::ActiveEventLoop,
|
|
||||||
_device_id:winit::event::DeviceId,
|
|
||||||
event:winit::event::DeviceEvent,
|
|
||||||
){
|
|
||||||
self.send_timed_instruction(Instruction::DeviceEvent(event));
|
|
||||||
}
|
|
||||||
|
|
||||||
fn about_to_wait(
|
|
||||||
&mut self,
|
|
||||||
_event_loop:&winit::event_loop::ActiveEventLoop
|
|
||||||
){
|
|
||||||
self.send_timed_instruction(Instruction::WindowEvent(winit::event::WindowEvent::RedrawRequested));
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,48 +1,23 @@
|
|||||||
use crate::model::{GigaTime,FEV,MeshQuery,DirectedEdge};
|
use crate::physics::Body;
|
||||||
use strafesnet_common::integer::{Fixed,Ratio,vec3::Vector3};
|
use crate::model_physics::{GigaTime,FEV,MeshQuery,DirectedEdge,MinkowskiMesh,MinkowskiFace,MinkowskiDirectedEdge,MinkowskiVert};
|
||||||
use crate::physics::{Time,Body};
|
use strafesnet_common::integer::{Time,Fixed,Ratio};
|
||||||
|
|
||||||
enum Transition<M:MeshQuery>{
|
#[derive(Debug)]
|
||||||
|
enum Transition<F,E:DirectedEdge,V>{
|
||||||
Miss,
|
Miss,
|
||||||
Next(FEV<M>,GigaTime),
|
Next(FEV<F,E,V>,GigaTime),
|
||||||
Hit(M::Face,GigaTime),
|
Hit(F,GigaTime),
|
||||||
}
|
}
|
||||||
|
|
||||||
pub enum CrawlResult<M:MeshQuery>{
|
type MinkowskiFEV=FEV<MinkowskiFace,MinkowskiDirectedEdge,MinkowskiVert>;
|
||||||
Miss(FEV<M>),
|
type MinkowskiTransition=Transition<MinkowskiFace,MinkowskiDirectedEdge,MinkowskiVert>;
|
||||||
Hit(M::Face,GigaTime),
|
|
||||||
}
|
|
||||||
impl<M:MeshQuery> CrawlResult<M>{
|
|
||||||
pub fn hit(self)->Option<(M::Face,GigaTime)>{
|
|
||||||
match self{
|
|
||||||
CrawlResult::Miss(_)=>None,
|
|
||||||
CrawlResult::Hit(face,time)=>Some((face,time)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub fn miss(self)->Option<FEV<M>>{
|
|
||||||
match self{
|
|
||||||
CrawlResult::Miss(fev)=>Some(fev),
|
|
||||||
CrawlResult::Hit(_,_)=>None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<F:Copy,M:MeshQuery<Normal=Vector3<F>,Offset=Fixed<4,128>>> FEV<M>
|
fn next_transition(fev:&MinkowskiFEV,body_time:GigaTime,mesh:&MinkowskiMesh,body:&Body,mut best_time:GigaTime)->MinkowskiTransition{
|
||||||
where
|
|
||||||
// This is hardcoded for MinkowskiMesh lol
|
|
||||||
M::Face:Copy,
|
|
||||||
M::Edge:Copy,
|
|
||||||
M::Vert:Copy,
|
|
||||||
F:core::ops::Mul<Fixed<1,32>,Output=Fixed<4,128>>,
|
|
||||||
<F as core::ops::Mul<Fixed<1,32>>>::Output:core::iter::Sum,
|
|
||||||
<M as MeshQuery>::Offset:core::ops::Sub<<F as std::ops::Mul<Fixed<1,32>>>::Output>,
|
|
||||||
{
|
|
||||||
fn next_transition(&self,body_time:GigaTime,mesh:&M,body:&Body,mut best_time:GigaTime)->Transition<M>{
|
|
||||||
//conflicting derivative means it crosses in the wrong direction.
|
//conflicting derivative means it crosses in the wrong direction.
|
||||||
//if the transition time is equal to an already tested transition, do not replace the current best.
|
//if the transition time is equal to an already tested transition, do not replace the current best.
|
||||||
let mut best_transition=Transition::Miss;
|
let mut best_transition=MinkowskiTransition::Miss;
|
||||||
match self{
|
match fev{
|
||||||
&FEV::Face(face_id)=>{
|
&MinkowskiFEV::Face(face_id)=>{
|
||||||
//test own face collision time, ignoring roots with zero or conflicting derivative
|
//test own face collision time, ignoring roots with zero or conflicting derivative
|
||||||
//n=face.normal d=face.dot
|
//n=face.normal d=face.dot
|
||||||
//n.a t^2+n.v t+n.p-d==0
|
//n.a t^2+n.v t+n.p-d==0
|
||||||
@ -52,7 +27,7 @@ impl<F:Copy,M:MeshQuery<Normal=Vector3<F>,Offset=Fixed<4,128>>> FEV<M>
|
|||||||
for dt in Fixed::<4,128>::zeroes2((n.dot(body.position)-d)*2,n.dot(body.velocity)*2,n.dot(body.acceleration)){
|
for dt in Fixed::<4,128>::zeroes2((n.dot(body.position)-d)*2,n.dot(body.velocity)*2,n.dot(body.acceleration)){
|
||||||
if body_time.le_ratio(dt)&&dt.lt_ratio(best_time)&&n.dot(body.extrapolated_velocity_ratio_dt(dt)).is_negative(){
|
if body_time.le_ratio(dt)&&dt.lt_ratio(best_time)&&n.dot(body.extrapolated_velocity_ratio_dt(dt)).is_negative(){
|
||||||
best_time=dt;
|
best_time=dt;
|
||||||
best_transition=Transition::Hit(face_id,dt);
|
best_transition=MinkowskiTransition::Hit(face_id,dt);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -66,14 +41,14 @@ impl<F:Copy,M:MeshQuery<Normal=Vector3<F>,Offset=Fixed<4,128>>> FEV<M>
|
|||||||
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()){
|
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(){
|
if body_time.le_ratio(dt)&&dt.lt_ratio(best_time)&&n.dot(body.extrapolated_velocity_ratio_dt(dt)).is_negative(){
|
||||||
best_time=dt;
|
best_time=dt;
|
||||||
best_transition=Transition::Next(FEV::Edge(directed_edge_id.as_undirected()),dt);
|
best_transition=MinkowskiTransition::Next(MinkowskiFEV::Edge(directed_edge_id.as_undirected()),dt);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
//if none:
|
//if none:
|
||||||
},
|
},
|
||||||
&FEV::Edge(edge_id)=>{
|
&MinkowskiFEV::Edge(edge_id)=>{
|
||||||
//test each face collision time, ignoring roots with zero or conflicting derivative
|
//test each face collision time, ignoring roots with zero or conflicting derivative
|
||||||
let edge_n=mesh.edge_n(edge_id);
|
let edge_n=mesh.edge_n(edge_id);
|
||||||
let edge_verts=mesh.edge_verts(edge_id);
|
let edge_verts=mesh.edge_verts(edge_id);
|
||||||
@ -86,7 +61,7 @@ impl<F:Copy,M:MeshQuery<Normal=Vector3<F>,Offset=Fixed<4,128>>> FEV<M>
|
|||||||
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()){
|
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(){
|
if body_time.le_ratio(dt)&&dt.lt_ratio(best_time)&&n.dot(body.extrapolated_velocity_ratio_dt(dt)).is_negative(){
|
||||||
best_time=dt;
|
best_time=dt;
|
||||||
best_transition=Transition::Next(FEV::Face(edge_face_id),dt);
|
best_transition=MinkowskiTransition::Next(MinkowskiFEV::Face(edge_face_id),dt);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -99,14 +74,14 @@ impl<F:Copy,M:MeshQuery<Normal=Vector3<F>,Offset=Fixed<4,128>>> FEV<M>
|
|||||||
if body_time.le_ratio(dt)&&dt.lt_ratio(best_time)&&n.dot(body.extrapolated_velocity_ratio_dt(dt)).is_negative(){
|
if body_time.le_ratio(dt)&&dt.lt_ratio(best_time)&&n.dot(body.extrapolated_velocity_ratio_dt(dt)).is_negative(){
|
||||||
let dt=Ratio::new(dt.num.fix_4(),dt.den.fix_4());
|
let dt=Ratio::new(dt.num.fix_4(),dt.den.fix_4());
|
||||||
best_time=dt;
|
best_time=dt;
|
||||||
best_transition=Transition::Next(FEV::Vert(vert_id),dt);
|
best_transition=MinkowskiTransition::Next(MinkowskiFEV::Vert(vert_id),dt);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
//if none:
|
//if none:
|
||||||
},
|
},
|
||||||
&FEV::Vert(vert_id)=>{
|
&MinkowskiFEV::Vert(vert_id)=>{
|
||||||
//test each edge collision time, ignoring roots with zero or conflicting derivative
|
//test each edge collision time, ignoring roots with zero or conflicting derivative
|
||||||
for &directed_edge_id in mesh.vert_edges(vert_id).iter(){
|
for &directed_edge_id in mesh.vert_edges(vert_id).iter(){
|
||||||
//edge is directed away from vertex, but we want the dot product to turn out negative
|
//edge is directed away from vertex, but we want the dot product to turn out negative
|
||||||
@ -115,7 +90,7 @@ impl<F:Copy,M:MeshQuery<Normal=Vector3<F>,Offset=Fixed<4,128>>> FEV<M>
|
|||||||
if body_time.le_ratio(dt)&&dt.lt_ratio(best_time)&&n.dot(body.extrapolated_velocity_ratio_dt(dt)).is_negative(){
|
if body_time.le_ratio(dt)&&dt.lt_ratio(best_time)&&n.dot(body.extrapolated_velocity_ratio_dt(dt)).is_negative(){
|
||||||
let dt=Ratio::new(dt.num.fix_4(),dt.den.fix_4());
|
let dt=Ratio::new(dt.num.fix_4(),dt.den.fix_4());
|
||||||
best_time=dt;
|
best_time=dt;
|
||||||
best_transition=Transition::Next(FEV::Edge(directed_edge_id.as_undirected()),dt);
|
best_transition=MinkowskiTransition::Next(MinkowskiFEV::Edge(directed_edge_id.as_undirected()),dt);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -125,7 +100,12 @@ impl<F:Copy,M:MeshQuery<Normal=Vector3<F>,Offset=Fixed<4,128>>> FEV<M>
|
|||||||
}
|
}
|
||||||
best_transition
|
best_transition
|
||||||
}
|
}
|
||||||
pub fn crawl(mut self,mesh:&M,relative_body:&Body,start_time:Time,time_limit:Time)->CrawlResult<M>{
|
pub enum CrawlResult<F,E:DirectedEdge,V>{
|
||||||
|
Miss(FEV<F,E,V>),
|
||||||
|
Hit(F,GigaTime),
|
||||||
|
}
|
||||||
|
type MinkowskiCrawlResult=CrawlResult<MinkowskiFace,MinkowskiDirectedEdge,MinkowskiVert>;
|
||||||
|
pub fn crawl_fev(mut fev:MinkowskiFEV,mesh:&MinkowskiMesh,relative_body:&Body,start_time:Time,time_limit:Time)->MinkowskiCrawlResult{
|
||||||
let mut body_time={
|
let mut body_time={
|
||||||
let r=(start_time-relative_body.time).to_ratio();
|
let r=(start_time-relative_body.time).to_ratio();
|
||||||
Ratio::new(r.num.fix_4(),r.den.fix_4())
|
Ratio::new(r.num.fix_4(),r.den.fix_4())
|
||||||
@ -135,14 +115,13 @@ impl<F:Copy,M:MeshQuery<Normal=Vector3<F>,Offset=Fixed<4,128>>> FEV<M>
|
|||||||
Ratio::new(r.num.fix_4(),r.den.fix_4())
|
Ratio::new(r.num.fix_4(),r.den.fix_4())
|
||||||
};
|
};
|
||||||
for _ in 0..20{
|
for _ in 0..20{
|
||||||
match self.next_transition(body_time,mesh,relative_body,time_limit){
|
match next_transition(&fev,body_time,mesh,relative_body,time_limit){
|
||||||
Transition::Miss=>return CrawlResult::Miss(self),
|
Transition::Miss=>return CrawlResult::Miss(fev),
|
||||||
Transition::Next(next_fev,next_time)=>(self,body_time)=(next_fev,next_time),
|
Transition::Next(next_fev,next_time)=>(fev,body_time)=(next_fev,next_time),
|
||||||
Transition::Hit(face,time)=>return CrawlResult::Hit(face,time),
|
Transition::Hit(face,time)=>return CrawlResult::Hit(face,time),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
//TODO: fix all bugs
|
//TODO: fix all bugs
|
||||||
//println!("Too many iterations! Using default behaviour instead of crashing...");
|
//println!("Too many iterations! Using default behaviour instead of crashing...");
|
||||||
CrawlResult::Miss(self)
|
CrawlResult::Miss(fev)
|
||||||
}
|
|
||||||
}
|
}
|
@ -1,9 +1,5 @@
|
|||||||
use std::io::Read;
|
use std::io::Read;
|
||||||
|
|
||||||
#[cfg(any(feature="roblox",feature="source"))]
|
|
||||||
use strafesnet_deferred_loader::deferred_loader::LoadFailureMode;
|
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum ReadError{
|
pub enum ReadError{
|
||||||
#[cfg(feature="roblox")]
|
#[cfg(feature="roblox")]
|
||||||
@ -14,8 +10,6 @@ pub enum ReadError{
|
|||||||
StrafesNET(strafesnet_snf::Error),
|
StrafesNET(strafesnet_snf::Error),
|
||||||
#[cfg(feature="snf")]
|
#[cfg(feature="snf")]
|
||||||
StrafesNETMap(strafesnet_snf::map::Error),
|
StrafesNETMap(strafesnet_snf::map::Error),
|
||||||
#[cfg(feature="snf")]
|
|
||||||
StrafesNETBot(strafesnet_snf::bot::Error),
|
|
||||||
Io(std::io::Error),
|
Io(std::io::Error),
|
||||||
UnknownFileFormat,
|
UnknownFileFormat,
|
||||||
}
|
}
|
||||||
@ -26,52 +20,37 @@ impl std::fmt::Display for ReadError{
|
|||||||
}
|
}
|
||||||
impl std::error::Error for ReadError{}
|
impl std::error::Error for ReadError{}
|
||||||
|
|
||||||
pub enum ReadFormat{
|
pub enum DataStructure{
|
||||||
#[cfg(feature="roblox")]
|
#[cfg(feature="roblox")]
|
||||||
Roblox(strafesnet_rbx_loader::Model),
|
Roblox(strafesnet_rbx_loader::Model),
|
||||||
#[cfg(feature="source")]
|
#[cfg(feature="source")]
|
||||||
Source(strafesnet_bsp_loader::Bsp),
|
Source(strafesnet_bsp_loader::Bsp),
|
||||||
#[cfg(feature="snf")]
|
#[cfg(feature="snf")]
|
||||||
SNFM(strafesnet_common::map::CompleteMap),
|
StrafesNET(strafesnet_common::map::CompleteMap),
|
||||||
#[cfg(feature="snf")]
|
|
||||||
SNFB(strafesnet_snf::bot::Segment),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn read<R:Read+std::io::Seek>(input:R)->Result<ReadFormat,ReadError>{
|
pub fn read<R:Read+std::io::Seek>(input:R)->Result<DataStructure,ReadError>{
|
||||||
let mut buf=std::io::BufReader::new(input);
|
let mut buf=std::io::BufReader::new(input);
|
||||||
let peek=std::io::BufRead::fill_buf(&mut buf).map_err(ReadError::Io)?[0..4].to_owned();
|
let peek=std::io::BufRead::fill_buf(&mut buf).map_err(ReadError::Io)?;
|
||||||
// reading the entire file is way faster than round tripping the disk constantly
|
match &peek[0..4]{
|
||||||
let mut entire_file=Vec::new();
|
|
||||||
buf.read_to_end(&mut entire_file).map_err(ReadError::Io)?;
|
|
||||||
let cursor=std::io::Cursor::new(entire_file);
|
|
||||||
match peek.as_slice(){
|
|
||||||
#[cfg(feature="roblox")]
|
#[cfg(feature="roblox")]
|
||||||
b"<rob"=>Ok(ReadFormat::Roblox(strafesnet_rbx_loader::read(cursor).map_err(ReadError::Roblox)?)),
|
b"<rob"=>Ok(DataStructure::Roblox(strafesnet_rbx_loader::read(buf).map_err(ReadError::Roblox)?)),
|
||||||
#[cfg(feature="source")]
|
#[cfg(feature="source")]
|
||||||
b"VBSP"=>Ok(ReadFormat::Source(strafesnet_bsp_loader::read(cursor).map_err(ReadError::Source)?)),
|
b"VBSP"=>Ok(DataStructure::Source(strafesnet_bsp_loader::read(buf).map_err(ReadError::Source)?)),
|
||||||
#[cfg(feature="snf")]
|
#[cfg(feature="snf")]
|
||||||
b"SNFM"=>Ok(ReadFormat::SNFM(
|
b"SNFM"=>Ok(DataStructure::StrafesNET(
|
||||||
strafesnet_snf::read_map(cursor).map_err(ReadError::StrafesNET)?
|
strafesnet_snf::read_map(buf).map_err(ReadError::StrafesNET)?
|
||||||
.into_complete_map().map_err(ReadError::StrafesNETMap)?
|
.into_complete_map().map_err(ReadError::StrafesNETMap)?
|
||||||
)),
|
)),
|
||||||
#[cfg(feature="snf")]
|
|
||||||
b"SNFB"=>Ok(ReadFormat::SNFB(
|
|
||||||
strafesnet_snf::read_bot(cursor).map_err(ReadError::StrafesNET)?
|
|
||||||
.read_all().map_err(ReadError::StrafesNETBot)?
|
|
||||||
)),
|
|
||||||
_=>Err(ReadError::UnknownFileFormat),
|
_=>Err(ReadError::UnknownFileFormat),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum LoadError{
|
pub enum LoadError{
|
||||||
ReadError(ReadError),
|
ReadError(ReadError),
|
||||||
File(std::io::Error),
|
File(std::io::Error),
|
||||||
#[cfg(feature="roblox")]
|
Io(std::io::Error),
|
||||||
LoadRoblox(strafesnet_rbx_loader::LoadError),
|
|
||||||
#[cfg(feature="source")]
|
|
||||||
LoadSource(strafesnet_bsp_loader::LoadError),
|
|
||||||
}
|
}
|
||||||
impl std::fmt::Display for LoadError{
|
impl std::fmt::Display for LoadError{
|
||||||
fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{
|
fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{
|
||||||
@ -80,32 +59,86 @@ impl std::fmt::Display for LoadError{
|
|||||||
}
|
}
|
||||||
impl std::error::Error for LoadError{}
|
impl std::error::Error for LoadError{}
|
||||||
|
|
||||||
pub enum LoadFormat{
|
pub fn load<P:AsRef<std::path::Path>>(path:P)->Result<strafesnet_common::map::CompleteMap,LoadError>{
|
||||||
#[cfg(any(feature="snf",feature="roblox",feature="source"))]
|
|
||||||
Map(strafesnet_common::map::CompleteMap),
|
|
||||||
#[cfg(feature="snf")]
|
|
||||||
Bot(strafesnet_snf::bot::Segment),
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn load<P:AsRef<std::path::Path>>(path:P)->Result<LoadFormat,LoadError>{
|
|
||||||
//blocking because it's simpler...
|
//blocking because it's simpler...
|
||||||
let file=std::fs::File::open(path).map_err(LoadError::File)?;
|
let file=std::fs::File::open(path).map_err(LoadError::File)?;
|
||||||
match read(file).map_err(LoadError::ReadError)?{
|
match read(file).map_err(LoadError::ReadError)?{
|
||||||
#[cfg(feature="snf")]
|
#[cfg(feature="snf")]
|
||||||
ReadFormat::SNFB(bot)=>Ok(LoadFormat::Bot(bot)),
|
DataStructure::StrafesNET(map)=>Ok(map),
|
||||||
#[cfg(feature="snf")]
|
|
||||||
ReadFormat::SNFM(map)=>Ok(LoadFormat::Map(map)),
|
|
||||||
#[cfg(feature="roblox")]
|
#[cfg(feature="roblox")]
|
||||||
ReadFormat::Roblox(model)=>{
|
DataStructure::Roblox(model)=>{
|
||||||
let mut place=model.into_place();
|
let mut place=model.into_place();
|
||||||
place.run_scripts();
|
place.run_scripts();
|
||||||
Ok(LoadFormat::Map(
|
|
||||||
place.to_snf(LoadFailureMode::DefaultToNone).map_err(LoadError::LoadRoblox)?
|
let mut loader=strafesnet_deferred_loader::roblox_legacy();
|
||||||
))
|
|
||||||
|
let (texture_loader,mesh_loader)=loader.get_inner_mut();
|
||||||
|
|
||||||
|
let map_step1=strafesnet_rbx_loader::convert(
|
||||||
|
&place,
|
||||||
|
|name|texture_loader.acquire_render_config_id(name),
|
||||||
|
|name|mesh_loader.acquire_mesh_id(name),
|
||||||
|
);
|
||||||
|
|
||||||
|
let meshpart_meshes=mesh_loader.load_meshes().map_err(LoadError::Io)?;
|
||||||
|
|
||||||
|
let map_step2=map_step1.add_meshpart_meshes_and_calculate_attributes(
|
||||||
|
meshpart_meshes.into_iter().map(|(mesh_id,loader_model)|
|
||||||
|
(mesh_id,strafesnet_rbx_loader::data::RobloxMeshBytes::new(loader_model.get()))
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
let (textures,render_configs)=loader.into_render_configs().map_err(LoadError::Io)?.consume();
|
||||||
|
|
||||||
|
let map=map_step2.add_render_configs_and_textures(
|
||||||
|
render_configs.into_iter(),
|
||||||
|
textures.into_iter().map(|(texture_id,texture)|
|
||||||
|
(texture_id,match texture{
|
||||||
|
strafesnet_deferred_loader::texture::Texture::ImageDDS(data)=>data,
|
||||||
|
})
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(map)
|
||||||
},
|
},
|
||||||
#[cfg(feature="source")]
|
#[cfg(feature="source")]
|
||||||
ReadFormat::Source(bsp)=>Ok(LoadFormat::Map(
|
DataStructure::Source(bsp)=>{
|
||||||
bsp.to_snf(LoadFailureMode::DefaultToNone,&[]).map_err(LoadError::LoadSource)?
|
let mut loader=strafesnet_deferred_loader::source_legacy();
|
||||||
)),
|
|
||||||
|
let (texture_loader,mesh_loader)=loader.get_inner_mut();
|
||||||
|
|
||||||
|
let map_step1=strafesnet_bsp_loader::convert(
|
||||||
|
&bsp,
|
||||||
|
|name|texture_loader.acquire_render_config_id(name),
|
||||||
|
|name|mesh_loader.acquire_mesh_id(name),
|
||||||
|
);
|
||||||
|
|
||||||
|
let prop_meshes=mesh_loader.load_meshes(bsp.as_ref());
|
||||||
|
|
||||||
|
let map_step2=map_step1.add_prop_meshes(
|
||||||
|
//the type conflagulator 9000
|
||||||
|
prop_meshes.into_iter().map(|(mesh_id,loader_model)|
|
||||||
|
(mesh_id,strafesnet_bsp_loader::data::ModelData{
|
||||||
|
mdl:strafesnet_bsp_loader::data::MdlData::new(loader_model.mdl.get()),
|
||||||
|
vtx:strafesnet_bsp_loader::data::VtxData::new(loader_model.vtx.get()),
|
||||||
|
vvd:strafesnet_bsp_loader::data::VvdData::new(loader_model.vvd.get()),
|
||||||
|
})
|
||||||
|
),
|
||||||
|
|name|texture_loader.acquire_render_config_id(name),
|
||||||
|
);
|
||||||
|
|
||||||
|
let (textures,render_configs)=loader.into_render_configs().map_err(LoadError::Io)?.consume();
|
||||||
|
|
||||||
|
let map=map_step2.add_render_configs_and_textures(
|
||||||
|
render_configs.into_iter(),
|
||||||
|
textures.into_iter().map(|(texture_id,texture)|
|
||||||
|
(texture_id,match texture{
|
||||||
|
strafesnet_deferred_loader::texture::Texture::ImageDDS(data)=>data,
|
||||||
|
})
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(map)
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,15 +1,10 @@
|
|||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
use std::collections::{HashSet,HashMap};
|
use std::collections::{HashSet,HashMap};
|
||||||
use strafesnet_common::map;
|
use strafesnet_common::map;
|
||||||
use strafesnet_settings::settings;
|
use strafesnet_common::integer;
|
||||||
use strafesnet_session::session;
|
|
||||||
use strafesnet_common::model::{self, ColorId, NormalId, PolygonIter, PositionId, RenderConfigId, TextureCoordinateId, VertexId};
|
use strafesnet_common::model::{self, ColorId, NormalId, PolygonIter, PositionId, RenderConfigId, TextureCoordinateId, VertexId};
|
||||||
use wgpu::{util::DeviceExt,AstcBlock,AstcChannel};
|
use wgpu::{util::DeviceExt,AstcBlock,AstcChannel};
|
||||||
use crate::model::{self as model_graphics,IndexedGraphicsMeshOwnedRenderConfig,IndexedGraphicsMeshOwnedRenderConfigId,GraphicsMeshOwnedRenderConfig,GraphicsModelColor4,GraphicsModelOwned,GraphicsVertex};
|
use crate::model_graphics::{self,IndexedGraphicsMeshOwnedRenderConfig,IndexedGraphicsMeshOwnedRenderConfigId,GraphicsMeshOwnedRenderConfig,GraphicsModelColor4,GraphicsModelOwned,GraphicsVertex};
|
||||||
|
|
||||||
pub fn required_limits()->wgpu::Limits{
|
|
||||||
wgpu::Limits::default()
|
|
||||||
}
|
|
||||||
|
|
||||||
struct Indices{
|
struct Indices{
|
||||||
count:u32,
|
count:u32,
|
||||||
@ -103,6 +98,12 @@ impl std::default::Default for GraphicsCamera{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct FrameState{
|
||||||
|
pub body:crate::physics::Body,
|
||||||
|
pub camera:crate::physics::PhysicsCamera,
|
||||||
|
pub time:integer::Time,
|
||||||
|
}
|
||||||
|
|
||||||
pub struct GraphicsState{
|
pub struct GraphicsState{
|
||||||
pipelines:GraphicsPipelines,
|
pipelines:GraphicsPipelines,
|
||||||
bind_groups:GraphicsBindGroups,
|
bind_groups:GraphicsBindGroups,
|
||||||
@ -142,7 +143,7 @@ impl GraphicsState{
|
|||||||
pub fn clear(&mut self){
|
pub fn clear(&mut self){
|
||||||
self.models.clear();
|
self.models.clear();
|
||||||
}
|
}
|
||||||
pub fn load_user_settings(&mut self,user_settings:&settings::UserSettings){
|
pub fn load_user_settings(&mut self,user_settings:&crate::settings::UserSettings){
|
||||||
self.camera.fov=user_settings.calculate_fov(1.0,&self.camera.screen_size).as_vec2();
|
self.camera.fov=user_settings.calculate_fov(1.0,&self.camera.screen_size).as_vec2();
|
||||||
}
|
}
|
||||||
pub fn generate_models(&mut self,device:&wgpu::Device,queue:&wgpu::Queue,map:&map::CompleteMap){
|
pub fn generate_models(&mut self,device:&wgpu::Device,queue:&wgpu::Queue,map:&map::CompleteMap){
|
||||||
@ -454,7 +455,7 @@ impl GraphicsState{
|
|||||||
//.into_iter() the modeldata vec so entities can be /moved/ to models.entities
|
//.into_iter() the modeldata vec so entities can be /moved/ to models.entities
|
||||||
let mut model_count=0;
|
let mut model_count=0;
|
||||||
let mut instance_count=0;
|
let mut instance_count=0;
|
||||||
let uniform_buffer_binding_size=required_limits().max_uniform_buffer_binding_size as usize;
|
let uniform_buffer_binding_size=crate::setup::required_limits().max_uniform_buffer_binding_size as usize;
|
||||||
let chunk_size=uniform_buffer_binding_size/MODEL_BUFFER_SIZE_BYTES;
|
let chunk_size=uniform_buffer_binding_size/MODEL_BUFFER_SIZE_BYTES;
|
||||||
self.models.reserve(models.len());
|
self.models.reserve(models.len());
|
||||||
for model in models.into_iter(){
|
for model in models.into_iter(){
|
||||||
@ -614,7 +615,7 @@ impl GraphicsState{
|
|||||||
// Create the render pipeline
|
// Create the render pipeline
|
||||||
let shader=device.create_shader_module(wgpu::ShaderModuleDescriptor{
|
let shader=device.create_shader_module(wgpu::ShaderModuleDescriptor{
|
||||||
label:None,
|
label:None,
|
||||||
source:wgpu::ShaderSource::Wgsl(Cow::Borrowed(include_str!("../../../strafe-client/src/shader.wgsl"))),
|
source:wgpu::ShaderSource::Wgsl(Cow::Borrowed(include_str!("shader.wgsl"))),
|
||||||
});
|
});
|
||||||
|
|
||||||
//load textures
|
//load textures
|
||||||
@ -642,10 +643,10 @@ impl GraphicsState{
|
|||||||
wgpu::TextureFormat::Astc{
|
wgpu::TextureFormat::Astc{
|
||||||
block:AstcBlock::B4x4,
|
block:AstcBlock::B4x4,
|
||||||
channel:AstcChannel::UnormSrgb,
|
channel:AstcChannel::UnormSrgb,
|
||||||
}=>&include_bytes!("../../../strafe-client/images/astc.dds")[..],
|
}=>&include_bytes!("../images/astc.dds")[..],
|
||||||
wgpu::TextureFormat::Etc2Rgb8UnormSrgb=>&include_bytes!("../../../strafe-client/images/etc2.dds")[..],
|
wgpu::TextureFormat::Etc2Rgb8UnormSrgb=>&include_bytes!("../images/etc2.dds")[..],
|
||||||
wgpu::TextureFormat::Bc1RgbaUnormSrgb=>&include_bytes!("../../../strafe-client/images/bc1.dds")[..],
|
wgpu::TextureFormat::Bc1RgbaUnormSrgb=>&include_bytes!("../images/bc1.dds")[..],
|
||||||
wgpu::TextureFormat::Bgra8UnormSrgb=>&include_bytes!("../../../strafe-client/images/bgra.dds")[..],
|
wgpu::TextureFormat::Bgra8UnormSrgb=>&include_bytes!("../images/bgra.dds")[..],
|
||||||
_=>unreachable!(),
|
_=>unreachable!(),
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -688,7 +689,7 @@ impl GraphicsState{
|
|||||||
|
|
||||||
//squid
|
//squid
|
||||||
let squid_texture_view={
|
let squid_texture_view={
|
||||||
let bytes=include_bytes!("../../../strafe-client/images/squid.dds");
|
let bytes=include_bytes!("../images/squid.dds");
|
||||||
|
|
||||||
let image=ddsfile::Dds::read(&mut std::io::Cursor::new(bytes)).unwrap();
|
let image=ddsfile::Dds::read(&mut std::io::Cursor::new(bytes)).unwrap();
|
||||||
|
|
||||||
@ -870,7 +871,7 @@ impl GraphicsState{
|
|||||||
&mut self,
|
&mut self,
|
||||||
device:&wgpu::Device,
|
device:&wgpu::Device,
|
||||||
config:&wgpu::SurfaceConfiguration,
|
config:&wgpu::SurfaceConfiguration,
|
||||||
user_settings:&settings::UserSettings,
|
user_settings:&crate::settings::UserSettings,
|
||||||
){
|
){
|
||||||
self.depth_view=Self::create_depth_texture(config,device);
|
self.depth_view=Self::create_depth_texture(config,device);
|
||||||
self.camera.screen_size=glam::uvec2(config.width,config.height);
|
self.camera.screen_size=glam::uvec2(config.width,config.height);
|
||||||
@ -881,7 +882,7 @@ impl GraphicsState{
|
|||||||
view:&wgpu::TextureView,
|
view:&wgpu::TextureView,
|
||||||
device:&wgpu::Device,
|
device:&wgpu::Device,
|
||||||
queue:&wgpu::Queue,
|
queue:&wgpu::Queue,
|
||||||
frame_state:session::FrameState,
|
frame_state:FrameState,
|
||||||
){
|
){
|
||||||
//TODO:use scheduled frame times to create beautiful smoothing simulation physics extrapolation assuming no input
|
//TODO:use scheduled frame times to create beautiful smoothing simulation physics extrapolation assuming no input
|
||||||
|
|
@ -1,11 +1,7 @@
|
|||||||
use strafesnet_graphics::graphics;
|
|
||||||
use strafesnet_session::session;
|
|
||||||
use strafesnet_settings::settings;
|
|
||||||
|
|
||||||
pub enum Instruction{
|
pub enum Instruction{
|
||||||
Render(session::FrameState),
|
Render(crate::graphics::FrameState),
|
||||||
//UpdateModel(graphics::GraphicsModelUpdate),
|
//UpdateModel(crate::graphics::GraphicsModelUpdate),
|
||||||
Resize(winit::dpi::PhysicalSize<u32>,settings::UserSettings),
|
Resize(winit::dpi::PhysicalSize<u32>,crate::settings::UserSettings),
|
||||||
ChangeMap(strafesnet_common::map::CompleteMap),
|
ChangeMap(strafesnet_common::map::CompleteMap),
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -18,13 +14,14 @@ WorkerDescription{
|
|||||||
*/
|
*/
|
||||||
//up to three frames in flight, dropping new frame requests when all three are busy, and dropping output frames when one renders out of order
|
//up to three frames in flight, dropping new frame requests when all three are busy, and dropping output frames when one renders out of order
|
||||||
|
|
||||||
pub fn new(
|
pub fn new<'a>(
|
||||||
mut graphics:graphics::GraphicsState,
|
mut graphics:crate::graphics::GraphicsState,
|
||||||
mut config:wgpu::SurfaceConfiguration,
|
mut config:wgpu::SurfaceConfiguration,
|
||||||
surface:wgpu::Surface,
|
surface:wgpu::Surface<'a>,
|
||||||
device:wgpu::Device,
|
device:wgpu::Device,
|
||||||
queue:wgpu::Queue,
|
queue:wgpu::Queue,
|
||||||
)->crate::compat_worker::INWorker<'_,Instruction>{
|
)->crate::compat_worker::INWorker<'a,Instruction>{
|
||||||
|
let mut resize=None;
|
||||||
crate::compat_worker::INWorker::new(move |ins:Instruction|{
|
crate::compat_worker::INWorker::new(move |ins:Instruction|{
|
||||||
match ins{
|
match ins{
|
||||||
Instruction::ChangeMap(map)=>{
|
Instruction::ChangeMap(map)=>{
|
||||||
@ -32,6 +29,10 @@ pub fn new(
|
|||||||
graphics.generate_models(&device,&queue,&map);
|
graphics.generate_models(&device,&queue,&map);
|
||||||
},
|
},
|
||||||
Instruction::Resize(size,user_settings)=>{
|
Instruction::Resize(size,user_settings)=>{
|
||||||
|
resize=Some((size,user_settings));
|
||||||
|
}
|
||||||
|
Instruction::Render(frame_state)=>{
|
||||||
|
if let Some((size,user_settings))=resize.take(){
|
||||||
println!("Resizing to {:?}",size);
|
println!("Resizing to {:?}",size);
|
||||||
let t0=std::time::Instant::now();
|
let t0=std::time::Instant::now();
|
||||||
config.width=size.width.max(1);
|
config.width=size.width.max(1);
|
||||||
@ -40,7 +41,6 @@ pub fn new(
|
|||||||
graphics.resize(&device,&config,&user_settings);
|
graphics.resize(&device,&config,&user_settings);
|
||||||
println!("Resize took {:?}",t0.elapsed());
|
println!("Resize took {:?}",t0.elapsed());
|
||||||
}
|
}
|
||||||
Instruction::Render(frame_state)=>{
|
|
||||||
//this has to go deeper somehow
|
//this has to go deeper somehow
|
||||||
let frame=match surface.get_current_texture(){
|
let frame=match surface.get_current_texture(){
|
||||||
Ok(frame)=>frame,
|
Ok(frame)=>frame,
|
||||||
|
@ -1,14 +1,17 @@
|
|||||||
mod app;
|
|
||||||
mod file;
|
mod file;
|
||||||
mod setup;
|
mod setup;
|
||||||
mod window;
|
mod window;
|
||||||
mod worker;
|
mod worker;
|
||||||
|
mod physics;
|
||||||
|
mod graphics;
|
||||||
|
mod settings;
|
||||||
|
mod face_crawler;
|
||||||
mod compat_worker;
|
mod compat_worker;
|
||||||
|
mod model_physics;
|
||||||
|
mod model_graphics;
|
||||||
mod physics_worker;
|
mod physics_worker;
|
||||||
mod graphics_worker;
|
mod graphics_worker;
|
||||||
|
|
||||||
const TITLE:&'static str=concat!("Strafe Client v",env!("CARGO_PKG_VERSION"));
|
|
||||||
|
|
||||||
fn main(){
|
fn main(){
|
||||||
setup::setup_and_start(TITLE);
|
setup::setup_and_start(format!("Strafe Client v{}",env!("CARGO_PKG_VERSION")));
|
||||||
}
|
}
|
||||||
|
@ -1,12 +1,8 @@
|
|||||||
use std::borrow::{Borrow,Cow};
|
use std::borrow::{Borrow,Cow};
|
||||||
use std::collections::{HashSet,HashMap};
|
use std::collections::{HashSet,HashMap};
|
||||||
use core::ops::Range;
|
|
||||||
use strafesnet_common::integer::vec3::Vector3;
|
use strafesnet_common::integer::vec3::Vector3;
|
||||||
use strafesnet_common::model::{self,MeshId,PolygonIter};
|
use strafesnet_common::model::{self,MeshId,PolygonIter};
|
||||||
use strafesnet_common::integer::{self,vec3,Fixed,Planar64,Planar64Vec3,Ratio};
|
use strafesnet_common::integer::{self,vec3,Fixed,Planar64,Planar64Vec3,Ratio};
|
||||||
use strafesnet_common::physics::Time;
|
|
||||||
|
|
||||||
type Body=crate::body::Body<strafesnet_common::physics::TimeInner>;
|
|
||||||
|
|
||||||
pub trait UndirectedEdge{
|
pub trait UndirectedEdge{
|
||||||
type DirectedEdge:Copy+DirectedEdge;
|
type DirectedEdge:Copy+DirectedEdge;
|
||||||
@ -55,10 +51,10 @@ impl DirectedEdge for SubmeshDirectedEdgeId{
|
|||||||
|
|
||||||
//Vertex <-> Edge <-> Face -> Collide
|
//Vertex <-> Edge <-> Face -> Collide
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum FEV<M:MeshQuery>{
|
pub enum FEV<F,E:DirectedEdge,V>{
|
||||||
Face(M::Face),
|
Face(F),
|
||||||
Edge(<M::Edge as DirectedEdge>::UndirectedEdge),
|
Edge(E::UndirectedEdge),
|
||||||
Vert(M::Vert),
|
Vert(V),
|
||||||
}
|
}
|
||||||
|
|
||||||
//use Unit32 #[repr(C)] for map files
|
//use Unit32 #[repr(C)] for map files
|
||||||
@ -68,28 +64,25 @@ struct Face{
|
|||||||
dot:Planar64,
|
dot:Planar64,
|
||||||
}
|
}
|
||||||
struct Vert(Planar64Vec3);
|
struct Vert(Planar64Vec3);
|
||||||
pub trait MeshQuery{
|
pub trait MeshQuery<FACE:Clone,EDGE:Clone+DirectedEdge,VERT:Clone>{
|
||||||
type Face:Clone;
|
|
||||||
type Edge:Clone+DirectedEdge;
|
|
||||||
type Vert:Clone;
|
|
||||||
// Vertex must be Planar64Vec3 because it represents an actual position
|
// Vertex must be Planar64Vec3 because it represents an actual position
|
||||||
type Normal;
|
type Normal;
|
||||||
type Offset;
|
type Offset;
|
||||||
fn edge_n(&self,edge_id:<Self::Edge as DirectedEdge>::UndirectedEdge)->Planar64Vec3{
|
fn edge_n(&self,edge_id:EDGE::UndirectedEdge)->Planar64Vec3{
|
||||||
let verts=self.edge_verts(edge_id);
|
let verts=self.edge_verts(edge_id);
|
||||||
self.vert(verts[1].clone())-self.vert(verts[0].clone())
|
self.vert(verts[1].clone())-self.vert(verts[0].clone())
|
||||||
}
|
}
|
||||||
fn directed_edge_n(&self,directed_edge_id:Self::Edge)->Planar64Vec3{
|
fn directed_edge_n(&self,directed_edge_id:EDGE)->Planar64Vec3{
|
||||||
let verts=self.edge_verts(directed_edge_id.as_undirected());
|
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)
|
(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 vert(&self,vert_id:VERT)->Planar64Vec3;
|
||||||
fn face_nd(&self,face_id:Self::Face)->(Self::Normal,Self::Offset);
|
fn face_nd(&self,face_id:FACE)->(Self::Normal,Self::Offset);
|
||||||
fn face_edges(&self,face_id:Self::Face)->Cow<[Self::Edge]>;
|
fn face_edges(&self,face_id:FACE)->Cow<Vec<EDGE>>;
|
||||||
fn edge_faces(&self,edge_id:<Self::Edge as DirectedEdge>::UndirectedEdge)->Cow<[Self::Face;2]>;
|
fn edge_faces(&self,edge_id:EDGE::UndirectedEdge)->Cow<[FACE;2]>;
|
||||||
fn edge_verts(&self,edge_id:<Self::Edge as DirectedEdge>::UndirectedEdge)->Cow<[Self::Vert;2]>;
|
fn edge_verts(&self,edge_id:EDGE::UndirectedEdge)->Cow<[VERT;2]>;
|
||||||
fn vert_edges(&self,vert_id:Self::Vert)->Cow<[Self::Edge]>;
|
fn vert_edges(&self,vert_id:VERT)->Cow<Vec<EDGE>>;
|
||||||
fn vert_faces(&self,vert_id:Self::Vert)->Cow<[Self::Face]>;
|
fn vert_faces(&self,vert_id:VERT)->Cow<Vec<FACE>>;
|
||||||
}
|
}
|
||||||
struct FaceRefs{
|
struct FaceRefs{
|
||||||
edges:Vec<SubmeshDirectedEdgeId>,
|
edges:Vec<SubmeshDirectedEdgeId>,
|
||||||
@ -280,12 +273,15 @@ struct EdgePool{
|
|||||||
}
|
}
|
||||||
impl EdgePool{
|
impl EdgePool{
|
||||||
fn push(&mut self,edge_ref_verts:EdgeRefVerts)->(&mut EdgeRefFaces,SubmeshEdgeId){
|
fn push(&mut self,edge_ref_verts:EdgeRefVerts)->(&mut EdgeRefFaces,SubmeshEdgeId){
|
||||||
let edge_id=*self.edge_id_from_guy.entry(edge_ref_verts.clone()).or_insert_with(||{
|
let edge_id=if let Some(&edge_id)=self.edge_id_from_guy.get(&edge_ref_verts){
|
||||||
let edge_id=SubmeshEdgeId::new(self.edge_guys.len() as u32);
|
|
||||||
self.edge_guys.push((edge_ref_verts,EdgeRefFaces::new()));
|
|
||||||
edge_id
|
edge_id
|
||||||
});
|
}else{
|
||||||
(&mut self.edge_guys[edge_id.get() as usize].1,edge_id)
|
let edge_id=SubmeshEdgeId::new(self.edge_guys.len() as u32);
|
||||||
|
self.edge_guys.push((edge_ref_verts.clone(),EdgeRefFaces::new()));
|
||||||
|
self.edge_id_from_guy.insert(edge_ref_verts,edge_id);
|
||||||
|
edge_id
|
||||||
|
};
|
||||||
|
(&mut unsafe{self.edge_guys.get_unchecked_mut(edge_id.get() as usize)}.1,edge_id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -320,11 +316,16 @@ impl TryFrom<&model::Mesh> for PhysicsMesh{
|
|||||||
let mut submesh_verts=Vec::new();
|
let mut submesh_verts=Vec::new();
|
||||||
let mut submesh_vert_id_from_mesh_vert_id=HashMap::<MeshVertId,SubmeshVertId>::new();
|
let mut submesh_vert_id_from_mesh_vert_id=HashMap::<MeshVertId,SubmeshVertId>::new();
|
||||||
//lazy closure
|
//lazy closure
|
||||||
let mut get_submesh_vert_id=|vert_id:MeshVertId|*submesh_vert_id_from_mesh_vert_id.entry(vert_id).or_insert_with(||{
|
let mut get_submesh_vert_id=|vert_id:MeshVertId|{
|
||||||
|
if let Some(&submesh_vert_id)=submesh_vert_id_from_mesh_vert_id.get(&vert_id){
|
||||||
|
submesh_vert_id
|
||||||
|
}else{
|
||||||
let submesh_vert_id=SubmeshVertId::new(submesh_verts.len() as u32);
|
let submesh_vert_id=SubmeshVertId::new(submesh_verts.len() as u32);
|
||||||
submesh_verts.push(vert_id);
|
submesh_verts.push(vert_id);
|
||||||
|
submesh_vert_id_from_mesh_vert_id.insert(vert_id,submesh_vert_id);
|
||||||
submesh_vert_id
|
submesh_vert_id
|
||||||
});
|
}
|
||||||
|
};
|
||||||
let mut edge_pool=EdgePool::default();
|
let mut edge_pool=EdgePool::default();
|
||||||
let mut vert_ref_guys=vec![VertRefGuy::default();mesh.unique_pos.len()];
|
let mut vert_ref_guys=vec![VertRefGuy::default();mesh.unique_pos.len()];
|
||||||
let mut face_ref_guys=Vec::new();
|
let mut face_ref_guys=Vec::new();
|
||||||
@ -357,10 +358,10 @@ impl TryFrom<&model::Mesh> for PhysicsMesh{
|
|||||||
edge_ref_faces.push(!is_sorted as usize,submesh_face_id);
|
edge_ref_faces.push(!is_sorted as usize,submesh_face_id);
|
||||||
//index edges & face into vertices
|
//index edges & face into vertices
|
||||||
{
|
{
|
||||||
let vert_ref_guy=&mut vert_ref_guys[submesh_vert0_id.get() as usize];
|
let vert_ref_guy=unsafe{vert_ref_guys.get_unchecked_mut(submesh_vert0_id.get() as usize)};
|
||||||
vert_ref_guy.edges.insert(edge_id.as_directed(is_sorted));
|
vert_ref_guy.edges.insert(edge_id.as_directed(is_sorted));
|
||||||
vert_ref_guy.faces.insert(submesh_face_id);
|
vert_ref_guy.faces.insert(submesh_face_id);
|
||||||
vert_ref_guys[submesh_vert1_id.get() as usize].edges.insert(edge_id.as_directed(!is_sorted));
|
unsafe{vert_ref_guys.get_unchecked_mut(submesh_vert1_id.get() as usize)}.edges.insert(edge_id.as_directed(!is_sorted));
|
||||||
}
|
}
|
||||||
//return directed_edge_id
|
//return directed_edge_id
|
||||||
edge_id.as_directed(is_sorted)
|
edge_id.as_directed(is_sorted)
|
||||||
@ -420,10 +421,7 @@ pub struct PhysicsMeshView<'a>{
|
|||||||
data:&'a PhysicsMeshData,
|
data:&'a PhysicsMeshData,
|
||||||
topology:&'a PhysicsMeshTopology,
|
topology:&'a PhysicsMeshTopology,
|
||||||
}
|
}
|
||||||
impl MeshQuery for PhysicsMeshView<'_>{
|
impl MeshQuery<SubmeshFaceId,SubmeshDirectedEdgeId,SubmeshVertId> for PhysicsMeshView<'_>{
|
||||||
type Face=SubmeshFaceId;
|
|
||||||
type Edge=SubmeshDirectedEdgeId;
|
|
||||||
type Vert=SubmeshVertId;
|
|
||||||
type Normal=Planar64Vec3;
|
type Normal=Planar64Vec3;
|
||||||
type Offset=Planar64;
|
type Offset=Planar64;
|
||||||
fn face_nd(&self,face_id:SubmeshFaceId)->(Planar64Vec3,Planar64){
|
fn face_nd(&self,face_id:SubmeshFaceId)->(Planar64Vec3,Planar64){
|
||||||
@ -435,7 +433,7 @@ impl MeshQuery for PhysicsMeshView<'_>{
|
|||||||
let vert_idx=self.topology.verts[vert_id.get() as usize].get() as usize;
|
let vert_idx=self.topology.verts[vert_id.get() as usize].get() as usize;
|
||||||
self.data.verts[vert_idx].0
|
self.data.verts[vert_idx].0
|
||||||
}
|
}
|
||||||
fn face_edges(&self,face_id:SubmeshFaceId)->Cow<[SubmeshDirectedEdgeId]>{
|
fn face_edges(&self,face_id:SubmeshFaceId)->Cow<Vec<SubmeshDirectedEdgeId>>{
|
||||||
Cow::Borrowed(&self.topology.face_topology[face_id.get() as usize].edges)
|
Cow::Borrowed(&self.topology.face_topology[face_id.get() as usize].edges)
|
||||||
}
|
}
|
||||||
fn edge_faces(&self,edge_id:SubmeshEdgeId)->Cow<[SubmeshFaceId;2]>{
|
fn edge_faces(&self,edge_id:SubmeshEdgeId)->Cow<[SubmeshFaceId;2]>{
|
||||||
@ -444,10 +442,10 @@ impl MeshQuery for PhysicsMeshView<'_>{
|
|||||||
fn edge_verts(&self,edge_id:SubmeshEdgeId)->Cow<[SubmeshVertId;2]>{
|
fn edge_verts(&self,edge_id:SubmeshEdgeId)->Cow<[SubmeshVertId;2]>{
|
||||||
Cow::Borrowed(&self.topology.edge_topology[edge_id.get() as usize].verts)
|
Cow::Borrowed(&self.topology.edge_topology[edge_id.get() as usize].verts)
|
||||||
}
|
}
|
||||||
fn vert_edges(&self,vert_id:SubmeshVertId)->Cow<[SubmeshDirectedEdgeId]>{
|
fn vert_edges(&self,vert_id:SubmeshVertId)->Cow<Vec<SubmeshDirectedEdgeId>>{
|
||||||
Cow::Borrowed(&self.topology.vert_topology[vert_id.get() as usize].edges)
|
Cow::Borrowed(&self.topology.vert_topology[vert_id.get() as usize].edges)
|
||||||
}
|
}
|
||||||
fn vert_faces(&self,vert_id:SubmeshVertId)->Cow<[SubmeshFaceId]>{
|
fn vert_faces(&self,vert_id:SubmeshVertId)->Cow<Vec<SubmeshFaceId>>{
|
||||||
Cow::Borrowed(&self.topology.vert_topology[vert_id.get() as usize].faces)
|
Cow::Borrowed(&self.topology.vert_topology[vert_id.get() as usize].faces)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -497,10 +495,7 @@ impl TransformedMesh<'_>{
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl MeshQuery for TransformedMesh<'_>{
|
impl MeshQuery<SubmeshFaceId,SubmeshDirectedEdgeId,SubmeshVertId> for TransformedMesh<'_>{
|
||||||
type Face=SubmeshFaceId;
|
|
||||||
type Edge=SubmeshDirectedEdgeId;
|
|
||||||
type Vert=SubmeshVertId;
|
|
||||||
type Normal=Vector3<Fixed<3,96>>;
|
type Normal=Vector3<Fixed<3,96>>;
|
||||||
type Offset=Fixed<4,128>;
|
type Offset=Fixed<4,128>;
|
||||||
fn face_nd(&self,face_id:SubmeshFaceId)->(Self::Normal,Self::Offset){
|
fn face_nd(&self,face_id:SubmeshFaceId)->(Self::Normal,Self::Offset){
|
||||||
@ -513,7 +508,7 @@ impl MeshQuery for TransformedMesh<'_>{
|
|||||||
self.transform.vertex.transform_point3(self.view.vert(vert_id)).fix_1()
|
self.transform.vertex.transform_point3(self.view.vert(vert_id)).fix_1()
|
||||||
}
|
}
|
||||||
#[inline]
|
#[inline]
|
||||||
fn face_edges(&self,face_id:SubmeshFaceId)->Cow<[SubmeshDirectedEdgeId]>{
|
fn face_edges(&self,face_id:SubmeshFaceId)->Cow<Vec<SubmeshDirectedEdgeId>>{
|
||||||
self.view.face_edges(face_id)
|
self.view.face_edges(face_id)
|
||||||
}
|
}
|
||||||
#[inline]
|
#[inline]
|
||||||
@ -525,11 +520,11 @@ impl MeshQuery for TransformedMesh<'_>{
|
|||||||
self.view.edge_verts(edge_id)
|
self.view.edge_verts(edge_id)
|
||||||
}
|
}
|
||||||
#[inline]
|
#[inline]
|
||||||
fn vert_edges(&self,vert_id:SubmeshVertId)->Cow<[SubmeshDirectedEdgeId]>{
|
fn vert_edges(&self,vert_id:SubmeshVertId)->Cow<Vec<SubmeshDirectedEdgeId>>{
|
||||||
self.view.vert_edges(vert_id)
|
self.view.vert_edges(vert_id)
|
||||||
}
|
}
|
||||||
#[inline]
|
#[inline]
|
||||||
fn vert_faces(&self,vert_id:SubmeshVertId)->Cow<[SubmeshFaceId]>{
|
fn vert_faces(&self,vert_id:SubmeshVertId)->Cow<Vec<SubmeshFaceId>>{
|
||||||
self.view.vert_faces(vert_id)
|
self.view.vert_faces(vert_id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -674,13 +669,13 @@ impl MinkowskiMesh<'_>{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
/// This function drops a vertex down to an edge or a face if the path from infinity did not cross any vertex-edge boundaries but the point is supposed to have already crossed a boundary down from a vertex
|
/// This function drops a vertex down to an edge or a face if the path from infinity did not cross any vertex-edge boundaries but the point is supposed to have already crossed a boundary down from a vertex
|
||||||
fn infinity_fev(&self,infinity_dir:Planar64Vec3,point:Planar64Vec3)->FEV::<MinkowskiMesh>{
|
fn infinity_fev(&self,infinity_dir:Planar64Vec3,point:Planar64Vec3)->FEV::<MinkowskiFace,MinkowskiDirectedEdge,MinkowskiVert>{
|
||||||
//start on any vertex
|
//start on any vertex
|
||||||
//cross uncrossable vertex-edge boundaries until you find the closest vertex or edge
|
//cross uncrossable vertex-edge boundaries until you find the closest vertex or edge
|
||||||
//cross edge-face boundary if it's uncrossable
|
//cross edge-face boundary if it's uncrossable
|
||||||
match self.crawl_boundaries(self.farthest_vert(infinity_dir),infinity_dir,point){
|
match self.crawl_boundaries(self.farthest_vert(infinity_dir),infinity_dir,point){
|
||||||
//if a vert is returned, it is the closest point to the infinity point
|
//if a vert is returned, it is the closest point to the infinity point
|
||||||
EV::Vert(vert_id)=>FEV::Vert(vert_id),
|
EV::Vert(vert_id)=>FEV::<MinkowskiFace,MinkowskiDirectedEdge,MinkowskiVert>::Vert(vert_id),
|
||||||
EV::Edge(edge_id)=>{
|
EV::Edge(edge_id)=>{
|
||||||
//cross to face if the boundary is not crossable and we are on the wrong side
|
//cross to face if the boundary is not crossable and we are on the wrong side
|
||||||
let edge_n=self.edge_n(edge_id);
|
let edge_n=self.edge_n(edge_id);
|
||||||
@ -698,61 +693,55 @@ impl MinkowskiMesh<'_>{
|
|||||||
//infinity_dir can always be treated as a velocity
|
//infinity_dir can always be treated as a velocity
|
||||||
if !boundary_d.is_positive()&&boundary_n.dot(infinity_dir).is_zero(){
|
if !boundary_d.is_positive()&&boundary_n.dot(infinity_dir).is_zero(){
|
||||||
//both faces cannot pass this condition, return early if one does.
|
//both faces cannot pass this condition, return early if one does.
|
||||||
return FEV::Face(face_id);
|
return FEV::<MinkowskiFace,MinkowskiDirectedEdge,MinkowskiVert>::Face(face_id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
FEV::Edge(edge_id)
|
FEV::<MinkowskiFace,MinkowskiDirectedEdge,MinkowskiVert>::Edge(edge_id)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// TODO: fundamentally improve this algorithm.
|
fn closest_fev_not_inside(&self,mut infinity_body:crate::physics::Body)->Option<FEV::<MinkowskiFace,MinkowskiDirectedEdge,MinkowskiVert>>{
|
||||||
// All it needs to do is find the closest point on the mesh
|
infinity_body.infinity_dir().map_or(None,|dir|{
|
||||||
// and return the FEV which the point resides on.
|
|
||||||
//
|
|
||||||
// What it actually does is use the above functions to trace a ray in from infinity,
|
|
||||||
// crawling the closest point along the mesh surface until the ray reaches
|
|
||||||
// the starting point to discover the final FEV.
|
|
||||||
//
|
|
||||||
// The actual collision prediction probably does a single test
|
|
||||||
// and then immediately returns with 0 FEV transitions on average,
|
|
||||||
// because of the strict time_limit constraint.
|
|
||||||
//
|
|
||||||
// Most of the calculation time is just calculating the starting point
|
|
||||||
// for the "actual" crawling algorithm below (predict_collision_{in|out}).
|
|
||||||
fn closest_fev_not_inside(&self,mut infinity_body:Body,start_time:Time)->Option<FEV<MinkowskiMesh>>{
|
|
||||||
infinity_body.infinity_dir().and_then(|dir|{
|
|
||||||
let infinity_fev=self.infinity_fev(-dir,infinity_body.position);
|
let infinity_fev=self.infinity_fev(-dir,infinity_body.position);
|
||||||
//a line is simpler to solve than a parabola
|
//a line is simpler to solve than a parabola
|
||||||
infinity_body.velocity=dir;
|
infinity_body.velocity=dir;
|
||||||
infinity_body.acceleration=vec3::ZERO;
|
infinity_body.acceleration=vec3::ZERO;
|
||||||
//crawl in from negative infinity along a tangent line to get the closest fev
|
//crawl in from negative infinity along a tangent line to get the closest fev
|
||||||
// TODO: change crawl_fev args to delta time? Optional values?
|
// TODO: change crawl_fev args to delta time? Optional values?
|
||||||
infinity_fev.crawl(self,&infinity_body,Time::MIN/4,start_time).miss()
|
match crate::face_crawler::crawl_fev(infinity_fev,self,&infinity_body,integer::Time::MIN/4,infinity_body.time){
|
||||||
|
crate::face_crawler::CrawlResult::Miss(fev)=>Some(fev),
|
||||||
|
crate::face_crawler::CrawlResult::Hit(_,_)=>None,
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
pub fn predict_collision_in(&self,relative_body:&Body,Range{start:start_time,end:time_limit}:Range<Time>)->Option<(MinkowskiFace,GigaTime)>{
|
pub fn predict_collision_in(&self,relative_body:&crate::physics::Body,time_limit:integer::Time)->Option<(MinkowskiFace,GigaTime)>{
|
||||||
self.closest_fev_not_inside(relative_body.clone(),start_time).and_then(|fev|{
|
self.closest_fev_not_inside(relative_body.clone()).map_or(None,|fev|{
|
||||||
//continue forwards along the body parabola
|
//continue forwards along the body parabola
|
||||||
fev.crawl(self,relative_body,start_time,time_limit).hit()
|
match crate::face_crawler::crawl_fev(fev,self,relative_body,relative_body.time,time_limit){
|
||||||
|
crate::face_crawler::CrawlResult::Miss(_)=>None,
|
||||||
|
crate::face_crawler::CrawlResult::Hit(face,time)=>Some((face,time)),
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
pub fn predict_collision_out(&self,relative_body:&Body,Range{start:start_time,end:time_limit}:Range<Time>)->Option<(MinkowskiFace,GigaTime)>{
|
pub fn predict_collision_out(&self,relative_body:&crate::physics::Body,time_limit:integer::Time)->Option<(MinkowskiFace,GigaTime)>{
|
||||||
//create an extrapolated body at time_limit
|
//create an extrapolated body at time_limit
|
||||||
let infinity_body=-relative_body.clone();
|
let infinity_body=crate::physics::Body::new(
|
||||||
self.closest_fev_not_inside(infinity_body,-time_limit).and_then(|fev|{
|
relative_body.extrapolated_position(time_limit),
|
||||||
|
-relative_body.extrapolated_velocity(time_limit),
|
||||||
|
relative_body.acceleration,
|
||||||
|
-time_limit,
|
||||||
|
);
|
||||||
|
self.closest_fev_not_inside(infinity_body).map_or(None,|fev|{
|
||||||
//continue backwards along the body parabola
|
//continue backwards along the body parabola
|
||||||
fev.crawl(self,&infinity_body,-time_limit,-start_time).hit()
|
match crate::face_crawler::crawl_fev(fev,self,&-relative_body.clone(),-time_limit,-relative_body.time){
|
||||||
//no need to test -time<time_limit because of the first step
|
crate::face_crawler::CrawlResult::Miss(_)=>None,
|
||||||
.map(|(face,time)|(face,-time))
|
crate::face_crawler::CrawlResult::Hit(face,time)=>Some((face,-time)),//no need to test -time<time_limit because of the first step
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
pub fn predict_collision_face_out(&self,relative_body:&Body,Range{start:start_time,end:time_limit}:Range<Time>,contact_face_id:MinkowskiFace)->Option<(MinkowskiEdge,GigaTime)>{
|
pub fn predict_collision_face_out(&self,relative_body:&crate::physics::Body,time_limit:integer::Time,contact_face_id:MinkowskiFace)->Option<(MinkowskiEdge,GigaTime)>{
|
||||||
//no algorithm needed, there is only one state and two cases (Edge,None)
|
//no algorithm needed, there is only one state and two cases (Edge,None)
|
||||||
//determine when it passes an edge ("sliding off" case)
|
//determine when it passes an edge ("sliding off" case)
|
||||||
let start_time={
|
|
||||||
let r=(start_time-relative_body.time).to_ratio();
|
|
||||||
Ratio::new(r.num,r.den)
|
|
||||||
};
|
|
||||||
let mut best_time={
|
let mut best_time={
|
||||||
let r=(time_limit-relative_body.time).to_ratio();
|
let r=(time_limit-relative_body.time).to_ratio();
|
||||||
Ratio::new(r.num.fix_4(),r.den.fix_4())
|
Ratio::new(r.num.fix_4(),r.den.fix_4())
|
||||||
@ -768,7 +757,7 @@ impl MinkowskiMesh<'_>{
|
|||||||
//WARNING! d outside of *2
|
//WARNING! d outside of *2
|
||||||
//WARNING: truncated precision
|
//WARNING: truncated precision
|
||||||
for dt in Fixed::<4,128>::zeroes2(((n.dot(relative_body.position))*2-d).fix_4(),n.dot(relative_body.velocity).fix_4()*2,n.dot(relative_body.acceleration).fix_4()){
|
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(){
|
if Ratio::new(Planar64::ZERO,Planar64::EPSILON).le_ratio(dt)&&dt.lt_ratio(best_time)&&n.dot(relative_body.extrapolated_velocity_ratio_dt(dt)).is_negative(){
|
||||||
best_time=dt;
|
best_time=dt;
|
||||||
best_edge=Some(directed_edge_id);
|
best_edge=Some(directed_edge_id);
|
||||||
break;
|
break;
|
||||||
@ -777,12 +766,15 @@ impl MinkowskiMesh<'_>{
|
|||||||
}
|
}
|
||||||
best_edge.map(|e|(e.as_undirected(),best_time))
|
best_edge.map(|e|(e.as_undirected(),best_time))
|
||||||
}
|
}
|
||||||
fn infinity_in(&self,infinity_body:Body)->Option<(MinkowskiFace,GigaTime)>{
|
fn infinity_in(&self,infinity_body:crate::physics::Body)->Option<(MinkowskiFace,GigaTime)>{
|
||||||
let infinity_fev=self.infinity_fev(-infinity_body.velocity,infinity_body.position);
|
let infinity_fev=self.infinity_fev(-infinity_body.velocity,infinity_body.position);
|
||||||
infinity_fev.crawl(self,&infinity_body,Time::MIN/4,infinity_body.time).hit()
|
match crate::face_crawler::crawl_fev(infinity_fev,self,&infinity_body,integer::Time::MIN/4,infinity_body.time){
|
||||||
|
crate::face_crawler::CrawlResult::Miss(_)=>None,
|
||||||
|
crate::face_crawler::CrawlResult::Hit(face,time)=>Some((face,time)),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
pub fn is_point_in_mesh(&self,point:Planar64Vec3)->bool{
|
pub fn is_point_in_mesh(&self,point:Planar64Vec3)->bool{
|
||||||
let infinity_body=Body::new(point,vec3::Y,vec3::ZERO,Time::ZERO);
|
let infinity_body=crate::physics::Body::new(point,vec3::Y,vec3::ZERO,integer::Time::ZERO);
|
||||||
//movement must escape the mesh forwards and backwards in time,
|
//movement must escape the mesh forwards and backwards in time,
|
||||||
//otherwise the point is not inside the mesh
|
//otherwise the point is not inside the mesh
|
||||||
self.infinity_in(infinity_body)
|
self.infinity_in(infinity_body)
|
||||||
@ -792,13 +784,9 @@ impl MinkowskiMesh<'_>{
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl MeshQuery for MinkowskiMesh<'_>{
|
impl MeshQuery<MinkowskiFace,MinkowskiDirectedEdge,MinkowskiVert> for MinkowskiMesh<'_>{
|
||||||
type Face=MinkowskiFace;
|
|
||||||
type Edge=MinkowskiDirectedEdge;
|
|
||||||
type Vert=MinkowskiVert;
|
|
||||||
type Normal=Vector3<Fixed<3,96>>;
|
type Normal=Vector3<Fixed<3,96>>;
|
||||||
type Offset=Fixed<4,128>;
|
type Offset=Fixed<4,128>;
|
||||||
// TODO: relative d
|
|
||||||
fn face_nd(&self,face_id:MinkowskiFace)->(Self::Normal,Self::Offset){
|
fn face_nd(&self,face_id:MinkowskiFace)->(Self::Normal,Self::Offset){
|
||||||
match face_id{
|
match face_id{
|
||||||
MinkowskiFace::VertFace(v0,f1)=>{
|
MinkowskiFace::VertFace(v0,f1)=>{
|
||||||
@ -828,7 +816,7 @@ impl MeshQuery for MinkowskiMesh<'_>{
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn face_edges(&self,face_id:MinkowskiFace)->Cow<[MinkowskiDirectedEdge]>{
|
fn face_edges(&self,face_id:MinkowskiFace)->Cow<Vec<MinkowskiDirectedEdge>>{
|
||||||
match face_id{
|
match face_id{
|
||||||
MinkowskiFace::VertFace(v0,f1)=>{
|
MinkowskiFace::VertFace(v0,f1)=>{
|
||||||
Cow::Owned(self.mesh1.face_edges(f1).iter().map(|&edge_id1|{
|
Cow::Owned(self.mesh1.face_edges(f1).iter().map(|&edge_id1|{
|
||||||
@ -929,7 +917,7 @@ impl MeshQuery for MinkowskiMesh<'_>{
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn vert_edges(&self,vert_id:MinkowskiVert)->Cow<[MinkowskiDirectedEdge]>{
|
fn vert_edges(&self,vert_id:MinkowskiVert)->Cow<Vec<MinkowskiDirectedEdge>>{
|
||||||
match vert_id{
|
match vert_id{
|
||||||
MinkowskiVert::VertVert(v0,v1)=>{
|
MinkowskiVert::VertVert(v0,v1)=>{
|
||||||
let mut edges=Vec::new();
|
let mut edges=Vec::new();
|
||||||
@ -971,7 +959,7 @@ impl MeshQuery for MinkowskiMesh<'_>{
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn vert_faces(&self,_vert_id:MinkowskiVert)->Cow<[MinkowskiFace]>{
|
fn vert_faces(&self,_vert_id:MinkowskiVert)->Cow<Vec<MinkowskiFace>>{
|
||||||
unimplemented!()
|
unimplemented!()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -985,7 +973,7 @@ fn is_empty_volume(normals:Vec<Vector3<Fixed<3,96>>>)->bool{
|
|||||||
for k in 0..len{
|
for k in 0..len{
|
||||||
if k!=i&&k!=j{
|
if k!=i&&k!=j{
|
||||||
let d=n.dot(normals[k]).is_negative();
|
let d=n.dot(normals[k]).is_negative();
|
||||||
if let &Some(comp)=&d_comp{
|
if let Some(comp)=&d_comp{
|
||||||
// This is testing if d_comp*d < 0
|
// This is testing if d_comp*d < 0
|
||||||
if comp^d{
|
if comp^d{
|
||||||
return true;
|
return true;
|
||||||
@ -1005,3 +993,9 @@ fn test_is_empty_volume(){
|
|||||||
assert!(!is_empty_volume([vec3::X.fix_3(),vec3::Y.fix_3(),vec3::Z.fix_3()].to_vec()));
|
assert!(!is_empty_volume([vec3::X.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()));
|
assert!(is_empty_volume([vec3::X.fix_3(),vec3::Y.fix_3(),vec3::Z.fix_3(),vec3::NEG_X.fix_3()].to_vec()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn build_me_a_cube(){
|
||||||
|
let mesh=PhysicsMesh::unit_cube();
|
||||||
|
//println!("mesh={:?}",mesh);
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user