Compare commits
4 Commits
master
...
bot-worker
Author | SHA1 | Date | |
---|---|---|---|
c558819835 | |||
772c9eb0b7 | |||
2366ca0f5f | |||
a4077ed0e3 |
@ -1,2 +1,2 @@
|
||||
[registries.strafesnet]
|
||||
index = "sparse+https://git.itzana.me/api/packages/strafesnet/cargo/"
|
||||
index = "sparse+https://git.itzana.me/api/packages/strafesnet/cargo/"
|
1
CONTRIBUTING.md
Normal file
1
CONTRIBUTING.md
Normal file
@ -0,0 +1 @@
|
||||
By contributing code to the [StrafesNET project](https://git.itzana.me/StrafesNET/strafe-client), you agree to license your contribution under the [License](LICENSE).
|
1562
Cargo.lock
generated
1562
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
50
Cargo.toml
50
Cargo.toml
@ -1,22 +1,34 @@
|
||||
[workspace]
|
||||
members = [
|
||||
"engine/graphics",
|
||||
"engine/physics",
|
||||
"engine/session",
|
||||
"engine/settings",
|
||||
"integration-testing",
|
||||
"lib/bsp_loader",
|
||||
"lib/common",
|
||||
"lib/deferred_loader",
|
||||
"lib/fixed_wide",
|
||||
"lib/linear_ops",
|
||||
"lib/ratio_ops",
|
||||
"lib/rbx_loader",
|
||||
"lib/roblox_emulator",
|
||||
"lib/snf",
|
||||
"strafe-client",
|
||||
]
|
||||
resolver = "2"
|
||||
[package]
|
||||
name = "strafe-client"
|
||||
version = "0.10.3"
|
||||
edition = "2021"
|
||||
repository = "https://git.itzana.me/StrafesNET/strafe-client"
|
||||
license = "Custom"
|
||||
description = "StrafesNET game client for bhop and surf."
|
||||
authors = ["Rhys Lloyd <krakow20@gmail.com>"]
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
[features]
|
||||
default = ["snf"]
|
||||
snf = ["dep:strafesnet_snf"]
|
||||
source = ["dep:strafesnet_deferred_loader", "dep:strafesnet_bsp_loader"]
|
||||
roblox = ["dep:strafesnet_deferred_loader", "dep:strafesnet_rbx_loader"]
|
||||
|
||||
[dependencies]
|
||||
bytemuck = { version = "1.13.1", features = ["derive"] }
|
||||
configparser = "3.0.2"
|
||||
ddsfile = "0.5.1"
|
||||
glam = "0.28.0"
|
||||
id = { version = "0.1.0", registry = "strafesnet" }
|
||||
parking_lot = "0.12.1"
|
||||
pollster = "0.3.0"
|
||||
strafesnet_bsp_loader = { version = "0.1.3", registry = "strafesnet", optional = true }
|
||||
strafesnet_common = { version = "0.3.0", registry = "strafesnet" }
|
||||
strafesnet_deferred_loader = { version = "0.3.1", features = ["legacy"], registry = "strafesnet", optional = true }
|
||||
strafesnet_rbx_loader = { version = "0.3.2", registry = "strafesnet", optional = true }
|
||||
strafesnet_snf = { version = "0.1.3-bot", registry = "strafesnet", optional = true }
|
||||
wgpu = "22.0.0"
|
||||
winit = "0.30.4"
|
||||
|
||||
[profile.release]
|
||||
#lto = true
|
||||
|
18
README.md
18
README.md
@ -1,16 +1,10 @@
|
||||
<img align="right" width="25%" src="logo.png">
|
||||
<img align="right" width="25%" src="strafe.png">
|
||||
|
||||
# Strafe Project
|
||||
Monorepo for working on projects related to strafe client.
|
||||
|
||||
## Try it out
|
||||
See [releases](https://git.itzana.me/StrafesNET/strafe-project/releases) for downloads.
|
||||
# Strafe Client
|
||||
In development client for jumping on squares (and riding on triangles)
|
||||
|
||||
## How to build and run
|
||||
1. Have rust and git installed
|
||||
2. `git clone https://git.itzana.me/StrafesNET/strafe-project`
|
||||
3. `cd strafe-project`
|
||||
4. `cargo run --release --bin strafe-client`
|
||||
|
||||
## 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.
|
||||
2. `git clone https://git.itzana.me/StrafesNET/strafe-client`
|
||||
3. `cd strafe-client`
|
||||
4. `cargo run --release`
|
||||
|
@ -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,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,148 +0,0 @@
|
||||
use crate::model::{GigaTime,FEV,MeshQuery,DirectedEdge};
|
||||
use strafesnet_common::integer::{Fixed,Ratio,vec3::Vector3};
|
||||
use crate::physics::{Time,Body};
|
||||
|
||||
enum Transition<M:MeshQuery>{
|
||||
Miss,
|
||||
Next(FEV<M>,GigaTime),
|
||||
Hit(M::Face,GigaTime),
|
||||
}
|
||||
|
||||
pub enum CrawlResult<M:MeshQuery>{
|
||||
Miss(FEV<M>),
|
||||
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>
|
||||
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.
|
||||
//if the transition time is equal to an already tested transition, do not replace the current best.
|
||||
let mut best_transition=Transition::Miss;
|
||||
match self{
|
||||
&FEV::Face(face_id)=>{
|
||||
//test own face collision time, ignoring roots with zero or conflicting derivative
|
||||
//n=face.normal d=face.dot
|
||||
//n.a t^2+n.v t+n.p-d==0
|
||||
let (n,d)=mesh.face_nd(face_id);
|
||||
//TODO: use higher precision d value?
|
||||
//use the mesh transform translation instead of baking it into the d value.
|
||||
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(){
|
||||
best_time=dt;
|
||||
best_transition=Transition::Hit(face_id,dt);
|
||||
break;
|
||||
}
|
||||
}
|
||||
//test each edge collision time, ignoring roots with zero or conflicting derivative
|
||||
for &directed_edge_id in mesh.face_edges(face_id).iter(){
|
||||
let edge_n=mesh.directed_edge_n(directed_edge_id);
|
||||
let n=n.cross(edge_n);
|
||||
let verts=mesh.edge_verts(directed_edge_id.as_undirected());
|
||||
//WARNING: d is moved out of the *2 block because of adding two vertices!
|
||||
//WARNING: precision is swept under the rug!
|
||||
for dt in Fixed::<4,128>::zeroes2(n.dot(body.position*2-(mesh.vert(verts[0])+mesh.vert(verts[1]))).fix_4(),n.dot(body.velocity).fix_4()*2,n.dot(body.acceleration).fix_4()){
|
||||
if body_time.le_ratio(dt)&&dt.lt_ratio(best_time)&&n.dot(body.extrapolated_velocity_ratio_dt(dt)).is_negative(){
|
||||
best_time=dt;
|
||||
best_transition=Transition::Next(FEV::Edge(directed_edge_id.as_undirected()),dt);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
//if none:
|
||||
},
|
||||
&FEV::Edge(edge_id)=>{
|
||||
//test each face collision time, ignoring roots with zero or conflicting derivative
|
||||
let edge_n=mesh.edge_n(edge_id);
|
||||
let edge_verts=mesh.edge_verts(edge_id);
|
||||
let delta_pos=body.position*2-(mesh.vert(edge_verts[0])+mesh.vert(edge_verts[1]));
|
||||
for (i,&edge_face_id) in mesh.edge_faces(edge_id).iter().enumerate(){
|
||||
let face_n=mesh.face_nd(edge_face_id).0;
|
||||
//edge_n gets parity from the order of edge_faces
|
||||
let n=face_n.cross(edge_n)*((i as i64)*2-1);
|
||||
//WARNING yada yada d *2
|
||||
for dt in Fixed::<4,128>::zeroes2(n.dot(delta_pos).fix_4(),n.dot(body.velocity).fix_4()*2,n.dot(body.acceleration).fix_4()){
|
||||
if body_time.le_ratio(dt)&&dt.lt_ratio(best_time)&&n.dot(body.extrapolated_velocity_ratio_dt(dt)).is_negative(){
|
||||
best_time=dt;
|
||||
best_transition=Transition::Next(FEV::Face(edge_face_id),dt);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
//test each vertex collision time, ignoring roots with zero or conflicting derivative
|
||||
for (i,&vert_id) in edge_verts.iter().enumerate(){
|
||||
//vertex normal gets parity from vert index
|
||||
let n=edge_n*(1-2*(i as i64));
|
||||
for dt in Fixed::<2,64>::zeroes2((n.dot(body.position-mesh.vert(vert_id)))*2,n.dot(body.velocity)*2,n.dot(body.acceleration)){
|
||||
if body_time.le_ratio(dt)&&dt.lt_ratio(best_time)&&n.dot(body.extrapolated_velocity_ratio_dt(dt)).is_negative(){
|
||||
let dt=Ratio::new(dt.num.fix_4(),dt.den.fix_4());
|
||||
best_time=dt;
|
||||
best_transition=Transition::Next(FEV::Vert(vert_id),dt);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
//if none:
|
||||
},
|
||||
&FEV::Vert(vert_id)=>{
|
||||
//test each edge collision time, ignoring roots with zero or conflicting derivative
|
||||
for &directed_edge_id in mesh.vert_edges(vert_id).iter(){
|
||||
//edge is directed away from vertex, but we want the dot product to turn out negative
|
||||
let n=-mesh.directed_edge_n(directed_edge_id);
|
||||
for dt in Fixed::<2,64>::zeroes2((n.dot(body.position-mesh.vert(vert_id)))*2,n.dot(body.velocity)*2,n.dot(body.acceleration)){
|
||||
if body_time.le_ratio(dt)&&dt.lt_ratio(best_time)&&n.dot(body.extrapolated_velocity_ratio_dt(dt)).is_negative(){
|
||||
let dt=Ratio::new(dt.num.fix_4(),dt.den.fix_4());
|
||||
best_time=dt;
|
||||
best_transition=Transition::Next(FEV::Edge(directed_edge_id.as_undirected()),dt);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
//if none:
|
||||
},
|
||||
}
|
||||
best_transition
|
||||
}
|
||||
pub fn crawl(mut self,mesh:&M,relative_body:&Body,start_time:Time,time_limit:Time)->CrawlResult<M>{
|
||||
let mut body_time={
|
||||
let r=(start_time-relative_body.time).to_ratio();
|
||||
Ratio::new(r.num.fix_4(),r.den.fix_4())
|
||||
};
|
||||
let time_limit={
|
||||
let r=(time_limit-relative_body.time).to_ratio();
|
||||
Ratio::new(r.num.fix_4(),r.den.fix_4())
|
||||
};
|
||||
for _ in 0..20{
|
||||
match self.next_transition(body_time,mesh,relative_body,time_limit){
|
||||
Transition::Miss=>return CrawlResult::Miss(self),
|
||||
Transition::Next(next_fev,next_time)=>(self,body_time)=(next_fev,next_time),
|
||||
Transition::Hit(face,time)=>return CrawlResult::Hit(face,time),
|
||||
}
|
||||
}
|
||||
//TODO: fix all bugs
|
||||
//println!("Too many iterations! Using default behaviour instead of crashing...");
|
||||
CrawlResult::Miss(self)
|
||||
}
|
||||
}
|
@ -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 that 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)
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -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 = { version = "0.5.2", path = "../lib/common", registry = "strafesnet" }
|
||||
strafesnet_physics = { version = "0.1.0", 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,19 +0,0 @@
|
||||
Vectors: Fixed Size, Fixed Point, Wide
|
||||
======================================
|
||||
|
||||
## These exist separately in the Rust ecosystem, but not together.
|
||||
|
||||
#### 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
lib/bsp_loader/.gitignore
vendored
1
lib/bsp_loader/.gitignore
vendored
@ -1 +0,0 @@
|
||||
/target
|
@ -1,16 +0,0 @@
|
||||
[package]
|
||||
name = "strafesnet_bsp_loader"
|
||||
version = "0.2.2"
|
||||
edition = "2021"
|
||||
repository = "https://git.itzana.me/StrafesNET/strafe-project"
|
||||
license = "MIT OR Apache-2.0"
|
||||
description = "Convert Valve BSP files to StrafesNET data structures."
|
||||
authors = ["Rhys Lloyd <krakow20@gmail.com>"]
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
glam = "0.29.0"
|
||||
strafesnet_common = { path = "../common", registry = "strafesnet" }
|
||||
vbsp = "0.6.0"
|
||||
vmdl = "0.2.0"
|
@ -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,19 +0,0 @@
|
||||
StrafesNET BSP Loader
|
||||
=====================
|
||||
|
||||
## Convert Valve BSP files into StrafesNET data structures
|
||||
|
||||
#### 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,333 +0,0 @@
|
||||
use strafesnet_common::{map,model,integer,gameplay_attributes};
|
||||
|
||||
const VALVE_SCALE:f32=1.0/16.0;
|
||||
fn valve_transform([x,y,z]:[f32;3])->integer::Planar64Vec3{
|
||||
integer::vec3::try_from_f32_array([x*VALVE_SCALE,z*VALVE_SCALE,-y*VALVE_SCALE]).unwrap()
|
||||
}
|
||||
pub fn convert_bsp<AcquireRenderConfigId,AcquireMeshId>(
|
||||
bsp:&vbsp::Bsp,
|
||||
mut acquire_render_config_id:AcquireRenderConfigId,
|
||||
mut acquire_mesh_id:AcquireMeshId
|
||||
)->PartialMap1
|
||||
where
|
||||
AcquireRenderConfigId:FnMut(Option<&str>)->model::RenderConfigId,
|
||||
AcquireMeshId:FnMut(&str)->model::MeshId,
|
||||
{
|
||||
//figure out real attributes later
|
||||
let mut unique_attributes=Vec::new();
|
||||
unique_attributes.push(gameplay_attributes::CollisionAttributes::Decoration);
|
||||
const TEMP_TOUCH_ME_ATTRIBUTE:gameplay_attributes::CollisionAttributesId=gameplay_attributes::CollisionAttributesId::new(0);
|
||||
|
||||
let mut prop_mesh_count=0;
|
||||
//declare all prop models to Loader
|
||||
let prop_models=bsp.static_props().map(|prop|{
|
||||
//get or create mesh_id
|
||||
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();
|
||||
model::Model{
|
||||
mesh:mesh_id,
|
||||
attributes:TEMP_TOUCH_ME_ATTRIBUTE,
|
||||
transform:integer::Planar64Affine3::new(
|
||||
integer::mat3::try_from_f32_array_2d((
|
||||
glam::Mat3A::from_diagonal(glam::Vec3::splat(placement.scale))
|
||||
//TODO: figure this out
|
||||
*glam::Mat3A::from_quat(glam::Quat::from_array(placement.rotation.into()))
|
||||
).to_cols_array_2d()).unwrap(),
|
||||
valve_transform(placement.origin.into()),
|
||||
),
|
||||
color:glam::Vec4::ONE,
|
||||
}
|
||||
}).collect();
|
||||
|
||||
//TODO: make the main map one single mesh with a bunch of different physics groups and graphics groups
|
||||
|
||||
//the generated MeshIds in here will collide with the Loader Mesh Ids
|
||||
//but I can't think of a good workaround other than just remapping one later.
|
||||
let world_meshes:Vec<model::Mesh>=bsp.models().map(|world_model|{
|
||||
//non-deduplicated
|
||||
let mut spam_pos=Vec::new();
|
||||
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 physics_group=model::IndexedPhysicsGroup::default();
|
||||
let polygon_groups=world_model.faces().enumerate().map(|(polygon_group_id,face)|{
|
||||
let polygon_group_id=model::PolygonGroupId::new(polygon_group_id as u32);
|
||||
let face_texture=face.texture();
|
||||
let face_texture_data=face_texture.texture_data();
|
||||
//this would be better as a 4x2 matrix
|
||||
let texture_transform_u=glam::Vec4::from_array(face_texture.texture_transforms_u)/(face_texture_data.width as f32);
|
||||
let texture_transform_v=glam::Vec4::from_array(face_texture.texture_transforms_v)/(face_texture_data.height as f32);
|
||||
|
||||
//this automatically figures out what the texture is trying to do and creates
|
||||
//a render config for it, and then returns the id to that render config
|
||||
let render_id=acquire_render_config_id(Some(face_texture_data.name()));
|
||||
|
||||
//normal
|
||||
let normal=face.normal();
|
||||
let normal_idx=spam_normal.len() as u32;
|
||||
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||{
|
||||
match (polygon_iter.next(),polygon_iter.next(),polygon_iter.next()){
|
||||
(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
|
||||
_=>None,
|
||||
}
|
||||
}).collect();
|
||||
if face.is_visible(){
|
||||
//TODO: deduplicate graphics groups by render id
|
||||
graphics_groups.push(model::IndexedGraphicsGroup{
|
||||
render:render_id,
|
||||
groups:vec![polygon_group_id],
|
||||
})
|
||||
}
|
||||
physics_group.groups.push(polygon_group_id);
|
||||
model::PolygonGroup::PolygonList(model::PolygonList::new(polygon_list))
|
||||
}).collect();
|
||||
model::Mesh{
|
||||
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();
|
||||
|
||||
let world_models:Vec<model::Model>=
|
||||
//one instance of the main world mesh
|
||||
std::iter::once((
|
||||
//world_model
|
||||
model::MeshId::new(0),
|
||||
//model_origin
|
||||
vbsp::Vector::from([0.0,0.0,0.0]),
|
||||
//model_color
|
||||
vbsp::Color{r:255,g:255,b:255},
|
||||
)).chain(
|
||||
//entities sprinkle instances of the other meshes around
|
||||
bsp.entities.iter()
|
||||
.flat_map(|ent|ent.parse())//ignore entity parsing errors
|
||||
.filter_map(|ent|match ent{
|
||||
vbsp::Entity::Brush(brush)=>Some(brush),
|
||||
vbsp::Entity::BrushIllusionary(brush)=>Some(brush),
|
||||
vbsp::Entity::BrushWall(brush)=>Some(brush),
|
||||
vbsp::Entity::BrushWallToggle(brush)=>Some(brush),
|
||||
_=>None,
|
||||
}).flat_map(|brush|
|
||||
//The first character of brush.model is '*'
|
||||
brush.model[1..].parse().map(|mesh_id|//ignore parse int errors
|
||||
(model::MeshId::new(mesh_id),brush.origin,brush.color)
|
||||
)
|
||||
)
|
||||
).map(|(mesh_id,model_origin,vbsp::Color{r,g,b})|{
|
||||
model::Model{
|
||||
mesh:mesh_id,
|
||||
attributes:TEMP_TOUCH_ME_ATTRIBUTE,
|
||||
transform:integer::Planar64Affine3::new(
|
||||
integer::mat3::identity(),
|
||||
valve_transform(model_origin.into())
|
||||
),
|
||||
color:(glam::Vec3::from_array([r as f32,g as f32,b as f32])/255.0).extend(1.0),
|
||||
}
|
||||
}).collect();
|
||||
|
||||
PartialMap1{
|
||||
attributes:unique_attributes,
|
||||
world_meshes,
|
||||
prop_models,
|
||||
world_models,
|
||||
modes:strafesnet_common::gameplay_modes::Modes::new(Vec::new()),
|
||||
}
|
||||
}
|
||||
|
||||
//partially constructed map types
|
||||
pub struct PartialMap1{
|
||||
attributes:Vec<strafesnet_common::gameplay_attributes::CollisionAttributes>,
|
||||
prop_models:Vec<model::Model>,
|
||||
world_meshes:Vec<model::Mesh>,
|
||||
world_models:Vec<model::Model>,
|
||||
modes:strafesnet_common::gameplay_modes::Modes,
|
||||
}
|
||||
impl PartialMap1{
|
||||
pub fn add_prop_meshes<AcquireRenderConfigId>(
|
||||
self,
|
||||
prop_meshes:impl IntoIterator<Item=(model::MeshId,crate::data::ModelData)>,
|
||||
mut acquire_render_config_id:AcquireRenderConfigId,
|
||||
)->PartialMap2
|
||||
where
|
||||
AcquireRenderConfigId:FnMut(Option<&str>)->model::RenderConfigId,
|
||||
{
|
||||
PartialMap2{
|
||||
attributes:self.attributes,
|
||||
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,
|
||||
world_meshes:self.world_meshes,
|
||||
world_models:self.world_models,
|
||||
modes:self.modes,
|
||||
}
|
||||
}
|
||||
}
|
||||
pub struct PartialMap2{
|
||||
attributes:Vec<strafesnet_common::gameplay_attributes::CollisionAttributes>,
|
||||
prop_meshes:Vec<(model::MeshId,model::Mesh)>,
|
||||
prop_models:Vec<model::Model>,
|
||||
world_meshes:Vec<model::Mesh>,
|
||||
world_models:Vec<model::Model>,
|
||||
modes:strafesnet_common::gameplay_modes::Modes,
|
||||
}
|
||||
impl PartialMap2{
|
||||
pub fn add_render_configs_and_textures(
|
||||
mut self,
|
||||
render_configs:impl IntoIterator<Item=(model::RenderConfigId,model::RenderConfig)>,
|
||||
textures:impl IntoIterator<Item=(model::TextureId,Vec<u8>)>,
|
||||
)->map::CompleteMap{
|
||||
//merge mesh and model lists, flatten and remap all ids
|
||||
let mesh_id_offset=self.world_meshes.len();
|
||||
println!("prop_meshes.len()={}",self.prop_meshes.len());
|
||||
let (mut prop_meshes,prop_mesh_id_map):(Vec<model::Mesh>,std::collections::HashMap<model::MeshId,model::MeshId>)
|
||||
=self.prop_meshes.into_iter().enumerate().map(|(new_mesh_id,(old_mesh_id,mesh))|{
|
||||
(mesh,(old_mesh_id,model::MeshId::new((mesh_id_offset+new_mesh_id) as u32)))
|
||||
}).unzip();
|
||||
self.world_meshes.append(&mut prop_meshes);
|
||||
//there is no modes or runtime behaviour with references to the model ids currently
|
||||
//so just relentlessly cull them if the mesh is missing
|
||||
self.world_models.extend(self.prop_models.into_iter().filter_map(|mut model|
|
||||
prop_mesh_id_map.get(&model.mesh).map(|&new_mesh_id|{
|
||||
model.mesh=new_mesh_id;
|
||||
model
|
||||
})
|
||||
));
|
||||
//let mut models=Vec::new();
|
||||
let (textures,texture_id_map):(Vec<Vec<u8>>,std::collections::HashMap<model::TextureId,model::TextureId>)
|
||||
=textures.into_iter()
|
||||
//.filter_map(f) cull unused textures
|
||||
.enumerate().map(|(new_texture_id,(old_texture_id,texture))|{
|
||||
(texture,(old_texture_id,model::TextureId::new(new_texture_id as u32)))
|
||||
}).unzip();
|
||||
let render_configs=render_configs.into_iter().map(|(render_config_id,mut render_config)|{
|
||||
//this may generate duplicate no-texture render configs but idc
|
||||
render_config.texture=render_config.texture.and_then(|texture_id|
|
||||
texture_id_map.get(&texture_id).copied()
|
||||
);
|
||||
render_config
|
||||
}).collect();
|
||||
map::CompleteMap{
|
||||
modes:self.modes,
|
||||
attributes:self.attributes,
|
||||
meshes:self.world_meshes,
|
||||
models:self.world_models,
|
||||
textures,
|
||||
render_configs,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
})
|
||||
}
|
@ -1,60 +0,0 @@
|
||||
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,37 +0,0 @@
|
||||
mod bsp;
|
||||
pub mod data;
|
||||
|
||||
pub use data::Bsp;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum ReadError{
|
||||
Bsp(vbsp::BspError),
|
||||
Io(std::io::Error),
|
||||
}
|
||||
impl std::fmt::Display for ReadError{
|
||||
fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{
|
||||
write!(f,"{self:?}")
|
||||
}
|
||||
}
|
||||
impl std::error::Error for ReadError{}
|
||||
|
||||
pub fn read<R:std::io::Read>(mut input:R)->Result<Bsp,ReadError>{
|
||||
let mut s=Vec::new();
|
||||
|
||||
//TODO: mmap
|
||||
input.read_to_end(&mut s).map_err(ReadError::Io)?;
|
||||
|
||||
vbsp::Bsp::read(s.as_slice()).map(Bsp::new).map_err(ReadError::Bsp)
|
||||
}
|
||||
|
||||
pub fn convert<AcquireRenderConfigId,AcquireMeshId>(
|
||||
bsp:&Bsp,
|
||||
acquire_render_config_id:AcquireRenderConfigId,
|
||||
acquire_mesh_id:AcquireMeshId
|
||||
)->bsp::PartialMap1
|
||||
where
|
||||
AcquireRenderConfigId:FnMut(Option<&str>)->strafesnet_common::model::RenderConfigId,
|
||||
AcquireMeshId:FnMut(&str)->strafesnet_common::model::MeshId,
|
||||
{
|
||||
bsp::convert_bsp(bsp.as_ref(),acquire_render_config_id,acquire_mesh_id)
|
||||
}
|
1
lib/common/.gitignore
vendored
1
lib/common/.gitignore
vendored
@ -1 +0,0 @@
|
||||
/target
|
@ -1,19 +0,0 @@
|
||||
[package]
|
||||
name = "strafesnet_common"
|
||||
version = "0.5.2"
|
||||
edition = "2021"
|
||||
repository = "https://git.itzana.me/StrafesNET/strafe-project"
|
||||
license = "MIT OR Apache-2.0"
|
||||
description = "Common types and helpers for Strafe Client associated projects."
|
||||
authors = ["Rhys Lloyd <krakow20@gmail.com>"]
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
arrayvec = "0.7.4"
|
||||
bitflags = "2.6.0"
|
||||
fixed_wide = { path = "../fixed_wide", registry = "strafesnet", features = ["deferred-division","zeroes","wide-mul"] }
|
||||
linear_ops = { path = "../linear_ops", registry = "strafesnet", features = ["deferred-division","named-fields"] }
|
||||
ratio_ops = { path = "../ratio_ops", registry = "strafesnet" }
|
||||
glam = "0.29.0"
|
||||
id = { version = "0.1.0", registry = "strafesnet" }
|
@ -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,19 +0,0 @@
|
||||
StrafesNET Common Library
|
||||
=========================
|
||||
|
||||
## Common types used in the StrafesNET ecosystem
|
||||
|
||||
#### 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,56 +0,0 @@
|
||||
use crate::integer::{vec3,Planar64Vec3};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Aabb{
|
||||
min:Planar64Vec3,
|
||||
max:Planar64Vec3,
|
||||
}
|
||||
|
||||
impl Default for Aabb{
|
||||
fn default()->Self{
|
||||
Self{min:vec3::MAX,max:vec3::MIN}
|
||||
}
|
||||
}
|
||||
|
||||
impl Aabb{
|
||||
pub const fn new(min:Planar64Vec3,max:Planar64Vec3)->Self{
|
||||
Self{min,max}
|
||||
}
|
||||
pub const fn max(&self)->Planar64Vec3{
|
||||
self.max
|
||||
}
|
||||
pub const fn min(&self)->Planar64Vec3{
|
||||
self.min
|
||||
}
|
||||
pub fn grow(&mut self,point:Planar64Vec3){
|
||||
self.min=self.min.min(point);
|
||||
self.max=self.max.max(point);
|
||||
}
|
||||
pub fn join(&mut self,aabb:&Aabb){
|
||||
self.min=self.min.min(aabb.min);
|
||||
self.max=self.max.max(aabb.max);
|
||||
}
|
||||
pub fn inflate(&mut self,hs:Planar64Vec3){
|
||||
self.min-=hs;
|
||||
self.max+=hs;
|
||||
}
|
||||
pub fn intersects(&self,aabb:&Aabb)->bool{
|
||||
let bvec=self.min.lt(aabb.max)&aabb.min.lt(self.max);
|
||||
bvec.all()
|
||||
}
|
||||
pub fn size(&self)->Planar64Vec3{
|
||||
self.max-self.min
|
||||
}
|
||||
pub fn center(&self)->Planar64Vec3{
|
||||
self.min+(self.max-self.min)>>1
|
||||
}
|
||||
//probably use floats for area & volume because we don't care about precision
|
||||
// pub fn area_weight(&self)->f32{
|
||||
// let d=self.max-self.min;
|
||||
// d.x*d.y+d.y*d.z+d.z*d.x
|
||||
// }
|
||||
// pub fn volume(&self)->f32{
|
||||
// let d=self.max-self.min;
|
||||
// d.x*d.y*d.z
|
||||
// }
|
||||
}
|
@ -1,194 +0,0 @@
|
||||
use crate::aabb::Aabb;
|
||||
|
||||
//da algaritum
|
||||
//lista boxens
|
||||
//sort by {minx,maxx,miny,maxy,minz,maxz} (6 lists)
|
||||
//find the sets that minimizes the sum of surface areas
|
||||
//splitting is done when the minimum split sum of surface areas is larger than the node's own surface area
|
||||
|
||||
//start with bisection into octrees because a bad bvh is still 1000x better than no bvh
|
||||
//sort the centerpoints on each axis (3 lists)
|
||||
//bv is put into octant based on whether it is upper or lower in each list
|
||||
|
||||
pub enum RecursiveContent<R,T>{
|
||||
Branch(Vec<R>),
|
||||
Leaf(T),
|
||||
}
|
||||
impl<R,T> Default for RecursiveContent<R,T>{
|
||||
fn default()->Self{
|
||||
Self::Branch(Vec::new())
|
||||
}
|
||||
}
|
||||
pub struct BvhNode<T>{
|
||||
content:RecursiveContent<BvhNode<T>,T>,
|
||||
aabb:Aabb,
|
||||
}
|
||||
impl<T> Default for BvhNode<T>{
|
||||
fn default()->Self{
|
||||
Self{
|
||||
content:Default::default(),
|
||||
aabb:Aabb::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
pub struct BvhWeightNode<W,T>{
|
||||
content:RecursiveContent<BvhWeightNode<W,T>,T>,
|
||||
weight:W,
|
||||
aabb:Aabb,
|
||||
}
|
||||
|
||||
impl<T> BvhNode<T>{
|
||||
pub fn the_tester<F:FnMut(&T)>(&self,aabb:&Aabb,f:&mut F){
|
||||
match &self.content{
|
||||
RecursiveContent::Leaf(model)=>f(model),
|
||||
RecursiveContent::Branch(children)=>for child in children{
|
||||
//this test could be moved outside the match statement
|
||||
//but that would test the root node aabb
|
||||
//you're probably not going to spend a lot of time outside the map,
|
||||
//so the test is extra work for nothing
|
||||
if aabb.intersects(&child.aabb){
|
||||
child.the_tester(aabb,f);
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
pub fn into_visitor<F:FnMut(T)>(self,f:&mut F){
|
||||
match self.content{
|
||||
RecursiveContent::Leaf(model)=>f(model),
|
||||
RecursiveContent::Branch(children)=>for child in children{
|
||||
child.into_visitor(f)
|
||||
},
|
||||
}
|
||||
}
|
||||
pub fn weigh_contents<W:Copy+std::iter::Sum<W>,F:Fn(&T)->W>(self,f:&F)->BvhWeightNode<W,T>{
|
||||
match self.content{
|
||||
RecursiveContent::Leaf(model)=>BvhWeightNode{
|
||||
weight:f(&model),
|
||||
content:RecursiveContent::Leaf(model),
|
||||
aabb:self.aabb,
|
||||
},
|
||||
RecursiveContent::Branch(children)=>{
|
||||
let branch:Vec<BvhWeightNode<W,T>>=children.into_iter().map(|child|
|
||||
child.weigh_contents(f)
|
||||
).collect();
|
||||
BvhWeightNode{
|
||||
weight:branch.iter().map(|node|node.weight).sum(),
|
||||
content:RecursiveContent::Branch(branch),
|
||||
aabb:self.aabb,
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl <W,T> BvhWeightNode<W,T>{
|
||||
pub const fn weight(&self)->&W{
|
||||
&self.weight
|
||||
}
|
||||
pub const fn aabb(&self)->&Aabb{
|
||||
&self.aabb
|
||||
}
|
||||
pub fn into_content(self)->RecursiveContent<BvhWeightNode<W,T>,T>{
|
||||
self.content
|
||||
}
|
||||
pub fn into_visitor<F:FnMut(T)>(self,f:&mut F){
|
||||
match self.content{
|
||||
RecursiveContent::Leaf(model)=>f(model),
|
||||
RecursiveContent::Branch(children)=>for child in children{
|
||||
child.into_visitor(f)
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn generate_bvh<T>(boxen:Vec<(T,Aabb)>)->BvhNode<T>{
|
||||
generate_bvh_node(boxen,false)
|
||||
}
|
||||
|
||||
fn generate_bvh_node<T>(boxen:Vec<(T,Aabb)>,force:bool)->BvhNode<T>{
|
||||
let n=boxen.len();
|
||||
if force||n<20{
|
||||
let mut aabb=Aabb::default();
|
||||
let nodes=boxen.into_iter().map(|b|{
|
||||
aabb.join(&b.1);
|
||||
BvhNode{
|
||||
content:RecursiveContent::Leaf(b.0),
|
||||
aabb:b.1,
|
||||
}
|
||||
}).collect();
|
||||
BvhNode{
|
||||
content:RecursiveContent::Branch(nodes),
|
||||
aabb,
|
||||
}
|
||||
}else{
|
||||
let mut sort_x=Vec::with_capacity(n);
|
||||
let mut sort_y=Vec::with_capacity(n);
|
||||
let mut sort_z=Vec::with_capacity(n);
|
||||
for (i,(_,aabb)) in boxen.iter().enumerate(){
|
||||
let center=aabb.center();
|
||||
sort_x.push((i,center.x));
|
||||
sort_y.push((i,center.y));
|
||||
sort_z.push((i,center.z));
|
||||
}
|
||||
sort_x.sort_by(|tup0,tup1|tup0.1.cmp(&tup1.1));
|
||||
sort_y.sort_by(|tup0,tup1|tup0.1.cmp(&tup1.1));
|
||||
sort_z.sort_by(|tup0,tup1|tup0.1.cmp(&tup1.1));
|
||||
let h=n/2;
|
||||
let median_x=sort_x[h].1;
|
||||
let median_y=sort_y[h].1;
|
||||
let median_z=sort_z[h].1;
|
||||
//locate a run of values equal to the median
|
||||
//partition point gives the first index for which the predicate evaluates to false
|
||||
let first_index_eq_median_x=sort_x.partition_point(|&(_,x)|x<median_x);
|
||||
let first_index_eq_median_y=sort_y.partition_point(|&(_,y)|y<median_y);
|
||||
let first_index_eq_median_z=sort_z.partition_point(|&(_,z)|z<median_z);
|
||||
let first_index_gt_median_x=sort_x.partition_point(|&(_,x)|x<=median_x);
|
||||
let first_index_gt_median_y=sort_y.partition_point(|&(_,y)|y<=median_y);
|
||||
let first_index_gt_median_z=sort_z.partition_point(|&(_,z)|z<=median_z);
|
||||
//pick which side median value copies go into such that both sides are as balanced as possible based on distance from n/2
|
||||
let partition_point_x=if n.abs_diff(2*first_index_eq_median_x)<n.abs_diff(2*first_index_gt_median_x){first_index_eq_median_x}else{first_index_gt_median_x};
|
||||
let partition_point_y=if n.abs_diff(2*first_index_eq_median_y)<n.abs_diff(2*first_index_gt_median_y){first_index_eq_median_y}else{first_index_gt_median_y};
|
||||
let partition_point_z=if n.abs_diff(2*first_index_eq_median_z)<n.abs_diff(2*first_index_gt_median_z){first_index_eq_median_z}else{first_index_gt_median_z};
|
||||
//this ids which octant the boxen is put in
|
||||
let mut octant=vec![0;n];
|
||||
for &(i,_) in &sort_x[partition_point_x..]{
|
||||
octant[i]+=1<<0;
|
||||
}
|
||||
for &(i,_) in &sort_y[partition_point_y..]{
|
||||
octant[i]+=1<<1;
|
||||
}
|
||||
for &(i,_) in &sort_z[partition_point_z..]{
|
||||
octant[i]+=1<<2;
|
||||
}
|
||||
//generate lists for unique octant values
|
||||
let mut list_list=Vec::with_capacity(8);
|
||||
let mut octant_list=Vec::with_capacity(8);
|
||||
for (i,(data,aabb)) in boxen.into_iter().enumerate(){
|
||||
let octant_id=octant[i];
|
||||
let list_id=if let Some(list_id)=octant_list.iter().position(|&id|id==octant_id){
|
||||
list_id
|
||||
}else{
|
||||
let list_id=list_list.len();
|
||||
octant_list.push(octant_id);
|
||||
list_list.push(Vec::new());
|
||||
list_id
|
||||
};
|
||||
list_list[list_id].push((data,aabb));
|
||||
}
|
||||
let mut aabb=Aabb::default();
|
||||
if list_list.len()==1{
|
||||
generate_bvh_node(list_list.remove(0),true)
|
||||
}else{
|
||||
BvhNode{
|
||||
content:RecursiveContent::Branch(
|
||||
list_list.into_iter().map(|b|{
|
||||
let node=generate_bvh_node(b,false);
|
||||
aabb.join(&node.aabb);
|
||||
node
|
||||
}).collect()
|
||||
),
|
||||
aabb,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,25 +0,0 @@
|
||||
bitflags::bitflags!{
|
||||
#[derive(Clone,Copy,Debug,Default)]
|
||||
pub struct Controls:u32{
|
||||
const MoveForward=1<<0;
|
||||
const MoveLeft=1<<1;
|
||||
const MoveBackward=1<<2;
|
||||
const MoveRight=1<<3;
|
||||
const MoveUp=1<<4;
|
||||
const MoveDown=1<<5;
|
||||
const LookUp=1<<6;
|
||||
const LookLeft=1<<7;
|
||||
const LookDown=1<<8;
|
||||
const LookRight=1<<9;
|
||||
const Jump=1<<10;
|
||||
const Crouch=1<<11;
|
||||
const Sprint=1<<12;
|
||||
const Zoom=1<<13;
|
||||
const Use=1<<14;//Interact with object
|
||||
const PrimaryAction=1<<15;//LBM/Shoot/Melee
|
||||
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();
|
||||
}
|
||||
}
|
@ -1,174 +0,0 @@
|
||||
use crate::model;
|
||||
use crate::integer::{AbsoluteTime,Planar64,Planar64Vec3};
|
||||
|
||||
//you have this effect while in contact
|
||||
#[derive(Clone,Hash,Eq,PartialEq)]
|
||||
pub struct ContactingLadder{
|
||||
pub sticky:bool
|
||||
}
|
||||
#[derive(Clone,Hash,Eq,PartialEq)]
|
||||
pub enum ContactingBehaviour{
|
||||
Surf,
|
||||
Ladder(ContactingLadder),
|
||||
NoJump,
|
||||
Cling,//usable as a zipline, or other weird and wonderful things
|
||||
Elastic(u32),//[1/2^32,1] 0=None (elasticity+1)/2^32
|
||||
}
|
||||
//you have this effect while intersecting
|
||||
#[derive(Clone,Hash,Eq,PartialEq)]
|
||||
pub struct IntersectingWater{
|
||||
pub viscosity:Planar64,
|
||||
pub density:Planar64,
|
||||
pub velocity:Planar64Vec3,
|
||||
}
|
||||
//All models can be given these attributes
|
||||
#[derive(Clone,Hash,Eq,PartialEq)]
|
||||
pub struct Accelerator{
|
||||
pub acceleration:Planar64Vec3
|
||||
}
|
||||
#[derive(Clone,Hash,Eq,PartialEq)]
|
||||
pub enum Booster{
|
||||
//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
|
||||
Energy{direction:Planar64Vec3,energy:Planar64},//increase energy in direction
|
||||
AirTime(AbsoluteTime),//increase airtime, invariant across mass and gravity changes
|
||||
Height(Planar64),//increase height, invariant across mass and gravity changes
|
||||
}
|
||||
impl Booster{
|
||||
pub fn boost(&self,velocity:Planar64Vec3)->Planar64Vec3{
|
||||
match self{
|
||||
&Booster::Velocity(boost_velocity)=>velocity+boost_velocity,
|
||||
&Booster::Energy{..}=>{
|
||||
todo!()
|
||||
//let d=direction.dot(velocity);
|
||||
//TODO: think about negative
|
||||
//velocity+direction.with_length((d*d+energy).sqrt()-d)
|
||||
},
|
||||
Booster::AirTime(_)=>todo!(),
|
||||
Booster::Height(_)=>todo!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
#[derive(Clone,Hash,Eq,PartialEq)]
|
||||
pub enum TrajectoryChoice{
|
||||
HighArcLongDuration,//underhand lob at target: less horizontal speed and more air time
|
||||
LowArcShortDuration,//overhand throw at target: more horizontal speed and less air time
|
||||
}
|
||||
#[derive(Clone,Hash,Eq,PartialEq)]
|
||||
pub enum SetTrajectory{
|
||||
//Speed-type SetTrajectory
|
||||
AirTime(AbsoluteTime),//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
|
||||
DotVelocity{direction:Planar64Vec3,dot:Planar64},//set your velocity in a specific direction without touching other directions
|
||||
//Velocity-type SetTrajectory
|
||||
TargetPointTime{//launch on a trajectory that will land at a target point in a set amount of time
|
||||
target_point:Planar64Vec3,
|
||||
time:AbsoluteTime,//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
|
||||
target_point:Planar64Vec3,
|
||||
speed:Planar64,//if speed is too low this will fail to reach the target. The closest-passing trajectory will be chosen instead
|
||||
trajectory_choice:TrajectoryChoice,
|
||||
},
|
||||
Velocity(Planar64Vec3),//SetVelocity
|
||||
}
|
||||
impl SetTrajectory{
|
||||
pub const fn is_absolute(&self)->bool{
|
||||
match self{
|
||||
SetTrajectory::AirTime(_)
|
||||
|SetTrajectory::Height(_)
|
||||
|SetTrajectory::DotVelocity{direction:_,dot:_}=>false,
|
||||
SetTrajectory::TargetPointTime{target_point:_,time:_}
|
||||
|SetTrajectory::TargetPointSpeed{target_point:_,speed:_,trajectory_choice:_}
|
||||
|SetTrajectory::Velocity(_)=>true,
|
||||
}
|
||||
}
|
||||
}
|
||||
// enum TrapCondition{
|
||||
// FasterThan(Planar64),
|
||||
// SlowerThan(Planar64),
|
||||
// InRange(Planar64,Planar64),
|
||||
// OutsideRange(Planar64,Planar64),
|
||||
// }
|
||||
#[derive(Clone,Hash,Eq,PartialEq)]
|
||||
pub struct Wormhole{
|
||||
//destination does not need to be another wormhole
|
||||
//this defines a one way portal to a destination model transform
|
||||
//two of these can create a two way wormhole
|
||||
pub destination_model:model::ModelId,
|
||||
//(position,angles)*=origin.transform.inverse()*destination.transform
|
||||
}
|
||||
//attributes listed in order of handling
|
||||
#[derive(Default,Clone,Hash,Eq,PartialEq)]
|
||||
pub struct GeneralAttributes{
|
||||
pub booster:Option<Booster>,
|
||||
pub trajectory:Option<SetTrajectory>,
|
||||
pub wormhole:Option<Wormhole>,
|
||||
pub accelerator:Option<Accelerator>,
|
||||
}
|
||||
impl GeneralAttributes{
|
||||
pub const fn any(&self)->bool{
|
||||
self.booster.is_some()
|
||||
||self.trajectory.is_some()
|
||||
||self.wormhole.is_some()
|
||||
||self.accelerator.is_some()
|
||||
}
|
||||
pub fn is_wrcp(&self)->bool{
|
||||
self.trajectory.as_ref().map_or(false,|t|t.is_absolute())
|
||||
/*
|
||||
&&match &self.teleport_behaviour{
|
||||
Some(TeleportBehaviour::StageElement(
|
||||
StageElement{
|
||||
mode_id,
|
||||
stage_id:_,
|
||||
force:true,
|
||||
behaviour:StageElementBehaviour::Trigger|StageElementBehaviour::Teleport
|
||||
}
|
||||
))=>current_mode_id==*mode_id,
|
||||
_=>false,
|
||||
}
|
||||
*/
|
||||
}
|
||||
}
|
||||
#[derive(Default,Clone,Hash,Eq,PartialEq)]
|
||||
pub struct ContactingAttributes{
|
||||
//friction?
|
||||
pub contact_behaviour:Option<ContactingBehaviour>,
|
||||
}
|
||||
impl ContactingAttributes{
|
||||
pub const fn any(&self)->bool{
|
||||
self.contact_behaviour.is_some()
|
||||
}
|
||||
}
|
||||
#[derive(Default,Clone,Hash,Eq,PartialEq)]
|
||||
pub struct IntersectingAttributes{
|
||||
pub water:Option<IntersectingWater>,
|
||||
}
|
||||
impl IntersectingAttributes{
|
||||
pub const fn any(&self)->bool{
|
||||
self.water.is_some()
|
||||
}
|
||||
}
|
||||
#[derive(Clone,Copy,id::Id,Hash,Eq,PartialEq)]
|
||||
pub struct CollisionAttributesId(u32);
|
||||
#[derive(Clone,Default,Hash,Eq,PartialEq)]
|
||||
pub struct ContactAttributes{
|
||||
pub contacting:ContactingAttributes,
|
||||
pub general:GeneralAttributes,
|
||||
}
|
||||
#[derive(Clone,Default,Hash,Eq,PartialEq)]
|
||||
pub struct IntersectAttributes{
|
||||
pub intersecting:IntersectingAttributes,
|
||||
pub general:GeneralAttributes,
|
||||
}
|
||||
#[derive(Clone,Hash,Eq,PartialEq)]
|
||||
pub enum CollisionAttributes{
|
||||
Decoration,//visual only
|
||||
Contact(ContactAttributes),//track whether you are contacting the object
|
||||
Intersect(IntersectAttributes),//track whether you are intersecting the object
|
||||
}
|
||||
impl CollisionAttributes{
|
||||
pub fn contact_default()->Self{
|
||||
Self::Contact(ContactAttributes::default())
|
||||
}
|
||||
}
|
@ -1,332 +0,0 @@
|
||||
use std::collections::{HashSet,HashMap};
|
||||
use crate::model::ModelId;
|
||||
use crate::gameplay_style;
|
||||
use crate::updatable::Updatable;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct StageElement{
|
||||
stage_id:StageId,//which stage spawn to send to
|
||||
force:bool,//allow setting to lower spawn id i.e. 7->3
|
||||
behaviour:StageElementBehaviour,
|
||||
jump_limit:Option<u8>,
|
||||
}
|
||||
impl StageElement{
|
||||
#[inline]
|
||||
pub const fn new(stage_id:StageId,force:bool,behaviour:StageElementBehaviour,jump_limit:Option<u8>)->Self{
|
||||
Self{
|
||||
stage_id,
|
||||
force,
|
||||
behaviour,
|
||||
jump_limit,
|
||||
}
|
||||
}
|
||||
#[inline]
|
||||
pub const fn stage_id(&self)->StageId{
|
||||
self.stage_id
|
||||
}
|
||||
#[inline]
|
||||
pub const fn force(&self)->bool{
|
||||
self.force
|
||||
}
|
||||
#[inline]
|
||||
pub const fn behaviour(&self)->StageElementBehaviour{
|
||||
self.behaviour
|
||||
}
|
||||
#[inline]
|
||||
pub const fn jump_limit(&self)->Option<u8>{
|
||||
self.jump_limit
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone,Copy,Hash,Eq,PartialEq)]
|
||||
pub enum StageElementBehaviour{
|
||||
SpawnAt,//must be standing on top to get effect. except cancollide false
|
||||
Trigger,
|
||||
Teleport,
|
||||
Platform,
|
||||
//Check(point) acts like a trigger if you haven't hit all the checkpoints on previous stages yet.
|
||||
//Note that all stage elements act like this, this is just the isolated behaviour.
|
||||
Check,
|
||||
Checkpoint,//this is a combined behaviour for Ordered & Unordered in case a model is used multiple times or for both.
|
||||
}
|
||||
|
||||
#[derive(Clone,Copy,Debug,Hash,id::Id,Eq,PartialEq)]
|
||||
pub struct CheckpointId(u32);
|
||||
impl CheckpointId{
|
||||
pub const FIRST:Self=Self(0);
|
||||
}
|
||||
#[derive(Clone,Copy,Debug,Hash,id::Id,Eq,PartialEq,Ord,PartialOrd)]
|
||||
pub struct StageId(u32);
|
||||
impl StageId{
|
||||
pub const FIRST:Self=Self(0);
|
||||
}
|
||||
#[derive(Clone)]
|
||||
pub struct Stage{
|
||||
spawn:ModelId,
|
||||
//open world support lol
|
||||
ordered_checkpoints_count:u32,
|
||||
unordered_checkpoints_count:u32,
|
||||
//currently loaded checkpoint models
|
||||
ordered_checkpoints:HashMap<CheckpointId,ModelId>,
|
||||
unordered_checkpoints:HashSet<ModelId>,
|
||||
}
|
||||
impl Stage{
|
||||
pub fn new(
|
||||
spawn:ModelId,
|
||||
ordered_checkpoints_count:u32,
|
||||
unordered_checkpoints_count:u32,
|
||||
ordered_checkpoints:HashMap<CheckpointId,ModelId>,
|
||||
unordered_checkpoints:HashSet<ModelId>,
|
||||
)->Self{
|
||||
Self{
|
||||
spawn,
|
||||
ordered_checkpoints_count,
|
||||
unordered_checkpoints_count,
|
||||
ordered_checkpoints,
|
||||
unordered_checkpoints,
|
||||
}
|
||||
}
|
||||
pub fn empty(spawn:ModelId)->Self{
|
||||
Self{
|
||||
spawn,
|
||||
ordered_checkpoints_count:0,
|
||||
unordered_checkpoints_count:0,
|
||||
ordered_checkpoints:HashMap::new(),
|
||||
unordered_checkpoints:HashSet::new(),
|
||||
}
|
||||
}
|
||||
#[inline]
|
||||
pub const fn spawn(&self)->ModelId{
|
||||
self.spawn
|
||||
}
|
||||
#[inline]
|
||||
pub const fn ordered_checkpoints_count(&self)->u32{
|
||||
self.ordered_checkpoints_count
|
||||
}
|
||||
#[inline]
|
||||
pub const fn unordered_checkpoints_count(&self)->u32{
|
||||
self.unordered_checkpoints_count
|
||||
}
|
||||
pub fn into_inner(self)->(HashMap<CheckpointId,ModelId>,HashSet<ModelId>){
|
||||
(self.ordered_checkpoints,self.unordered_checkpoints)
|
||||
}
|
||||
/// Returns true if the stage has no checkpoints.
|
||||
#[inline]
|
||||
pub const fn is_empty(&self)->bool{
|
||||
self.is_complete(0,0)
|
||||
}
|
||||
#[inline]
|
||||
pub const fn is_complete(&self,ordered_checkpoints_count:u32,unordered_checkpoints_count:u32)->bool{
|
||||
self.ordered_checkpoints_count==ordered_checkpoints_count&&self.unordered_checkpoints_count==unordered_checkpoints_count
|
||||
}
|
||||
#[inline]
|
||||
pub fn is_next_ordered_checkpoint(&self,next_ordered_checkpoint_id:CheckpointId,model_id:ModelId)->bool{
|
||||
self.ordered_checkpoints.get(&next_ordered_checkpoint_id).is_some_and(|&next_checkpoint|model_id==next_checkpoint)
|
||||
}
|
||||
#[inline]
|
||||
pub fn is_unordered_checkpoint(&self,model_id:ModelId)->bool{
|
||||
self.unordered_checkpoints.contains(&model_id)
|
||||
}
|
||||
}
|
||||
#[derive(Default)]
|
||||
pub struct StageUpdate{
|
||||
//other behaviour models of this stage can have
|
||||
ordered_checkpoints:HashMap<CheckpointId,ModelId>,
|
||||
unordered_checkpoints:HashSet<ModelId>,
|
||||
}
|
||||
impl Updatable<StageUpdate> for Stage{
|
||||
fn update(&mut self,update:StageUpdate){
|
||||
self.ordered_checkpoints.extend(update.ordered_checkpoints);
|
||||
self.unordered_checkpoints.extend(update.unordered_checkpoints);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone,Copy,Hash,Eq,PartialEq)]
|
||||
pub enum Zone{
|
||||
Start,
|
||||
Finish,
|
||||
Anticheat,
|
||||
}
|
||||
#[derive(Clone,Copy,Debug,Hash,id::Id,Eq,PartialEq,Ord,PartialOrd)]
|
||||
pub struct ModeId(u32);
|
||||
impl ModeId{
|
||||
pub const MAIN:Self=Self(0);
|
||||
pub const BONUS:Self=Self(1);
|
||||
}
|
||||
#[derive(Clone)]
|
||||
pub struct Mode{
|
||||
style:gameplay_style::StyleModifiers,
|
||||
start:ModelId,//when you press reset you go here
|
||||
zones:HashMap<ModelId,Zone>,
|
||||
stages:Vec<Stage>,//when you load the map you go to stages[0].spawn
|
||||
//mutually exlusive stage element behaviour
|
||||
elements:HashMap<ModelId,StageElement>,
|
||||
}
|
||||
impl Mode{
|
||||
pub fn new(
|
||||
style:gameplay_style::StyleModifiers,
|
||||
start:ModelId,
|
||||
zones:HashMap<ModelId,Zone>,
|
||||
stages:Vec<Stage>,
|
||||
elements:HashMap<ModelId,StageElement>,
|
||||
)->Self{
|
||||
Self{
|
||||
style,
|
||||
start,
|
||||
zones,
|
||||
stages,
|
||||
elements,
|
||||
}
|
||||
}
|
||||
pub fn empty(style:gameplay_style::StyleModifiers,start:ModelId)->Self{
|
||||
Self{
|
||||
style,
|
||||
start,
|
||||
zones:HashMap::new(),
|
||||
stages:Vec::new(),
|
||||
elements:HashMap::new(),
|
||||
}
|
||||
}
|
||||
pub fn into_inner(self)->(
|
||||
gameplay_style::StyleModifiers,
|
||||
ModelId,
|
||||
HashMap<ModelId,Zone>,
|
||||
Vec<Stage>,
|
||||
HashMap<ModelId,StageElement>,
|
||||
){
|
||||
(
|
||||
self.style,
|
||||
self.start,
|
||||
self.zones,
|
||||
self.stages,
|
||||
self.elements,
|
||||
)
|
||||
}
|
||||
pub const fn get_start(&self)->ModelId{
|
||||
self.start
|
||||
}
|
||||
pub const fn get_style(&self)->&gameplay_style::StyleModifiers{
|
||||
&self.style
|
||||
}
|
||||
pub fn push_stage(&mut self,stage:Stage){
|
||||
self.stages.push(stage)
|
||||
}
|
||||
pub fn get_stage_mut(&mut self,stage:StageId)->Option<&mut Stage>{
|
||||
self.stages.get_mut(stage.0 as usize)
|
||||
}
|
||||
pub fn get_spawn_model_id(&self,stage:StageId)->Option<ModelId>{
|
||||
self.stages.get(stage.0 as usize).map(|s|s.spawn)
|
||||
}
|
||||
pub fn get_zone(&self,model_id:ModelId)->Option<&Zone>{
|
||||
self.zones.get(&model_id)
|
||||
}
|
||||
pub fn get_stage(&self,stage_id:StageId)->Option<&Stage>{
|
||||
self.stages.get(stage_id.0 as usize)
|
||||
}
|
||||
pub fn get_element(&self,model_id:ModelId)->Option<&StageElement>{
|
||||
self.elements.get(&model_id)
|
||||
}
|
||||
//TODO: put this in the SNF
|
||||
pub fn denormalize_data(&mut self){
|
||||
//expand and index normalized data
|
||||
self.zones.insert(self.start,Zone::Start);
|
||||
for (stage_id,stage) in self.stages.iter().enumerate(){
|
||||
self.elements.insert(stage.spawn,StageElement{
|
||||
stage_id:StageId(stage_id as u32),
|
||||
force:false,
|
||||
behaviour:StageElementBehaviour::SpawnAt,
|
||||
jump_limit:None,
|
||||
});
|
||||
for (_,&model) in &stage.ordered_checkpoints{
|
||||
self.elements.insert(model,StageElement{
|
||||
stage_id:StageId(stage_id as u32),
|
||||
force:false,
|
||||
behaviour:StageElementBehaviour::Checkpoint,
|
||||
jump_limit:None,
|
||||
});
|
||||
}
|
||||
for &model in &stage.unordered_checkpoints{
|
||||
self.elements.insert(model,StageElement{
|
||||
stage_id:StageId(stage_id as u32),
|
||||
force:false,
|
||||
behaviour:StageElementBehaviour::Checkpoint,
|
||||
jump_limit:None,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
//this would be nice as a macro
|
||||
#[derive(Default)]
|
||||
pub struct ModeUpdate{
|
||||
zones:HashMap<ModelId,Zone>,
|
||||
stages:HashMap<StageId,StageUpdate>,
|
||||
//mutually exlusive stage element behaviour
|
||||
elements:HashMap<ModelId,StageElement>,
|
||||
}
|
||||
impl Updatable<ModeUpdate> for Mode{
|
||||
fn update(&mut self,update:ModeUpdate){
|
||||
self.zones.extend(update.zones);
|
||||
for (stage,stage_update) in update.stages{
|
||||
if let Some(stage)=self.stages.get_mut(stage.0 as usize){
|
||||
stage.update(stage_update);
|
||||
}
|
||||
}
|
||||
self.elements.extend(update.elements);
|
||||
}
|
||||
}
|
||||
impl ModeUpdate{
|
||||
pub fn zone(model_id:ModelId,zone:Zone)->Self{
|
||||
let mut mu=Self::default();
|
||||
mu.zones.insert(model_id,zone);
|
||||
mu
|
||||
}
|
||||
pub fn stage(stage_id:StageId,stage_update:StageUpdate)->Self{
|
||||
let mut mu=Self::default();
|
||||
mu.stages.insert(stage_id,stage_update);
|
||||
mu
|
||||
}
|
||||
pub fn element(model_id:ModelId,element:StageElement)->Self{
|
||||
let mut mu=Self::default();
|
||||
mu.elements.insert(model_id,element);
|
||||
mu
|
||||
}
|
||||
pub fn map_stage_element_ids<F:Fn(StageId)->StageId>(&mut self,f:F){
|
||||
for (_,stage_element) in self.elements.iter_mut(){
|
||||
stage_element.stage_id=f(stage_element.stage_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default,Clone)]
|
||||
pub struct Modes{
|
||||
pub modes:Vec<Mode>,
|
||||
}
|
||||
impl Modes{
|
||||
pub const fn new(modes:Vec<Mode>)->Self{
|
||||
Self{
|
||||
modes,
|
||||
}
|
||||
}
|
||||
pub fn into_inner(self)->Vec<Mode>{
|
||||
self.modes
|
||||
}
|
||||
pub fn push_mode(&mut self,mode:Mode){
|
||||
self.modes.push(mode)
|
||||
}
|
||||
pub fn get_mode(&self,mode:ModeId)->Option<&Mode>{
|
||||
self.modes.get(mode.0 as usize)
|
||||
}
|
||||
}
|
||||
pub struct ModesUpdate{
|
||||
modes:HashMap<ModeId,ModeUpdate>,
|
||||
}
|
||||
impl Updatable<ModesUpdate> for Modes{
|
||||
fn update(&mut self,update:ModesUpdate){
|
||||
for (mode,mode_update) in update.modes{
|
||||
if let Some(mode)=self.modes.get_mut(mode.0 as usize){
|
||||
mode.update(mode_update);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,612 +0,0 @@
|
||||
const VALVE_SCALE:Planar64=Planar64::raw(1<<28);// 1/16
|
||||
|
||||
use crate::integer::{int,vec3::int as int3,AbsoluteTime,Ratio64,Planar64,Planar64Vec3};
|
||||
use crate::controls_bitflag::Controls;
|
||||
use crate::physics::Time as PhysicsTime;
|
||||
|
||||
#[derive(Clone,Debug)]
|
||||
pub struct StyleModifiers{
|
||||
//controls which are allowed to pass into gameplay (usually all)
|
||||
pub controls_mask:Controls,
|
||||
//controls which are masked from control state (e.g. !jump in scroll style)
|
||||
pub controls_mask_state:Controls,
|
||||
//strafing
|
||||
pub strafe:Option<StrafeSettings>,
|
||||
//player gets a controllable rocket force
|
||||
pub rocket:Option<PropulsionSettings>,
|
||||
//flying
|
||||
//pub move_type:MoveType::Fly(FlySettings)
|
||||
//MoveType::Physics(PhysicsSettings) -> PhysicsSettings (strafe,rocket,jump,walk,ladder,swim,gravity)
|
||||
//jumping is allowed
|
||||
pub jump:Option<JumpSettings>,
|
||||
//standing & walking is allowed
|
||||
pub walk:Option<WalkSettings>,
|
||||
//laddering is allowed
|
||||
pub ladder:Option<LadderSettings>,
|
||||
//water propulsion
|
||||
pub swim:Option<PropulsionSettings>,
|
||||
//maximum slope before sloped surfaces become frictionless
|
||||
pub gravity:Planar64Vec3,
|
||||
//hitbox
|
||||
pub hitbox:Hitbox,
|
||||
//camera location relative to the center (0,0,0) of the hitbox
|
||||
pub camera_offset:Planar64Vec3,
|
||||
//unused
|
||||
pub mass:Planar64,
|
||||
}
|
||||
impl std::default::Default for StyleModifiers{
|
||||
fn default()->Self{
|
||||
Self::roblox_bhop()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone,Debug)]
|
||||
pub enum JumpCalculation{
|
||||
Max,//Roblox: jumped_speed=max(velocity.boost(),velocity.jump())
|
||||
BoostThenJump,//jumped_speed=velocity.boost().jump()
|
||||
JumpThenBoost,//jumped_speed=velocity.jump().boost()
|
||||
}
|
||||
|
||||
#[derive(Clone,Debug)]
|
||||
pub enum JumpImpulse{
|
||||
Time(AbsoluteTime),//jump time 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
|
||||
Energy(Planar64),// :)
|
||||
}
|
||||
//Jumping acts on dot(walks_state.normal,body.velocity)
|
||||
//Energy means it adds energy
|
||||
//Linear means it linearly adds on
|
||||
impl JumpImpulse{
|
||||
pub fn jump(
|
||||
&self,
|
||||
velocity:Planar64Vec3,
|
||||
jump_dir:Planar64Vec3,
|
||||
gravity:&Planar64Vec3,
|
||||
mass:Planar64,
|
||||
)->Planar64Vec3{
|
||||
match self{
|
||||
&JumpImpulse::Time(time)=>velocity-(*gravity*time).map(|t|t.divide().fix_1()),
|
||||
&JumpImpulse::Height(height)=>{
|
||||
//height==-v.y*v.y/(2*g.y);
|
||||
//use energy to determine max height
|
||||
let gg=gravity.length_squared();
|
||||
let g=gg.sqrt().fix_1();
|
||||
let v_g=gravity.dot(velocity);
|
||||
//do it backwards
|
||||
let radicand=v_g*v_g+(g*height*2).fix_4();
|
||||
velocity-(*gravity*(radicand.sqrt().fix_2()+v_g)/gg).divide().fix_1()
|
||||
},
|
||||
&JumpImpulse::Linear(jump_speed)=>velocity+(jump_dir*jump_speed/jump_dir.length()).divide().fix_1(),
|
||||
&JumpImpulse::Energy(energy)=>{
|
||||
//calculate energy
|
||||
//let e=gravity.dot(velocity);
|
||||
//add
|
||||
//you get the idea
|
||||
todo!()
|
||||
},
|
||||
}
|
||||
}
|
||||
//TODO: remove this and implement JumpCalculation properly
|
||||
pub fn get_jump_deltav(&self,gravity:&Planar64Vec3,mass:Planar64)->Planar64{
|
||||
//gravity.length() is actually the proper calculation because the jump is always opposite the gravity direction
|
||||
match self{
|
||||
&JumpImpulse::Time(time)=>(gravity.length().fix_1()*time/2).divide().fix_1(),
|
||||
&JumpImpulse::Height(height)=>(gravity.length()*height*2).sqrt().fix_1(),
|
||||
&JumpImpulse::Linear(deltav)=>deltav,
|
||||
&JumpImpulse::Energy(energy)=>(energy.sqrt()*2/mass.sqrt()).divide().fix_1(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone,Debug)]
|
||||
pub struct JumpSettings{
|
||||
//information used to calculate jump power
|
||||
pub impulse:JumpImpulse,
|
||||
//information used to calculate jump behaviour
|
||||
pub calculation:JumpCalculation,
|
||||
//limit the minimum jump power when combined with downwards momentum
|
||||
//This is true in both roblox and source
|
||||
pub limit_minimum:bool,
|
||||
}
|
||||
impl JumpSettings{
|
||||
pub fn jumped_velocity(
|
||||
&self,
|
||||
style:&StyleModifiers,
|
||||
jump_dir:Planar64Vec3,
|
||||
rel_velocity:Planar64Vec3,
|
||||
booster:Option<&crate::gameplay_attributes::Booster>,
|
||||
)->Planar64Vec3{
|
||||
let jump_speed=self.impulse.get_jump_deltav(&style.gravity,style.mass);
|
||||
match (self.limit_minimum,&self.calculation){
|
||||
(true,JumpCalculation::Max)=>{
|
||||
//the roblox calculation
|
||||
let boost_vel=match booster{
|
||||
Some(booster)=>booster.boost(rel_velocity),
|
||||
None=>rel_velocity,
|
||||
};
|
||||
let j=boost_vel.dot(jump_dir);
|
||||
let js=jump_speed.fix_2();
|
||||
if j<js{
|
||||
//weak booster: just do a regular jump
|
||||
boost_vel+jump_dir.with_length(js-j).divide().fix_1()
|
||||
}else{
|
||||
//activate booster normally, jump does nothing
|
||||
boost_vel
|
||||
}
|
||||
},
|
||||
(true,_)=>{
|
||||
//the source calculation (?)
|
||||
let boost_vel=match booster{
|
||||
Some(booster)=>booster.boost(rel_velocity),
|
||||
None=>rel_velocity,
|
||||
};
|
||||
let j=boost_vel.dot(jump_dir);
|
||||
let js=jump_speed.fix_2();
|
||||
if j<js{
|
||||
//speed in direction of jump cannot be lower than amount
|
||||
boost_vel+jump_dir.with_length(js-j).divide().fix_1()
|
||||
}else{
|
||||
//boost and jump add together
|
||||
boost_vel+jump_dir.with_length(js).divide().fix_1()
|
||||
}
|
||||
}
|
||||
(false,JumpCalculation::Max)=>{
|
||||
//??? calculation
|
||||
//max(boost_vel,jump_vel)
|
||||
let boost_vel=match booster{
|
||||
Some(booster)=>booster.boost(rel_velocity),
|
||||
None=>rel_velocity,
|
||||
};
|
||||
let boost_dot=boost_vel.dot(jump_dir);
|
||||
let js=jump_speed.fix_2();
|
||||
if boost_dot<js{
|
||||
//weak boost is extended to jump speed
|
||||
boost_vel+jump_dir.with_length(js-boost_dot).divide().fix_1()
|
||||
}else{
|
||||
//activate booster normally, jump does nothing
|
||||
boost_vel
|
||||
}
|
||||
},
|
||||
//the strafe client calculation
|
||||
(false,_)=>{
|
||||
let boost_vel=match booster{
|
||||
Some(booster)=>booster.boost(rel_velocity),
|
||||
None=>rel_velocity,
|
||||
};
|
||||
boost_vel+jump_dir.with_length(jump_speed).divide().fix_1()
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone,Debug)]
|
||||
pub struct ControlsActivation{
|
||||
//allowed keys
|
||||
pub controls_mask:Controls,
|
||||
//allow strafing only if any of the masked controls are held, eg W|S for shsw
|
||||
pub controls_intersects:Controls,
|
||||
//allow strafing only if all of the masked controls are held, eg W for hsw, w-only
|
||||
pub controls_contains:Controls,
|
||||
//Function(Box<dyn Fn(u32)->bool>),
|
||||
}
|
||||
impl ControlsActivation{
|
||||
pub const fn mask(&self,controls:Controls)->Controls{
|
||||
controls.intersection(self.controls_mask)
|
||||
}
|
||||
pub const fn activates(&self,controls:Controls)->bool{
|
||||
(self.controls_intersects.is_empty()||controls.intersects(self.controls_intersects))
|
||||
&&controls.contains(self.controls_contains)
|
||||
}
|
||||
pub const fn full_3d()->Self{
|
||||
Self{
|
||||
controls_mask:Controls::WASDQE,
|
||||
controls_intersects:Controls::WASDQE,
|
||||
controls_contains:Controls::empty(),
|
||||
}
|
||||
}
|
||||
//classical styles
|
||||
//Normal
|
||||
pub const fn full_2d()->Self{
|
||||
Self{
|
||||
controls_mask:Controls::WASD,
|
||||
controls_intersects:Controls::WASD,
|
||||
controls_contains:Controls::empty(),
|
||||
}
|
||||
}
|
||||
//Sideways
|
||||
pub const fn sideways()->Self{
|
||||
Self{
|
||||
controls_mask:Controls::MoveForward.union(Controls::MoveBackward),
|
||||
controls_intersects:Controls::MoveForward.union(Controls::MoveBackward),
|
||||
controls_contains:Controls::empty(),
|
||||
}
|
||||
}
|
||||
//Half-Sideways
|
||||
pub const fn half_sideways()->Self{
|
||||
Self{
|
||||
controls_mask:Controls::MoveForward.union(Controls::MoveLeft).union(Controls::MoveRight),
|
||||
controls_intersects:Controls::MoveLeft.union(Controls::MoveRight),
|
||||
controls_contains:Controls::MoveForward,
|
||||
}
|
||||
}
|
||||
//Surf Half-Sideways
|
||||
pub const fn surf_half_sideways()->Self{
|
||||
Self{
|
||||
controls_mask:Controls::MoveForward.union(Controls::MoveBackward).union(Controls::MoveLeft).union(Controls::MoveRight),
|
||||
controls_intersects:Controls::MoveForward.union(Controls::MoveBackward),
|
||||
controls_contains:Controls::empty(),
|
||||
}
|
||||
}
|
||||
//W-Only
|
||||
pub const fn w_only()->Self{
|
||||
Self{
|
||||
controls_mask:Controls::MoveForward,
|
||||
controls_intersects:Controls::empty(),
|
||||
controls_contains:Controls::MoveForward,
|
||||
}
|
||||
}
|
||||
//A-Only
|
||||
pub const fn a_only()->Self{
|
||||
Self{
|
||||
controls_mask:Controls::MoveLeft,
|
||||
controls_intersects:Controls::empty(),
|
||||
controls_contains:Controls::MoveLeft,
|
||||
}
|
||||
}
|
||||
//Backwards
|
||||
}
|
||||
|
||||
#[derive(Clone,Debug)]
|
||||
pub struct StrafeSettings{
|
||||
pub enable:ControlsActivation,
|
||||
pub mv:Planar64,
|
||||
pub air_accel_limit:Option<Planar64>,
|
||||
pub tick_rate:Ratio64,
|
||||
}
|
||||
impl StrafeSettings{
|
||||
pub fn tick_velocity(&self,velocity:Planar64Vec3,control_dir:Planar64Vec3)->Option<Planar64Vec3>{
|
||||
let d=velocity.dot(control_dir);
|
||||
let mv=self.mv.fix_2();
|
||||
match d<mv{
|
||||
true=>Some(velocity+(control_dir*self.air_accel_limit.map_or(mv-d,|limit|limit.fix_2().min(mv-d))).fix_1()),
|
||||
false=>None,
|
||||
}
|
||||
}
|
||||
pub fn next_tick(&self,time:PhysicsTime)->PhysicsTime{
|
||||
PhysicsTime::from_nanos(self.tick_rate.rhs_div_int(self.tick_rate.mul_int(time.nanos())+1))
|
||||
}
|
||||
pub const fn activates(&self,controls:Controls)->bool{
|
||||
self.enable.activates(controls)
|
||||
}
|
||||
pub const fn mask(&self,controls:Controls)->Controls{
|
||||
self.enable.mask(controls)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone,Debug)]
|
||||
pub struct PropulsionSettings{
|
||||
pub magnitude:Planar64,
|
||||
}
|
||||
impl PropulsionSettings{
|
||||
pub fn acceleration(&self,control_dir:Planar64Vec3)->Planar64Vec3{
|
||||
(control_dir*self.magnitude).fix_1()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone,Debug)]
|
||||
pub struct AccelerateSettings{
|
||||
pub accel:Planar64,
|
||||
pub topspeed:Planar64,
|
||||
}
|
||||
#[derive(Clone,Debug)]
|
||||
pub struct WalkSettings{
|
||||
pub accelerate:AccelerateSettings,
|
||||
pub static_friction:Planar64,
|
||||
pub kinetic_friction:Planar64,
|
||||
//if a surf slope angle does not exist, then everything is slippery and walking is impossible
|
||||
pub surf_dot:Planar64,//surf_dot<n.dot(up)/n.length()
|
||||
}
|
||||
impl WalkSettings{
|
||||
pub fn accel(&self,target_diff:Planar64Vec3,gravity:Planar64Vec3)->Planar64{
|
||||
//TODO: fallible walk accel
|
||||
let diff_len=target_diff.length().fix_1();
|
||||
let friction=if diff_len<self.accelerate.topspeed{
|
||||
self.static_friction
|
||||
}else{
|
||||
self.kinetic_friction
|
||||
};
|
||||
self.accelerate.accel.min((-gravity.y*friction).fix_1())
|
||||
}
|
||||
pub fn get_walk_target_velocity(&self,control_dir:Planar64Vec3,normal:Planar64Vec3)->Planar64Vec3{
|
||||
if control_dir==crate::integer::vec3::ZERO{
|
||||
return control_dir;
|
||||
}
|
||||
let nn=normal.length_squared();
|
||||
let mm=control_dir.length_squared();
|
||||
let nnmm=nn*mm;
|
||||
let d=normal.dot(control_dir);
|
||||
let dd=d*d;
|
||||
if dd<nnmm{
|
||||
let cr=normal.cross(control_dir);
|
||||
if cr==crate::integer::vec3::ZERO_2{
|
||||
crate::integer::vec3::ZERO
|
||||
}else{
|
||||
(cr.cross(normal)*self.accelerate.topspeed/((nn*(nnmm-dd)).sqrt())).divide().fix_1()
|
||||
}
|
||||
}else{
|
||||
crate::integer::vec3::ZERO
|
||||
}
|
||||
}
|
||||
pub fn is_slope_walkable(&self,normal:Planar64Vec3,up:Planar64Vec3)->bool{
|
||||
//normal is not guaranteed to be unit length
|
||||
let ny=normal.dot(up);
|
||||
let h=normal.length().fix_1();
|
||||
//remember this is a normal vector
|
||||
ny.is_positive()&&h*self.surf_dot<ny
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone,Debug)]
|
||||
pub struct LadderSettings{
|
||||
pub accelerate:AccelerateSettings,
|
||||
//how close to pushing directly into/out of the ladder normal
|
||||
//does your input need to be to redirect straight up/down the ladder
|
||||
pub dot:Planar64,
|
||||
}
|
||||
impl LadderSettings{
|
||||
pub const fn accel(&self,target_diff:Planar64Vec3,gravity:Planar64Vec3)->Planar64{
|
||||
//TODO: fallible ladder accel
|
||||
self.accelerate.accel
|
||||
}
|
||||
pub fn get_ladder_target_velocity(&self,mut control_dir:Planar64Vec3,normal:Planar64Vec3)->Planar64Vec3{
|
||||
if control_dir==crate::integer::vec3::ZERO{
|
||||
return control_dir;
|
||||
}
|
||||
let nn=normal.length_squared();
|
||||
let mm=control_dir.length_squared();
|
||||
let nnmm=nn*mm;
|
||||
let d=normal.dot(control_dir);
|
||||
let mut dd=d*d;
|
||||
if (self.dot*self.dot*nnmm).fix_4()<dd{
|
||||
if d.is_negative(){
|
||||
control_dir=Planar64Vec3::new([Planar64::ZERO,mm.fix_1(),Planar64::ZERO]);
|
||||
}else{
|
||||
control_dir=Planar64Vec3::new([Planar64::ZERO,-mm.fix_1(),Planar64::ZERO]);
|
||||
}
|
||||
dd=(normal.y*normal.y).fix_4();
|
||||
}
|
||||
//n=d if you are standing on top of a ladder and press E.
|
||||
//two fixes:
|
||||
//- ladder movement is not allowed on walkable surfaces
|
||||
//- fix the underlying issue
|
||||
if dd<nnmm{
|
||||
let cr=normal.cross(control_dir);
|
||||
if cr==crate::integer::vec3::ZERO_2{
|
||||
crate::integer::vec3::ZERO
|
||||
}else{
|
||||
(cr.cross(normal)*self.accelerate.topspeed/((nn*(nnmm-dd)).sqrt())).divide().fix_1()
|
||||
}
|
||||
}else{
|
||||
crate::integer::vec3::ZERO
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone,Debug)]
|
||||
pub enum HitboxMesh{
|
||||
Box,//source
|
||||
Cylinder,//roblox
|
||||
//Sphere,//roblox old physics
|
||||
//Point,
|
||||
//Line,
|
||||
//DualCone,
|
||||
}
|
||||
|
||||
#[derive(Clone,Debug)]
|
||||
pub struct Hitbox{
|
||||
pub halfsize:Planar64Vec3,
|
||||
pub mesh:HitboxMesh,
|
||||
}
|
||||
impl Hitbox{
|
||||
pub fn roblox()->Self{
|
||||
Self{
|
||||
halfsize:int3(2,5,2)>>1,
|
||||
mesh:HitboxMesh::Cylinder,
|
||||
}
|
||||
}
|
||||
pub fn source()->Self{
|
||||
Self{
|
||||
halfsize:((int3(33,73,33)>>1)*VALVE_SCALE).fix_1(),
|
||||
mesh:HitboxMesh::Box,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl StyleModifiers{
|
||||
pub const RIGHT_DIR:Planar64Vec3=crate::integer::vec3::X;
|
||||
pub const UP_DIR:Planar64Vec3=crate::integer::vec3::Y;
|
||||
pub const FORWARD_DIR:Planar64Vec3=crate::integer::vec3::NEG_Z;
|
||||
|
||||
pub fn neo()->Self{
|
||||
Self{
|
||||
controls_mask:Controls::all(),
|
||||
controls_mask_state:Controls::all(),
|
||||
strafe:Some(StrafeSettings{
|
||||
enable:ControlsActivation::full_2d(),
|
||||
air_accel_limit:None,
|
||||
mv:int(3),
|
||||
tick_rate:Ratio64::new(64,AbsoluteTime::ONE_SECOND.get() as u64).unwrap(),
|
||||
}),
|
||||
jump:Some(JumpSettings{
|
||||
impulse:JumpImpulse::Energy(int(512)),
|
||||
calculation:JumpCalculation::JumpThenBoost,
|
||||
limit_minimum:false,
|
||||
}),
|
||||
gravity:int3(0,-80,0),
|
||||
mass:int(1),
|
||||
rocket:None,
|
||||
walk:Some(WalkSettings{
|
||||
accelerate:AccelerateSettings{
|
||||
topspeed:int(16),
|
||||
accel:int(80),
|
||||
},
|
||||
static_friction:int(2),
|
||||
kinetic_friction:int(3),//unrealistic: kinetic friction is typically lower than static
|
||||
surf_dot:int(3)/4,
|
||||
}),
|
||||
ladder:Some(LadderSettings{
|
||||
accelerate:AccelerateSettings{
|
||||
topspeed:int(16),
|
||||
accel:int(160),
|
||||
},
|
||||
dot:(int(1)/2).sqrt(),
|
||||
}),
|
||||
swim:Some(PropulsionSettings{
|
||||
magnitude:int(12),
|
||||
}),
|
||||
hitbox:Hitbox::roblox(),
|
||||
camera_offset:int3(0,2,0),//4.5-2.5=2
|
||||
}
|
||||
}
|
||||
|
||||
pub fn roblox_bhop()->Self{
|
||||
Self{
|
||||
controls_mask:Controls::all(),
|
||||
controls_mask_state:Controls::all(),
|
||||
strafe:Some(StrafeSettings{
|
||||
enable:ControlsActivation::full_2d(),
|
||||
air_accel_limit:None,
|
||||
mv:int(27)/10,
|
||||
tick_rate:Ratio64::new(100,AbsoluteTime::ONE_SECOND.get() as u64).unwrap(),
|
||||
}),
|
||||
jump:Some(JumpSettings{
|
||||
impulse:JumpImpulse::Time(AbsoluteTime::from_micros(715_588)),
|
||||
calculation:JumpCalculation::Max,
|
||||
limit_minimum:true,
|
||||
}),
|
||||
gravity:int3(0,-100,0),
|
||||
mass:int(1),
|
||||
rocket:None,
|
||||
walk:Some(WalkSettings{
|
||||
accelerate:AccelerateSettings{
|
||||
topspeed:int(18),
|
||||
accel:int(90),
|
||||
},
|
||||
static_friction:int(2),
|
||||
kinetic_friction:int(3),//unrealistic: kinetic friction is typically lower than static
|
||||
surf_dot:int(3)/4,// normal.y=0.75
|
||||
}),
|
||||
ladder:Some(LadderSettings{
|
||||
accelerate:AccelerateSettings{
|
||||
topspeed:int(18),
|
||||
accel:int(180),
|
||||
},
|
||||
dot:(int(1)/2).sqrt(),
|
||||
}),
|
||||
swim:Some(PropulsionSettings{
|
||||
magnitude:int(12),
|
||||
}),
|
||||
hitbox:Hitbox::roblox(),
|
||||
camera_offset:int3(0,2,0),//4.5-2.5=2
|
||||
}
|
||||
}
|
||||
pub fn roblox_surf()->Self{
|
||||
Self{
|
||||
gravity:int3(0,-50,0),
|
||||
..Self::roblox_bhop()
|
||||
}
|
||||
}
|
||||
pub fn roblox_rocket()->Self{
|
||||
Self{
|
||||
strafe:None,
|
||||
rocket:Some(PropulsionSettings{
|
||||
magnitude:int(200),
|
||||
}),
|
||||
..Self::roblox_bhop()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn source_bhop()->Self{
|
||||
Self{
|
||||
controls_mask:Controls::all()-Controls::MoveUp-Controls::MoveDown,
|
||||
controls_mask_state:Controls::all(),
|
||||
strafe:Some(StrafeSettings{
|
||||
enable:ControlsActivation::full_2d(),
|
||||
air_accel_limit:Some(Planar64::raw(150<<28)*100),
|
||||
mv:(Planar64::raw(30)*VALVE_SCALE).fix_1(),
|
||||
tick_rate:Ratio64::new(100,AbsoluteTime::ONE_SECOND.get() as u64).unwrap(),
|
||||
}),
|
||||
jump:Some(JumpSettings{
|
||||
impulse:JumpImpulse::Height((int(52)*VALVE_SCALE).fix_1()),
|
||||
calculation:JumpCalculation::JumpThenBoost,
|
||||
limit_minimum:true,
|
||||
}),
|
||||
gravity:(int3(0,-800,0)*VALVE_SCALE).fix_1(),
|
||||
mass:int(1),
|
||||
rocket:None,
|
||||
walk:Some(WalkSettings{
|
||||
accelerate:AccelerateSettings{
|
||||
topspeed:int(18),//?
|
||||
accel:int(90),//?
|
||||
},
|
||||
static_friction:int(2),//?
|
||||
kinetic_friction:int(3),//?
|
||||
surf_dot:int(3)/4,// normal.y=0.75
|
||||
}),
|
||||
ladder:Some(LadderSettings{
|
||||
accelerate:AccelerateSettings{
|
||||
topspeed:int(18),//?
|
||||
accel:int(180),//?
|
||||
},
|
||||
dot:(int(1)/2).sqrt(),//?
|
||||
}),
|
||||
swim:Some(PropulsionSettings{
|
||||
magnitude:int(12),//?
|
||||
}),
|
||||
hitbox:Hitbox::source(),
|
||||
camera_offset:((int3(0,64,0)-(int3(0,73,0)>>1))*VALVE_SCALE).fix_1(),
|
||||
}
|
||||
}
|
||||
pub fn source_surf()->Self{
|
||||
Self{
|
||||
controls_mask:Controls::all()-Controls::MoveUp-Controls::MoveDown,
|
||||
controls_mask_state:Controls::all(),
|
||||
strafe:Some(StrafeSettings{
|
||||
enable:ControlsActivation::full_2d(),
|
||||
air_accel_limit:Some((int(150)*66*VALVE_SCALE).fix_1()),
|
||||
mv:(int(30)*VALVE_SCALE).fix_1(),
|
||||
tick_rate:Ratio64::new(66,AbsoluteTime::ONE_SECOND.get() as u64).unwrap(),
|
||||
}),
|
||||
jump:Some(JumpSettings{
|
||||
impulse:JumpImpulse::Height((int(52)*VALVE_SCALE).fix_1()),
|
||||
calculation:JumpCalculation::JumpThenBoost,
|
||||
limit_minimum:true,
|
||||
}),
|
||||
gravity:(int3(0,-800,0)*VALVE_SCALE).fix_1(),
|
||||
mass:int(1),
|
||||
rocket:None,
|
||||
walk:Some(WalkSettings{
|
||||
accelerate:AccelerateSettings{
|
||||
topspeed:int(18),//?
|
||||
accel:int(90),//?
|
||||
},
|
||||
static_friction:int(2),//?
|
||||
kinetic_friction:int(3),//?
|
||||
surf_dot:int(3)/4,// normal.y=0.75
|
||||
}),
|
||||
ladder:Some(LadderSettings{
|
||||
accelerate:AccelerateSettings{
|
||||
topspeed:int(18),//?
|
||||
accel:int(180),//?
|
||||
},
|
||||
dot:(int(1)/2).sqrt(),//?
|
||||
}),
|
||||
swim:Some(PropulsionSettings{
|
||||
magnitude:int(12),//?
|
||||
}),
|
||||
hitbox:Hitbox::source(),
|
||||
camera_offset:((int3(0,64,0)-(int3(0,73,0)>>1))*VALVE_SCALE).fix_1(),
|
||||
}
|
||||
}
|
||||
}
|
@ -1,82 +0,0 @@
|
||||
use crate::integer::Time;
|
||||
|
||||
#[derive(Clone,Debug)]
|
||||
pub struct TimedInstruction<I,T>{
|
||||
pub time:Time<T>,
|
||||
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>{
|
||||
type TimeInner;
|
||||
fn next_instruction(&self,time_limit:Time<Self::TimeInner>)->Option<TimedInstruction<I,Self::TimeInner>>;
|
||||
}
|
||||
/// Apply an atomic state update
|
||||
pub trait InstructionConsumer<I>{
|
||||
type TimeInner;
|
||||
fn process_instruction(&mut self,instruction:TimedInstruction<I,Self::TimeInner>);
|
||||
}
|
||||
/// If the object produces its own instructions, allow exhaustively feeding them back in
|
||||
pub trait InstructionFeedback<I,T>:InstructionEmitter<I,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!!!
|
||||
pub struct InstructionCollector<I,T>{
|
||||
time:Time<T>,
|
||||
instruction:Option<I>,
|
||||
}
|
||||
impl<I,T> InstructionCollector<I,T>
|
||||
where Time<T>:Copy+PartialOrd,
|
||||
{
|
||||
#[inline]
|
||||
pub const fn new(time:Time<T>)->Self{
|
||||
Self{
|
||||
time,
|
||||
instruction:None
|
||||
}
|
||||
}
|
||||
#[inline]
|
||||
pub const fn time(&self)->Time<T>{
|
||||
self.time
|
||||
}
|
||||
#[inline]
|
||||
pub fn collect(&mut self,instruction:Option<TimedInstruction<I,T>>){
|
||||
if let Some(ins)=instruction{
|
||||
if ins.time<self.time{
|
||||
self.time=ins.time;
|
||||
self.instruction=Some(ins.instruction);
|
||||
}
|
||||
}
|
||||
}
|
||||
#[inline]
|
||||
pub fn take(self)->Option<TimedInstruction<I,T>>{
|
||||
//STEAL INSTRUCTION AND DESTROY INSTRUCTIONCOLLECTOR
|
||||
self.instruction.map(|instruction|TimedInstruction{
|
||||
time:self.time,
|
||||
instruction
|
||||
})
|
||||
}
|
||||
}
|
@ -1,680 +0,0 @@
|
||||
pub use fixed_wide::fixed::{Fixed,Fix};
|
||||
pub use ratio_ops::ratio::{Ratio,Divide};
|
||||
|
||||
//integer units
|
||||
|
||||
/// specific example of a "default" time type
|
||||
#[derive(Clone,Copy,Hash,Eq,PartialEq,PartialOrd,Debug)]
|
||||
pub enum TimeInner{}
|
||||
pub type AbsoluteTime=Time<TimeInner>;
|
||||
|
||||
#[derive(Clone,Copy,Hash,Eq,PartialEq,PartialOrd,Debug)]
|
||||
pub struct Time<T>(i64,core::marker::PhantomData<T>);
|
||||
impl<T> Time<T>{
|
||||
pub const MIN:Self=Self::raw(i64::MIN);
|
||||
pub const MAX:Self=Self::raw(i64::MAX);
|
||||
pub const ZERO:Self=Self::raw(0);
|
||||
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]
|
||||
pub const fn raw(num:i64)->Self{
|
||||
Self(num,core::marker::PhantomData)
|
||||
}
|
||||
#[inline]
|
||||
pub const fn get(self)->i64{
|
||||
self.0
|
||||
}
|
||||
#[inline]
|
||||
pub const fn from_secs(num:i64)->Self{
|
||||
Self::raw(Self::ONE_SECOND.0*num)
|
||||
}
|
||||
#[inline]
|
||||
pub const fn from_millis(num:i64)->Self{
|
||||
Self::raw(Self::ONE_MILLISECOND.0*num)
|
||||
}
|
||||
#[inline]
|
||||
pub const fn from_micros(num:i64)->Self{
|
||||
Self::raw(Self::ONE_MICROSECOND.0*num)
|
||||
}
|
||||
#[inline]
|
||||
pub const fn from_nanos(num:i64)->Self{
|
||||
Self::raw(Self::ONE_NANOSECOND.0*num)
|
||||
}
|
||||
//should I have checked subtraction? force all time variables to be positive?
|
||||
#[inline]
|
||||
pub const fn nanos(self)->i64{
|
||||
self.0
|
||||
}
|
||||
#[inline]
|
||||
pub const fn to_ratio(self)->Ratio<Planar64,Planar64>{
|
||||
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<T> From<Planar64> for Time<T>{
|
||||
#[inline]
|
||||
fn from(value:Planar64)->Self{
|
||||
Self::raw((value*Planar64::raw(1_000_000_000)).fix_1().to_raw())
|
||||
}
|
||||
}
|
||||
impl<T,Num,Den,N1,T1> From<Ratio<Num,Den>> for Time<T>
|
||||
where
|
||||
Num:core::ops::Mul<Planar64,Output=N1>,
|
||||
N1:Divide<Den,Output=T1>,
|
||||
T1:Fix<Planar64>,
|
||||
{
|
||||
#[inline]
|
||||
fn from(value:Ratio<Num,Den>)->Self{
|
||||
Self::raw((value*Planar64::raw(1_000_000_000)).divide().fix().to_raw())
|
||||
}
|
||||
}
|
||||
impl<T> std::fmt::Display for Time<T>{
|
||||
#[inline]
|
||||
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)
|
||||
}
|
||||
}
|
||||
impl<T> std::default::Default for Time<T>{
|
||||
fn default()->Self{
|
||||
Self::raw(0)
|
||||
}
|
||||
}
|
||||
impl<T> std::ops::Neg for Time<T>{
|
||||
type Output=Self;
|
||||
#[inline]
|
||||
fn neg(self)->Self::Output {
|
||||
Self::raw(-self.0)
|
||||
}
|
||||
}
|
||||
macro_rules! impl_time_additive_operator {
|
||||
($trait:ty, $method:ident) => {
|
||||
impl<T> $trait for Time<T>{
|
||||
type Output=Self;
|
||||
#[inline]
|
||||
fn $method(self,rhs:Self)->Self::Output {
|
||||
Self::raw(self.0.$method(rhs.0))
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
impl_time_additive_operator!(core::ops::Add,add);
|
||||
impl_time_additive_operator!(core::ops::Sub,sub);
|
||||
impl_time_additive_operator!(core::ops::Rem,rem);
|
||||
macro_rules! impl_time_additive_assign_operator {
|
||||
($trait:ty, $method:ident) => {
|
||||
impl<T> $trait for Time<T>{
|
||||
#[inline]
|
||||
fn $method(&mut self,rhs:Self){
|
||||
self.0.$method(rhs.0)
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
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::RemAssign,rem_assign);
|
||||
impl<T> std::ops::Mul for Time<T>{
|
||||
type Output=Ratio<fixed_wide::fixed::Fixed<2,64>,fixed_wide::fixed::Fixed<2,64>>;
|
||||
#[inline]
|
||||
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)))
|
||||
}
|
||||
}
|
||||
impl<T> std::ops::Div<i64> for Time<T>{
|
||||
type Output=Self;
|
||||
#[inline]
|
||||
fn div(self,rhs:i64)->Self::Output{
|
||||
Self::raw(self.0/rhs)
|
||||
}
|
||||
}
|
||||
impl<T> std::ops::Mul<i64> for Time<T>{
|
||||
type Output=Self;
|
||||
#[inline]
|
||||
fn mul(self,rhs:i64)->Self::Output{
|
||||
Self::raw(self.0*rhs)
|
||||
}
|
||||
}
|
||||
impl<T> core::ops::Mul<Time<T>> for Planar64{
|
||||
type Output=Ratio<Fixed<2,64>,Planar64>;
|
||||
fn mul(self,rhs:Time<T>)->Self::Output{
|
||||
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]
|
||||
fn time_from_planar64(){
|
||||
let a:Time=Planar64::from(1).into();
|
||||
assert_eq!(a,Time::ONE_SECOND);
|
||||
}
|
||||
#[test]
|
||||
fn time_from_ratio(){
|
||||
let a:Time=Ratio::new(Planar64::from(1),Planar64::from(1)).into();
|
||||
assert_eq!(a,Time::ONE_SECOND);
|
||||
}
|
||||
#[test]
|
||||
fn time_squared(){
|
||||
let a=Time::from_secs(2);
|
||||
assert_eq!(a*a,Ratio::new(Fixed::<2,64>::raw_digit(1_000_000_000i64.pow(2))*4,Fixed::<2,64>::raw_digit(1_000_000_000i64.pow(2))));
|
||||
}
|
||||
#[test]
|
||||
fn time_times_planar64(){
|
||||
let a=Time::from_secs(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)));
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
const fn gcd(mut a:u64,mut b:u64)->u64{
|
||||
while b!=0{
|
||||
(a,b)=(b,a.rem_euclid(b));
|
||||
};
|
||||
a
|
||||
}
|
||||
#[derive(Clone,Copy,Debug,Hash)]
|
||||
pub struct Ratio64{
|
||||
num:i64,
|
||||
den:u64,
|
||||
}
|
||||
impl Ratio64{
|
||||
pub const ZERO:Self=Ratio64{num:0,den:1};
|
||||
pub const ONE:Self=Ratio64{num:1,den:1};
|
||||
#[inline]
|
||||
pub const fn new(num:i64,den:u64)->Option<Ratio64>{
|
||||
if den==0{
|
||||
None
|
||||
}else{
|
||||
let d=gcd(num.unsigned_abs(),den);
|
||||
Some(Self{num:num/(d as i64),den:den/d})
|
||||
}
|
||||
}
|
||||
#[inline]
|
||||
pub const fn num(self)->i64{
|
||||
self.num
|
||||
}
|
||||
#[inline]
|
||||
pub const fn den(self)->u64{
|
||||
self.den
|
||||
}
|
||||
#[inline]
|
||||
pub const fn mul_int(&self,rhs:i64)->i64{
|
||||
rhs*self.num/(self.den as i64)
|
||||
}
|
||||
#[inline]
|
||||
pub const fn rhs_div_int(&self,rhs:i64)->i64{
|
||||
rhs*(self.den as i64)/self.num
|
||||
}
|
||||
#[inline]
|
||||
pub const fn mul_ref(&self,rhs:&Ratio64)->Ratio64{
|
||||
let (num,den)=(self.num*rhs.num,self.den*rhs.den);
|
||||
let d=gcd(num.unsigned_abs(),den);
|
||||
Self{
|
||||
num:num/(d as i64),
|
||||
den:den/d,
|
||||
}
|
||||
}
|
||||
}
|
||||
//from num_traits crate
|
||||
#[inline]
|
||||
fn integer_decode_f32(f: f32) -> (u64, i16, i8) {
|
||||
let bits: u32 = f.to_bits();
|
||||
let sign: i8 = if bits >> 31 == 0 { 1 } else { -1 };
|
||||
let mut exponent: i16 = ((bits >> 23) & 0xff) as i16;
|
||||
let mantissa = if exponent == 0 {
|
||||
(bits & 0x7fffff) << 1
|
||||
} else {
|
||||
(bits & 0x7fffff) | 0x800000
|
||||
};
|
||||
// Exponent bias + mantissa shift
|
||||
exponent -= 127 + 23;
|
||||
(mantissa as u64, exponent, sign)
|
||||
}
|
||||
#[inline]
|
||||
fn integer_decode_f64(f: f64) -> (u64, i16, i8) {
|
||||
let bits: u64 = f.to_bits();
|
||||
let sign: i8 = if bits >> 63 == 0 { 1 } else { -1 };
|
||||
let mut exponent: i16 = ((bits >> 52) & 0x7ff) as i16;
|
||||
let mantissa = if exponent == 0 {
|
||||
(bits & 0xfffffffffffff) << 1
|
||||
} else {
|
||||
(bits & 0xfffffffffffff) | 0x10000000000000
|
||||
};
|
||||
// Exponent bias + mantissa shift
|
||||
exponent -= 1023 + 52;
|
||||
(mantissa, exponent, sign)
|
||||
}
|
||||
#[derive(Debug)]
|
||||
pub enum Ratio64TryFromFloatError{
|
||||
Nan,
|
||||
Infinite,
|
||||
Subnormal,
|
||||
HighlyNegativeExponent(i16),
|
||||
HighlyPositiveExponent(i16),
|
||||
}
|
||||
const MAX_DENOMINATOR:u128=u64::MAX as u128;
|
||||
#[inline]
|
||||
fn ratio64_from_mes((m,e,s):(u64,i16,i8))->Result<Ratio64,Ratio64TryFromFloatError>{
|
||||
if e< -127{
|
||||
//this can also just be zero
|
||||
Err(Ratio64TryFromFloatError::HighlyNegativeExponent(e))
|
||||
}else if e< -63{
|
||||
//approximate input ratio within denominator limit
|
||||
let mut target_num=m as u128;
|
||||
let mut target_den=1u128<<-e;
|
||||
|
||||
let mut num=1;
|
||||
let mut den=0;
|
||||
let mut prev_num=0;
|
||||
let mut prev_den=1;
|
||||
|
||||
while target_den!=0{
|
||||
let whole=target_num/target_den;
|
||||
(target_num,target_den)=(target_den,target_num-whole*target_den);
|
||||
let new_num=whole*num+prev_num;
|
||||
let new_den=whole*den+prev_den;
|
||||
if MAX_DENOMINATOR<new_den{
|
||||
break;
|
||||
}else{
|
||||
(prev_num,prev_den)=(num,den);
|
||||
(num,den)=(new_num,new_den);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Ratio64::new(num as i64,den as u64).unwrap())
|
||||
}else if e<0{
|
||||
Ok(Ratio64::new((m as i64)*(s as i64),1<<-e).unwrap())
|
||||
}else if (64-m.leading_zeros() as i16)+e<64{
|
||||
Ok(Ratio64::new((m as i64)*(s as i64)*(1<<e),1).unwrap())
|
||||
}else{
|
||||
Err(Ratio64TryFromFloatError::HighlyPositiveExponent(e))
|
||||
}
|
||||
}
|
||||
impl TryFrom<f32> for Ratio64{
|
||||
type Error=Ratio64TryFromFloatError;
|
||||
#[inline]
|
||||
fn try_from(value:f32)->Result<Self,Self::Error>{
|
||||
match value.classify(){
|
||||
std::num::FpCategory::Nan=>Err(Self::Error::Nan),
|
||||
std::num::FpCategory::Infinite=>Err(Self::Error::Infinite),
|
||||
std::num::FpCategory::Zero=>Ok(Self::ZERO),
|
||||
std::num::FpCategory::Subnormal
|
||||
|std::num::FpCategory::Normal=>ratio64_from_mes(integer_decode_f32(value)),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl TryFrom<f64> for Ratio64{
|
||||
type Error=Ratio64TryFromFloatError;
|
||||
#[inline]
|
||||
fn try_from(value:f64)->Result<Self,Self::Error>{
|
||||
match value.classify(){
|
||||
std::num::FpCategory::Nan=>Err(Self::Error::Nan),
|
||||
std::num::FpCategory::Infinite=>Err(Self::Error::Infinite),
|
||||
std::num::FpCategory::Zero=>Ok(Self::ZERO),
|
||||
std::num::FpCategory::Subnormal
|
||||
|std::num::FpCategory::Normal=>ratio64_from_mes(integer_decode_f64(value)),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl std::ops::Mul<Ratio64> for Ratio64{
|
||||
type Output=Ratio64;
|
||||
#[inline]
|
||||
fn mul(self,rhs:Ratio64)->Self::Output{
|
||||
let (num,den)=(self.num*rhs.num,self.den*rhs.den);
|
||||
let d=gcd(num.unsigned_abs(),den);
|
||||
Self{
|
||||
num:num/(d as i64),
|
||||
den:den/d,
|
||||
}
|
||||
}
|
||||
}
|
||||
impl std::ops::Mul<i64> for Ratio64{
|
||||
type Output=Ratio64;
|
||||
#[inline]
|
||||
fn mul(self,rhs:i64)->Self::Output {
|
||||
Self{
|
||||
num:self.num*rhs,
|
||||
den:self.den,
|
||||
}
|
||||
}
|
||||
}
|
||||
impl std::ops::Div<u64> for Ratio64{
|
||||
type Output=Ratio64;
|
||||
#[inline]
|
||||
fn div(self,rhs:u64)->Self::Output {
|
||||
Self{
|
||||
num:self.num,
|
||||
den:self.den*rhs,
|
||||
}
|
||||
}
|
||||
}
|
||||
#[derive(Clone,Copy,Debug,Hash)]
|
||||
pub struct Ratio64Vec2{
|
||||
pub x:Ratio64,
|
||||
pub y:Ratio64,
|
||||
}
|
||||
impl Ratio64Vec2{
|
||||
pub const ONE:Self=Self{x:Ratio64::ONE,y:Ratio64::ONE};
|
||||
#[inline]
|
||||
pub const fn new(x:Ratio64,y:Ratio64)->Self{
|
||||
Self{x,y}
|
||||
}
|
||||
#[inline]
|
||||
pub const fn mul_int(&self,rhs:glam::I64Vec2)->glam::I64Vec2{
|
||||
glam::i64vec2(
|
||||
self.x.mul_int(rhs.x),
|
||||
self.y.mul_int(rhs.y),
|
||||
)
|
||||
}
|
||||
}
|
||||
impl std::ops::Mul<i64> for Ratio64Vec2{
|
||||
type Output=Ratio64Vec2;
|
||||
#[inline]
|
||||
fn mul(self,rhs:i64)->Self::Output {
|
||||
Self{
|
||||
x:self.x*rhs,
|
||||
y:self.y*rhs,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
///[-pi,pi) = [-2^31,2^31-1]
|
||||
#[derive(Clone,Copy,Hash)]
|
||||
pub struct Angle32(i32);
|
||||
impl Angle32{
|
||||
const ANGLE32_TO_FLOAT64_RADIANS:f64=std::f64::consts::PI/((1i64<<31) as f64);
|
||||
pub const FRAC_PI_2:Self=Self(1<<30);
|
||||
pub const NEG_FRAC_PI_2:Self=Self(-1<<30);
|
||||
pub const PI:Self=Self(-1<<31);
|
||||
#[inline]
|
||||
pub const fn wrap_from_i64(theta:i64)->Self{
|
||||
//take lower bits
|
||||
//note: this was checked on compiler explorer and compiles to 1 instruction!
|
||||
Self(i32::from_ne_bytes(((theta&((1<<32)-1)) as u32).to_ne_bytes()))
|
||||
}
|
||||
#[inline]
|
||||
pub fn clamp_from_i64(theta:i64)->Self{
|
||||
//the assembly is a bit confusing for this, I thought it was checking the same thing twice
|
||||
//but it's just checking and then overwriting the value for both upper and lower bounds.
|
||||
Self(theta.clamp(i32::MIN as i64,i32::MAX as i64) as i32)
|
||||
}
|
||||
#[inline]
|
||||
pub const fn get(&self)->i32{
|
||||
self.0
|
||||
}
|
||||
/// Clamps the value towards the midpoint of the range.
|
||||
/// Note that theta_min can be larger than theta_max and it will wrap clamp the other way around
|
||||
#[inline]
|
||||
pub fn clamp(&self,theta_min:Self,theta_max:Self)->Self{
|
||||
//((max-min as u32)/2 as i32)+min
|
||||
let midpoint=((
|
||||
(theta_max.0 as u32)
|
||||
.wrapping_sub(theta_min.0 as u32)
|
||||
/2
|
||||
) as i32)//(u32::MAX/2) as i32 ALWAYS works
|
||||
.wrapping_add(theta_min.0);
|
||||
//(theta-mid).clamp(max-mid,min-mid)+mid
|
||||
Self(
|
||||
self.0.wrapping_sub(midpoint)
|
||||
.max(theta_min.0.wrapping_sub(midpoint))
|
||||
.min(theta_max.0.wrapping_sub(midpoint))
|
||||
.wrapping_add(midpoint)
|
||||
)
|
||||
}
|
||||
#[inline]
|
||||
pub fn cos_sin(&self)->(Planar64,Planar64){
|
||||
/*
|
||||
//cordic
|
||||
let a=self.0 as u32;
|
||||
//initialize based on the quadrant
|
||||
let (mut x,mut y)=match (a&(1<<31)!=0,a&(1<<30)!=0){
|
||||
(false,false)=>( 1i64<<32, 0i64 ),//TR
|
||||
(false,true )=>( 0i64 , 1i64<<32),//TL
|
||||
(true ,false)=>(-1i64<<32, 0i64 ),//BL
|
||||
(true ,true )=>( 0i64 ,-1i64<<32),//BR
|
||||
};
|
||||
println!("x={} y={}",Planar64::raw(x),Planar64::raw(y));
|
||||
for i in 0..30{
|
||||
if a&(1<<(29-i))!=0{
|
||||
(x,y)=(x-(y>>i),y+(x>>i));
|
||||
}
|
||||
println!("i={i} t={} x={} y={}",(a&(1<<(29-i))!=0) as u8,Planar64::raw(x),Planar64::raw(y));
|
||||
}
|
||||
//don't forget the gain
|
||||
(Planar64::raw(x),Planar64::raw(y))
|
||||
*/
|
||||
let (s,c)=(self.0 as f64*Self::ANGLE32_TO_FLOAT64_RADIANS).sin_cos();
|
||||
(Planar64::raw((c*((1u64<<32) as f64)) as i64),Planar64::raw((s*((1u64<<32) as f64)) as i64))
|
||||
}
|
||||
}
|
||||
impl Into<f32> for Angle32{
|
||||
#[inline]
|
||||
fn into(self)->f32{
|
||||
(self.0 as f64*Self::ANGLE32_TO_FLOAT64_RADIANS) as f32
|
||||
}
|
||||
}
|
||||
impl std::ops::Neg for Angle32{
|
||||
type Output=Angle32;
|
||||
#[inline]
|
||||
fn neg(self)->Self::Output{
|
||||
Angle32(self.0.wrapping_neg())
|
||||
}
|
||||
}
|
||||
impl std::ops::Add<Angle32> for Angle32{
|
||||
type Output=Angle32;
|
||||
#[inline]
|
||||
fn add(self,rhs:Self)->Self::Output {
|
||||
Angle32(self.0.wrapping_add(rhs.0))
|
||||
}
|
||||
}
|
||||
impl std::ops::Sub<Angle32> for Angle32{
|
||||
type Output=Angle32;
|
||||
#[inline]
|
||||
fn sub(self,rhs:Self)->Self::Output {
|
||||
Angle32(self.0.wrapping_sub(rhs.0))
|
||||
}
|
||||
}
|
||||
impl std::ops::Mul<i32> for Angle32{
|
||||
type Output=Angle32;
|
||||
#[inline]
|
||||
fn mul(self,rhs:i32)->Self::Output {
|
||||
Angle32(self.0.wrapping_mul(rhs))
|
||||
}
|
||||
}
|
||||
impl std::ops::Mul<Angle32> for Angle32{
|
||||
type Output=Angle32;
|
||||
#[inline]
|
||||
fn mul(self,rhs:Self)->Self::Output {
|
||||
Angle32(self.0.wrapping_mul(rhs.0))
|
||||
}
|
||||
}
|
||||
#[test]
|
||||
fn angle_sin_cos(){
|
||||
fn close_enough(lhs:Planar64,rhs:Planar64)->bool{
|
||||
(lhs-rhs).abs()<Planar64::EPSILON*4
|
||||
}
|
||||
fn test_angle(f:f64){
|
||||
let a=Angle32((f/Angle32::ANGLE32_TO_FLOAT64_RADIANS) as i32);
|
||||
println!("a={:#034b}",a.0);
|
||||
let (c,s)=a.cos_sin();
|
||||
let h=(s*s+c*c).sqrt();
|
||||
println!("cordic s={} c={}",(s/h).divide(),(c/h).divide());
|
||||
let (fs,fc)=f.sin_cos();
|
||||
println!("float s={} c={}",fs,fc);
|
||||
assert!(close_enough((c/h).divide().fix_1(),Planar64::raw((fc*((1u64<<32) as f64)) as i64)));
|
||||
assert!(close_enough((s/h).divide().fix_1(),Planar64::raw((fs*((1u64<<32) as f64)) as i64)));
|
||||
}
|
||||
test_angle(1.0);
|
||||
test_angle(std::f64::consts::PI/4.0);
|
||||
test_angle(std::f64::consts::PI/8.0);
|
||||
}
|
||||
|
||||
/* Unit type unused for now, may revive it for map files
|
||||
///[-1.0,1.0] = [-2^30,2^30]
|
||||
pub struct Unit32(i32);
|
||||
impl Unit32{
|
||||
#[inline]
|
||||
pub fn as_planar64(&self) -> Planar64{
|
||||
Planar64(4*(self.0 as i64))
|
||||
}
|
||||
}
|
||||
const UNIT32_ONE_FLOAT64=((1<<30) as f64);
|
||||
///[-1.0,1.0] = [-2^30,2^30]
|
||||
pub struct Unit32Vec3(glam::IVec3);
|
||||
impl TryFrom<[f32;3]> for Unit32Vec3{
|
||||
type Error=Unit32TryFromFloatError;
|
||||
fn try_from(value:[f32;3])->Result<Self,Self::Error>{
|
||||
Ok(Self(glam::ivec3(
|
||||
Unit32::try_from(Planar64::try_from(value[0])?)?.0,
|
||||
Unit32::try_from(Planar64::try_from(value[1])?)?.0,
|
||||
Unit32::try_from(Planar64::try_from(value[2])?)?.0,
|
||||
)))
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
pub type Planar64TryFromFloatError=fixed_wide::fixed::FixedFromFloatError;
|
||||
pub type Planar64=fixed_wide::types::I32F32;
|
||||
pub type Planar64Vec3=linear_ops::types::Vector3<Planar64>;
|
||||
pub type Planar64Mat3=linear_ops::types::Matrix3<Planar64>;
|
||||
pub mod vec3{
|
||||
use super::*;
|
||||
pub use linear_ops::types::Vector3;
|
||||
pub const MIN:Planar64Vec3=Planar64Vec3::new([Planar64::MIN;3]);
|
||||
pub const MAX:Planar64Vec3=Planar64Vec3::new([Planar64::MAX;3]);
|
||||
pub const ZERO:Planar64Vec3=Planar64Vec3::new([Planar64::ZERO;3]);
|
||||
pub const ZERO_2:linear_ops::types::Vector3<Fixed::<2,64>>=linear_ops::types::Vector3::new([Fixed::<2,64>::ZERO;3]);
|
||||
pub const X:Planar64Vec3=Planar64Vec3::new([Planar64::ONE,Planar64::ZERO,Planar64::ZERO]);
|
||||
pub const Y:Planar64Vec3=Planar64Vec3::new([Planar64::ZERO,Planar64::ONE,Planar64::ZERO]);
|
||||
pub const Z:Planar64Vec3=Planar64Vec3::new([Planar64::ZERO,Planar64::ZERO,Planar64::ONE]);
|
||||
pub const ONE:Planar64Vec3=Planar64Vec3::new([Planar64::ONE,Planar64::ONE,Planar64::ONE]);
|
||||
pub const NEG_X:Planar64Vec3=Planar64Vec3::new([Planar64::NEG_ONE,Planar64::ZERO,Planar64::ZERO]);
|
||||
pub const NEG_Y:Planar64Vec3=Planar64Vec3::new([Planar64::ZERO,Planar64::NEG_ONE,Planar64::ZERO]);
|
||||
pub const NEG_Z:Planar64Vec3=Planar64Vec3::new([Planar64::ZERO,Planar64::ZERO,Planar64::NEG_ONE]);
|
||||
pub const NEG_ONE:Planar64Vec3=Planar64Vec3::new([Planar64::NEG_ONE,Planar64::NEG_ONE,Planar64::NEG_ONE]);
|
||||
#[inline]
|
||||
pub const fn int(x:i32,y:i32,z:i32)->Planar64Vec3{
|
||||
Planar64Vec3::new([Planar64::raw((x as i64)<<32),Planar64::raw((y as i64)<<32),Planar64::raw((z as i64)<<32)])
|
||||
}
|
||||
#[inline]
|
||||
pub fn raw_array(array:[i64;3])->Planar64Vec3{
|
||||
Planar64Vec3::new(array.map(Planar64::raw))
|
||||
}
|
||||
#[inline]
|
||||
pub fn raw_xyz(x:i64,y:i64,z:i64)->Planar64Vec3{
|
||||
Planar64Vec3::new([Planar64::raw(x),Planar64::raw(y),Planar64::raw(z)])
|
||||
}
|
||||
#[inline]
|
||||
pub fn try_from_f32_array([x,y,z]:[f32;3])->Result<Planar64Vec3,Planar64TryFromFloatError>{
|
||||
Ok(Planar64Vec3::new([
|
||||
try_from_f32(x)?,
|
||||
try_from_f32(y)?,
|
||||
try_from_f32(z)?,
|
||||
]))
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn int(value:i32)->Planar64{
|
||||
Planar64::from(value)
|
||||
}
|
||||
#[inline]
|
||||
pub fn try_from_f32(value:f32)->Result<Planar64,Planar64TryFromFloatError>{
|
||||
let result:Result<Planar64,_>=value.try_into();
|
||||
match result{
|
||||
Ok(ok)=>Ok(ok),
|
||||
Err(e)=>e.underflow_to_zero(),
|
||||
}
|
||||
}
|
||||
pub mod mat3{
|
||||
use super::*;
|
||||
pub use linear_ops::types::Matrix3;
|
||||
#[inline]
|
||||
pub const fn identity()->Planar64Mat3{
|
||||
Planar64Mat3::new([
|
||||
[Planar64::ONE,Planar64::ZERO,Planar64::ZERO],
|
||||
[Planar64::ZERO,Planar64::ONE,Planar64::ZERO],
|
||||
[Planar64::ZERO,Planar64::ZERO,Planar64::ONE],
|
||||
])
|
||||
}
|
||||
#[inline]
|
||||
pub fn from_diagonal(diag:Planar64Vec3)->Planar64Mat3{
|
||||
Planar64Mat3::new([
|
||||
[diag.x,Planar64::ZERO,Planar64::ZERO],
|
||||
[Planar64::ZERO,diag.y,Planar64::ZERO],
|
||||
[Planar64::ZERO,Planar64::ZERO,diag.z],
|
||||
])
|
||||
}
|
||||
#[inline]
|
||||
pub fn from_rotation_yx(x:Angle32,y:Angle32)->Planar64Mat3{
|
||||
let (xc,xs)=x.cos_sin();
|
||||
let (yc,ys)=y.cos_sin();
|
||||
Planar64Mat3::from_cols([
|
||||
Planar64Vec3::new([xc,Planar64::ZERO,-xs]),
|
||||
Planar64Vec3::new([(xs*ys).fix_1(),yc,(xc*ys).fix_1()]),
|
||||
Planar64Vec3::new([(xs*yc).fix_1(),-ys,(xc*yc).fix_1()]),
|
||||
])
|
||||
}
|
||||
#[inline]
|
||||
pub fn from_rotation_y(y:Angle32)->Planar64Mat3{
|
||||
let (c,s)=y.cos_sin();
|
||||
Planar64Mat3::from_cols([
|
||||
Planar64Vec3::new([c,Planar64::ZERO,-s]),
|
||||
vec3::Y,
|
||||
Planar64Vec3::new([s,Planar64::ZERO,c]),
|
||||
])
|
||||
}
|
||||
#[inline]
|
||||
pub fn try_from_f32_array_2d([x_axis,y_axis,z_axis]:[[f32;3];3])->Result<Planar64Mat3,Planar64TryFromFloatError>{
|
||||
Ok(Planar64Mat3::new([
|
||||
vec3::try_from_f32_array(x_axis)?.to_array(),
|
||||
vec3::try_from_f32_array(y_axis)?.to_array(),
|
||||
vec3::try_from_f32_array(z_axis)?.to_array(),
|
||||
]))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone,Copy,Default,Hash,Eq,PartialEq)]
|
||||
pub struct Planar64Affine3{
|
||||
pub matrix3:Planar64Mat3,//includes scale above 1
|
||||
pub translation:Planar64Vec3,
|
||||
}
|
||||
impl Planar64Affine3{
|
||||
#[inline]
|
||||
pub const fn new(matrix3:Planar64Mat3,translation:Planar64Vec3)->Self{
|
||||
Self{matrix3,translation}
|
||||
}
|
||||
#[inline]
|
||||
pub fn transform_point3(&self,point:Planar64Vec3)->vec3::Vector3<Fixed<2,64>>{
|
||||
self.translation.fix_2()+self.matrix3*point
|
||||
}
|
||||
}
|
||||
impl Into<glam::Mat4> for Planar64Affine3{
|
||||
#[inline]
|
||||
fn into(self)->glam::Mat4{
|
||||
let matrix3=self.matrix3.to_array().map(|row|row.map(Into::<f32>::into));
|
||||
let translation=self.translation.to_array().map(Into::<f32>::into);
|
||||
glam::Mat4::from_cols_array(&[
|
||||
matrix3[0][0],matrix3[0][1],matrix3[0][2],0.0,
|
||||
matrix3[1][0],matrix3[1][1],matrix3[1][2],0.0,
|
||||
matrix3[2][0],matrix3[2][1],matrix3[2][2],0.0,
|
||||
translation[0],translation[1],translation[2],1.0
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_sqrt(){
|
||||
let r=int(400);
|
||||
assert_eq!(r,Planar64::raw(1717986918400));
|
||||
let s=r.sqrt();
|
||||
assert_eq!(s,Planar64::raw(85899345920));
|
||||
}
|
@ -1,16 +0,0 @@
|
||||
pub mod bvh;
|
||||
pub mod map;
|
||||
pub mod run;
|
||||
pub mod aabb;
|
||||
pub mod model;
|
||||
pub mod mouse;
|
||||
pub mod timer;
|
||||
pub mod integer;
|
||||
pub mod physics;
|
||||
pub mod session;
|
||||
pub mod updatable;
|
||||
pub mod instruction;
|
||||
pub mod gameplay_attributes;
|
||||
pub mod gameplay_modes;
|
||||
pub mod gameplay_style;
|
||||
pub mod controls_bitflag;
|
@ -1,14 +0,0 @@
|
||||
use crate::model;
|
||||
use crate::gameplay_modes;
|
||||
use crate::gameplay_attributes;
|
||||
//this is a temporary struct to try to get the code running again
|
||||
//TODO: use snf::map::Region to update the data in physics and graphics instead of this
|
||||
pub struct CompleteMap{
|
||||
pub modes:gameplay_modes::Modes,
|
||||
pub attributes:Vec<gameplay_attributes::CollisionAttributes>,
|
||||
pub meshes:Vec<model::Mesh>,
|
||||
pub models:Vec<model::Model>,
|
||||
//RenderPattern
|
||||
pub textures:Vec<Vec<u8>>,
|
||||
pub render_configs:Vec<model::RenderConfig>,
|
||||
}
|
@ -1,133 +0,0 @@
|
||||
use crate::integer::{Planar64Vec3,Planar64Affine3};
|
||||
use crate::gameplay_attributes;
|
||||
|
||||
pub type TextureCoordinate=glam::Vec2;
|
||||
pub type Color4=glam::Vec4;
|
||||
#[derive(Clone,Copy,Hash,id::Id,PartialEq,Eq)]
|
||||
pub struct PositionId(u32);
|
||||
#[derive(Clone,Copy,Hash,id::Id,PartialEq,Eq)]
|
||||
pub struct TextureCoordinateId(u32);
|
||||
#[derive(Clone,Copy,Hash,id::Id,PartialEq,Eq)]
|
||||
pub struct NormalId(u32);
|
||||
#[derive(Clone,Copy,Hash,id::Id,PartialEq,Eq)]
|
||||
pub struct ColorId(u32);
|
||||
#[derive(Clone,Hash,PartialEq,Eq)]
|
||||
pub struct IndexedVertex{
|
||||
pub pos:PositionId,
|
||||
pub tex:TextureCoordinateId,
|
||||
pub normal:NormalId,
|
||||
pub color:ColorId,
|
||||
}
|
||||
#[derive(Clone,Copy,Hash,id::Id,PartialEq,Eq)]
|
||||
pub struct VertexId(u32);
|
||||
pub type IndexedVertexList=Vec<VertexId>;
|
||||
pub trait PolygonIter{
|
||||
fn polys(&self)->impl Iterator<Item=&[VertexId]>;
|
||||
}
|
||||
pub trait MapVertexId{
|
||||
fn map_vertex_id<F:Fn(VertexId)->VertexId>(self,f:F)->Self;
|
||||
}
|
||||
#[derive(Clone)]
|
||||
pub struct PolygonList(Vec<IndexedVertexList>);
|
||||
impl PolygonList{
|
||||
pub const fn new(list:Vec<IndexedVertexList>)->Self{
|
||||
Self(list)
|
||||
}
|
||||
pub fn extend<T:IntoIterator<Item=IndexedVertexList>>(&mut self,iter:T){
|
||||
self.0.extend(iter);
|
||||
}
|
||||
}
|
||||
impl PolygonIter for PolygonList{
|
||||
fn polys(&self)->impl Iterator<Item=&[VertexId]>{
|
||||
self.0.iter().map(|poly|poly.as_slice())
|
||||
}
|
||||
}
|
||||
impl MapVertexId for PolygonList{
|
||||
fn map_vertex_id<F:Fn(VertexId)->VertexId>(self,f:F)->Self{
|
||||
Self(self.0.into_iter().map(|ivl|ivl.into_iter().map(&f).collect()).collect())
|
||||
}
|
||||
}
|
||||
// pub struct TriangleStrip(IndexedVertexList);
|
||||
// impl PolygonIter for TriangleStrip{
|
||||
// fn polys(&self)->impl Iterator<Item=&[VertexId]>{
|
||||
// self.0.vertices.windows(3).enumerate().map(|(i,s)|if i&0!=0{return s.iter().rev()}else{return s.iter()})
|
||||
// }
|
||||
// }
|
||||
#[derive(Clone,Copy,Hash,id::Id,PartialEq,Eq)]
|
||||
pub struct PolygonGroupId(u32);
|
||||
#[derive(Clone)]
|
||||
pub enum PolygonGroup{
|
||||
PolygonList(PolygonList),
|
||||
//TriangleStrip(TriangleStrip),
|
||||
}
|
||||
impl PolygonIter for PolygonGroup{
|
||||
fn polys(&self)->impl Iterator<Item=&[VertexId]>{
|
||||
match self{
|
||||
PolygonGroup::PolygonList(list)=>list.polys(),
|
||||
//PolygonGroup::TriangleStrip(strip)=>strip.polys(),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl MapVertexId for PolygonGroup{
|
||||
fn map_vertex_id<F:Fn(VertexId)->VertexId>(self,f:F)->Self{
|
||||
match self{
|
||||
PolygonGroup::PolygonList(polys)=>Self::PolygonList(polys.map_vertex_id(f)),
|
||||
}
|
||||
}
|
||||
}
|
||||
/// Ah yes, a group of things to render at the same time
|
||||
#[derive(Clone,Copy,Debug,Hash,id::Id,Eq,PartialEq)]
|
||||
pub struct TextureId(u32);
|
||||
#[derive(Clone,Copy,Hash,id::Id,Eq,PartialEq)]
|
||||
pub struct RenderConfigId(u32);
|
||||
#[derive(Clone,Copy,Default)]
|
||||
pub struct RenderConfig{
|
||||
pub texture:Option<TextureId>,
|
||||
}
|
||||
impl RenderConfig{
|
||||
pub const fn texture(texture:TextureId)->Self{
|
||||
Self{
|
||||
texture:Some(texture),
|
||||
}
|
||||
}
|
||||
}
|
||||
#[derive(Clone)]
|
||||
pub struct IndexedGraphicsGroup{
|
||||
//Render pattern material/texture/shader/flat color
|
||||
pub render:RenderConfigId,
|
||||
pub groups:Vec<PolygonGroupId>,
|
||||
}
|
||||
#[derive(Clone,Default)]
|
||||
pub struct IndexedPhysicsGroup{
|
||||
//the polygons in this group are guaranteed to make a closed convex shape
|
||||
pub groups:Vec<PolygonGroupId>,
|
||||
}
|
||||
//This is a superset of PhysicsModel and GraphicsModel
|
||||
#[derive(Clone,Copy,Debug,Hash,id::Id,Eq,PartialEq)]
|
||||
pub struct MeshId(u32);
|
||||
#[derive(Clone)]
|
||||
pub struct Mesh{
|
||||
pub unique_pos:Vec<Planar64Vec3>,//Unit32Vec3
|
||||
pub unique_normal:Vec<Planar64Vec3>,//Unit32Vec3
|
||||
pub unique_tex:Vec<TextureCoordinate>,
|
||||
pub unique_color:Vec<Color4>,
|
||||
pub unique_vertices:Vec<IndexedVertex>,
|
||||
//polygon groups are constant texture AND convexity slices
|
||||
//note that this may need to be changed to be a list of individual faces
|
||||
//for submeshes to work since face ids need to be consistent across submeshes
|
||||
//so face == polygon_groups[face_id]
|
||||
pub polygon_groups:Vec<PolygonGroup>,
|
||||
//graphics indexed (by texture)
|
||||
pub graphics_groups:Vec<IndexedGraphicsGroup>,
|
||||
//physics indexed (by convexity)
|
||||
pub physics_groups:Vec<IndexedPhysicsGroup>,
|
||||
}
|
||||
|
||||
#[derive(Debug,Clone,Copy,Hash,id::Id,Eq,PartialEq)]
|
||||
pub struct ModelId(u32);
|
||||
pub struct Model{
|
||||
pub mesh:MeshId,
|
||||
pub attributes:gameplay_attributes::CollisionAttributesId,
|
||||
pub color:Color4,//transparency is in here
|
||||
pub transform:Planar64Affine3,
|
||||
}
|
@ -1,28 +0,0 @@
|
||||
use crate::integer::Time;
|
||||
|
||||
#[derive(Clone,Debug)]
|
||||
pub struct MouseState<T>{
|
||||
pub pos:glam::IVec2,
|
||||
pub time:Time<T>,
|
||||
}
|
||||
impl<T> Default for MouseState<T>{
|
||||
fn default()->Self{
|
||||
Self{
|
||||
time:Time::ZERO,
|
||||
pos:glam::IVec2::ZERO,
|
||||
}
|
||||
}
|
||||
}
|
||||
impl<T> MouseState<T>
|
||||
where Time<T>:Copy,
|
||||
{
|
||||
pub fn lerp(&self,target:&MouseState<T>,time:Time<T>)->glam::IVec2{
|
||||
let m0=self.pos.as_i64vec2();
|
||||
let m1=target.pos.as_i64vec2();
|
||||
//these are deltas
|
||||
let t1t=(target.time-time).nanos();
|
||||
let tt0=(time-self.time).nanos();
|
||||
let dt=(target.time-self.time).nanos();
|
||||
((m0*t1t+m1*tt0)/dt).as_ivec2()
|
||||
}
|
||||
}
|
@ -1,56 +0,0 @@
|
||||
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)]
|
||||
pub enum Instruction{
|
||||
Mouse(MouseInstruction),
|
||||
SetControl(SetControlInstruction),
|
||||
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),
|
||||
SetMoveUp(bool),
|
||||
SetMoveBack(bool),
|
||||
SetMoveLeft(bool),
|
||||
SetMoveDown(bool),
|
||||
SetMoveForward(bool),
|
||||
SetJump(bool),
|
||||
SetZoom(bool),
|
||||
}
|
||||
#[derive(Clone,Debug)]
|
||||
pub enum ModeInstruction{
|
||||
/// Reset: fully replace the physics state.
|
||||
/// This forgets all inputs and settings which need to be reapplied.
|
||||
Reset,
|
||||
/// Restart: Teleport to the start zone.
|
||||
/// This runs when you press R or teleport to a bonus
|
||||
Restart(ModeId),
|
||||
/// Spawn: Teleport to a specific mode's spawn
|
||||
/// This runs when the map loads to put you at the map lobby
|
||||
Spawn(ModeId,StageId),
|
||||
}
|
||||
#[derive(Clone,Debug)]
|
||||
pub enum MiscInstruction{
|
||||
PracticeFly,
|
||||
SetSensitivity(crate::integer::Ratio64Vec2),
|
||||
}
|
@ -1,119 +0,0 @@
|
||||
use crate::timer::{TimerFixed,Realtime,Paused,Unpaused};
|
||||
|
||||
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)]
|
||||
pub enum FlagReason{
|
||||
Anticheat,
|
||||
StyleChange,
|
||||
Clock,
|
||||
Pause,
|
||||
Flying,
|
||||
Gravity,
|
||||
Timescale,
|
||||
TimeTravel,
|
||||
Teleport,
|
||||
}
|
||||
impl ToString for FlagReason{
|
||||
fn to_string(&self)->String{
|
||||
self.as_ref().to_owned()
|
||||
}
|
||||
}
|
||||
impl AsRef<str> for FlagReason{
|
||||
fn as_ref(&self)->&str{
|
||||
match self{
|
||||
FlagReason::Anticheat=>"Passed through anticheat zone.",
|
||||
FlagReason::StyleChange=>"Changed style.",
|
||||
FlagReason::Clock=>"Incorrect clock. (This can be caused by internet hiccups)",
|
||||
FlagReason::Pause=>"Pausing is not allowed in this style.",
|
||||
FlagReason::Flying=>"Flying is not allowed in this style.",
|
||||
FlagReason::Gravity=>"Gravity modification 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::Teleport=>"Illegal teleport.",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Error{
|
||||
NotStarted,
|
||||
AlreadyStarted,
|
||||
AlreadyFinished,
|
||||
}
|
||||
impl std::fmt::Display for Error{
|
||||
fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{
|
||||
write!(f,"{self:?}")
|
||||
}
|
||||
}
|
||||
impl std::error::Error for Error{}
|
||||
|
||||
#[derive(Clone,Copy,Debug)]
|
||||
enum RunState{
|
||||
Created,
|
||||
Started{timer:TimerFixed<Realtime<PhysicsTimeInner,TimeInner>,Unpaused>},
|
||||
Finished{timer:TimerFixed<Realtime<PhysicsTimeInner,TimeInner>,Paused>},
|
||||
}
|
||||
|
||||
#[derive(Clone,Copy,Debug)]
|
||||
pub struct Run{
|
||||
state:RunState,
|
||||
flagged:Option<FlagReason>,
|
||||
}
|
||||
|
||||
impl Run{
|
||||
pub fn new()->Self{
|
||||
Self{
|
||||
state:RunState::Created,
|
||||
flagged:None,
|
||||
}
|
||||
}
|
||||
pub fn time(&self,time:PhysicsTime)->Time{
|
||||
match &self.state{
|
||||
RunState::Created=>Time::ZERO,
|
||||
RunState::Started{timer}=>timer.time(time),
|
||||
RunState::Finished{timer}=>timer.time(),
|
||||
}
|
||||
}
|
||||
pub fn start(&mut self,time:PhysicsTime)->Result<(),Error>{
|
||||
match &self.state{
|
||||
RunState::Created=>{
|
||||
self.state=RunState::Started{
|
||||
timer:TimerFixed::new(time,Time::ZERO),
|
||||
};
|
||||
Ok(())
|
||||
},
|
||||
RunState::Started{..}=>Err(Error::AlreadyStarted),
|
||||
RunState::Finished{..}=>Err(Error::AlreadyFinished),
|
||||
}
|
||||
}
|
||||
pub fn finish(&mut self,time:PhysicsTime)->Result<(),Error>{
|
||||
//this uses Copy
|
||||
match &self.state{
|
||||
RunState::Created=>Err(Error::NotStarted),
|
||||
RunState::Started{timer}=>{
|
||||
self.state=RunState::Finished{
|
||||
timer:timer.into_paused(time),
|
||||
};
|
||||
Ok(())
|
||||
},
|
||||
RunState::Finished{..}=>Err(Error::AlreadyFinished),
|
||||
}
|
||||
}
|
||||
pub fn flag(&mut self,flag_reason:FlagReason){
|
||||
//don't replace the first reason the run was flagged
|
||||
if self.flagged.is_none(){
|
||||
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>;
|
@ -1,363 +0,0 @@
|
||||
use crate::integer::{Time,Ratio64};
|
||||
|
||||
#[derive(Clone,Copy,Debug)]
|
||||
pub struct Paused;
|
||||
#[derive(Clone,Copy,Debug)]
|
||||
pub struct Unpaused;
|
||||
|
||||
pub trait PauseState:Copy+std::fmt::Debug{
|
||||
const IS_PAUSED:bool;
|
||||
fn new()->Self;
|
||||
}
|
||||
impl PauseState for Paused{
|
||||
const IS_PAUSED:bool=true;
|
||||
fn new()->Self{
|
||||
Self
|
||||
}
|
||||
}
|
||||
impl PauseState for Unpaused{
|
||||
const IS_PAUSED:bool=false;
|
||||
fn new()->Self{
|
||||
Self
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone,Copy,Hash,Eq,PartialEq,PartialOrd,Debug)]
|
||||
enum Inner{}
|
||||
type InnerTime=Time<Inner>;
|
||||
|
||||
#[derive(Clone,Copy,Debug)]
|
||||
pub struct Realtime<In,Out>{
|
||||
offset:InnerTime,
|
||||
_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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone,Copy,Debug)]
|
||||
pub struct Scaled<In,Out>{
|
||||
scale:Ratio64,
|
||||
offset:InnerTime,
|
||||
_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,
|
||||
}
|
||||
}
|
||||
const fn with_scale(scale:Ratio64)->Self{
|
||||
Self::new(scale,InnerTime::ZERO)
|
||||
}
|
||||
const fn scale(&self,time:Time<In>)->InnerTime{
|
||||
InnerTime::raw(self.scale.mul_int(time.get()))
|
||||
}
|
||||
const fn get_scale(&self)->Ratio64{
|
||||
self.scale
|
||||
}
|
||||
fn set_scale(&mut self,time:Time<In>,new_scale:Ratio64){
|
||||
let new_time=self.get_time(time);
|
||||
self.scale=new_scale;
|
||||
self.set_time(time,new_time);
|
||||
}
|
||||
}
|
||||
|
||||
pub trait TimerState{
|
||||
type In;
|
||||
type Out;
|
||||
fn identity()->Self;
|
||||
fn get_time(&self,time:Time<Self::In>)->Time<Self::Out>;
|
||||
fn set_time(&mut self,time:Time<Self::In>,new_time:Time<Self::Out>);
|
||||
fn get_offset(&self)->InnerTime;
|
||||
fn set_offset(&mut self,offset:InnerTime);
|
||||
}
|
||||
impl<In,Out> TimerState for Realtime<In,Out>{
|
||||
type In=In;
|
||||
type Out=Out;
|
||||
fn identity()->Self{
|
||||
Self::new(InnerTime::ZERO)
|
||||
}
|
||||
fn get_time(&self,time:Time<In>)->Time<Out>{
|
||||
time.coerce()+self.offset.coerce()
|
||||
}
|
||||
fn set_time(&mut self,time:Time<In>,new_time:Time<Out>){
|
||||
self.offset=new_time.coerce()-time.coerce();
|
||||
}
|
||||
fn get_offset(&self)->InnerTime{
|
||||
self.offset
|
||||
}
|
||||
fn set_offset(&mut self,offset:InnerTime){
|
||||
self.offset=offset;
|
||||
}
|
||||
}
|
||||
impl<In,Out> TimerState for Scaled<In,Out>
|
||||
where Time<In>:Copy,
|
||||
{
|
||||
type In=In;
|
||||
type Out=Out;
|
||||
fn identity()->Self{
|
||||
Self::new(Ratio64::ONE,InnerTime::ZERO)
|
||||
}
|
||||
fn get_time(&self,time:Time<In>)->Time<Out>{
|
||||
(self.scale(time)+self.offset).coerce()
|
||||
}
|
||||
fn set_time(&mut self,time:Time<In>,new_time:Time<Out>){
|
||||
self.offset=new_time.coerce()-self.scale(time);
|
||||
}
|
||||
fn get_offset(&self)->InnerTime{
|
||||
self.offset
|
||||
}
|
||||
fn set_offset(&mut self,offset:InnerTime){
|
||||
self.offset=offset;
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone,Copy,Debug)]
|
||||
pub struct TimerFixed<T:TimerState,P:PauseState>{
|
||||
state:T,
|
||||
_paused:P,
|
||||
}
|
||||
|
||||
//scaled timer methods are generic across PauseState
|
||||
impl<P:PauseState,In,Out> TimerFixed<Scaled<In,Out>,P>
|
||||
where Time<In>:Copy,
|
||||
{
|
||||
pub fn scaled(time:Time<In>,new_time:Time<Out>,scale:Ratio64)->Self{
|
||||
let mut timer=Self{
|
||||
state:Scaled::with_scale(scale),
|
||||
_paused:P::new(),
|
||||
};
|
||||
timer.set_time(time,new_time);
|
||||
timer
|
||||
}
|
||||
pub const fn get_scale(&self)->Ratio64{
|
||||
self.state.get_scale()
|
||||
}
|
||||
pub fn set_scale(&mut self,time:Time<In>,new_scale:Ratio64){
|
||||
self.state.set_scale(time,new_scale)
|
||||
}
|
||||
}
|
||||
|
||||
//pause and unpause is generic across TimerState
|
||||
impl<T:TimerState> TimerFixed<T,Paused>
|
||||
where Time<T::In>:Copy,
|
||||
{
|
||||
pub fn into_unpaused(self,time:Time<T::In>)->TimerFixed<T,Unpaused>{
|
||||
let new_time=self.time();
|
||||
let mut timer=TimerFixed{
|
||||
state:self.state,
|
||||
_paused:Unpaused,
|
||||
};
|
||||
timer.set_time(time,new_time);
|
||||
timer
|
||||
}
|
||||
pub fn time(&self)->Time<T::Out>{
|
||||
self.state.get_offset().coerce()
|
||||
}
|
||||
}
|
||||
impl<T:TimerState> TimerFixed<T,Unpaused>
|
||||
where Time<T::In>:Copy,
|
||||
{
|
||||
pub fn into_paused(self,time:Time<T::In>)->TimerFixed<T,Paused>{
|
||||
let new_time=self.time(time);
|
||||
let mut timer=TimerFixed{
|
||||
state:self.state,
|
||||
_paused:Paused,
|
||||
};
|
||||
timer.set_time(time,new_time);
|
||||
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
|
||||
impl<T:TimerState,P:PauseState> TimerFixed<T,P>{
|
||||
pub fn new(time:Time<T::In>,new_time:Time<T::Out>)->Self{
|
||||
let mut timer=Self{
|
||||
state:T::identity(),
|
||||
_paused:P::new(),
|
||||
};
|
||||
timer.set_time(time,new_time);
|
||||
timer
|
||||
}
|
||||
pub fn from_state(state:T)->Self{
|
||||
Self{
|
||||
state,
|
||||
_paused:P::new(),
|
||||
}
|
||||
}
|
||||
pub fn into_state(self)->T{
|
||||
self.state
|
||||
}
|
||||
pub fn set_time(&mut self,time:Time<T::In>,new_time:Time<T::Out>){
|
||||
match P::IS_PAUSED{
|
||||
true=>self.state.set_offset(new_time.coerce()),
|
||||
false=>self.state.set_time(time,new_time),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Error{
|
||||
AlreadyPaused,
|
||||
AlreadyUnpaused,
|
||||
}
|
||||
impl std::fmt::Display for Error{
|
||||
fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{
|
||||
write!(f,"{self:?}")
|
||||
}
|
||||
}
|
||||
impl std::error::Error for Error{}
|
||||
|
||||
//wrapper type which holds type state internally
|
||||
#[derive(Clone,Debug)]
|
||||
pub enum Timer<T:TimerState>{
|
||||
Paused(TimerFixed<T,Paused>),
|
||||
Unpaused(TimerFixed<T,Unpaused>),
|
||||
}
|
||||
impl<T:TimerState> Timer<T>
|
||||
where
|
||||
T:Copy,
|
||||
Time<T::In>:Copy,
|
||||
{
|
||||
pub fn from_state(state:T,paused:bool)->Self{
|
||||
match paused{
|
||||
true=>Self::Paused(TimerFixed::from_state(state)),
|
||||
false=>Self::Unpaused(TimerFixed::from_state(state)),
|
||||
}
|
||||
}
|
||||
pub fn into_state(self)->(T,bool){
|
||||
match self{
|
||||
Self::Paused(timer)=>(timer.into_state(),true),
|
||||
Self::Unpaused(timer)=>(timer.into_state(),false),
|
||||
}
|
||||
}
|
||||
pub fn paused(time:Time<T::In>,new_time:Time<T::Out>)->Self{
|
||||
Self::Paused(TimerFixed::new(time,new_time))
|
||||
}
|
||||
pub fn unpaused(time:Time<T::In>,new_time:Time<T::Out>)->Self{
|
||||
Self::Unpaused(TimerFixed::new(time,new_time))
|
||||
}
|
||||
pub fn time(&self,time:Time<T::In>)->Time<T::Out>{
|
||||
match self{
|
||||
Self::Paused(timer)=>timer.time(),
|
||||
Self::Unpaused(timer)=>timer.time(time),
|
||||
}
|
||||
}
|
||||
pub fn set_time(&mut self,time:Time<T::In>,new_time:Time<T::Out>){
|
||||
match self{
|
||||
Self::Paused(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>{
|
||||
*self=match *self{
|
||||
Self::Paused(_)=>return Err(Error::AlreadyPaused),
|
||||
Self::Unpaused(timer)=>Self::Paused(timer.into_paused(time)),
|
||||
};
|
||||
Ok(())
|
||||
}
|
||||
pub fn unpause(&mut self,time:Time<T::In>)->Result<(),Error>{
|
||||
*self=match *self{
|
||||
Self::Paused(timer)=>Self::Unpaused(timer.into_unpaused(time)),
|
||||
Self::Unpaused(_)=>return Err(Error::AlreadyUnpaused),
|
||||
};
|
||||
Ok(())
|
||||
}
|
||||
pub fn is_paused(&self)->bool{
|
||||
match self{
|
||||
Self::Paused(_)=>true,
|
||||
Self::Unpaused(_)=>false,
|
||||
}
|
||||
}
|
||||
pub fn set_paused(&mut self,time:Time<T::In>,paused:bool)->Result<(),Error>{
|
||||
match paused{
|
||||
true=>self.pause(time),
|
||||
false=>self.unpause(time),
|
||||
}
|
||||
}
|
||||
}
|
||||
//scaled timer methods are generic across PauseState
|
||||
impl<In,Out> Timer<Scaled<In,Out>>
|
||||
where Time<In>:Copy,
|
||||
{
|
||||
pub const fn get_scale(&self)->Ratio64{
|
||||
match self{
|
||||
Self::Paused(timer)=>timer.get_scale(),
|
||||
Self::Unpaused(timer)=>timer.get_scale(),
|
||||
}
|
||||
}
|
||||
pub fn set_scale(&mut self,time:Time<In>,new_scale:Ratio64){
|
||||
match self{
|
||||
Self::Paused(timer)=>timer.set_scale(time,new_scale),
|
||||
Self::Unpaused(timer)=>timer.set_scale(time,new_scale),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test{
|
||||
use super::*;
|
||||
macro_rules! sec {
|
||||
($s: expr) => {
|
||||
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]
|
||||
fn test_timerfixed_scaled(){
|
||||
//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)));
|
||||
//the paused timer at 1 second should read 0s
|
||||
assert_eq!(timer.time(),sec!(0));
|
||||
|
||||
//unpause it after one second
|
||||
let timer=timer.into_unpaused(sec!(1));
|
||||
//the timer at 6 seconds should read 2.5s
|
||||
assert_eq!(timer.time(sec!(6)),Time::from_millis(2500));
|
||||
|
||||
//pause the timer after 11 seconds
|
||||
let timer=timer.into_paused(sec!(11));
|
||||
//the paused timer at 20 seconds should read 5s
|
||||
assert_eq!(timer.time(),sec!(5));
|
||||
}
|
||||
#[test]
|
||||
fn test_timer()->Result<(),Error>{
|
||||
//create a paused timer that reads 0s
|
||||
let mut timer=Timer::<Realtime<Parent,Calculated>>::paused(sec!(0),sec!(0));
|
||||
//the paused timer at 1 second should read 0s
|
||||
assert_eq!(timer.time(sec!(1)),sec!(0));
|
||||
|
||||
//unpause it after one second
|
||||
timer.unpause(sec!(1))?;
|
||||
//the timer at 6 seconds should read 5s
|
||||
assert_eq!(timer.time(sec!(6)),sec!(5));
|
||||
|
||||
//pause the timer after 11 seconds
|
||||
timer.pause(sec!(11))?;
|
||||
//the paused timer at 20 seconds should read 10s
|
||||
assert_eq!(timer.time(sec!(20)),sec!(10));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
@ -1,56 +0,0 @@
|
||||
pub trait Updatable<Updater>{
|
||||
fn update(&mut self,update:Updater);
|
||||
}
|
||||
#[derive(Clone,Copy,Hash,Eq,PartialEq)]
|
||||
struct InnerId(u32);
|
||||
#[derive(Clone)]
|
||||
struct Inner{
|
||||
id:InnerId,
|
||||
enabled:bool,
|
||||
}
|
||||
#[derive(Clone,Copy,Hash,Eq,PartialEq)]
|
||||
struct OuterId(u32);
|
||||
struct Outer{
|
||||
id:OuterId,
|
||||
inners:std::collections::HashMap<InnerId,Inner>,
|
||||
}
|
||||
|
||||
enum Update<I,U>{
|
||||
Insert(I),
|
||||
Update(U),
|
||||
Remove
|
||||
}
|
||||
|
||||
struct InnerUpdate{
|
||||
//#[updatable(Update)]
|
||||
enabled:Option<bool>,
|
||||
}
|
||||
struct OuterUpdate{
|
||||
//#[updatable(Insert,Update,Remove)]
|
||||
inners:std::collections::HashMap<InnerId,Update<Inner,InnerUpdate>>,
|
||||
//#[updatable(Update)]
|
||||
//inners:std::collections::HashMap<InnerId,InnerUpdate>,
|
||||
}
|
||||
impl Updatable<InnerUpdate> for Inner{
|
||||
fn update(&mut self,update:InnerUpdate){
|
||||
if let Some(enabled)=update.enabled{
|
||||
self.enabled=enabled;
|
||||
}
|
||||
}
|
||||
}
|
||||
impl Updatable<OuterUpdate> for Outer{
|
||||
fn update(&mut self,update:OuterUpdate){
|
||||
for (id,up) in update.inners{
|
||||
match up{
|
||||
Update::Insert(new_inner)=>self.inners.insert(id,new_inner),
|
||||
Update::Update(inner_update)=>self.inners.get_mut(&id).map(|inner|{
|
||||
let old=inner.clone();
|
||||
inner.update(inner_update);
|
||||
old
|
||||
}),
|
||||
Update::Remove=>self.inners.remove(&id),
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
//*/
|
1
lib/deferred_loader/.gitignore
vendored
1
lib/deferred_loader/.gitignore
vendored
@ -1 +0,0 @@
|
||||
/target
|
@ -1,21 +0,0 @@
|
||||
[package]
|
||||
name = "strafesnet_deferred_loader"
|
||||
version = "0.4.1"
|
||||
edition = "2021"
|
||||
repository = "https://git.itzana.me/StrafesNET/strafe-project"
|
||||
license = "MIT OR Apache-2.0"
|
||||
description = "Acquire IDs for objects before loading them in bulk."
|
||||
authors = ["Rhys Lloyd <krakow20@gmail.com>"]
|
||||
|
||||
# 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]
|
||||
strafesnet_common = { path = "../common", registry = "strafesnet" }
|
||||
url = { version = "2.5.2", optional = true }
|
||||
vbsp = { version = "0.6.0", optional = true }
|
@ -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,19 +0,0 @@
|
||||
Texture Loader
|
||||
==============
|
||||
|
||||
## Texture loader, designed to be used in conjunction with rbx_loader, bsp_loader or Strafe Client
|
||||
|
||||
#### 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,34 +0,0 @@
|
||||
#[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 texture;
|
||||
#[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,48 +0,0 @@
|
||||
#[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)))?))
|
||||
}
|
||||
}
|
@ -1,112 +0,0 @@
|
||||
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,
|
||||
))
|
||||
}
|
||||
}
|
@ -1,30 +0,0 @@
|
||||
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))
|
||||
)
|
||||
}
|
||||
}
|
@ -1,102 +0,0 @@
|
||||
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,
|
||||
))
|
||||
}
|
||||
}
|
@ -1,39 +0,0 @@
|
||||
use strafesnet_common::model::{TextureId,RenderConfigId,RenderConfig};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub enum Texture{
|
||||
ImageDDS(Vec<u8>),
|
||||
}
|
||||
impl AsRef<[u8]> for Texture{
|
||||
fn as_ref(&self)->&[u8]{
|
||||
match self{
|
||||
Texture::ImageDDS(data)=>data.as_ref(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct RenderConfigs{
|
||||
textures:Vec<Option<Texture>>,
|
||||
render_configs:Vec<RenderConfig>,
|
||||
}
|
||||
impl RenderConfigs{
|
||||
pub(crate) const fn new(textures:Vec<Option<Texture>>,render_configs:Vec<RenderConfig>)->Self{
|
||||
Self{
|
||||
textures,
|
||||
render_configs,
|
||||
}
|
||||
}
|
||||
pub fn consume(self)->(
|
||||
impl Iterator<Item=(TextureId,Texture)>,
|
||||
impl Iterator<Item=(RenderConfigId,RenderConfig)>
|
||||
){
|
||||
(
|
||||
self.textures.into_iter().enumerate().filter_map(|(texture_id,maybe_texture)|
|
||||
maybe_texture.map(|texture|(TextureId::new(texture_id as u32),texture))
|
||||
),
|
||||
self.render_configs.into_iter().enumerate().map(|(render_id,render)|
|
||||
(RenderConfigId::new(render_id as u32),render)
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
@ -1,60 +0,0 @@
|
||||
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
lib/fixed_wide/.gitignore
vendored
1
lib/fixed_wide/.gitignore
vendored
@ -1 +0,0 @@
|
||||
/target
|
@ -1,20 +0,0 @@
|
||||
[package]
|
||||
name = "fixed_wide"
|
||||
version = "0.1.1"
|
||||
edition = "2021"
|
||||
repository = "https://git.itzana.me/StrafesNET/strafe-project"
|
||||
license = "MIT OR Apache-2.0"
|
||||
description = "Fixed point numbers with optional widening Mul operator."
|
||||
authors = ["Rhys Lloyd <krakow20@gmail.com>"]
|
||||
|
||||
[features]
|
||||
default=[]
|
||||
deferred-division=["dep:ratio_ops"]
|
||||
wide-mul=[]
|
||||
zeroes=["dep:arrayvec"]
|
||||
|
||||
[dependencies]
|
||||
bnum = "0.12.0"
|
||||
arrayvec = { version = "0.7.6", optional = true }
|
||||
paste = "1.0.15"
|
||||
ratio_ops = { path = "../ratio_ops", registry = "strafesnet", optional = true }
|
@ -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,829 +0,0 @@
|
||||
use bnum::{BInt,cast::As};
|
||||
|
||||
#[derive(Clone,Copy,Debug,Default,Hash,PartialEq,Eq,PartialOrd,Ord)]
|
||||
/// 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
|
||||
/// F is the number of fractional bits (always N*32 lol)
|
||||
pub struct Fixed<const N:usize,const F:usize>{
|
||||
pub(crate)bits:BInt<{N}>,
|
||||
}
|
||||
|
||||
impl<const N:usize,const F:usize> Fixed<N,F>{
|
||||
pub const MAX:Self=Self::from_bits(BInt::<N>::MAX);
|
||||
pub const MIN:Self=Self::from_bits(BInt::<N>::MIN);
|
||||
pub const ZERO:Self=Self::from_bits(BInt::<N>::ZERO);
|
||||
pub const EPSILON:Self=Self::from_bits(BInt::<N>::ONE);
|
||||
pub const NEG_EPSILON:Self=Self::from_bits(BInt::<N>::NEG_ONE);
|
||||
pub const ONE:Self=Self::from_bits(BInt::<N>::ONE.shl(F as u32));
|
||||
pub const TWO:Self=Self::from_bits(BInt::<N>::TWO.shl(F as u32));
|
||||
pub const HALF:Self=Self::from_bits(BInt::<N>::ONE.shl(F as u32-1));
|
||||
pub const NEG_ONE:Self=Self::from_bits(BInt::<N>::NEG_ONE.shl(F as u32));
|
||||
pub const NEG_TWO:Self=Self::from_bits(BInt::<N>::NEG_TWO.shl(F as u32));
|
||||
pub const NEG_HALF:Self=Self::from_bits(BInt::<N>::NEG_ONE.shl(F as u32-1));
|
||||
}
|
||||
impl<const N:usize,const F:usize> Fixed<N,F>{
|
||||
#[inline]
|
||||
pub const fn from_bits(bits:BInt::<N>)->Self{
|
||||
Self{
|
||||
bits,
|
||||
}
|
||||
}
|
||||
#[inline]
|
||||
pub const fn to_bits(self)->BInt<N>{
|
||||
self.bits
|
||||
}
|
||||
#[inline]
|
||||
pub const fn raw_digit(value:i64)->Self{
|
||||
let mut digits=[0u64;N];
|
||||
digits[0]=value.abs() as u64;
|
||||
//sign bit
|
||||
digits[N-1]|=(value&i64::MIN) as u64;
|
||||
Self::from_bits(BInt::from_bits(bnum::BUint::from_digits(digits)))
|
||||
}
|
||||
#[inline]
|
||||
pub const fn is_zero(self)->bool{
|
||||
self.bits.is_zero()
|
||||
}
|
||||
#[inline]
|
||||
pub const fn is_negative(self)->bool{
|
||||
self.bits.is_negative()
|
||||
}
|
||||
#[inline]
|
||||
pub const fn is_positive(self)->bool{
|
||||
self.bits.is_positive()
|
||||
}
|
||||
#[inline]
|
||||
pub const fn abs(self)->Self{
|
||||
Self::from_bits(self.bits.abs())
|
||||
}
|
||||
}
|
||||
impl<const F:usize> Fixed<1,F>{
|
||||
/// My old code called this function everywhere so let's provide it
|
||||
#[inline]
|
||||
pub const fn raw(value:i64)->Self{
|
||||
Self::from_bits(BInt::from_bits(bnum::BUint::from_digit(value as u64)))
|
||||
}
|
||||
#[inline]
|
||||
pub const fn to_raw(self)->i64{
|
||||
let &[digit]=self.to_bits().to_bits().digits();
|
||||
digit as i64
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! impl_from {
|
||||
($($from:ty),*)=>{
|
||||
$(
|
||||
impl<const N:usize,const F:usize> From<$from> for Fixed<N,F>{
|
||||
#[inline]
|
||||
fn from(value:$from)->Self{
|
||||
Self::from_bits(BInt::<{N}>::from(value)<<F as u32)
|
||||
}
|
||||
}
|
||||
)*
|
||||
};
|
||||
}
|
||||
impl_from!(
|
||||
u8,u16,u32,u64,u128,usize,
|
||||
i8,i16,i32,i64,i128,isize
|
||||
);
|
||||
|
||||
impl<const N:usize,const F:usize,T> PartialEq<T> for Fixed<N,F>
|
||||
where
|
||||
T:Copy,
|
||||
BInt::<N>:From<T>,
|
||||
{
|
||||
#[inline]
|
||||
fn eq(&self,&other:&T)->bool{
|
||||
self.bits.eq(&other.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl<const N:usize,const F:usize,T> PartialOrd<T> for Fixed<N,F>
|
||||
where
|
||||
T:Copy,
|
||||
BInt::<N>:From<T>,
|
||||
{
|
||||
#[inline]
|
||||
fn partial_cmp(&self,&other:&T)->Option<std::cmp::Ordering>{
|
||||
self.bits.partial_cmp(&other.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl<const N:usize,const F:usize> std::ops::Neg for Fixed<N,F>{
|
||||
type Output=Self;
|
||||
#[inline]
|
||||
fn neg(self)->Self{
|
||||
Self::from_bits(self.bits.neg())
|
||||
}
|
||||
}
|
||||
impl<const N:usize,const F:usize> std::iter::Sum for Fixed<N,F>{
|
||||
#[inline]
|
||||
fn sum<I:Iterator<Item=Self>>(iter:I)->Self{
|
||||
let mut sum=Self::ZERO;
|
||||
for elem in iter{
|
||||
sum+=elem;
|
||||
}
|
||||
sum
|
||||
}
|
||||
}
|
||||
|
||||
const fn signed_shift(lhs:u64,rhs:i32)->u64{
|
||||
if rhs.is_negative(){
|
||||
lhs>>-rhs
|
||||
}else{
|
||||
lhs<<rhs
|
||||
}
|
||||
}
|
||||
macro_rules! impl_into_float {
|
||||
( $output: ty, $unsigned:ty, $exponent_bits:expr, $mantissa_bits:expr ) => {
|
||||
impl<const N:usize,const F:usize> Into<$output> for Fixed<N,F>{
|
||||
#[inline]
|
||||
fn into(self)->$output{
|
||||
const DIGIT_SHIFT:u32=6;//Log2[64]
|
||||
// SBBB BBBB
|
||||
// 1001 1110 0000 0000
|
||||
let sign=if self.bits.is_negative(){(1 as $unsigned)<<(<$unsigned>::BITS-1)}else{0};
|
||||
let unsigned=self.bits.unsigned_abs();
|
||||
let most_significant_bit=unsigned.bits();
|
||||
let exp=if unsigned.is_zero(){
|
||||
0
|
||||
}else{
|
||||
let msb=most_significant_bit as $unsigned;
|
||||
let _127=((1 as $unsigned)<<($exponent_bits-1))-1;
|
||||
let msb_offset=msb+_127-1-F as $unsigned;
|
||||
msb_offset<<($mantissa_bits-1)
|
||||
};
|
||||
let digits=unsigned.digits();
|
||||
let digit_index=most_significant_bit.saturating_sub(1)>>DIGIT_SHIFT;
|
||||
let digit=digits[digit_index as usize];
|
||||
//How many bits does the mantissa take from this digit
|
||||
let take_bits=most_significant_bit-(digit_index<<DIGIT_SHIFT);
|
||||
let rest_of_mantissa=$mantissa_bits as i32-(take_bits as i32);
|
||||
let mut unmasked_mant=signed_shift(digit,rest_of_mantissa) as $unsigned;
|
||||
if 0<rest_of_mantissa&&digit_index!=0{
|
||||
//take the next digit down and shove some of its bits onto the bottom of the mantissa
|
||||
let digit=digits[digit_index as usize-1];
|
||||
let take_bits=most_significant_bit-((digit_index-1)<<DIGIT_SHIFT);
|
||||
let rest_of_mantissa=$mantissa_bits as i32-(take_bits as i32);
|
||||
let unmasked_mant2=signed_shift(digit,rest_of_mantissa) as $unsigned;
|
||||
unmasked_mant|=unmasked_mant2;
|
||||
}
|
||||
let mant=unmasked_mant&((1 as $unsigned)<<($mantissa_bits-1))-1;
|
||||
let bits=sign|exp|mant;
|
||||
<$output>::from_bits(bits)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
impl_into_float!(f32,u32,8,24);
|
||||
impl_into_float!(f64,u64,11,53);
|
||||
|
||||
#[inline]
|
||||
fn integer_decode_f32(f: f32) -> (u64, i16, bool) {
|
||||
let bits: u32 = f.to_bits();
|
||||
let sign: bool = bits & (1<<31) != 0;
|
||||
let mut exponent: i16 = ((bits >> 23) & 0xff) as i16;
|
||||
let mantissa = if exponent == 0 {
|
||||
(bits & 0x7fffff) << 1
|
||||
} else {
|
||||
(bits & 0x7fffff) | 0x800000
|
||||
};
|
||||
// Exponent bias + mantissa shift
|
||||
exponent -= 127 + 23;
|
||||
(mantissa as u64, exponent, sign)
|
||||
}
|
||||
#[inline]
|
||||
fn integer_decode_f64(f: f64) -> (u64, i16, bool) {
|
||||
let bits: u64 = f.to_bits();
|
||||
let sign: bool = bits & (1u64<<63) != 0;
|
||||
let mut exponent: i16 = ((bits >> 52) & 0x7ff) as i16;
|
||||
let mantissa = if exponent == 0 {
|
||||
(bits & 0xfffffffffffff) << 1
|
||||
} else {
|
||||
(bits & 0xfffffffffffff) | 0x10000000000000
|
||||
};
|
||||
// Exponent bias + mantissa shift
|
||||
exponent -= 1023 + 52;
|
||||
(mantissa, exponent, sign)
|
||||
}
|
||||
#[derive(Debug,Eq,PartialEq)]
|
||||
pub enum FixedFromFloatError{
|
||||
Nan,
|
||||
Infinite,
|
||||
Overflow,
|
||||
Underflow,
|
||||
}
|
||||
impl FixedFromFloatError{
|
||||
pub fn underflow_to_zero<const N:usize,const F:usize>(self)->Result<Fixed<N,F>,Self>{
|
||||
match self{
|
||||
FixedFromFloatError::Underflow=>Ok(Fixed::ZERO),
|
||||
_=>Err(self),
|
||||
}
|
||||
}
|
||||
}
|
||||
macro_rules! impl_from_float {
|
||||
( $decode:ident, $input: ty, $mantissa_bits:expr ) => {
|
||||
impl<const N:usize,const F:usize> TryFrom<$input> for Fixed<N,F>{
|
||||
type Error=FixedFromFloatError;
|
||||
#[inline]
|
||||
fn try_from(value:$input)->Result<Self,Self::Error>{
|
||||
const DIGIT_SHIFT:u32=6;
|
||||
match value.classify(){
|
||||
std::num::FpCategory::Nan=>Err(FixedFromFloatError::Nan),
|
||||
std::num::FpCategory::Infinite=>Err(FixedFromFloatError::Infinite),
|
||||
std::num::FpCategory::Zero=>Ok(Self::ZERO),
|
||||
std::num::FpCategory::Subnormal
|
||||
|std::num::FpCategory::Normal
|
||||
=>{
|
||||
let (m,e,s)=$decode(value);
|
||||
let mut digits=[0u64;N];
|
||||
let most_significant_bit=e as i32+$mantissa_bits as i32+F as i32;
|
||||
if most_significant_bit<0{
|
||||
return Err(FixedFromFloatError::Underflow);
|
||||
}
|
||||
let digit_index=most_significant_bit>>DIGIT_SHIFT;
|
||||
let digit=digits.get_mut(digit_index as usize).ok_or(FixedFromFloatError::Overflow)?;
|
||||
let take_bits=most_significant_bit-(digit_index<<DIGIT_SHIFT);
|
||||
let rest_of_mantissa=-($mantissa_bits as i32-(take_bits as i32));
|
||||
*digit=signed_shift(m,rest_of_mantissa);
|
||||
if rest_of_mantissa<0&&digit_index!=0{
|
||||
//we don't care if some float bits are partially truncated
|
||||
if let Some(digit)=digits.get_mut((digit_index-1) as usize){
|
||||
let take_bits=most_significant_bit-((digit_index-1)<<DIGIT_SHIFT);
|
||||
let rest_of_mantissa=-($mantissa_bits as i32-(take_bits as i32));
|
||||
*digit=signed_shift(m,rest_of_mantissa);
|
||||
}
|
||||
}
|
||||
let bits=BInt::from_bits(bnum::BUint::from_digits(digits));
|
||||
Ok(if s{
|
||||
Self::from_bits(bits.overflowing_neg().0)
|
||||
}else{
|
||||
Self::from_bits(bits)
|
||||
})
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
impl_from_float!(integer_decode_f32,f32,24);
|
||||
impl_from_float!(integer_decode_f64,f64,53);
|
||||
|
||||
impl<const N:usize,const F:usize> core::fmt::Display for Fixed<N,F>{
|
||||
#[inline]
|
||||
fn fmt(&self,f:&mut core::fmt::Formatter)->Result<(),core::fmt::Error>{
|
||||
let float:f32=(*self).into();
|
||||
core::write!(f,"{:.3}",float)
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! impl_additive_operator {
|
||||
( $struct: ident, $trait: ident, $method: ident, $output: ty ) => {
|
||||
impl<const N:usize,const F:usize> $struct<N,F>{
|
||||
#[inline]
|
||||
pub const fn $method(self, other: Self) -> Self {
|
||||
Self::from_bits(self.bits.$method(other.bits))
|
||||
}
|
||||
}
|
||||
impl<const N:usize,const F:usize> core::ops::$trait for $struct<N,F>{
|
||||
type Output = $output;
|
||||
#[inline]
|
||||
fn $method(self, other: Self) -> Self::Output {
|
||||
self.$method(other)
|
||||
}
|
||||
}
|
||||
impl<const N:usize,const F:usize,U> core::ops::$trait<U> for $struct<N,F>
|
||||
where
|
||||
BInt::<N>:From<U>,
|
||||
{
|
||||
type Output = $output;
|
||||
#[inline]
|
||||
fn $method(self, other: U) -> Self::Output {
|
||||
Self::from_bits(self.bits.$method(BInt::<N>::from(other).shl(F as u32)))
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
macro_rules! impl_additive_assign_operator {
|
||||
( $struct: ident, $trait: ident, $method: ident ) => {
|
||||
impl<const N:usize,const F:usize> core::ops::$trait for $struct<N,F>{
|
||||
#[inline]
|
||||
fn $method(&mut self, other: Self) {
|
||||
self.bits.$method(other.bits);
|
||||
}
|
||||
}
|
||||
impl<const N:usize,const F:usize,U> core::ops::$trait<U> for $struct<N,F>
|
||||
where
|
||||
BInt::<N>:From<U>,
|
||||
{
|
||||
#[inline]
|
||||
fn $method(&mut self, other: U) {
|
||||
self.bits.$method(BInt::<N>::from(other).shl(F as u32));
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Impl arithmetic pperators
|
||||
impl_additive_assign_operator!( Fixed, AddAssign, add_assign );
|
||||
impl_additive_operator!( Fixed, Add, add, Self );
|
||||
impl_additive_assign_operator!( Fixed, SubAssign, sub_assign );
|
||||
impl_additive_operator!( Fixed, Sub, sub, Self );
|
||||
impl_additive_assign_operator!( Fixed, RemAssign, rem_assign );
|
||||
impl_additive_operator!( Fixed, Rem, rem, Self );
|
||||
|
||||
// Impl bitwise operators
|
||||
impl_additive_assign_operator!( Fixed, BitAndAssign, bitand_assign );
|
||||
impl_additive_operator!( Fixed, BitAnd, bitand, Self );
|
||||
impl_additive_assign_operator!( Fixed, BitOrAssign, bitor_assign );
|
||||
impl_additive_operator!( Fixed, BitOr, bitor, Self );
|
||||
impl_additive_assign_operator!( Fixed, BitXorAssign, bitxor_assign );
|
||||
impl_additive_operator!( Fixed, BitXor, bitxor, Self );
|
||||
|
||||
// non-wide operators. The result is the same width as the inputs.
|
||||
|
||||
// This macro is not used in the default configuration.
|
||||
#[allow(unused_macros)]
|
||||
macro_rules! impl_multiplicative_operator_not_const_generic {
|
||||
( ($struct: ident, $trait: ident, $method: ident, $output: ty ), $width:expr ) => {
|
||||
impl<const F:usize> core::ops::$trait for $struct<$width,F>{
|
||||
type Output = $output;
|
||||
#[inline]
|
||||
fn $method(self, other: Self) -> Self::Output {
|
||||
paste::item!{
|
||||
self.[<fixed_ $method>](other)
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
macro_rules! impl_multiplicative_assign_operator_not_const_generic {
|
||||
( ($struct: ident, $trait: ident, $method: ident, $non_assign_method: ident ), $width:expr ) => {
|
||||
impl<const F:usize> core::ops::$trait for $struct<$width,F>{
|
||||
#[inline]
|
||||
fn $method(&mut self, other: Self) {
|
||||
paste::item!{
|
||||
*self=self.[<fixed_ $non_assign_method>](other);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! impl_multiply_operator_not_const_generic {
|
||||
( ($struct: ident, $trait: ident, $method: ident, $output: ty ), $width:expr ) => {
|
||||
impl<const F:usize> $struct<$width,F>{
|
||||
paste::item!{
|
||||
#[inline]
|
||||
pub fn [<fixed_ $method>](self, rhs: Self) -> Self {
|
||||
let (low,high)=self.bits.unsigned_abs().widening_mul(rhs.bits.unsigned_abs());
|
||||
let out:BInt::<{$width*2}>=unsafe{core::mem::transmute([low,high])};
|
||||
if self.is_negative()==rhs.is_negative(){
|
||||
Self::from_bits(out.shr(F as u32).as_())
|
||||
}else{
|
||||
-Self::from_bits(out.shr(F as u32).as_())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#[cfg(not(feature="wide-mul"))]
|
||||
impl_multiplicative_operator_not_const_generic!(($struct, $trait, $method, $output ), $width);
|
||||
#[cfg(feature="deferred-division")]
|
||||
impl ratio_ops::ratio::Divide<i64> for Fixed<$width,{$width*32}>{
|
||||
type Output=Self;
|
||||
#[inline]
|
||||
fn divide(self, other: i64)->Self::Output{
|
||||
Self::from_bits(self.bits.div_euclid(BInt::from(other)))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
macro_rules! impl_divide_operator_not_const_generic {
|
||||
( ($struct: ident, $trait: ident, $method: ident, $output: ty ), $width:expr ) => {
|
||||
impl<const F:usize> $struct<$width,F>{
|
||||
paste::item!{
|
||||
#[inline]
|
||||
pub fn [<fixed_ $method>](self,other:Self)->Self{
|
||||
//this only needs to be $width+F as u32/64+1 but MUH CONST GENERICS!!!!!
|
||||
let lhs=self.bits.as_::<BInt::<{$width*2}>>().shl(F as u32);
|
||||
let rhs=other.bits.as_::<BInt::<{$width*2}>>();
|
||||
Self::from_bits(lhs.div_euclid(rhs).as_())
|
||||
}
|
||||
}
|
||||
}
|
||||
#[cfg(all(not(feature="wide-mul"),not(feature="deferred-division")))]
|
||||
impl_multiplicative_operator_not_const_generic!(($struct, $trait, $method, $output ), $width);
|
||||
#[cfg(all(not(feature="wide-mul"),feature="deferred-division"))]
|
||||
impl<const F:usize> ratio_ops::ratio::Divide for $struct<$width,F>{
|
||||
type Output = $output;
|
||||
#[inline]
|
||||
fn divide(self, other: Self) -> Self::Output {
|
||||
paste::item!{
|
||||
self.[<fixed_ $method>](other)
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! impl_multiplicative_operator {
|
||||
( $struct: ident, $trait: ident, $method: ident, $inner_method: ident, $output: ty ) => {
|
||||
impl<const N:usize,const F:usize,U> core::ops::$trait<U> for $struct<N,F>
|
||||
where
|
||||
BInt::<N>:From<U>+core::ops::$trait,
|
||||
{
|
||||
type Output = $output;
|
||||
#[inline]
|
||||
fn $method(self,other:U)->Self::Output{
|
||||
Self::from_bits(self.bits.$inner_method(BInt::<N>::from(other)))
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
macro_rules! impl_multiplicative_assign_operator {
|
||||
( $struct: ident, $trait: ident, $method: ident, $not_assign_method: ident ) => {
|
||||
impl<const N:usize,const F:usize,U> core::ops::$trait<U> for $struct<N,F>
|
||||
where
|
||||
BInt::<N>:From<U>+core::ops::$trait,
|
||||
{
|
||||
#[inline]
|
||||
fn $method(&mut self,other:U){
|
||||
self.bits=self.bits.$not_assign_method(BInt::<N>::from(other));
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! macro_repeated{
|
||||
(
|
||||
$macro:ident,
|
||||
$any:tt,
|
||||
$($repeated:tt),*
|
||||
)=>{
|
||||
$(
|
||||
$macro!($any, $repeated);
|
||||
)*
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! macro_16 {
|
||||
( $macro: ident, $any:tt ) => {
|
||||
macro_repeated!($macro,$any,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16);
|
||||
}
|
||||
}
|
||||
|
||||
macro_16!( impl_multiplicative_assign_operator_not_const_generic, (Fixed, MulAssign, mul_assign, mul) );
|
||||
macro_16!( impl_multiply_operator_not_const_generic, (Fixed, Mul, mul, Self) );
|
||||
macro_16!( impl_multiplicative_assign_operator_not_const_generic, (Fixed, DivAssign, div_assign, div) );
|
||||
macro_16!( impl_divide_operator_not_const_generic, (Fixed, Div, div, Self) );
|
||||
impl_multiplicative_assign_operator!( Fixed, MulAssign, mul_assign, mul );
|
||||
impl_multiplicative_operator!( Fixed, Mul, mul, mul, Self );
|
||||
impl_multiplicative_assign_operator!( Fixed, DivAssign, div_assign, div_euclid );
|
||||
impl_multiplicative_operator!( Fixed, Div, div, div_euclid, Self );
|
||||
#[cfg(feature="deferred-division")]
|
||||
impl<const LHS_N:usize,const LHS_F:usize,const RHS_N:usize,const RHS_F:usize> core::ops::Div<Fixed<RHS_N,RHS_F>> for Fixed<LHS_N,LHS_F>{
|
||||
type Output=ratio_ops::ratio::Ratio<Fixed<LHS_N,LHS_F>,Fixed<RHS_N,RHS_F>>;
|
||||
#[inline]
|
||||
fn div(self, other: Fixed<RHS_N,RHS_F>)->Self::Output{
|
||||
ratio_ops::ratio::Ratio::new(self,other)
|
||||
}
|
||||
}
|
||||
#[cfg(feature="deferred-division")]
|
||||
impl<const N:usize,const F:usize> ratio_ops::ratio::Parity for Fixed<N,F>{
|
||||
fn parity(&self)->bool{
|
||||
self.is_negative()
|
||||
}
|
||||
}
|
||||
macro_rules! impl_shift_operator {
|
||||
( $struct: ident, $trait: ident, $method: ident, $output: ty ) => {
|
||||
impl<const N:usize,const F:usize> core::ops::$trait<u32> for $struct<N,F>{
|
||||
type Output = $output;
|
||||
#[inline]
|
||||
fn $method(self, other: u32) -> Self::Output {
|
||||
Self::from_bits(self.bits.$method(other))
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
macro_rules! impl_shift_assign_operator {
|
||||
( $struct: ident, $trait: ident, $method: ident ) => {
|
||||
impl<const N:usize,const F:usize> core::ops::$trait<u32> for $struct<N,F>{
|
||||
#[inline]
|
||||
fn $method(&mut self, other: u32) {
|
||||
self.bits.$method(other);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
impl_shift_assign_operator!( Fixed, ShlAssign, shl_assign );
|
||||
impl_shift_operator!( Fixed, Shl, shl, Self );
|
||||
impl_shift_assign_operator!( Fixed, ShrAssign, shr_assign );
|
||||
impl_shift_operator!( Fixed, Shr, shr, Self );
|
||||
|
||||
// wide operators. The result width is the sum of the input widths, i.e. none of the multiplication
|
||||
|
||||
#[allow(unused_macros)]
|
||||
macro_rules! impl_wide_operators{
|
||||
($lhs:expr,$rhs:expr)=>{
|
||||
impl core::ops::Mul<Fixed<$rhs,{$rhs*32}>> for Fixed<$lhs,{$lhs*32}>{
|
||||
type Output=Fixed<{$lhs+$rhs},{($lhs+$rhs)*32}>;
|
||||
#[inline]
|
||||
fn mul(self, other: Fixed<$rhs,{$rhs*32}>)->Self::Output{
|
||||
paste::item!{
|
||||
self.[<wide_mul_ $lhs _ $rhs>](other)
|
||||
}
|
||||
}
|
||||
}
|
||||
#[cfg(not(feature="deferred-division"))]
|
||||
impl core::ops::Div<Fixed<$rhs,{$rhs*32}>> for Fixed<$lhs,{$lhs*32}>{
|
||||
type Output=Fixed<{$lhs+$rhs},{($lhs+$rhs)*32}>;
|
||||
#[inline]
|
||||
fn div(self, other: Fixed<$rhs,{$rhs*32}>)->Self::Output{
|
||||
paste::item!{
|
||||
self.[<wide_div_ $lhs _ $rhs>](other)
|
||||
}
|
||||
}
|
||||
}
|
||||
#[cfg(feature="deferred-division")]
|
||||
impl ratio_ops::ratio::Divide<Fixed<$rhs,{$rhs*32}>> for Fixed<$lhs,{$lhs*32}>{
|
||||
type Output=Fixed<{$lhs+$rhs},{($lhs+$rhs)*32}>;
|
||||
#[inline]
|
||||
fn divide(self, other: Fixed<$rhs,{$rhs*32}>)->Self::Output{
|
||||
paste::item!{
|
||||
self.[<wide_div_ $lhs _ $rhs>](other)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// WIDE MUL: multiply into a wider type
|
||||
// let a = I32F32::ONE;
|
||||
// let b:I64F64 = a.wide_mul(a);
|
||||
macro_rules! impl_wide_not_const_generic{
|
||||
(
|
||||
(),
|
||||
($lhs:expr,$rhs:expr)
|
||||
)=>{
|
||||
impl Fixed<$lhs,{$lhs*32}>
|
||||
{
|
||||
paste::item!{
|
||||
#[inline]
|
||||
pub fn [<wide_mul_ $lhs _ $rhs>](self,rhs:Fixed<$rhs,{$rhs*32}>)->Fixed<{$lhs+$rhs},{($lhs+$rhs)*32}>{
|
||||
let lhs=self.bits.as_::<BInt<{$lhs+$rhs}>>();
|
||||
let rhs=rhs.bits.as_::<BInt<{$lhs+$rhs}>>();
|
||||
Fixed::from_bits(lhs*rhs)
|
||||
}
|
||||
/// This operation cannot represent the fraction exactly,
|
||||
/// but it shapes the output to have precision for the
|
||||
/// largest and smallest possible fractions.
|
||||
#[inline]
|
||||
pub fn [<wide_div_ $lhs _ $rhs>](self,rhs:Fixed<$rhs,{$rhs*32}>)->Fixed<{$lhs+$rhs},{($lhs+$rhs)*32}>{
|
||||
// (lhs/2^LHS_FRAC)/(rhs/2^RHS_FRAC)
|
||||
let lhs=self.bits.as_::<BInt<{$lhs+$rhs}>>().shl($rhs*64);
|
||||
let rhs=rhs.bits.as_::<BInt<{$lhs+$rhs}>>();
|
||||
Fixed::from_bits(lhs/rhs)
|
||||
}
|
||||
}
|
||||
}
|
||||
#[cfg(feature="wide-mul")]
|
||||
impl_wide_operators!($lhs,$rhs);
|
||||
};
|
||||
}
|
||||
macro_rules! impl_wide_same_size_not_const_generic{
|
||||
(
|
||||
(),
|
||||
$width:expr
|
||||
)=>{
|
||||
impl Fixed<$width,{$width*32}>
|
||||
{
|
||||
paste::item!{
|
||||
#[inline]
|
||||
pub fn [<wide_mul_ $width _ $width>](self,rhs:Fixed<$width,{$width*32}>)->Fixed<{$width*2},{$width*2*32}>{
|
||||
let (low,high)=self.bits.unsigned_abs().widening_mul(rhs.bits.unsigned_abs());
|
||||
let out:BInt::<{$width*2}>=unsafe{core::mem::transmute([low,high])};
|
||||
if self.is_negative()==rhs.is_negative(){
|
||||
Fixed::from_bits(out)
|
||||
}else{
|
||||
// Normal neg is the cheapest negation operation
|
||||
// And the inputs cannot reach the point where it matters
|
||||
Fixed::from_bits(out.neg())
|
||||
}
|
||||
}
|
||||
/// This operation cannot represent the fraction exactly,
|
||||
/// but it shapes the output to have precision for the
|
||||
/// largest and smallest possible fractions.
|
||||
#[inline]
|
||||
pub fn [<wide_div_ $width _ $width>](self,rhs:Fixed<$width,{$width*32}>)->Fixed<{$width*2},{$width*2*32}>{
|
||||
// (lhs/2^LHS_FRAC)/(rhs/2^RHS_FRAC)
|
||||
let lhs=self.bits.as_::<BInt<{$width*2}>>().shl($width*64);
|
||||
let rhs=rhs.bits.as_::<BInt<{$width*2}>>();
|
||||
Fixed::from_bits(lhs/rhs)
|
||||
}
|
||||
}
|
||||
}
|
||||
#[cfg(feature="wide-mul")]
|
||||
impl_wide_operators!($width,$width);
|
||||
};
|
||||
}
|
||||
|
||||
//const generics sidestepped wahoo
|
||||
macro_repeated!(
|
||||
impl_wide_not_const_generic,(),
|
||||
(2,1),(3,1),(4,1),(5,1),(6,1),(7,1),(8,1),(9,1),(10,1),(11,1),(12,1),(13,1),(14,1),(15,1),
|
||||
(1,2), (3,2),(4,2),(5,2),(6,2),(7,2),(8,2),(9,2),(10,2),(11,2),(12,2),(13,2),(14,2),
|
||||
(1,3),(2,3), (4,3),(5,3),(6,3),(7,3),(8,3),(9,3),(10,3),(11,3),(12,3),(13,3),
|
||||
(1,4),(2,4),(3,4), (5,4),(6,4),(7,4),(8,4),(9,4),(10,4),(11,4),(12,4),
|
||||
(1,5),(2,5),(3,5),(4,5), (6,5),(7,5),(8,5),(9,5),(10,5),(11,5),
|
||||
(1,6),(2,6),(3,6),(4,6),(5,6), (7,6),(8,6),(9,6),(10,6),
|
||||
(1,7),(2,7),(3,7),(4,7),(5,7),(6,7), (8,7),(9,7),
|
||||
(1,8),(2,8),(3,8),(4,8),(5,8),(6,8),(7,8), (9,8),
|
||||
(1,9),(2,9),(3,9),(4,9),(5,9),(6,9),(7,9),
|
||||
(1,10),(2,10),(3,10),(4,10),(5,10),(6,10),
|
||||
(1,11),(2,11),(3,11),(4,11),(5,11),
|
||||
(1,12),(2,12),(3,12),(4,12),
|
||||
(1,13),(2,13),(3,13),
|
||||
(1,14),(2,14),
|
||||
(1,15)
|
||||
);
|
||||
macro_repeated!(
|
||||
impl_wide_same_size_not_const_generic,(),
|
||||
1,2,3,4,5,6,7,8
|
||||
);
|
||||
|
||||
pub trait Fix<Out>{
|
||||
fn fix(self)->Out;
|
||||
}
|
||||
|
||||
macro_rules! impl_fix_rhs_lt_lhs_not_const_generic{
|
||||
(
|
||||
(),
|
||||
($lhs:expr,$rhs:expr)
|
||||
)=>{
|
||||
impl Fixed<$lhs,{$lhs*32}>
|
||||
{
|
||||
paste::item!{
|
||||
#[inline]
|
||||
pub fn [<fix_ $rhs>](self)->Fixed<$rhs,{$rhs*32}>{
|
||||
Fixed::from_bits(bnum::cast::As::as_::<BInt::<$rhs>>(self.bits.shr(($lhs-$rhs)*32)))
|
||||
}
|
||||
}
|
||||
}
|
||||
impl Fix<Fixed<$rhs,{$rhs*32}>> for Fixed<$lhs,{$lhs*32}>{
|
||||
fn fix(self)->Fixed<$rhs,{$rhs*32}>{
|
||||
paste::item!{
|
||||
self.[<fix_ $rhs>]()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
macro_rules! impl_fix_lhs_lt_rhs_not_const_generic{
|
||||
(
|
||||
(),
|
||||
($lhs:expr,$rhs:expr)
|
||||
)=>{
|
||||
impl Fixed<$lhs,{$lhs*32}>
|
||||
{
|
||||
paste::item!{
|
||||
#[inline]
|
||||
pub fn [<fix_ $rhs>](self)->Fixed<$rhs,{$rhs*32}>{
|
||||
Fixed::from_bits(bnum::cast::As::as_::<BInt::<$rhs>>(self.bits).shl(($rhs-$lhs)*32))
|
||||
}
|
||||
}
|
||||
}
|
||||
impl Fix<Fixed<$rhs,{$rhs*32}>> for Fixed<$lhs,{$lhs*32}>{
|
||||
fn fix(self)->Fixed<$rhs,{$rhs*32}>{
|
||||
paste::item!{
|
||||
self.[<fix_ $rhs>]()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
macro_rules! impl_fix_lhs_eq_rhs_not_const_generic{
|
||||
(
|
||||
(),
|
||||
($lhs:expr,$rhs:expr)
|
||||
)=>{
|
||||
impl Fixed<$lhs,{$lhs*32}>
|
||||
{
|
||||
paste::item!{
|
||||
#[inline]
|
||||
pub fn [<fix_ $rhs>](self)->Fixed<$rhs,{$rhs*32}>{
|
||||
self
|
||||
}
|
||||
}
|
||||
}
|
||||
impl Fix<Fixed<$rhs,{$rhs*32}>> for Fixed<$lhs,{$lhs*32}>{
|
||||
fn fix(self)->Fixed<$rhs,{$rhs*32}>{
|
||||
paste::item!{
|
||||
self.[<fix_ $rhs>]()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// I LOVE NOT BEING ABLE TO USE CONST GENERICS
|
||||
|
||||
macro_repeated!(
|
||||
impl_fix_rhs_lt_lhs_not_const_generic,(),
|
||||
(2,1),(3,1),(4,1),(5,1),(6,1),(7,1),(8,1),(9,1),(10,1),(11,1),(12,1),(13,1),(14,1),(15,1),(16,1),(17,1),
|
||||
(3,2),(4,2),(5,2),(6,2),(7,2),(8,2),(9,2),(10,2),(11,2),(12,2),(13,2),(14,2),(15,2),(16,2),
|
||||
(4,3),(5,3),(6,3),(7,3),(8,3),(9,3),(10,3),(11,3),(12,3),(13,3),(14,3),(15,3),(16,3),
|
||||
(5,4),(6,4),(7,4),(8,4),(9,4),(10,4),(11,4),(12,4),(13,4),(14,4),(15,4),(16,4),
|
||||
(6,5),(7,5),(8,5),(9,5),(10,5),(11,5),(12,5),(13,5),(14,5),(15,5),(16,5),
|
||||
(7,6),(8,6),(9,6),(10,6),(11,6),(12,6),(13,6),(14,6),(15,6),(16,6),
|
||||
(8,7),(9,7),(10,7),(11,7),(12,7),(13,7),(14,7),(15,7),(16,7),
|
||||
(9,8),(10,8),(11,8),(12,8),(13,8),(14,8),(15,8),(16,8),
|
||||
(10,9),(11,9),(12,9),(13,9),(14,9),(15,9),(16,9),
|
||||
(11,10),(12,10),(13,10),(14,10),(15,10),(16,10),
|
||||
(12,11),(13,11),(14,11),(15,11),(16,11),
|
||||
(13,12),(14,12),(15,12),(16,12),
|
||||
(14,13),(15,13),(16,13),
|
||||
(15,14),(16,14),
|
||||
(16,15)
|
||||
);
|
||||
macro_repeated!(
|
||||
impl_fix_lhs_lt_rhs_not_const_generic,(),
|
||||
(1,2),
|
||||
(1,3),(2,3),
|
||||
(1,4),(2,4),(3,4),
|
||||
(1,5),(2,5),(3,5),(4,5),
|
||||
(1,6),(2,6),(3,6),(4,6),(5,6),
|
||||
(1,7),(2,7),(3,7),(4,7),(5,7),(6,7),
|
||||
(1,8),(2,8),(3,8),(4,8),(5,8),(6,8),(7,8),
|
||||
(1,9),(2,9),(3,9),(4,9),(5,9),(6,9),(7,9),(8,9),
|
||||
(1,10),(2,10),(3,10),(4,10),(5,10),(6,10),(7,10),(8,10),(9,10),
|
||||
(1,11),(2,11),(3,11),(4,11),(5,11),(6,11),(7,11),(8,11),(9,11),(10,11),
|
||||
(1,12),(2,12),(3,12),(4,12),(5,12),(6,12),(7,12),(8,12),(9,12),(10,12),(11,12),
|
||||
(1,13),(2,13),(3,13),(4,13),(5,13),(6,13),(7,13),(8,13),(9,13),(10,13),(11,13),(12,13),
|
||||
(1,14),(2,14),(3,14),(4,14),(5,14),(6,14),(7,14),(8,14),(9,14),(10,14),(11,14),(12,14),(13,14),
|
||||
(1,15),(2,15),(3,15),(4,15),(5,15),(6,15),(7,15),(8,15),(9,15),(10,15),(11,15),(12,15),(13,15),(14,15),
|
||||
(1,16),(2,16),(3,16),(4,16),(5,16),(6,16),(7,16),(8,16),(9,16),(10,16),(11,16),(12,16),(13,16),(14,16),(15,16)
|
||||
);
|
||||
macro_repeated!(
|
||||
impl_fix_lhs_eq_rhs_not_const_generic,(),
|
||||
(1,1),(2,2),(3,3),(4,4),(5,5),(6,6),(7,7),(8,8),(9,9),(10,10),(11,11),(12,12),(13,13),(14,14),(15,15),(16,16)
|
||||
);
|
||||
|
||||
macro_rules! impl_not_const_generic{
|
||||
($n:expr,$_2n:expr)=>{
|
||||
impl Fixed<$n,{$n*32}>{
|
||||
paste::item!{
|
||||
#[inline]
|
||||
pub fn sqrt_unchecked(self)->Self{
|
||||
//1<<max_shift must be the minimum power of two which when squared is greater than self
|
||||
//calculating max_shift:
|
||||
//1. count "used" bits to the left of the decimal, not including the sign bit (so -1)
|
||||
//2. divide by 2 via >>1 (sqrt-ish)
|
||||
//3. add on fractional offset
|
||||
//Voila
|
||||
let used_bits=self.bits.bits() as i32-1-($n*32) as i32;
|
||||
let max_shift=((used_bits>>1)+($n*32) as i32) as u32;
|
||||
let mut result=Self::ZERO;
|
||||
|
||||
//resize self to match the wide mul output
|
||||
let wide_self=self.[<fix_ $_2n>]();
|
||||
//descend down the bits and check if flipping each bit would push the square over the input value
|
||||
for shift in (0..=max_shift).rev(){
|
||||
let new_result={
|
||||
let mut bits=result.to_bits().to_bits();
|
||||
bits.set_bit(shift,true);
|
||||
Self::from_bits(BInt::from_bits(bits))
|
||||
};
|
||||
if new_result.[<wide_mul_ $n _ $n>](new_result)<=wide_self{
|
||||
result=new_result;
|
||||
}
|
||||
}
|
||||
result
|
||||
}
|
||||
}
|
||||
#[inline]
|
||||
pub fn sqrt(self)->Self{
|
||||
if self<Self::ZERO{
|
||||
panic!("Square root less than zero")
|
||||
}else{
|
||||
self.sqrt_unchecked()
|
||||
}
|
||||
}
|
||||
#[inline]
|
||||
pub fn sqrt_checked(self)->Option<Self>{
|
||||
if self<Self::ZERO{
|
||||
None
|
||||
}else{
|
||||
Some(self.sqrt_unchecked())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
impl_not_const_generic!(1,2);
|
||||
impl_not_const_generic!(2,4);
|
||||
impl_not_const_generic!(3,6);
|
||||
impl_not_const_generic!(4,8);
|
||||
impl_not_const_generic!(5,10);
|
||||
impl_not_const_generic!(6,12);
|
||||
impl_not_const_generic!(7,14);
|
||||
impl_not_const_generic!(8,16);
|
@ -1,8 +0,0 @@
|
||||
pub mod fixed;
|
||||
pub mod types;
|
||||
|
||||
#[cfg(feature="zeroes")]
|
||||
pub mod zeroes;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
@ -1,218 +0,0 @@
|
||||
use crate::types::I32F32;
|
||||
use crate::types::I256F256;
|
||||
|
||||
#[test]
|
||||
fn you_can_add_numbers(){
|
||||
let a=I256F256::from((3i128*2).pow(4));
|
||||
assert_eq!(a+a,I256F256::from((3i128*2).pow(4)*2));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn to_f32(){
|
||||
let a=I256F256::from(1)>>2;
|
||||
let f:f32=a.into();
|
||||
assert_eq!(f,0.25f32);
|
||||
let f:f32=(-a).into();
|
||||
assert_eq!(f,-0.25f32);
|
||||
let a=I256F256::from(0);
|
||||
let f:f32=(-a).into();
|
||||
assert_eq!(f,0f32);
|
||||
let a=I256F256::from(237946589723468975i64)<<16;
|
||||
let f:f32=a.into();
|
||||
assert_eq!(f,237946589723468975f32*2.0f32.powi(16));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn to_f64(){
|
||||
let a=I256F256::from(1)>>2;
|
||||
let f:f64=a.into();
|
||||
assert_eq!(f,0.25f64);
|
||||
let f:f64=(-a).into();
|
||||
assert_eq!(f,-0.25f64);
|
||||
let a=I256F256::from(0);
|
||||
let f:f64=(-a).into();
|
||||
assert_eq!(f,0f64);
|
||||
let a=I256F256::from(237946589723468975i64)<<16;
|
||||
let f:f64=a.into();
|
||||
assert_eq!(f,237946589723468975f64*2.0f64.powi(16));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn from_f32(){
|
||||
let a=I256F256::from(1)>>2;
|
||||
let b:Result<I256F256,_>=0.25f32.try_into();
|
||||
assert_eq!(b,Ok(a));
|
||||
let a=I256F256::from(-1)>>2;
|
||||
let b:Result<I256F256,_>=(-0.25f32).try_into();
|
||||
assert_eq!(b,Ok(a));
|
||||
let a=I256F256::from(0);
|
||||
let b:Result<I256F256,_>=0.try_into();
|
||||
assert_eq!(b,Ok(a));
|
||||
let a=I256F256::from(0b101011110101001010101010000000000000000000000000000i64)<<16;
|
||||
let b:Result<I256F256,_>=(0b101011110101001010101010000000000000000000000000000u64 as f32*2.0f32.powi(16)).try_into();
|
||||
assert_eq!(b,Ok(a));
|
||||
//I32F32::MAX into f32 is truncated into this value
|
||||
let a=I32F32::raw(0b111111111111111111111111000000000000000000000000000000000000000i64);
|
||||
let b:Result<I32F32,_>=Into::<f32>::into(I32F32::MAX).try_into();
|
||||
assert_eq!(b,Ok(a));
|
||||
//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
|
||||
let a=I32F32::MIN;
|
||||
let b:Result<I32F32,_>=Into::<f32>::into(I32F32::MIN).try_into();
|
||||
assert_eq!(b,Err(crate::fixed::FixedFromFloatError::Overflow));
|
||||
//16 is within the 24 bits of float precision
|
||||
let b:Result<I32F32,_>=Into::<f32>::into(-I32F32::MIN.fix_2()).try_into();
|
||||
assert_eq!(b,Err(crate::fixed::FixedFromFloatError::Overflow));
|
||||
let b:Result<I32F32,_>=f32::MIN_POSITIVE.try_into();
|
||||
assert_eq!(b,Err(crate::fixed::FixedFromFloatError::Underflow));
|
||||
//test many cases
|
||||
for i in 0..64{
|
||||
let a=crate::fixed::Fixed::<2,64>::raw_digit(0b111111111111111111111111000000000000000000000000000000000000000i64)<<i;
|
||||
let f:f32=a.into();
|
||||
let b:Result<crate::fixed::Fixed<2,64>,_>=f.try_into();
|
||||
assert_eq!(b,Ok(a));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn from_f64(){
|
||||
let a=I256F256::from(1)>>2;
|
||||
let b:Result<I256F256,_>=0.25f64.try_into();
|
||||
assert_eq!(b,Ok(a));
|
||||
let a=I256F256::from(-1)>>2;
|
||||
let b:Result<I256F256,_>=(-0.25f64).try_into();
|
||||
assert_eq!(b,Ok(a));
|
||||
let a=I256F256::from(0);
|
||||
let b:Result<I256F256,_>=0.try_into();
|
||||
assert_eq!(b,Ok(a));
|
||||
let a=I256F256::from(0b101011110101001010101010000000000000000000000000000i64)<<16;
|
||||
let b:Result<I256F256,_>=(0b101011110101001010101010000000000000000000000000000u64 as f64*2.0f64.powi(16)).try_into();
|
||||
assert_eq!(b,Ok(a));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn you_can_shr_numbers(){
|
||||
let a=I32F32::from(4);
|
||||
assert_eq!(a>>1,I32F32::from(2));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_wide_mul(){
|
||||
let a=I32F32::ONE;
|
||||
let aa=a.wide_mul_1_1(a);
|
||||
assert_eq!(aa,crate::types::I64F64::ONE);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_wide_div(){
|
||||
let a=I32F32::ONE*4;
|
||||
let b=I32F32::ONE*2;
|
||||
let wide_a=a.wide_mul_1_1(I32F32::ONE);
|
||||
let wide_b=b.wide_mul_1_1(I32F32::ONE);
|
||||
let ab=a.wide_div_1_1(b);
|
||||
assert_eq!(ab,crate::types::I64F64::ONE*2);
|
||||
let wab=wide_a.wide_div_2_1(b);
|
||||
assert_eq!(wab,crate::fixed::Fixed::<3,96>::ONE*2);
|
||||
let awb=a.wide_div_1_2(wide_b);
|
||||
assert_eq!(awb,crate::fixed::Fixed::<3,96>::ONE*2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_wide_mul_repeated() {
|
||||
let a=I32F32::from(2);
|
||||
let b=I32F32::from(3);
|
||||
|
||||
let w1=a.wide_mul_1_1(b);
|
||||
let w2=w1.wide_mul_2_2(w1);
|
||||
let w3=w2.wide_mul_4_4(w2);
|
||||
|
||||
assert_eq!(w3,I256F256::from((3i128*2).pow(4)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_bint(){
|
||||
let a=I32F32::ONE;
|
||||
assert_eq!(a*2,I32F32::from(2));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_fix(){
|
||||
assert_eq!(I32F32::ONE.fix_8(),I256F256::ONE);
|
||||
assert_eq!(I32F32::ONE,I256F256::ONE.fix_1());
|
||||
assert_eq!(I32F32::NEG_ONE.fix_8(),I256F256::NEG_ONE);
|
||||
assert_eq!(I32F32::NEG_ONE,I256F256::NEG_ONE.fix_1());
|
||||
}
|
||||
#[test]
|
||||
fn test_sqrt(){
|
||||
let a=I32F32::ONE*4;
|
||||
assert_eq!(a.sqrt(),I32F32::from(2));
|
||||
}
|
||||
#[test]
|
||||
fn test_sqrt_zero(){
|
||||
let a=I32F32::ZERO;
|
||||
assert_eq!(a.sqrt(),I32F32::ZERO);
|
||||
}
|
||||
#[test]
|
||||
fn test_sqrt_low(){
|
||||
let a=I32F32::HALF;
|
||||
let b=a.fixed_mul(a);
|
||||
assert_eq!(b.sqrt(),a);
|
||||
}
|
||||
fn find_equiv_sqrt_via_f64(n:I32F32)->I32F32{
|
||||
//GIMME THEM BITS BOY
|
||||
let &[bits]=n.to_bits().to_bits().digits();
|
||||
let ibits=bits as i64;
|
||||
let f=(ibits as f64)/((1u64<<32) as f64);
|
||||
let f_ans=f.sqrt();
|
||||
let i=(f_ans*((1u64<<32) as f64)) as i64;
|
||||
let r=I32F32::from_bits(bnum::BInt::<1>::from(i));
|
||||
//mimic the behaviour of the algorithm,
|
||||
//return the result if it truncates to the exact answer
|
||||
if (r+I32F32::EPSILON).wide_mul_1_1(r+I32F32::EPSILON)==n.wide_mul_1_1(I32F32::ONE){
|
||||
return r+I32F32::EPSILON;
|
||||
}
|
||||
if (r-I32F32::EPSILON).wide_mul_1_1(r-I32F32::EPSILON)==n.wide_mul_1_1(I32F32::ONE){
|
||||
return r-I32F32::EPSILON;
|
||||
}
|
||||
return r;
|
||||
}
|
||||
fn test_exact(n:I32F32){
|
||||
assert_eq!(n.sqrt(),find_equiv_sqrt_via_f64(n));
|
||||
}
|
||||
#[test]
|
||||
fn test_sqrt_exact(){
|
||||
//43
|
||||
for i in 0..((i64::MAX as f32).ln() as u32){
|
||||
let n=I32F32::from_bits(bnum::BInt::<1>::from((i as f32).exp() as i64));
|
||||
test_exact(n);
|
||||
}
|
||||
}
|
||||
#[test]
|
||||
fn test_sqrt_max(){
|
||||
let a=I32F32::MAX;
|
||||
test_exact(a);
|
||||
}
|
||||
#[test]
|
||||
#[cfg(all(feature="zeroes",not(feature="deferred-division")))]
|
||||
fn test_zeroes_normal(){
|
||||
// (x-1)*(x+1)
|
||||
// x^2-1
|
||||
let zeroes=I32F32::zeroes2(I32F32::NEG_ONE,I32F32::ZERO,I32F32::ONE);
|
||||
assert_eq!(zeroes,arrayvec::ArrayVec::from_iter([I32F32::NEG_ONE,I32F32::ONE]));
|
||||
let zeroes=I32F32::zeroes2(I32F32::NEG_ONE*3,I32F32::ONE*2,I32F32::ONE);
|
||||
assert_eq!(zeroes,arrayvec::ArrayVec::from_iter([I32F32::NEG_ONE*3,I32F32::ONE]));
|
||||
}
|
||||
#[test]
|
||||
#[cfg(all(feature="zeroes",feature="deferred-division"))]
|
||||
fn test_zeroes_deferred_division(){
|
||||
// (x-1)*(x+1)
|
||||
// x^2-1
|
||||
let zeroes=I32F32::zeroes2(I32F32::NEG_ONE,I32F32::ZERO,I32F32::ONE);
|
||||
assert_eq!(
|
||||
zeroes,
|
||||
arrayvec::ArrayVec::from_iter([
|
||||
ratio_ops::ratio::Ratio::new(I32F32::ONE*2,I32F32::NEG_ONE*2),
|
||||
ratio_ops::ratio::Ratio::new(I32F32::ONE*2,I32F32::ONE*2),
|
||||
])
|
||||
);
|
||||
}
|
@ -1,4 +0,0 @@
|
||||
pub type I32F32=crate::fixed::Fixed<1,32>;
|
||||
pub type I64F64=crate::fixed::Fixed<2,64>;
|
||||
pub type I128F128=crate::fixed::Fixed<4,128>;
|
||||
pub type I256F256=crate::fixed::Fixed<8,256>;
|
@ -1,53 +0,0 @@
|
||||
use crate::fixed::Fixed;
|
||||
|
||||
use arrayvec::ArrayVec;
|
||||
use std::cmp::Ordering;
|
||||
macro_rules! impl_zeroes{
|
||||
($n:expr)=>{
|
||||
impl Fixed<$n,{$n*32}>{
|
||||
#[inline]
|
||||
pub fn zeroes2(a0:Self,a1:Self,a2:Self)->ArrayVec<<Self as core::ops::Div>::Output,2>{
|
||||
let a2pos=match a2.cmp(&Self::ZERO){
|
||||
Ordering::Greater=>true,
|
||||
Ordering::Equal=>return ArrayVec::from_iter(Self::zeroes1(a0,a1).into_iter()),
|
||||
Ordering::Less=>false,
|
||||
};
|
||||
let radicand=a1*a1-a2*a0*4;
|
||||
match radicand.cmp(&<Self as core::ops::Mul>::Output::ZERO){
|
||||
Ordering::Greater=>{
|
||||
paste::item!{
|
||||
let planar_radicand=radicand.sqrt().[<fix_ $n>]();
|
||||
}
|
||||
//sort roots ascending and avoid taking the difference of large numbers
|
||||
let zeroes=match (a2pos,Self::ZERO<a1){
|
||||
(true, true )=>[(-a1-planar_radicand)/(a2*2),(a0*2)/(-a1-planar_radicand)],
|
||||
(true, false)=>[(a0*2)/(-a1+planar_radicand),(-a1+planar_radicand)/(a2*2)],
|
||||
(false,true )=>[(a0*2)/(-a1-planar_radicand),(-a1-planar_radicand)/(a2*2)],
|
||||
(false,false)=>[(-a1+planar_radicand)/(a2*2),(a0*2)/(-a1+planar_radicand)],
|
||||
};
|
||||
ArrayVec::from_iter(zeroes)
|
||||
},
|
||||
Ordering::Equal=>ArrayVec::from_iter([(a1)/(a2*-2)]),
|
||||
Ordering::Less=>ArrayVec::new_const(),
|
||||
}
|
||||
}
|
||||
#[inline]
|
||||
pub fn zeroes1(a0:Self,a1:Self)->ArrayVec<<Self as core::ops::Div>::Output,1>{
|
||||
if a1==Self::ZERO{
|
||||
ArrayVec::new_const()
|
||||
}else{
|
||||
ArrayVec::from_iter([(-a0)/(a1)])
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
impl_zeroes!(1);
|
||||
impl_zeroes!(2);
|
||||
impl_zeroes!(3);
|
||||
impl_zeroes!(4);
|
||||
//sqrt doubles twice!
|
||||
//impl_zeroes!(5);
|
||||
//impl_zeroes!(6);
|
||||
//impl_zeroes!(7);
|
||||
//impl_zeroes!(8);
|
1
lib/linear_ops/.gitignore
vendored
1
lib/linear_ops/.gitignore
vendored
@ -1 +0,0 @@
|
||||
/target
|
@ -1,22 +0,0 @@
|
||||
[package]
|
||||
name = "linear_ops"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
repository = "https://git.itzana.me/StrafesNET/strafe-project"
|
||||
license = "MIT OR Apache-2.0"
|
||||
description = "Vector/Matrix operations using trait bounds."
|
||||
authors = ["Rhys Lloyd <krakow20@gmail.com>"]
|
||||
|
||||
[features]
|
||||
default=["named-fields","fixed-wide"]
|
||||
named-fields=[]
|
||||
fixed-wide=["dep:fixed_wide","dep:paste"]
|
||||
deferred-division=["dep:ratio_ops"]
|
||||
|
||||
[dependencies]
|
||||
ratio_ops = { path = "../ratio_ops", registry = "strafesnet", optional = true }
|
||||
fixed_wide = { path = "../fixed_wide", registry = "strafesnet", optional = true }
|
||||
paste = { version = "1.0.15", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
fixed_wide = { path = "../fixed_wide", registry = "strafesnet", features = ["wide-mul"] }
|
@ -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,10 +0,0 @@
|
||||
mod macros;
|
||||
pub mod types;
|
||||
pub mod vector;
|
||||
pub mod matrix;
|
||||
|
||||
#[cfg(feature="named-fields")]
|
||||
mod named;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
@ -1 +0,0 @@
|
||||
|
@ -1,79 +0,0 @@
|
||||
#[doc(hidden)]
|
||||
#[macro_export(local_inner_macros)]
|
||||
macro_rules! impl_fixed_wide_vector_not_const_generic {
|
||||
(
|
||||
(),
|
||||
$n:expr
|
||||
) => {
|
||||
impl<const N:usize> Vector<N,fixed_wide::fixed::Fixed<$n,{$n*32}>>{
|
||||
#[inline]
|
||||
pub fn length(self)-><fixed_wide::fixed::Fixed::<$n,{$n*32}> as core::ops::Mul>::Output{
|
||||
self.length_squared().sqrt_unchecked()
|
||||
}
|
||||
#[inline]
|
||||
pub fn with_length<U,V>(self,length:U)-><Vector<N,V> as core::ops::Div<<fixed_wide::fixed::Fixed::<$n,{$n*32}> as core::ops::Mul>::Output>>::Output
|
||||
where
|
||||
fixed_wide::fixed::Fixed<$n,{$n*32}>:core::ops::Mul<U,Output=V>,
|
||||
U:Copy,
|
||||
V:core::ops::Div<<fixed_wide::fixed::Fixed::<$n,{$n*32}> as core::ops::Mul>::Output>,
|
||||
{
|
||||
self*length/self.length()
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
#[macro_export(local_inner_macros)]
|
||||
macro_rules! macro_4 {
|
||||
( $macro: ident, $any:tt ) => {
|
||||
$crate::macro_repeated!($macro,$any,1,2,3,4);
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
#[macro_export(local_inner_macros)]
|
||||
macro_rules! impl_fixed_wide_vector {
|
||||
() => {
|
||||
$crate::macro_4!(impl_fixed_wide_vector_not_const_generic,());
|
||||
// I LOVE NOT BEING ABLE TO USE CONST GENERICS
|
||||
$crate::macro_repeated!(
|
||||
impl_fix_not_const_generic,(),
|
||||
(1,1),(2,1),(3,1),(4,1),(5,1),(6,1),(7,1),(8,1),(9,1),(10,1),(11,1),(12,1),(13,1),(14,1),(15,1),(16,1),
|
||||
(1,2),(2,2),(3,2),(4,2),(5,2),(6,2),(7,2),(8,2),(9,2),(10,2),(11,2),(12,2),(13,2),(14,2),(15,2),(16,2),
|
||||
(1,3),(2,3),(3,3),(4,3),(5,3),(6,3),(7,3),(8,3),(9,3),(10,3),(11,3),(12,3),(13,3),(14,3),(15,3),(16,3),
|
||||
(1,4),(2,4),(3,4),(4,4),(5,4),(6,4),(7,4),(8,4),(9,4),(10,4),(11,4),(12,4),(13,4),(14,4),(15,4),(16,4),
|
||||
(1,5),(2,5),(3,5),(4,5),(5,5),(6,5),(7,5),(8,5),(9,5),(10,5),(11,5),(12,5),(13,5),(14,5),(15,5),(16,5),
|
||||
(1,6),(2,6),(3,6),(4,6),(5,6),(6,6),(7,6),(8,6),(9,6),(10,6),(11,6),(12,6),(13,6),(14,6),(15,6),(16,6),
|
||||
(1,7),(2,7),(3,7),(4,7),(5,7),(6,7),(7,7),(8,7),(9,7),(10,7),(11,7),(12,7),(13,7),(14,7),(15,7),(16,7),
|
||||
(1,8),(2,8),(3,8),(4,8),(5,8),(6,8),(7,8),(8,8),(9,8),(10,8),(11,8),(12,8),(13,8),(14,8),(15,8),(16,8),
|
||||
(1,9),(2,9),(3,9),(4,9),(5,9),(6,9),(7,9),(8,9),(9,9),(10,9),(11,9),(12,9),(13,9),(14,9),(15,9),(16,9),
|
||||
(1,10),(2,10),(3,10),(4,10),(5,10),(6,10),(7,10),(8,10),(9,10),(10,10),(11,10),(12,10),(13,10),(14,10),(15,10),(16,10),
|
||||
(1,11),(2,11),(3,11),(4,11),(5,11),(6,11),(7,11),(8,11),(9,11),(10,11),(11,11),(12,11),(13,11),(14,11),(15,11),(16,11),
|
||||
(1,12),(2,12),(3,12),(4,12),(5,12),(6,12),(7,12),(8,12),(9,12),(10,12),(11,12),(12,12),(13,12),(14,12),(15,12),(16,12),
|
||||
(1,13),(2,13),(3,13),(4,13),(5,13),(6,13),(7,13),(8,13),(9,13),(10,13),(11,13),(12,13),(13,13),(14,13),(15,13),(16,13),
|
||||
(1,14),(2,14),(3,14),(4,14),(5,14),(6,14),(7,14),(8,14),(9,14),(10,14),(11,14),(12,14),(13,14),(14,14),(15,14),(16,14),
|
||||
(1,15),(2,15),(3,15),(4,15),(5,15),(6,15),(7,15),(8,15),(9,15),(10,15),(11,15),(12,15),(13,15),(14,15),(15,15),(16,15),
|
||||
(1,16),(2,16),(3,16),(4,16),(5,16),(6,16),(7,16),(8,16),(9,16),(10,16),(11,16),(12,16),(13,16),(14,16),(15,16),(16,16)
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
#[macro_export(local_inner_macros)]
|
||||
macro_rules! impl_fix_not_const_generic{
|
||||
(
|
||||
(),
|
||||
($lhs:expr,$rhs:expr)
|
||||
)=>{
|
||||
impl<const N:usize> Vector<N,fixed_wide::fixed::Fixed<$lhs,{$lhs*32}>>
|
||||
{
|
||||
paste::item!{
|
||||
#[inline]
|
||||
pub fn [<fix_ $rhs>](self)->Vector<N,fixed_wide::fixed::Fixed<$rhs,{$rhs*32}>>{
|
||||
self.map(|t|t.[<fix_ $rhs>]())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,272 +0,0 @@
|
||||
#[doc(hidden)]
|
||||
#[macro_export(local_inner_macros)]
|
||||
macro_rules! impl_matrix {
|
||||
() => {
|
||||
impl<const X:usize,const Y:usize,T> Matrix<X,Y,T>{
|
||||
#[inline(always)]
|
||||
pub const fn new(array:[[T;Y];X])->Self{
|
||||
Self{array}
|
||||
}
|
||||
#[inline(always)]
|
||||
pub fn to_array(self)->[[T;Y];X]{
|
||||
self.array
|
||||
}
|
||||
#[inline]
|
||||
pub fn from_cols(cols:[Vector<Y,T>;X])->Self
|
||||
{
|
||||
Matrix::new(
|
||||
cols.map(|col|col.array),
|
||||
)
|
||||
}
|
||||
#[inline]
|
||||
pub fn map<F,U>(self,f:F)->Matrix<X,Y,U>
|
||||
where
|
||||
F:Fn(T)->U
|
||||
{
|
||||
Matrix::new(
|
||||
self.array.map(|inner|inner.map(&f)),
|
||||
)
|
||||
}
|
||||
#[inline]
|
||||
pub fn transpose(self)->Matrix<Y,X,T>{
|
||||
//how did I think of this
|
||||
let mut array_of_iterators=self.array.map(|axis|axis.into_iter());
|
||||
Matrix::new(
|
||||
core::array::from_fn(|_|
|
||||
array_of_iterators.each_mut().map(|iter|
|
||||
iter.next().unwrap()
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
#[inline]
|
||||
// old (list of rows) MatY<VecX>.MatX<VecZ> = MatY<VecZ>
|
||||
// new (list of columns) MatX<VecY>.MatZ<VecX> = MatZ<VecY>
|
||||
pub fn dot<const Z:usize,U,V>(self,rhs:Matrix<Z,X,U>)->Matrix<Z,Y,V>
|
||||
where
|
||||
T:core::ops::Mul<U,Output=V>+Copy,
|
||||
V:core::iter::Sum,
|
||||
U:Copy,
|
||||
{
|
||||
let mut array_of_iterators=self.array.map(|axis|axis.into_iter().cycle());
|
||||
Matrix{
|
||||
array:rhs.array.map(|rhs_axis|
|
||||
core::array::from_fn(|_|
|
||||
array_of_iterators
|
||||
.iter_mut()
|
||||
.zip(rhs_axis.iter())
|
||||
.map(|(lhs_iter,&rhs_value)|
|
||||
lhs_iter.next().unwrap()*rhs_value
|
||||
).sum()
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
#[inline]
|
||||
// MatX<VecY>.VecY = VecX
|
||||
pub fn transform_vector<U,V>(self,rhs:Vector<X,U>)->Vector<Y,V>
|
||||
where
|
||||
T:core::ops::Mul<U,Output=V>,
|
||||
V:core::iter::Sum,
|
||||
U:Copy,
|
||||
{
|
||||
let mut array_of_iterators=self.array.map(|axis|axis.into_iter());
|
||||
Vector::new(
|
||||
core::array::from_fn(|_|
|
||||
array_of_iterators
|
||||
.iter_mut()
|
||||
.zip(rhs.array.iter())
|
||||
.map(|(lhs_iter,&rhs_value)|
|
||||
lhs_iter.next().unwrap()*rhs_value
|
||||
).sum()
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
impl<const X:usize,const Y:usize,T> Matrix<X,Y,T>
|
||||
where
|
||||
T:Copy
|
||||
{
|
||||
#[inline(always)]
|
||||
pub const fn from_value(value:T)->Self{
|
||||
Self::new([[value;Y];X])
|
||||
}
|
||||
}
|
||||
|
||||
impl<const X:usize,const Y:usize,T:Default> Default for Matrix<X,Y,T>{
|
||||
#[inline]
|
||||
fn default()->Self{
|
||||
Self::new(
|
||||
core::array::from_fn(|_|core::array::from_fn(|_|Default::default()))
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<const X:usize,const Y:usize,T:core::fmt::Display> core::fmt::Display for Matrix<X,Y,T>{
|
||||
#[inline]
|
||||
fn fmt(&self,f:&mut core::fmt::Formatter)->Result<(),core::fmt::Error>{
|
||||
for col in &self.array[0..X]{
|
||||
core::write!(f,"\n")?;
|
||||
for elem in &col[0..Y-1]{
|
||||
core::write!(f,"{}, ",elem)?;
|
||||
}
|
||||
// assume we will be using matrices of size 1x1 or greater
|
||||
core::write!(f,"{}",col.last().unwrap())?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<const X:usize,const Y:usize,const Z:usize,T,U,V> core::ops::Mul<Matrix<Z,X,U>> for Matrix<X,Y,T>
|
||||
where
|
||||
T:core::ops::Mul<U,Output=V>+Copy,
|
||||
V:core::iter::Sum,
|
||||
U:Copy,
|
||||
{
|
||||
type Output=Matrix<Z,Y,V>;
|
||||
#[inline]
|
||||
fn mul(self,rhs:Matrix<Z,X,U>)->Self::Output{
|
||||
self.dot(rhs)
|
||||
}
|
||||
}
|
||||
impl<const X:usize,const Y:usize,T,U,V> core::ops::Mul<Vector<X,U>> for Matrix<X,Y,T>
|
||||
where
|
||||
T:core::ops::Mul<U,Output=V>,
|
||||
V:core::iter::Sum,
|
||||
U:Copy,
|
||||
{
|
||||
type Output=Vector<Y,V>;
|
||||
#[inline]
|
||||
fn mul(self,rhs:Vector<X,U>)->Self::Output{
|
||||
self.transform_vector(rhs)
|
||||
}
|
||||
}
|
||||
#[cfg(feature="deferred-division")]
|
||||
$crate::impl_matrix_deferred_division!();
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
#[macro_export(local_inner_macros)]
|
||||
macro_rules! impl_matrix_deferred_division {
|
||||
() => {
|
||||
impl<const X:usize,const Y:usize,T:ratio_ops::ratio::Divide<U,Output=V>,U:Copy,V> ratio_ops::ratio::Divide<U> for Matrix<X,Y,T>{
|
||||
type Output=Matrix<X,Y,V>;
|
||||
#[inline]
|
||||
fn divide(self,rhs:U)->Self::Output{
|
||||
self.map(|t|t.divide(rhs))
|
||||
}
|
||||
}
|
||||
impl<const X:usize,const Y:usize,T,U> core::ops::Div<U> for Matrix<X,Y,T>{
|
||||
type Output=ratio_ops::ratio::Ratio<Matrix<X,Y,T>,U>;
|
||||
#[inline]
|
||||
fn div(self,rhs:U)->Self::Output{
|
||||
ratio_ops::ratio::Ratio::new(self,rhs)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
#[macro_export(local_inner_macros)]
|
||||
macro_rules! impl_matrix_extend {
|
||||
( $x: expr, $y: expr ) => {
|
||||
impl<T> Matrix<$x,$y,T>{
|
||||
#[inline]
|
||||
pub fn extend_column(self,value:Vector<$y,T>)->Matrix<{$x+1},$y,T>{
|
||||
let mut iter=self.array.into_iter().chain(core::iter::once(value.array));
|
||||
Matrix::new(
|
||||
core::array::from_fn(|_|iter.next().unwrap()),
|
||||
)
|
||||
}
|
||||
#[inline]
|
||||
pub fn extend_row(self,value:Vector<$x,T>)->Matrix<$x,{$y+1},T>{
|
||||
let mut iter_rows=value.array.into_iter();
|
||||
Matrix::new(
|
||||
self.array.map(|axis|{
|
||||
let mut elements_iter=axis.into_iter().chain(core::iter::once(iter_rows.next().unwrap()));
|
||||
core::array::from_fn(|_|elements_iter.next().unwrap())
|
||||
})
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
#[macro_export(local_inner_macros)]
|
||||
macro_rules! impl_matrix_named_fields_shape {
|
||||
(
|
||||
($struct_outer:ident, $size_outer: expr),
|
||||
($size_inner: expr)
|
||||
) => {
|
||||
impl<T> core::ops::Deref for Matrix<$size_outer,$size_inner,T>{
|
||||
type Target=$struct_outer<Vector<$size_inner,T>>;
|
||||
#[inline]
|
||||
fn deref(&self)->&Self::Target{
|
||||
unsafe{core::mem::transmute(&self.array)}
|
||||
}
|
||||
}
|
||||
impl<T> core::ops::DerefMut for Matrix<$size_outer,$size_inner,T>{
|
||||
#[inline]
|
||||
fn deref_mut(&mut self)->&mut Self::Target{
|
||||
unsafe{core::mem::transmute(&mut self.array)}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
#[macro_export(local_inner_macros)]
|
||||
macro_rules! impl_matrix_named_fields_shape_shim {
|
||||
(
|
||||
($($vector_info:tt),+),
|
||||
$matrix_info:tt
|
||||
) => {
|
||||
$crate::macro_repeated!(impl_matrix_named_fields_shape,$matrix_info,$($vector_info),+);
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
#[macro_export(local_inner_macros)]
|
||||
macro_rules! impl_matrix_named_fields {
|
||||
(
|
||||
($($matrix_info:tt),+),
|
||||
$vector_infos:tt
|
||||
) => {
|
||||
$crate::macro_repeated!(impl_matrix_named_fields_shape_shim,$vector_infos,$($matrix_info),+);
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
#[macro_export(local_inner_macros)]
|
||||
macro_rules! impl_matrix_3x3 {
|
||||
()=>{
|
||||
impl<T,T2,T3> Matrix<3,3,T>
|
||||
where
|
||||
//cross
|
||||
T:core::ops::Mul<T,Output=T2>+Copy,
|
||||
T2:core::ops::Sub,
|
||||
//dot
|
||||
T:core::ops::Mul<<T2 as core::ops::Sub>::Output,Output=T3>,
|
||||
T3:core::iter::Sum,
|
||||
{
|
||||
pub fn det(self)->T3{
|
||||
self.x_axis.dot(self.y_axis.cross(self.z_axis))
|
||||
}
|
||||
}
|
||||
impl<T,T2> Matrix<3,3,T>
|
||||
where
|
||||
T:core::ops::Mul<T,Output=T2>+Copy,
|
||||
T2:core::ops::Sub,
|
||||
{
|
||||
pub fn adjugate(self)->Matrix<3,3,<T2 as core::ops::Sub>::Output>{
|
||||
Matrix::new([
|
||||
[self.y_axis.y*self.z_axis.z-self.y_axis.z*self.z_axis.y,self.x_axis.z*self.z_axis.y-self.x_axis.y*self.z_axis.z,self.x_axis.y*self.y_axis.z-self.x_axis.z*self.y_axis.y],
|
||||
[self.y_axis.z*self.z_axis.x-self.y_axis.x*self.z_axis.z,self.x_axis.x*self.z_axis.z-self.x_axis.z*self.z_axis.x,self.x_axis.z*self.y_axis.x-self.x_axis.x*self.y_axis.z],
|
||||
[self.y_axis.x*self.z_axis.y-self.y_axis.y*self.z_axis.x,self.x_axis.y*self.z_axis.x-self.x_axis.x*self.z_axis.y,self.x_axis.x*self.y_axis.y-self.x_axis.y*self.y_axis.x],
|
||||
])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,20 +0,0 @@
|
||||
pub mod common;
|
||||
pub mod vector;
|
||||
pub mod matrix;
|
||||
|
||||
#[cfg(feature="fixed-wide")]
|
||||
pub mod fixed_wide;
|
||||
|
||||
#[doc(hidden)]
|
||||
#[macro_export(local_inner_macros)]
|
||||
macro_rules! macro_repeated{
|
||||
(
|
||||
$macro:ident,
|
||||
$any:tt,
|
||||
$($repeated:tt),*
|
||||
)=>{
|
||||
$(
|
||||
$crate::$macro!($any, $repeated);
|
||||
)*
|
||||
};
|
||||
}
|
@ -1,357 +0,0 @@
|
||||
#[doc(hidden)]
|
||||
#[macro_export(local_inner_macros)]
|
||||
macro_rules! impl_vector {
|
||||
() => {
|
||||
impl<const N:usize,T> Vector<N,T>{
|
||||
#[inline(always)]
|
||||
pub const fn new(array:[T;N])->Self{
|
||||
Self{array}
|
||||
}
|
||||
#[inline(always)]
|
||||
pub fn to_array(self)->[T;N]{
|
||||
self.array
|
||||
}
|
||||
#[inline]
|
||||
pub fn map<F,U>(self,f:F)->Vector<N,U>
|
||||
where
|
||||
F:Fn(T)->U
|
||||
{
|
||||
Vector::new(
|
||||
self.array.map(f)
|
||||
)
|
||||
}
|
||||
#[inline]
|
||||
pub fn map_zip<F,U,V>(self,other:Vector<N,U>,f:F)->Vector<N,V>
|
||||
where
|
||||
F:Fn((T,U))->V,
|
||||
{
|
||||
let mut iter=self.array.into_iter().zip(other.array);
|
||||
Vector::new(
|
||||
core::array::from_fn(|_|f(iter.next().unwrap())),
|
||||
)
|
||||
}
|
||||
}
|
||||
impl<const N:usize,T:Copy> Vector<N,T>{
|
||||
#[inline(always)]
|
||||
pub const fn from_value(value:T)->Self{
|
||||
Self::new([value;N])
|
||||
}
|
||||
}
|
||||
|
||||
impl<const N:usize,T:Default> Default for Vector<N,T>{
|
||||
#[inline]
|
||||
fn default()->Self{
|
||||
Self::new(
|
||||
core::array::from_fn(|_|Default::default())
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<const N:usize,T:core::fmt::Display> core::fmt::Display for Vector<N,T>{
|
||||
#[inline]
|
||||
fn fmt(&self,f:&mut core::fmt::Formatter)->Result<(),core::fmt::Error>{
|
||||
for elem in &self.array[0..N-1]{
|
||||
core::write!(f,"{}, ",elem)?;
|
||||
}
|
||||
// assume we will be using vectors of length 1 or greater
|
||||
core::write!(f,"{}",self.array.last().unwrap())
|
||||
}
|
||||
}
|
||||
|
||||
impl<const N:usize,T:Ord> Vector<N,T>{
|
||||
#[inline]
|
||||
pub fn min(self,rhs:Self)->Self{
|
||||
self.map_zip(rhs,|(a,b)|a.min(b))
|
||||
}
|
||||
#[inline]
|
||||
pub fn max(self,rhs:Self)->Self{
|
||||
self.map_zip(rhs,|(a,b)|a.max(b))
|
||||
}
|
||||
#[inline]
|
||||
pub fn cmp(self,rhs:Self)->Vector<N,core::cmp::Ordering>{
|
||||
self.map_zip(rhs,|(a,b)|a.cmp(&b))
|
||||
}
|
||||
#[inline]
|
||||
pub fn lt(self,rhs:Self)->Vector<N,bool>{
|
||||
self.map_zip(rhs,|(a,b)|a.lt(&b))
|
||||
}
|
||||
#[inline]
|
||||
pub fn gt(self,rhs:Self)->Vector<N,bool>{
|
||||
self.map_zip(rhs,|(a,b)|a.gt(&b))
|
||||
}
|
||||
#[inline]
|
||||
pub fn ge(self,rhs:Self)->Vector<N,bool>{
|
||||
self.map_zip(rhs,|(a,b)|a.ge(&b))
|
||||
}
|
||||
#[inline]
|
||||
pub fn le(self,rhs:Self)->Vector<N,bool>{
|
||||
self.map_zip(rhs,|(a,b)|a.le(&b))
|
||||
}
|
||||
}
|
||||
|
||||
impl<const N:usize> Vector<N,bool>{
|
||||
#[inline]
|
||||
pub fn all(&self)->bool{
|
||||
self.array==[true;N]
|
||||
}
|
||||
#[inline]
|
||||
pub fn any(&self)->bool{
|
||||
self.array!=[false;N]
|
||||
}
|
||||
}
|
||||
|
||||
impl<const N:usize,T:core::ops::Neg<Output=V>,V> core::ops::Neg for Vector<N,T>{
|
||||
type Output=Vector<N,V>;
|
||||
#[inline]
|
||||
fn neg(self)->Self::Output{
|
||||
Vector::new(
|
||||
self.array.map(|t|-t)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<const N:usize,T> Vector<N,T>
|
||||
{
|
||||
#[inline]
|
||||
pub fn dot<U,V>(self,rhs:Vector<N,U>)->V
|
||||
where
|
||||
T:core::ops::Mul<U,Output=V>,
|
||||
V:core::iter::Sum,
|
||||
{
|
||||
self.array.into_iter().zip(rhs.array).map(|(a,b)|a*b).sum()
|
||||
}
|
||||
}
|
||||
|
||||
impl<const N:usize,T,V> Vector<N,T>
|
||||
where
|
||||
T:core::ops::Mul<Output=V>+Copy,
|
||||
V:core::iter::Sum,
|
||||
{
|
||||
#[inline]
|
||||
pub fn length_squared(self)->V{
|
||||
self.array.into_iter().map(|t|t*t).sum()
|
||||
}
|
||||
}
|
||||
|
||||
// Impl arithmetic operators
|
||||
$crate::impl_vector_assign_operator!(AddAssign, add_assign );
|
||||
$crate::impl_vector_operator!(Add, add );
|
||||
$crate::impl_vector_assign_operator!(SubAssign, sub_assign );
|
||||
$crate::impl_vector_operator!(Sub, sub );
|
||||
$crate::impl_vector_assign_operator!(RemAssign, rem_assign );
|
||||
$crate::impl_vector_operator!(Rem, rem );
|
||||
|
||||
// mul and div are special, usually you multiply by a scalar
|
||||
// and implementing both vec*vec and vec*scalar is conflicting implementations Q_Q
|
||||
$crate::impl_vector_assign_operator_scalar!(MulAssign, mul_assign );
|
||||
$crate::impl_vector_operator_scalar!(Mul, mul );
|
||||
$crate::impl_vector_assign_operator_scalar!(DivAssign, div_assign );
|
||||
#[cfg(not(feature="deferred-division"))]
|
||||
$crate::impl_vector_operator_scalar!(Div, div );
|
||||
#[cfg(feature="deferred-division")]
|
||||
$crate::impl_vector_deferred_division!();
|
||||
|
||||
// Impl bitwise operators
|
||||
$crate::impl_vector_assign_operator!(BitAndAssign, bitand_assign );
|
||||
$crate::impl_vector_operator!(BitAnd, bitand );
|
||||
$crate::impl_vector_assign_operator!(BitOrAssign, bitor_assign );
|
||||
$crate::impl_vector_operator!(BitOr, bitor );
|
||||
$crate::impl_vector_assign_operator!(BitXorAssign, bitxor_assign );
|
||||
$crate::impl_vector_operator!(BitXor, bitxor );
|
||||
|
||||
// Impl shift operators
|
||||
$crate::impl_vector_shift_assign_operator!(ShlAssign, shl_assign);
|
||||
$crate::impl_vector_shift_operator!(Shl, shl);
|
||||
$crate::impl_vector_shift_assign_operator!(ShrAssign, shr_assign);
|
||||
$crate::impl_vector_shift_operator!(Shr, shr);
|
||||
|
||||
// dedicated methods for this type
|
||||
#[cfg(feature="fixed-wide")]
|
||||
$crate::impl_fixed_wide_vector!();
|
||||
}
|
||||
}
|
||||
#[doc(hidden)]
|
||||
#[macro_export(local_inner_macros)]
|
||||
macro_rules! impl_vector_deferred_division {
|
||||
() => {
|
||||
impl<const N:usize,T:ratio_ops::ratio::Divide<U,Output=V>,U:Copy,V> ratio_ops::ratio::Divide<U> for Vector<N,T>{
|
||||
type Output=Vector<N,V>;
|
||||
#[inline]
|
||||
fn divide(self,rhs:U)->Self::Output{
|
||||
self.map(|t|t.divide(rhs))
|
||||
}
|
||||
}
|
||||
impl<const N:usize,T,U> core::ops::Div<U> for Vector<N,T>{
|
||||
type Output=ratio_ops::ratio::Ratio<Vector<N,T>,U>;
|
||||
#[inline]
|
||||
fn div(self,rhs:U)->Self::Output{
|
||||
ratio_ops::ratio::Ratio::new(self,rhs)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#[doc(hidden)]
|
||||
#[macro_export(local_inner_macros)]
|
||||
macro_rules! impl_vector_operator_scalar {
|
||||
($trait: ident, $method: ident ) => {
|
||||
impl<const N:usize,T:core::ops::$trait<U,Output=V>,U:Copy,V> core::ops::$trait<U> for Vector<N,T>{
|
||||
type Output=Vector<N,V>;
|
||||
#[inline]
|
||||
fn $method(self,rhs:U)->Self::Output{
|
||||
self.map(|t|t.$method(rhs))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#[doc(hidden)]
|
||||
#[macro_export(local_inner_macros)]
|
||||
macro_rules! impl_vector_operator {
|
||||
($trait: ident, $method: ident ) => {
|
||||
impl<const N:usize,T:core::ops::$trait<U,Output=V>,U,V> core::ops::$trait<Vector<N,U>> for Vector<N,T>{
|
||||
type Output=Vector<N,V>;
|
||||
#[inline]
|
||||
fn $method(self,rhs:Vector<N,U>)->Self::Output{
|
||||
self.map_zip(rhs,|(a,b)|a.$method(b))
|
||||
}
|
||||
}
|
||||
impl<const N:usize,T:core::ops::$trait<i64,Output=T>> core::ops::$trait<i64> for Vector<N,T>{
|
||||
type Output=Self;
|
||||
#[inline]
|
||||
fn $method(self,rhs:i64)->Self::Output{
|
||||
self.map(|t|t.$method(rhs))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#[doc(hidden)]
|
||||
#[macro_export(local_inner_macros)]
|
||||
macro_rules! impl_vector_assign_operator_scalar {
|
||||
($trait: ident, $method: ident ) => {
|
||||
impl<const N:usize,T:core::ops::$trait<U>,U:Copy> core::ops::$trait<U> for Vector<N,T>{
|
||||
#[inline]
|
||||
fn $method(&mut self,rhs:U){
|
||||
self.array.iter_mut()
|
||||
.for_each(|t|t.$method(rhs))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#[doc(hidden)]
|
||||
#[macro_export(local_inner_macros)]
|
||||
macro_rules! impl_vector_assign_operator {
|
||||
($trait: ident, $method: ident ) => {
|
||||
impl<const N:usize,T:core::ops::$trait<U>,U> core::ops::$trait<Vector<N,U>> for Vector<N,T>{
|
||||
#[inline]
|
||||
fn $method(&mut self,rhs:Vector<N,U>){
|
||||
self.array.iter_mut().zip(rhs.array)
|
||||
.for_each(|(a,b)|a.$method(b))
|
||||
}
|
||||
}
|
||||
impl<const N:usize,T:core::ops::$trait<i64>> core::ops::$trait<i64> for Vector<N,T>{
|
||||
#[inline]
|
||||
fn $method(&mut self,rhs:i64){
|
||||
self.array.iter_mut()
|
||||
.for_each(|t|t.$method(rhs))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#[doc(hidden)]
|
||||
#[macro_export(local_inner_macros)]
|
||||
macro_rules! impl_vector_shift_operator {
|
||||
($trait: ident, $method: ident ) => {
|
||||
impl<const N:usize,T:core::ops::$trait<U,Output=V>,U,V> core::ops::$trait<Vector<N,U>> for Vector<N,T>{
|
||||
type Output=Vector<N,V>;
|
||||
#[inline]
|
||||
fn $method(self,rhs:Vector<N,U>)->Self::Output{
|
||||
self.map_zip(rhs,|(a,b)|a.$method(b))
|
||||
}
|
||||
}
|
||||
impl<const N:usize,T:core::ops::$trait<u32,Output=V>,V> core::ops::$trait<u32> for Vector<N,T>{
|
||||
type Output=Vector<N,V>;
|
||||
#[inline]
|
||||
fn $method(self,rhs:u32)->Self::Output{
|
||||
self.map(|t|t.$method(rhs))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#[doc(hidden)]
|
||||
#[macro_export(local_inner_macros)]
|
||||
macro_rules! impl_vector_shift_assign_operator {
|
||||
($trait: ident, $method: ident ) => {
|
||||
impl<const N:usize,T:core::ops::$trait<U>,U> core::ops::$trait<Vector<N,U>> for Vector<N,T>{
|
||||
#[inline]
|
||||
fn $method(&mut self,rhs:Vector<N,U>){
|
||||
self.array.iter_mut().zip(rhs.array)
|
||||
.for_each(|(a,b)|a.$method(b))
|
||||
}
|
||||
}
|
||||
impl<const N:usize,T:core::ops::$trait<u32>> core::ops::$trait<u32> for Vector<N,T>{
|
||||
#[inline]
|
||||
fn $method(&mut self,rhs:u32){
|
||||
self.array.iter_mut()
|
||||
.for_each(|t|t.$method(rhs))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
#[macro_export(local_inner_macros)]
|
||||
macro_rules! impl_vector_extend {
|
||||
( $size: expr ) => {
|
||||
impl<T> Vector<$size,T>{
|
||||
#[inline]
|
||||
pub fn extend(self,value:T)->Vector<{$size+1},T>{
|
||||
let mut iter=self.array.into_iter().chain(core::iter::once(value));
|
||||
Vector::new(
|
||||
core::array::from_fn(|_|iter.next().unwrap()),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
#[macro_export(local_inner_macros)]
|
||||
macro_rules! impl_vector_named_fields {
|
||||
( $struct:ident, $size: expr ) => {
|
||||
impl<T> core::ops::Deref for Vector<$size,T>{
|
||||
type Target=$struct<T>;
|
||||
#[inline]
|
||||
fn deref(&self)->&Self::Target{
|
||||
unsafe{core::mem::transmute(&self.array)}
|
||||
}
|
||||
}
|
||||
impl<T> core::ops::DerefMut for Vector<$size,T>{
|
||||
#[inline]
|
||||
fn deref_mut(&mut self)->&mut Self::Target{
|
||||
unsafe{core::mem::transmute(&mut self.array)}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
#[macro_export(local_inner_macros)]
|
||||
macro_rules! impl_vector_3 {
|
||||
()=>{
|
||||
impl<T> Vector<3,T>
|
||||
{
|
||||
#[inline]
|
||||
pub fn cross<U,V>(self,rhs:Vector<3,U>)->Vector<3,<V as core::ops::Sub>::Output>
|
||||
where
|
||||
T:core::ops::Mul<U,Output=V>+Copy,
|
||||
U:Copy,
|
||||
V:core::ops::Sub,
|
||||
{
|
||||
Vector::new([
|
||||
self.y*rhs.z-self.z*rhs.y,
|
||||
self.z*rhs.x-self.x*rhs.z,
|
||||
self.x*rhs.y-self.y*rhs.x,
|
||||
])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,17 +0,0 @@
|
||||
use crate::vector::Vector;
|
||||
|
||||
#[derive(Clone,Copy,Debug,Hash,Eq,PartialEq)]
|
||||
pub struct Matrix<const X:usize,const Y:usize,T>{
|
||||
pub(crate) array:[[T;Y];X],
|
||||
}
|
||||
|
||||
crate::impl_matrix!();
|
||||
|
||||
crate::impl_matrix_extend!(2,2);
|
||||
crate::impl_matrix_extend!(2,3);
|
||||
crate::impl_matrix_extend!(3,2);
|
||||
crate::impl_matrix_extend!(3,3);
|
||||
|
||||
//Special case 3x3 matrix operations because I cba to write macros for the arbitrary cases
|
||||
#[cfg(feature="named-fields")]
|
||||
crate::impl_matrix_3x3!();
|
@ -1,59 +0,0 @@
|
||||
use crate::vector::Vector;
|
||||
use crate::matrix::Matrix;
|
||||
|
||||
#[repr(C)]
|
||||
pub struct Vector2<T> {
|
||||
pub x: T,
|
||||
pub y: T,
|
||||
}
|
||||
#[repr(C)]
|
||||
pub struct Vector3<T> {
|
||||
pub x: T,
|
||||
pub y: T,
|
||||
pub z: T,
|
||||
}
|
||||
#[repr(C)]
|
||||
pub struct Vector4<T> {
|
||||
pub x: T,
|
||||
pub y: T,
|
||||
pub z: T,
|
||||
pub w: T,
|
||||
}
|
||||
|
||||
crate::impl_vector_named_fields!(Vector2, 2);
|
||||
crate::impl_vector_named_fields!(Vector3, 3);
|
||||
crate::impl_vector_named_fields!(Vector4, 4);
|
||||
|
||||
#[repr(C)]
|
||||
pub struct Matrix2<T> {
|
||||
pub x_axis: T,
|
||||
pub y_axis: T,
|
||||
}
|
||||
#[repr(C)]
|
||||
pub struct Matrix3<T> {
|
||||
pub x_axis: T,
|
||||
pub y_axis: T,
|
||||
pub z_axis: T,
|
||||
}
|
||||
#[repr(C)]
|
||||
pub struct Matrix4<T> {
|
||||
pub x_axis: T,
|
||||
pub y_axis: T,
|
||||
pub z_axis: T,
|
||||
pub w_axis: T,
|
||||
}
|
||||
|
||||
crate::impl_matrix_named_fields!(
|
||||
//outer struct
|
||||
(
|
||||
(Matrix2, 2),
|
||||
(Matrix3, 3),
|
||||
(Matrix4, 4)
|
||||
),
|
||||
//inner struct
|
||||
(
|
||||
(2),
|
||||
(3),
|
||||
(4)
|
||||
)
|
||||
);
|
@ -1,96 +0,0 @@
|
||||
use crate::types::{Matrix3,Matrix3x2,Matrix3x4,Matrix4x2,Vector3};
|
||||
|
||||
type Planar64=fixed_wide::types::I32F32;
|
||||
type Planar64Wide1=fixed_wide::types::I64F64;
|
||||
//type Planar64Wide2=fixed_wide::types::I128F128;
|
||||
type Planar64Wide3=fixed_wide::types::I256F256;
|
||||
|
||||
#[test]
|
||||
fn wide_vec3(){
|
||||
let v=Vector3::from_value(Planar64::from(3));
|
||||
let v1=v*v.x;
|
||||
let v2=v1*v1.y;
|
||||
let v3=v2*v2.z;
|
||||
|
||||
assert_eq!(v3.array,Vector3::from_value(Planar64Wide3::from(3i128.pow(8))).array);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn wide_vec3_dot(){
|
||||
let v=Vector3::from_value(Planar64::from(3));
|
||||
let v1=v*v.x;
|
||||
let v2=v1*v1.y;
|
||||
let v3=v2.dot(v2);
|
||||
|
||||
assert_eq!(v3,Planar64Wide3::from(3i128.pow(8)*3));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn wide_vec3_length_squared(){
|
||||
let v=Vector3::from_value(Planar64::from(3));
|
||||
let v1=v*v.x;
|
||||
let v2=v1*v1.y;
|
||||
let v3=v2.length_squared();
|
||||
|
||||
assert_eq!(v3,Planar64Wide3::from(3i128.pow(8)*3));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn wide_matrix_dot(){
|
||||
let lhs=Matrix3x4::new([
|
||||
[Planar64::from(1),Planar64::from(2),Planar64::from(3),Planar64::from(4)],
|
||||
[Planar64::from(5),Planar64::from(6),Planar64::from(7),Planar64::from(8)],
|
||||
[Planar64::from(9),Planar64::from(10),Planar64::from(11),Planar64::from(12)],
|
||||
]).transpose();
|
||||
let rhs=Matrix4x2::new([
|
||||
[Planar64::from(1),Planar64::from(2)],
|
||||
[Planar64::from(3),Planar64::from(4)],
|
||||
[Planar64::from(5),Planar64::from(6)],
|
||||
[Planar64::from(7),Planar64::from(8)],
|
||||
]).transpose();
|
||||
// Mat3<Vec4>.dot(Mat4<Vec2>) -> Mat3<Vec2>
|
||||
let m_dot=lhs*rhs;
|
||||
//In[1]:= {{1, 2, 3, 4}, {5, 6, 7, 8}, {9, 10, 11, 12}} . {{1, 2}, {3, 4}, {5, 6}, {7, 8}}
|
||||
//Out[1]= {{50, 60}, {114, 140}, {178, 220}}
|
||||
assert_eq!(
|
||||
m_dot.array,
|
||||
Matrix3x2::new([
|
||||
[Planar64Wide1::from(50),Planar64Wide1::from(60)],
|
||||
[Planar64Wide1::from(114),Planar64Wide1::from(140)],
|
||||
[Planar64Wide1::from(178),Planar64Wide1::from(220)],
|
||||
]).transpose().array
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(feature="named-fields")]
|
||||
fn wide_matrix_det(){
|
||||
let m=Matrix3::new([
|
||||
[Planar64::from(1),Planar64::from(2),Planar64::from(3)],
|
||||
[Planar64::from(4),Planar64::from(5),Planar64::from(7)],
|
||||
[Planar64::from(6),Planar64::from(8),Planar64::from(9)],
|
||||
]);
|
||||
// In[2]:= Det[{{1, 2, 3}, {4, 5, 7}, {6, 8, 9}}]
|
||||
// Out[2]= 7
|
||||
assert_eq!(m.det(),fixed_wide::fixed::Fixed::<3,96>::from(7));
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(feature="named-fields")]
|
||||
fn wide_matrix_adjugate(){
|
||||
let m=Matrix3::new([
|
||||
[Planar64::from(1),Planar64::from(2),Planar64::from(3)],
|
||||
[Planar64::from(4),Planar64::from(5),Planar64::from(7)],
|
||||
[Planar64::from(6),Planar64::from(8),Planar64::from(9)],
|
||||
]);
|
||||
// In[6]:= Adjugate[{{1, 2, 3}, {4, 5, 7}, {6, 8, 9}}]
|
||||
// Out[6]= {{-11, 6, -1}, {6, -9, 5}, {2, 4, -3}}
|
||||
assert_eq!(
|
||||
m.adjugate().array,
|
||||
Matrix3::new([
|
||||
[Planar64Wide1::from(-11),Planar64Wide1::from(6),Planar64Wide1::from(-1)],
|
||||
[Planar64Wide1::from(6),Planar64Wide1::from(-9),Planar64Wide1::from(5)],
|
||||
[Planar64Wide1::from(2),Planar64Wide1::from(4),Planar64Wide1::from(-3)],
|
||||
]).array
|
||||
);
|
||||
}
|
@ -1,6 +0,0 @@
|
||||
mod tests;
|
||||
|
||||
#[cfg(feature="named-fields")]
|
||||
mod named;
|
||||
|
||||
mod fixed_wide;
|
@ -1,30 +0,0 @@
|
||||
use crate::types::{Vector3,Matrix3};
|
||||
|
||||
#[test]
|
||||
fn test_vector(){
|
||||
let mut v=Vector3::new([1,2,3]);
|
||||
assert_eq!(v.x,1);
|
||||
assert_eq!(v.y,2);
|
||||
assert_eq!(v.z,3);
|
||||
|
||||
v.x=5;
|
||||
assert_eq!(v.x,5);
|
||||
|
||||
v.y*=v.x;
|
||||
assert_eq!(v.y,10);
|
||||
}
|
||||
|
||||
|
||||
#[test]
|
||||
fn test_matrix(){
|
||||
let mut v=Matrix3::from_value(2);
|
||||
assert_eq!(v.x_axis.x,2);
|
||||
assert_eq!(v.y_axis.y,2);
|
||||
assert_eq!(v.z_axis.z,2);
|
||||
|
||||
v.x_axis.x=5;
|
||||
assert_eq!(v.x_axis.x,5);
|
||||
|
||||
v.y_axis.z*=v.x_axis.x;
|
||||
assert_eq!(v.y_axis.z,10);
|
||||
}
|
@ -1,59 +0,0 @@
|
||||
use crate::types::{Vector2,Vector3,Matrix3x4,Matrix4x2,Matrix3x2,Matrix2x3};
|
||||
|
||||
#[test]
|
||||
fn test_bool(){
|
||||
assert_eq!(Vector3::new([false,false,false]).any(),false);
|
||||
assert_eq!(Vector3::new([false,false,true]).any(),true);
|
||||
assert_eq!(Vector3::new([false,false,true]).all(),false);
|
||||
assert_eq!(Vector3::new([true,true,true]).all(),true);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_length_squared(){
|
||||
assert_eq!(Vector3::new([1,2,3]).length_squared(),14);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_arithmetic(){
|
||||
let a=Vector3::new([1,2,3]);
|
||||
assert_eq!((a+a*2).array,Vector3::new([1*3,2*3,3*3]).array);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn matrix_transform_vector(){
|
||||
let m=Matrix2x3::new([
|
||||
[1,2,3],
|
||||
[4,5,6],
|
||||
]).transpose();
|
||||
let v=Vector3::new([1,2,3]);
|
||||
let transformed=m*v;
|
||||
assert_eq!(transformed.array,Vector2::new([14,32]).array);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn matrix_dot(){
|
||||
// All this code was written row major and I converted the lib to colum major
|
||||
let rhs=Matrix4x2::new([
|
||||
[ 1.0, 2.0],
|
||||
[ 3.0, 4.0],
|
||||
[ 5.0, 6.0],
|
||||
[ 7.0, 8.0],
|
||||
]).transpose(); // | | |
|
||||
let lhs=Matrix3x4::new([ // | | |
|
||||
[1.0, 2.0, 3.0, 4.0],// [ 50.0, 60.0],
|
||||
[5.0, 6.0, 7.0, 8.0],// [114.0,140.0],
|
||||
[9.0,10.0,11.0,12.0],// [178.0,220.0],
|
||||
]).transpose();
|
||||
// Mat3<Vec4>.dot(Mat4<Vec2>) -> Mat3<Vec2>
|
||||
let m_dot=lhs*rhs;
|
||||
//In[1]:= {{1, 2, 3, 4}, {5, 6, 7, 8}, {9, 10, 11, 12}} . {{1, 2}, {3, 4}, {5, 6}, {7, 8}}
|
||||
//Out[1]= {{50, 60}, {114, 140}, {178, 220}}
|
||||
assert_eq!(
|
||||
m_dot.array,
|
||||
Matrix3x2::new([
|
||||
[50.0,60.0],
|
||||
[114.0,140.0],
|
||||
[178.0,220.0],
|
||||
]).transpose().array
|
||||
);
|
||||
}
|
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