forked from StrafesNET/strafe-project
Compare commits
4 Commits
integer-un
...
load-roblo
Author | SHA1 | Date | |
---|---|---|---|
382ecfa713 | |||
28cad39b10 | |||
2af43480f1 | |||
bb7ccd97bb |
35
Cargo.lock
generated
35
Cargo.lock
generated
@ -331,12 +331,6 @@ dependencies = [
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "configparser"
|
||||
version = "3.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5458d9d1a587efaf5091602c59d299696a3877a439c8f6d461a2d3cce11df87a"
|
||||
|
||||
[[package]]
|
||||
name = "constant_time_eq"
|
||||
version = "0.3.0"
|
||||
@ -840,29 +834,6 @@ dependencies = [
|
||||
"pkg-config",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lazy-regex"
|
||||
version = "3.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e723bd417b2df60a0f6a2b6825f297ea04b245d4ba52b5a22cb679bdf58b05fa"
|
||||
dependencies = [
|
||||
"lazy-regex-proc_macros",
|
||||
"once_cell",
|
||||
"regex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lazy-regex-proc_macros"
|
||||
version = "3.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0f0a1d9139f0ee2e862e08a9c5d0ba0470f2aa21cd1e1aa1b1562f83116c725f"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"regex",
|
||||
"syn 2.0.29",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lazy_static"
|
||||
version = "1.4.0"
|
||||
@ -1688,23 +1659,21 @@ checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
|
||||
|
||||
[[package]]
|
||||
name = "strafe-client"
|
||||
version = "0.8.0"
|
||||
version = "0.5.0"
|
||||
dependencies = [
|
||||
"async-executor",
|
||||
"bytemuck",
|
||||
"configparser",
|
||||
"ddsfile",
|
||||
"env_logger",
|
||||
"glam",
|
||||
"lazy-regex",
|
||||
"log",
|
||||
"obj",
|
||||
"parking_lot",
|
||||
"pollster",
|
||||
"rbx_binary",
|
||||
"rbx_dom_weak",
|
||||
"rbx_reflection_database",
|
||||
"rbx_xml",
|
||||
"regex",
|
||||
"wgpu",
|
||||
"winit",
|
||||
]
|
||||
|
14
Cargo.toml
14
Cargo.toml
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "strafe-client"
|
||||
version = "0.8.0"
|
||||
version = "0.5.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
@ -8,23 +8,21 @@ edition = "2021"
|
||||
[dependencies]
|
||||
async-executor = "1.5.1"
|
||||
bytemuck = { version = "1.13.1", features = ["derive"] }
|
||||
configparser = "3.0.2"
|
||||
ddsfile = "0.5.1"
|
||||
env_logger = "0.10.0"
|
||||
glam = "0.24.1"
|
||||
lazy-regex = "3.0.2"
|
||||
log = "0.4.20"
|
||||
obj = "0.10.2"
|
||||
parking_lot = "0.12.1"
|
||||
pollster = "0.3.0"
|
||||
rbx_binary = "0.7.1"
|
||||
rbx_dom_weak = "2.5.0"
|
||||
rbx_reflection_database = "0.2.7"
|
||||
rbx_xml = "0.13.1"
|
||||
regex = "1.9.5"
|
||||
wgpu = "0.17.0"
|
||||
winit = "0.28.6"
|
||||
|
||||
#[profile.release]
|
||||
#lto = true
|
||||
#strip = true
|
||||
#codegen-units = 1
|
||||
[profile.release]
|
||||
lto = true
|
||||
strip = true
|
||||
codegen-units = 1
|
||||
|
89
src/aabb.rs
89
src/aabb.rs
@ -1,89 +0,0 @@
|
||||
use crate::integer::Planar64Vec3;
|
||||
|
||||
#[derive(Debug,Clone,Copy,Hash,Eq,PartialEq)]
|
||||
pub enum AabbFace{
|
||||
Right,//+X
|
||||
Top,
|
||||
Back,
|
||||
Left,
|
||||
Bottom,
|
||||
Front,
|
||||
}
|
||||
#[derive(Clone)]
|
||||
pub struct Aabb{
|
||||
pub min:Planar64Vec3,
|
||||
pub max:Planar64Vec3,
|
||||
}
|
||||
|
||||
impl Default for Aabb {
|
||||
fn default()->Self {
|
||||
Self{min:Planar64Vec3::MAX,max:Planar64Vec3::MIN}
|
||||
}
|
||||
}
|
||||
|
||||
impl Aabb{
|
||||
const VERTEX_DATA:[Planar64Vec3;8]=[
|
||||
Planar64Vec3::int( 1,-1,-1),
|
||||
Planar64Vec3::int( 1, 1,-1),
|
||||
Planar64Vec3::int( 1, 1, 1),
|
||||
Planar64Vec3::int( 1,-1, 1),
|
||||
Planar64Vec3::int(-1,-1, 1),
|
||||
Planar64Vec3::int(-1, 1, 1),
|
||||
Planar64Vec3::int(-1, 1,-1),
|
||||
Planar64Vec3::int(-1,-1,-1),
|
||||
];
|
||||
|
||||
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{
|
||||
(self.min.cmplt(aabb.max)&aabb.min.cmplt(self.max)).all()
|
||||
}
|
||||
pub fn normal(face:AabbFace)->Planar64Vec3{
|
||||
match face {
|
||||
AabbFace::Right=>Planar64Vec3::int(1,0,0),
|
||||
AabbFace::Top=>Planar64Vec3::int(0,1,0),
|
||||
AabbFace::Back=>Planar64Vec3::int(0,0,1),
|
||||
AabbFace::Left=>Planar64Vec3::int(-1,0,0),
|
||||
AabbFace::Bottom=>Planar64Vec3::int(0,-1,0),
|
||||
AabbFace::Front=>Planar64Vec3::int(0,0,-1),
|
||||
}
|
||||
}
|
||||
pub fn unit_vertices()->[Planar64Vec3;8] {
|
||||
return Self::VERTEX_DATA;
|
||||
}
|
||||
// pub fn face(&self,face:AabbFace)->Aabb {
|
||||
// let mut aabb=self.clone();
|
||||
// //in this implementation face = worldspace aabb face
|
||||
// match face {
|
||||
// AabbFace::Right => aabb.min.x=aabb.max.x,
|
||||
// AabbFace::Top => aabb.min.y=aabb.max.y,
|
||||
// AabbFace::Back => aabb.min.z=aabb.max.z,
|
||||
// AabbFace::Left => aabb.max.x=aabb.min.x,
|
||||
// AabbFace::Bottom => aabb.max.y=aabb.min.y,
|
||||
// AabbFace::Front => aabb.max.z=aabb.min.z,
|
||||
// }
|
||||
// return aabb;
|
||||
// }
|
||||
pub fn center(&self)->Planar64Vec3{
|
||||
return self.min.midpoint(self.max)
|
||||
}
|
||||
//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
|
||||
// }
|
||||
}
|
869
src/body.rs
Normal file
869
src/body.rs
Normal file
@ -0,0 +1,869 @@
|
||||
use crate::{instruction::{InstructionEmitter, InstructionConsumer, TimedInstruction}, zeroes::zeroes2};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum PhysicsInstruction {
|
||||
CollisionStart(RelativeCollision),
|
||||
CollisionEnd(RelativeCollision),
|
||||
SetControlDir(glam::Vec3),
|
||||
StrafeTick,
|
||||
Jump,
|
||||
SetWalkTargetVelocity(glam::Vec3),
|
||||
RefreshWalkTarget,
|
||||
ReachWalkTargetVelocity,
|
||||
// Water,
|
||||
// Spawn(
|
||||
// Option<SpawnId>,
|
||||
// bool,//true = Trigger; false = teleport
|
||||
// bool,//true = Force
|
||||
// )
|
||||
//temp
|
||||
SetPosition(glam::Vec3),
|
||||
}
|
||||
|
||||
pub struct Body {
|
||||
position: glam::Vec3,//I64 where 2^32 = 1 u
|
||||
velocity: glam::Vec3,//I64 where 2^32 = 1 u/s
|
||||
acceleration: glam::Vec3,//I64 where 2^32 = 1 u/s/s
|
||||
time: TIME,//nanoseconds x xxxxD!
|
||||
}
|
||||
trait MyHash{
|
||||
fn hash(&self) -> u64;
|
||||
}
|
||||
impl MyHash for Body {
|
||||
fn hash(&self) -> u64 {
|
||||
let mut hasher=std::collections::hash_map::DefaultHasher::new();
|
||||
for &el in self.position.as_ref().iter() {
|
||||
std::hash::Hasher::write(&mut hasher, el.to_ne_bytes().as_slice());
|
||||
}
|
||||
for &el in self.velocity.as_ref().iter() {
|
||||
std::hash::Hasher::write(&mut hasher, el.to_ne_bytes().as_slice());
|
||||
}
|
||||
for &el in self.acceleration.as_ref().iter() {
|
||||
std::hash::Hasher::write(&mut hasher, el.to_ne_bytes().as_slice());
|
||||
}
|
||||
std::hash::Hasher::write(&mut hasher, self.time.to_ne_bytes().as_slice());
|
||||
return std::hash::Hasher::finish(&hasher);//hash check to see if walk target is valid
|
||||
}
|
||||
}
|
||||
|
||||
pub enum MoveRestriction {
|
||||
Air,
|
||||
Water,
|
||||
Ground,
|
||||
Ladder,//multiple ladders how
|
||||
}
|
||||
|
||||
enum MouseInterpolation {
|
||||
First,//just checks the last value
|
||||
Lerp,//lerps between
|
||||
}
|
||||
|
||||
enum InputInstruction {
|
||||
MoveMouse(glam::IVec2),
|
||||
Jump(bool),
|
||||
}
|
||||
|
||||
struct InputState {
|
||||
controls: u32,
|
||||
mouse_interpolation: MouseInterpolation,
|
||||
time: TIME,
|
||||
}
|
||||
|
||||
impl InputState {
|
||||
pub fn get_control(&self,control:u32) -> bool {
|
||||
self.controls&control!=0
|
||||
}
|
||||
pub fn process_instruction(&mut self,ins:InputInstruction){
|
||||
match ins {
|
||||
InputInstruction::MoveMouse(m) => todo!("set mouse_interpolation"),
|
||||
InputInstruction::Jump(b) => todo!("how does info about style modifiers get here"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct MouseInterpolationState {
|
||||
interpolation: MouseInterpolation,
|
||||
time0: TIME,
|
||||
time1: TIME,
|
||||
mouse0: glam::IVec2,
|
||||
mouse1: glam::IVec2,
|
||||
}
|
||||
|
||||
impl MouseInterpolationState {
|
||||
pub fn move_mouse(&mut self,time:TIME,pos:glam::IVec2){
|
||||
self.time0=self.time1;
|
||||
self.mouse0=self.mouse1;
|
||||
self.time1=time;
|
||||
self.mouse1=pos;
|
||||
}
|
||||
pub fn interpolated_position(&self,time:TIME) -> glam::IVec2 {
|
||||
match self.interpolation {
|
||||
MouseInterpolation::First => self.mouse0,
|
||||
MouseInterpolation::Lerp => {
|
||||
let m0=self.mouse0.as_i64vec2();
|
||||
let m1=self.mouse1.as_i64vec2();
|
||||
//these are deltas
|
||||
let t1t=(self.time1-time) as i64;
|
||||
let tt0=(time-self.time0) as i64;
|
||||
let dt=(self.time1-self.time0) as i64;
|
||||
((m0*t1t+m1*tt0)/dt).as_ivec2()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub enum WalkEnum{
|
||||
Reached,
|
||||
Transient,
|
||||
Invalid,
|
||||
}
|
||||
pub struct WalkState {
|
||||
pub target_velocity: glam::Vec3,
|
||||
pub target_time: TIME,
|
||||
pub state: WalkEnum,
|
||||
}
|
||||
impl WalkState {
|
||||
pub fn new() -> Self {
|
||||
Self{
|
||||
target_velocity:glam::Vec3::ZERO,
|
||||
target_time:0,
|
||||
state:WalkEnum::Invalid,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct PhysicsState {
|
||||
pub body: Body,
|
||||
pub hitbox_halfsize: glam::Vec3,
|
||||
pub contacts: std::collections::HashSet::<RelativeCollision>,
|
||||
//pub intersections: Vec<ModelId>,
|
||||
pub models: Vec<ModelPhysics>,
|
||||
//temp
|
||||
pub temp_control_dir: glam::Vec3,
|
||||
//camera must exist in state because wormholes modify the camera, also camera punch
|
||||
//pub camera: Camera,
|
||||
//pub mouse_interpolation: MouseInterpolationState,
|
||||
pub time: TIME,
|
||||
pub strafe_tick_num: TIME,
|
||||
pub strafe_tick_den: TIME,
|
||||
pub tick: u32,
|
||||
pub mv: f32,
|
||||
pub walk: WalkState,
|
||||
pub walkspeed: f32,
|
||||
pub friction: f32,
|
||||
pub walk_accel: f32,
|
||||
pub gravity: glam::Vec3,
|
||||
pub grounded: bool,
|
||||
pub jump_trying: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug,Clone,Copy,Hash,Eq,PartialEq)]
|
||||
pub enum AabbFace{
|
||||
Right,//+X
|
||||
Top,
|
||||
Back,
|
||||
Left,
|
||||
Bottom,
|
||||
Front,
|
||||
}
|
||||
|
||||
pub struct Aabb {
|
||||
min: glam::Vec3,
|
||||
max: glam::Vec3,
|
||||
}
|
||||
|
||||
impl Aabb {
|
||||
// const FACE_DATA: [[f32; 3]; 6] = [
|
||||
// [0.0f32, 0., 1.],
|
||||
// [0.0f32, 0., -1.],
|
||||
// [1.0f32, 0., 0.],
|
||||
// [-1.0f32, 0., 0.],
|
||||
// [0.0f32, 1., 0.],
|
||||
// [0.0f32, -1., 0.],
|
||||
// ];
|
||||
const VERTEX_DATA: [glam::Vec3; 8] = [
|
||||
glam::vec3(1., -1., -1.),
|
||||
glam::vec3(1., 1., -1.),
|
||||
glam::vec3(1., 1., 1.),
|
||||
glam::vec3(1., -1., 1.),
|
||||
glam::vec3(-1., -1., 1.),
|
||||
glam::vec3(-1., 1., 1.),
|
||||
glam::vec3(-1., 1., -1.),
|
||||
glam::vec3(-1., -1., -1.),
|
||||
];
|
||||
const VERTEX_DATA_RIGHT: [glam::Vec3; 4] = [
|
||||
glam::vec3(1., -1., -1.),
|
||||
glam::vec3(1., 1., -1.),
|
||||
glam::vec3(1., 1., 1.),
|
||||
glam::vec3(1., -1., 1.),
|
||||
];
|
||||
const VERTEX_DATA_TOP: [glam::Vec3; 4] = [
|
||||
glam::vec3(1., 1., -1.),
|
||||
glam::vec3(-1., 1., -1.),
|
||||
glam::vec3(-1., 1., 1.),
|
||||
glam::vec3(1., 1., 1.),
|
||||
];
|
||||
const VERTEX_DATA_BACK: [glam::Vec3; 4] = [
|
||||
glam::vec3(-1., -1., 1.),
|
||||
glam::vec3(1., -1., 1.),
|
||||
glam::vec3(1., 1., 1.),
|
||||
glam::vec3(-1., 1., 1.),
|
||||
];
|
||||
const VERTEX_DATA_LEFT: [glam::Vec3; 4] = [
|
||||
glam::vec3(-1., -1., 1.),
|
||||
glam::vec3(-1., 1., 1.),
|
||||
glam::vec3(-1., 1., -1.),
|
||||
glam::vec3(-1., -1., -1.),
|
||||
];
|
||||
const VERTEX_DATA_BOTTOM: [glam::Vec3; 4] = [
|
||||
glam::vec3(1., -1., 1.),
|
||||
glam::vec3(-1., -1., 1.),
|
||||
glam::vec3(-1., -1., -1.),
|
||||
glam::vec3(1., -1., -1.),
|
||||
];
|
||||
const VERTEX_DATA_FRONT: [glam::Vec3; 4] = [
|
||||
glam::vec3(-1., 1., -1.),
|
||||
glam::vec3(1., 1., -1.),
|
||||
glam::vec3(1., -1., -1.),
|
||||
glam::vec3(-1., -1., -1.),
|
||||
];
|
||||
|
||||
pub fn new() -> Self {
|
||||
Self {min: glam::Vec3::INFINITY,max: glam::Vec3::NEG_INFINITY}
|
||||
}
|
||||
|
||||
pub fn grow(&mut self, point:glam::Vec3){
|
||||
self.min=self.min.min(point);
|
||||
self.max=self.max.max(point);
|
||||
}
|
||||
|
||||
pub fn normal(face:AabbFace) -> glam::Vec3 {
|
||||
match face {
|
||||
AabbFace::Right => glam::vec3(1.,0.,0.),
|
||||
AabbFace::Top => glam::vec3(0.,1.,0.),
|
||||
AabbFace::Back => glam::vec3(0.,0.,1.),
|
||||
AabbFace::Left => glam::vec3(-1.,0.,0.),
|
||||
AabbFace::Bottom => glam::vec3(0.,-1.,0.),
|
||||
AabbFace::Front => glam::vec3(0.,0.,-1.),
|
||||
}
|
||||
}
|
||||
pub fn unit_vertices() -> [glam::Vec3;8] {
|
||||
return Self::VERTEX_DATA;
|
||||
}
|
||||
pub fn unit_face_vertices(face:AabbFace) -> [glam::Vec3;4] {
|
||||
match face {
|
||||
AabbFace::Right => Self::VERTEX_DATA_RIGHT,
|
||||
AabbFace::Top => Self::VERTEX_DATA_TOP,
|
||||
AabbFace::Back => Self::VERTEX_DATA_BACK,
|
||||
AabbFace::Left => Self::VERTEX_DATA_LEFT,
|
||||
AabbFace::Bottom => Self::VERTEX_DATA_BOTTOM,
|
||||
AabbFace::Front => Self::VERTEX_DATA_FRONT,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//pretend to be using what we want to eventually do
|
||||
type TreyMeshFace = AabbFace;
|
||||
type TreyMesh = Aabb;
|
||||
|
||||
pub struct ModelPhysics {
|
||||
//A model is a thing that has a hitbox. can be represented by a list of TreyMesh-es
|
||||
//in this iteration, all it needs is extents.
|
||||
transform: glam::Mat4,
|
||||
}
|
||||
|
||||
impl ModelPhysics {
|
||||
pub fn new(transform:glam::Mat4) -> Self {
|
||||
Self{transform}
|
||||
}
|
||||
pub fn unit_vertices(&self) -> [glam::Vec3;8] {
|
||||
Aabb::unit_vertices()
|
||||
}
|
||||
pub fn mesh(&self) -> TreyMesh {
|
||||
let mut aabb=Aabb::new();
|
||||
for &vertex in self.unit_vertices().iter() {
|
||||
aabb.grow(glam::Vec4Swizzles::xyz(self.transform*vertex.extend(1.0)));
|
||||
}
|
||||
return aabb;
|
||||
}
|
||||
pub fn unit_face_vertices(&self,face:TreyMeshFace) -> [glam::Vec3;4] {
|
||||
Aabb::unit_face_vertices(face)
|
||||
}
|
||||
pub fn face_mesh(&self,face:TreyMeshFace) -> TreyMesh {
|
||||
let mut aabb=self.mesh();
|
||||
//in this implementation face = worldspace aabb face
|
||||
match face {
|
||||
AabbFace::Right => aabb.min.x=aabb.max.x,
|
||||
AabbFace::Top => aabb.min.y=aabb.max.y,
|
||||
AabbFace::Back => aabb.min.z=aabb.max.z,
|
||||
AabbFace::Left => aabb.max.x=aabb.min.x,
|
||||
AabbFace::Bottom => aabb.max.y=aabb.min.y,
|
||||
AabbFace::Front => aabb.max.z=aabb.min.z,
|
||||
}
|
||||
return aabb;
|
||||
}
|
||||
pub fn face_normal(&self,face:TreyMeshFace) -> glam::Vec3 {
|
||||
glam::Vec4Swizzles::xyz(Aabb::normal(face).extend(0.0))//this is wrong for scale
|
||||
}
|
||||
}
|
||||
|
||||
//need non-face (full model) variant for CanCollide false objects
|
||||
//OR have a separate list from contacts for model intersection
|
||||
#[derive(Debug,Clone,Eq,Hash,PartialEq)]
|
||||
pub struct RelativeCollision {
|
||||
face: TreyMeshFace,//just an id
|
||||
model: u32,//using id to avoid lifetimes
|
||||
}
|
||||
|
||||
impl RelativeCollision {
|
||||
pub fn mesh(&self,models:&Vec<ModelPhysics>) -> TreyMesh {
|
||||
return models.get(self.model as usize).unwrap().face_mesh(self.face)
|
||||
}
|
||||
pub fn normal(&self,models:&Vec<ModelPhysics>) -> glam::Vec3 {
|
||||
return models.get(self.model as usize).unwrap().face_normal(self.face)
|
||||
}
|
||||
}
|
||||
|
||||
pub type TIME = i64;
|
||||
|
||||
impl Body {
|
||||
pub fn with_pva(position:glam::Vec3,velocity:glam::Vec3,acceleration:glam::Vec3) -> Self {
|
||||
Self{
|
||||
position,
|
||||
velocity,
|
||||
acceleration,
|
||||
time: 0,
|
||||
}
|
||||
}
|
||||
pub fn extrapolated_position(&self,time: TIME)->glam::Vec3{
|
||||
let dt=(time-self.time) as f64/1_000_000_000f64;
|
||||
self.position+self.velocity*(dt as f32)+self.acceleration*((0.5*dt*dt) as f32)
|
||||
}
|
||||
pub fn extrapolated_velocity(&self,time: TIME)->glam::Vec3{
|
||||
let dt=(time-self.time) as f64/1_000_000_000f64;
|
||||
self.velocity+self.acceleration*(dt as f32)
|
||||
}
|
||||
pub fn advance_time(&mut self, time: TIME){
|
||||
self.position=self.extrapolated_position(time);
|
||||
self.velocity=self.extrapolated_velocity(time);
|
||||
self.time=time;
|
||||
}
|
||||
}
|
||||
|
||||
impl PhysicsState {
|
||||
//tickless gaming
|
||||
pub fn run(&mut self, time_limit:TIME){
|
||||
//prepare is ommitted - everything is done via instructions.
|
||||
while let Some(instruction) = self.next_instruction(time_limit) {//collect
|
||||
//advance
|
||||
//self.advance_time(instruction.time);
|
||||
//process
|
||||
self.process_instruction(instruction);
|
||||
//write hash lol
|
||||
}
|
||||
}
|
||||
|
||||
pub fn advance_time(&mut self, time: TIME){
|
||||
self.body.advance_time(time);
|
||||
self.time=time;
|
||||
}
|
||||
|
||||
fn contact_constrain_velocity(&self,velocity:&mut glam::Vec3){
|
||||
for contact in self.contacts.iter() {
|
||||
let n=contact.normal(&self.models);
|
||||
let d=velocity.dot(n);
|
||||
if d<0f32{
|
||||
(*velocity)-=d/n.length_squared()*n;
|
||||
}
|
||||
}
|
||||
}
|
||||
fn contact_constrain_acceleration(&self,acceleration:&mut glam::Vec3){
|
||||
for contact in self.contacts.iter() {
|
||||
let n=contact.normal(&self.models);
|
||||
let d=acceleration.dot(n);
|
||||
if d<0f32{
|
||||
(*acceleration)-=d/n.length_squared()*n;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn next_strafe_instruction(&self) -> Option<TimedInstruction<PhysicsInstruction>> {
|
||||
return Some(TimedInstruction{
|
||||
time:(self.time*self.strafe_tick_num/self.strafe_tick_den+1)*self.strafe_tick_den/self.strafe_tick_num,
|
||||
//only poll the physics if there is a before and after mouse event
|
||||
instruction:PhysicsInstruction::StrafeTick
|
||||
});
|
||||
}
|
||||
|
||||
//state mutated on collision:
|
||||
//Accelerator
|
||||
//stair step-up
|
||||
|
||||
//state mutated on instruction
|
||||
//change fly acceleration (fly_sustain)
|
||||
//change fly velocity
|
||||
|
||||
//generic event emmiters
|
||||
//PlatformStandTime
|
||||
//walk/swim/air/ladder sounds
|
||||
//VState?
|
||||
|
||||
//falling under the map
|
||||
// fn next_respawn_instruction(&self) -> Option<TimedInstruction<PhysicsInstruction>> {
|
||||
// if self.body.position<self.world.min_y {
|
||||
// return Some(TimedInstruction{
|
||||
// time:self.time,
|
||||
// instruction:PhysicsInstruction::Trigger(None)
|
||||
// });
|
||||
// }
|
||||
// }
|
||||
|
||||
// fn next_water_instruction(&self) -> Option<TimedInstruction<PhysicsInstruction>> {
|
||||
// return Some(TimedInstruction{
|
||||
// time:(self.time*self.strafe_tick_num/self.strafe_tick_den+1)*self.strafe_tick_den/self.strafe_tick_num,
|
||||
// //only poll the physics if there is a before and after mouse event
|
||||
// instruction:PhysicsInstruction::Water
|
||||
// });
|
||||
// }
|
||||
|
||||
fn next_walk_instruction(&self) -> Option<TimedInstruction<PhysicsInstruction>> {
|
||||
//check if you have a valid walk state and create an instruction
|
||||
if self.grounded{
|
||||
match self.walk.state{
|
||||
WalkEnum::Transient=>Some(TimedInstruction{
|
||||
time:self.walk.target_time,
|
||||
instruction:PhysicsInstruction::ReachWalkTargetVelocity
|
||||
}),
|
||||
WalkEnum::Invalid=>Some(TimedInstruction{
|
||||
time:self.time,
|
||||
instruction:PhysicsInstruction::RefreshWalkTarget,
|
||||
}),
|
||||
WalkEnum::Reached=>None,
|
||||
}
|
||||
}else{
|
||||
return None;
|
||||
}
|
||||
}
|
||||
fn mesh(&self) -> TreyMesh {
|
||||
let mut aabb=Aabb::new();
|
||||
for vertex in Aabb::unit_vertices(){
|
||||
aabb.grow(self.body.position+self.hitbox_halfsize*vertex);
|
||||
}
|
||||
aabb
|
||||
}
|
||||
fn predict_collision_end(&self,time:TIME,time_limit:TIME,collision_data:&RelativeCollision) -> Option<TimedInstruction<PhysicsInstruction>> {
|
||||
//must treat cancollide false objects differently: you may not exit through the same face you entered.
|
||||
//RelativeCollsion must reference the full model instead of a particular face
|
||||
//this is Ctrl+C Ctrl+V of predict_collision_start but with v=-v before the calc and t=-t after the calc
|
||||
//find best t
|
||||
let mut best_time=time_limit;
|
||||
let mut exit_face:Option<TreyMeshFace>=None;
|
||||
let mesh0=self.mesh();
|
||||
let mesh1=self.models.get(collision_data.model as usize).unwrap().mesh();
|
||||
let (v,a)=(-self.body.velocity,self.body.acceleration);
|
||||
//collect x
|
||||
match collision_data.face {
|
||||
AabbFace::Top|AabbFace::Back|AabbFace::Bottom|AabbFace::Front=>{
|
||||
for t in zeroes2(mesh0.max.x-mesh1.min.x,v.x,0.5*a.x) {
|
||||
//negative t = back in time
|
||||
//must be moving towards surface to collide
|
||||
//must beat the current soonest collision time
|
||||
//must be moving towards surface
|
||||
let t_time=self.body.time+((-t as f64)*1_000_000_000f64) as TIME;
|
||||
if time<=t_time&&t_time<best_time&&0f32<v.x+a.x*-t{
|
||||
//collect valid t
|
||||
best_time=t_time;
|
||||
exit_face=Some(TreyMeshFace::Left);
|
||||
break;
|
||||
}
|
||||
}
|
||||
for t in zeroes2(mesh0.min.x-mesh1.max.x,v.x,0.5*a.x) {
|
||||
//negative t = back in time
|
||||
//must be moving towards surface to collide
|
||||
//must beat the current soonest collision time
|
||||
//must be moving towards surface
|
||||
let t_time=self.body.time+((-t as f64)*1_000_000_000f64) as TIME;
|
||||
if time<=t_time&&t_time<best_time&&v.x+a.x*-t<0f32{
|
||||
//collect valid t
|
||||
best_time=t_time;
|
||||
exit_face=Some(TreyMeshFace::Right);
|
||||
break;
|
||||
}
|
||||
}
|
||||
},
|
||||
AabbFace::Left=>{
|
||||
//generate event if v.x<0||a.x<0
|
||||
if -v.x<0f32{
|
||||
best_time=time;
|
||||
exit_face=Some(TreyMeshFace::Left);
|
||||
}
|
||||
},
|
||||
AabbFace::Right=>{
|
||||
//generate event if 0<v.x||0<a.x
|
||||
if 0f32<(-v.x){
|
||||
best_time=time;
|
||||
exit_face=Some(TreyMeshFace::Right);
|
||||
}
|
||||
},
|
||||
}
|
||||
//collect y
|
||||
match collision_data.face {
|
||||
AabbFace::Left|AabbFace::Back|AabbFace::Right|AabbFace::Front=>{
|
||||
for t in zeroes2(mesh0.max.y-mesh1.min.y,v.y,0.5*a.y) {
|
||||
//negative t = back in time
|
||||
//must be moving towards surface to collide
|
||||
//must beat the current soonest collision time
|
||||
//must be moving towards surface
|
||||
let t_time=self.body.time+((-t as f64)*1_000_000_000f64) as TIME;
|
||||
if time<=t_time&&t_time<best_time&&0f32<v.y+a.y*-t{
|
||||
//collect valid t
|
||||
best_time=t_time;
|
||||
exit_face=Some(TreyMeshFace::Bottom);
|
||||
break;
|
||||
}
|
||||
}
|
||||
for t in zeroes2(mesh0.min.y-mesh1.max.y,v.y,0.5*a.y) {
|
||||
//negative t = back in time
|
||||
//must be moving towards surface to collide
|
||||
//must beat the current soonest collision time
|
||||
//must be moving towards surface
|
||||
let t_time=self.body.time+((-t as f64)*1_000_000_000f64) as TIME;
|
||||
if time<=t_time&&t_time<best_time&&v.y+a.y*-t<0f32{
|
||||
//collect valid t
|
||||
best_time=t_time;
|
||||
exit_face=Some(TreyMeshFace::Top);
|
||||
break;
|
||||
}
|
||||
}
|
||||
},
|
||||
AabbFace::Bottom=>{
|
||||
//generate event if v.y<0||a.y<0
|
||||
if -v.y<0f32{
|
||||
best_time=time;
|
||||
exit_face=Some(TreyMeshFace::Bottom);
|
||||
}
|
||||
},
|
||||
AabbFace::Top=>{
|
||||
//generate event if 0<v.y||0<a.y
|
||||
if 0f32<(-v.y){
|
||||
best_time=time;
|
||||
exit_face=Some(TreyMeshFace::Top);
|
||||
}
|
||||
},
|
||||
}
|
||||
//collect z
|
||||
match collision_data.face {
|
||||
AabbFace::Left|AabbFace::Bottom|AabbFace::Right|AabbFace::Top=>{
|
||||
for t in zeroes2(mesh0.max.z-mesh1.min.z,v.z,0.5*a.z) {
|
||||
//negative t = back in time
|
||||
//must be moving towards surface to collide
|
||||
//must beat the current soonest collision time
|
||||
//must be moving towards surface
|
||||
let t_time=self.body.time+((-t as f64)*1_000_000_000f64) as TIME;
|
||||
if time<=t_time&&t_time<best_time&&0f32<v.z+a.z*-t{
|
||||
//collect valid t
|
||||
best_time=t_time;
|
||||
exit_face=Some(TreyMeshFace::Front);
|
||||
break;
|
||||
}
|
||||
}
|
||||
for t in zeroes2(mesh0.min.z-mesh1.max.z,v.z,0.5*a.z) {
|
||||
//negative t = back in time
|
||||
//must be moving towards surface to collide
|
||||
//must beat the current soonest collision time
|
||||
//must be moving towards surface
|
||||
let t_time=self.body.time+((-t as f64)*1_000_000_000f64) as TIME;
|
||||
if time<=t_time&&t_time<best_time&&v.z+a.z*-t<0f32{
|
||||
//collect valid t
|
||||
best_time=t_time;
|
||||
exit_face=Some(TreyMeshFace::Back);
|
||||
break;
|
||||
}
|
||||
}
|
||||
},
|
||||
AabbFace::Front=>{
|
||||
//generate event if v.z<0||a.z<0
|
||||
if -v.z<0f32{
|
||||
best_time=time;
|
||||
exit_face=Some(TreyMeshFace::Front);
|
||||
}
|
||||
},
|
||||
AabbFace::Back=>{
|
||||
//generate event if 0<v.z||0<a.z
|
||||
if 0f32<(-v.z){
|
||||
best_time=time;
|
||||
exit_face=Some(TreyMeshFace::Back);
|
||||
}
|
||||
},
|
||||
}
|
||||
//generate instruction
|
||||
if let Some(face) = exit_face{
|
||||
return Some(TimedInstruction {
|
||||
time: best_time,
|
||||
instruction: PhysicsInstruction::CollisionEnd(collision_data.clone())
|
||||
})
|
||||
}
|
||||
None
|
||||
}
|
||||
fn predict_collision_start(&self,time:TIME,time_limit:TIME,model_id:u32) -> Option<TimedInstruction<PhysicsInstruction>> {
|
||||
//find best t
|
||||
let mut best_time=time_limit;
|
||||
let mut best_face:Option<TreyMeshFace>=None;
|
||||
let mesh0=self.mesh();
|
||||
let mesh1=self.models.get(model_id as usize).unwrap().mesh();
|
||||
let (p,v,a)=(self.body.position,self.body.velocity,self.body.acceleration);
|
||||
//collect x
|
||||
for t in zeroes2(mesh0.max.x-mesh1.min.x,v.x,0.5*a.x) {
|
||||
//must collide now or in the future
|
||||
//must beat the current soonest collision time
|
||||
//must be moving towards surface
|
||||
let t_time=self.body.time+((t as f64)*1_000_000_000f64) as TIME;
|
||||
if time<=t_time&&t_time<best_time&&0f32<v.x+a.x*t{
|
||||
let dp=self.body.extrapolated_position(t_time)-p;
|
||||
//faces must be overlapping
|
||||
if mesh1.min.y<mesh0.max.y+dp.y&&mesh0.min.y+dp.y<mesh1.max.y&&mesh1.min.z<mesh0.max.z+dp.z&&mesh0.min.z+dp.z<mesh1.max.z {
|
||||
//collect valid t
|
||||
best_time=t_time;
|
||||
best_face=Some(TreyMeshFace::Left);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
for t in zeroes2(mesh0.min.x-mesh1.max.x,v.x,0.5*a.x) {
|
||||
//must collide now or in the future
|
||||
//must beat the current soonest collision time
|
||||
//must be moving towards surface
|
||||
let t_time=self.body.time+((t as f64)*1_000_000_000f64) as TIME;
|
||||
if time<=t_time&&t_time<best_time&&v.x+a.x*t<0f32{
|
||||
let dp=self.body.extrapolated_position(t_time)-p;
|
||||
//faces must be overlapping
|
||||
if mesh1.min.y<mesh0.max.y+dp.y&&mesh0.min.y+dp.y<mesh1.max.y&&mesh1.min.z<mesh0.max.z+dp.z&&mesh0.min.z+dp.z<mesh1.max.z {
|
||||
//collect valid t
|
||||
best_time=t_time;
|
||||
best_face=Some(TreyMeshFace::Right);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
//collect y
|
||||
for t in zeroes2(mesh0.max.y-mesh1.min.y,v.y,0.5*a.y) {
|
||||
//must collide now or in the future
|
||||
//must beat the current soonest collision time
|
||||
//must be moving towards surface
|
||||
let t_time=self.body.time+((t as f64)*1_000_000_000f64) as TIME;
|
||||
if time<=t_time&&t_time<best_time&&0f32<v.y+a.y*t{
|
||||
let dp=self.body.extrapolated_position(t_time)-p;
|
||||
//faces must be overlapping
|
||||
if mesh1.min.x<mesh0.max.x+dp.x&&mesh0.min.x+dp.x<mesh1.max.x&&mesh1.min.z<mesh0.max.z+dp.z&&mesh0.min.z+dp.z<mesh1.max.z {
|
||||
//collect valid t
|
||||
best_time=t_time;
|
||||
best_face=Some(TreyMeshFace::Bottom);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
for t in zeroes2(mesh0.min.y-mesh1.max.y,v.y,0.5*a.y) {
|
||||
//must collide now or in the future
|
||||
//must beat the current soonest collision time
|
||||
//must be moving towards surface
|
||||
let t_time=self.body.time+((t as f64)*1_000_000_000f64) as TIME;
|
||||
if time<=t_time&&t_time<best_time&&v.y+a.y*t<0f32{
|
||||
let dp=self.body.extrapolated_position(t_time)-p;
|
||||
//faces must be overlapping
|
||||
if mesh1.min.x<mesh0.max.x+dp.x&&mesh0.min.x+dp.x<mesh1.max.x&&mesh1.min.z<mesh0.max.z+dp.z&&mesh0.min.z+dp.z<mesh1.max.z {
|
||||
//collect valid t
|
||||
best_time=t_time;
|
||||
best_face=Some(TreyMeshFace::Top);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
//collect z
|
||||
for t in zeroes2(mesh0.max.z-mesh1.min.z,v.z,0.5*a.z) {
|
||||
//must collide now or in the future
|
||||
//must beat the current soonest collision time
|
||||
//must be moving towards surface
|
||||
let t_time=self.body.time+((t as f64)*1_000_000_000f64) as TIME;
|
||||
if time<=t_time&&t_time<best_time&&0f32<v.z+a.z*t{
|
||||
let dp=self.body.extrapolated_position(t_time)-p;
|
||||
//faces must be overlapping
|
||||
if mesh1.min.y<mesh0.max.y+dp.y&&mesh0.min.y+dp.y<mesh1.max.y&&mesh1.min.x<mesh0.max.x+dp.x&&mesh0.min.x+dp.x<mesh1.max.x {
|
||||
//collect valid t
|
||||
best_time=t_time;
|
||||
best_face=Some(TreyMeshFace::Front);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
for t in zeroes2(mesh0.min.z-mesh1.max.z,v.z,0.5*a.z) {
|
||||
//must collide now or in the future
|
||||
//must beat the current soonest collision time
|
||||
//must be moving towards surface
|
||||
let t_time=self.body.time+((t as f64)*1_000_000_000f64) as TIME;
|
||||
if time<=t_time&&t_time<best_time&&v.z+a.z*t<0f32{
|
||||
let dp=self.body.extrapolated_position(t_time)-p;
|
||||
//faces must be overlapping
|
||||
if mesh1.min.y<mesh0.max.y+dp.y&&mesh0.min.y+dp.y<mesh1.max.y&&mesh1.min.x<mesh0.max.x+dp.x&&mesh0.min.x+dp.x<mesh1.max.x {
|
||||
//collect valid t
|
||||
best_time=t_time;
|
||||
best_face=Some(TreyMeshFace::Back);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
//generate instruction
|
||||
if let Some(face) = best_face{
|
||||
return Some(TimedInstruction {
|
||||
time: best_time,
|
||||
instruction: PhysicsInstruction::CollisionStart(RelativeCollision {
|
||||
face,
|
||||
model: model_id
|
||||
})
|
||||
})
|
||||
}
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
impl crate::instruction::InstructionEmitter<PhysicsInstruction> for PhysicsState {
|
||||
//this little next instruction function can cache its return value and invalidate the cached value by watching the State.
|
||||
fn next_instruction(&self,time_limit:TIME) -> Option<TimedInstruction<PhysicsInstruction>> {
|
||||
//JUST POLLING!!! NO MUTATION
|
||||
let mut collector = crate::instruction::InstructionCollector::new(time_limit);
|
||||
//check for collision stop instructions with curent contacts
|
||||
for collision_data in self.contacts.iter() {
|
||||
collector.collect(self.predict_collision_end(self.time,time_limit,collision_data));
|
||||
}
|
||||
//check for collision start instructions (against every part in the game with no optimization!!)
|
||||
for i in 0..self.models.len() {
|
||||
collector.collect(self.predict_collision_start(self.time,time_limit,i as u32));
|
||||
}
|
||||
if self.grounded {
|
||||
//walk maintenance
|
||||
collector.collect(self.next_walk_instruction());
|
||||
}else{
|
||||
//check to see when the next strafe tick is
|
||||
collector.collect(self.next_strafe_instruction());
|
||||
}
|
||||
collector.instruction()
|
||||
}
|
||||
}
|
||||
|
||||
impl crate::instruction::InstructionConsumer<PhysicsInstruction> for PhysicsState {
|
||||
fn process_instruction(&mut self, ins:TimedInstruction<PhysicsInstruction>) {
|
||||
match &ins.instruction {
|
||||
PhysicsInstruction::StrafeTick => (),
|
||||
_=>println!("{:?}",ins),
|
||||
}
|
||||
//selectively update body
|
||||
match &ins.instruction {
|
||||
PhysicsInstruction::SetWalkTargetVelocity(_)
|
||||
|PhysicsInstruction::SetPosition(_)
|
||||
|PhysicsInstruction::SetControlDir(_) => self.time=ins.time,//TODO: queue instructions
|
||||
PhysicsInstruction::RefreshWalkTarget
|
||||
|PhysicsInstruction::ReachWalkTargetVelocity
|
||||
|PhysicsInstruction::CollisionStart(_)
|
||||
|PhysicsInstruction::CollisionEnd(_)
|
||||
|PhysicsInstruction::StrafeTick
|
||||
|PhysicsInstruction::Jump => self.advance_time(ins.time),
|
||||
}
|
||||
match ins.instruction {
|
||||
PhysicsInstruction::SetPosition(position)=>{
|
||||
//temp
|
||||
self.body.position=position;
|
||||
//manual clear //for c in self.contacts{process_instruction(CollisionEnd(c))}
|
||||
self.contacts.clear();
|
||||
self.body.acceleration=self.gravity;
|
||||
self.walk.state=WalkEnum::Reached;
|
||||
self.grounded=false;
|
||||
}
|
||||
PhysicsInstruction::CollisionStart(c) => {
|
||||
//check ground
|
||||
match &c.face {
|
||||
AabbFace::Top => {
|
||||
//ground
|
||||
self.grounded=true;
|
||||
},
|
||||
_ => (),
|
||||
}
|
||||
self.contacts.insert(c);
|
||||
//flatten v
|
||||
let mut v=self.body.velocity;
|
||||
self.contact_constrain_velocity(&mut v);
|
||||
self.body.velocity=v;
|
||||
self.walk.state=WalkEnum::Invalid;
|
||||
},
|
||||
PhysicsInstruction::CollisionEnd(c) => {
|
||||
self.contacts.remove(&c);//remove contact before calling contact_constrain_acceleration
|
||||
let mut a=self.gravity;
|
||||
self.contact_constrain_acceleration(&mut a);
|
||||
self.body.acceleration=a;
|
||||
self.walk.state=WalkEnum::Invalid;
|
||||
//check ground
|
||||
match &c.face {
|
||||
AabbFace::Top => {
|
||||
self.grounded=false;
|
||||
},
|
||||
_ => (),
|
||||
}
|
||||
},
|
||||
PhysicsInstruction::SetControlDir(control_dir)=>{
|
||||
self.temp_control_dir=control_dir;
|
||||
self.walk.state=WalkEnum::Invalid;
|
||||
},
|
||||
PhysicsInstruction::StrafeTick => {
|
||||
//let control_dir=self.get_control_dir();//this should respect your mouse interpolation settings
|
||||
let d=self.body.velocity.dot(self.temp_control_dir);
|
||||
if d<self.mv {
|
||||
let mut v=self.body.velocity+(self.mv-d)*self.temp_control_dir;
|
||||
self.contact_constrain_velocity(&mut v);
|
||||
self.body.velocity=v;
|
||||
}
|
||||
}
|
||||
PhysicsInstruction::Jump => {
|
||||
self.grounded=false;//do I need this?
|
||||
let mut v=self.body.velocity+glam::Vec3::new(0.0,0.715588/2.0*100.0,0.0);
|
||||
self.contact_constrain_velocity(&mut v);
|
||||
self.body.velocity=v;
|
||||
self.walk.state=WalkEnum::Invalid;
|
||||
},
|
||||
PhysicsInstruction::ReachWalkTargetVelocity => {
|
||||
//precisely set velocity
|
||||
let mut a=glam::Vec3::ZERO;
|
||||
self.contact_constrain_acceleration(&mut a);
|
||||
self.body.acceleration=a;
|
||||
let mut v=self.walk.target_velocity;
|
||||
self.contact_constrain_velocity(&mut v);
|
||||
self.body.velocity=v;
|
||||
self.walk.state=WalkEnum::Reached;
|
||||
},
|
||||
PhysicsInstruction::RefreshWalkTarget => {
|
||||
//calculate acceleration yada yada
|
||||
if self.grounded{
|
||||
let mut v=self.walk.target_velocity;
|
||||
self.contact_constrain_velocity(&mut v);
|
||||
let mut target_diff=v-self.body.velocity;
|
||||
target_diff.y=0f32;
|
||||
if target_diff==glam::Vec3::ZERO{
|
||||
let mut a=glam::Vec3::ZERO;
|
||||
self.contact_constrain_acceleration(&mut a);
|
||||
self.body.acceleration=a;
|
||||
self.walk.state=WalkEnum::Reached;
|
||||
}else{
|
||||
let accel=self.walk_accel.min(self.gravity.length()*self.friction);
|
||||
let time_delta=target_diff.length()/accel;
|
||||
let mut a=target_diff/time_delta;
|
||||
self.contact_constrain_acceleration(&mut a);
|
||||
self.body.acceleration=a;
|
||||
self.walk.target_time=self.body.time+((time_delta as f64)*1_000_000_000f64) as TIME;
|
||||
self.walk.state=WalkEnum::Transient;
|
||||
}
|
||||
}
|
||||
},
|
||||
PhysicsInstruction::SetWalkTargetVelocity(v) => {
|
||||
self.walk.target_velocity=v;
|
||||
self.walk.state=WalkEnum::Invalid;
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
107
src/bvh.rs
107
src/bvh.rs
@ -1,107 +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
|
||||
#[derive(Default)]
|
||||
pub struct BvhNode{
|
||||
children:Vec<Self>,
|
||||
models:Vec<u32>,
|
||||
aabb:Aabb,
|
||||
}
|
||||
|
||||
impl BvhNode{
|
||||
pub fn the_tester<F:FnMut(u32)>(&self,aabb:&Aabb,f:&mut F){
|
||||
for &model in &self.models{
|
||||
f(model);
|
||||
}
|
||||
for child in &self.children{
|
||||
if aabb.intersects(&child.aabb){
|
||||
child.the_tester(aabb,f);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn generate_bvh(boxen:Vec<Aabb>)->BvhNode{
|
||||
generate_bvh_node(boxen.into_iter().enumerate().collect())
|
||||
}
|
||||
|
||||
fn generate_bvh_node(boxen:Vec<(usize,Aabb)>)->BvhNode{
|
||||
let n=boxen.len();
|
||||
if n<20{
|
||||
let mut aabb=Aabb::default();
|
||||
let models=boxen.into_iter().map(|b|{aabb.join(&b.1);b.0 as u32}).collect();
|
||||
BvhNode{
|
||||
children:Vec::new(),
|
||||
models,
|
||||
aabb,
|
||||
}
|
||||
}else{
|
||||
let mut octant=std::collections::HashMap::with_capacity(n);//this ids which octant the boxen is put in
|
||||
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(){
|
||||
let center=aabb.center();
|
||||
octant.insert(*i,0);
|
||||
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.partial_cmp(&tup1.1).unwrap());
|
||||
sort_y.sort_by(|tup0,tup1|tup0.1.partial_cmp(&tup1.1).unwrap());
|
||||
sort_z.sort_by(|tup0,tup1|tup0.1.partial_cmp(&tup1.1).unwrap());
|
||||
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;
|
||||
for (i,c) in sort_x{
|
||||
if median_x<c{
|
||||
octant.insert(i,octant[&i]+1<<0);
|
||||
}
|
||||
}
|
||||
for (i,c) in sort_y{
|
||||
if median_y<c{
|
||||
octant.insert(i,octant[&i]+1<<1);
|
||||
}
|
||||
}
|
||||
for (i,c) in sort_z{
|
||||
if median_z<c{
|
||||
octant.insert(i,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,aabb) in boxen.into_iter(){
|
||||
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((i,aabb));
|
||||
}
|
||||
let mut aabb=Aabb::default();
|
||||
let children=list_list.into_iter().map(|b|{
|
||||
let node=generate_bvh_node(b);
|
||||
aabb.join(&node.aabb);
|
||||
node
|
||||
}).collect();
|
||||
BvhNode{
|
||||
children,
|
||||
models:Vec::new(),
|
||||
aabb,
|
||||
}
|
||||
}
|
||||
}
|
@ -1,10 +1,12 @@
|
||||
use std::future::Future;
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
use std::str::FromStr;
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
use std::time::Instant;
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
use web_sys::{ImageBitmapRenderingContext, OffscreenCanvas};
|
||||
use winit::{
|
||||
event::{self, WindowEvent, DeviceEvent},
|
||||
event::{self, WindowEvent},
|
||||
event_loop::{ControlFlow, EventLoop},
|
||||
};
|
||||
|
||||
@ -51,9 +53,8 @@ pub trait Example: 'static + Sized {
|
||||
device: &wgpu::Device,
|
||||
queue: &wgpu::Queue,
|
||||
);
|
||||
fn update(&mut self, window: &winit::window::Window, device: &wgpu::Device, queue: &wgpu::Queue, event: WindowEvent);
|
||||
fn device_event(&mut self, window: &winit::window::Window, event: DeviceEvent);
|
||||
fn load_file(&mut self, path:std::path::PathBuf, device: &wgpu::Device, queue: &wgpu::Queue);
|
||||
fn update(&mut self, device: &wgpu::Device, queue: &wgpu::Queue, event: WindowEvent);
|
||||
fn move_mouse(&mut self, delta: (f64,f64));
|
||||
fn render(
|
||||
&mut self,
|
||||
view: &wgpu::TextureView,
|
||||
@ -359,7 +360,7 @@ fn start<E: Example>(
|
||||
WindowEvent::KeyboardInput {
|
||||
input:
|
||||
event::KeyboardInput {
|
||||
virtual_keycode: Some(event::VirtualKeyCode::Scroll),
|
||||
virtual_keycode: Some(event::VirtualKeyCode::R),
|
||||
state: event::ElementState::Pressed,
|
||||
..
|
||||
},
|
||||
@ -368,14 +369,17 @@ fn start<E: Example>(
|
||||
println!("{:#?}", instance.generate_report());
|
||||
}
|
||||
_ => {
|
||||
example.update(&window,&device,&queue,event);
|
||||
example.update(&device,&queue,event);
|
||||
}
|
||||
},
|
||||
event::Event::DeviceEvent {
|
||||
event,
|
||||
event:
|
||||
winit::event::DeviceEvent::MouseMotion {
|
||||
delta,
|
||||
},
|
||||
..
|
||||
} => {
|
||||
example.device_event(&window,event);
|
||||
example.move_mouse(delta);
|
||||
},
|
||||
event::Event::RedrawRequested(_) => {
|
||||
|
||||
|
@ -1,25 +1,23 @@
|
||||
use crate::integer::Time;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct TimedInstruction<I>{
|
||||
pub time:Time,
|
||||
pub instruction:I,
|
||||
pub struct TimedInstruction<I> {
|
||||
pub time: crate::body::TIME,
|
||||
pub instruction: I,
|
||||
}
|
||||
|
||||
pub trait InstructionEmitter<I>{
|
||||
fn next_instruction(&self,time_limit:Time)->Option<TimedInstruction<I>>;
|
||||
pub trait InstructionEmitter<I> {
|
||||
fn next_instruction(&self, time_limit:crate::body::TIME) -> Option<TimedInstruction<I>>;
|
||||
}
|
||||
pub trait InstructionConsumer<I>{
|
||||
pub trait InstructionConsumer<I> {
|
||||
fn process_instruction(&mut self, instruction:TimedInstruction<I>);
|
||||
}
|
||||
|
||||
//PROPER PRIVATE FIELDS!!!
|
||||
pub struct InstructionCollector<I>{
|
||||
time:Time,
|
||||
instruction:Option<I>,
|
||||
pub struct InstructionCollector<I> {
|
||||
time: crate::body::TIME,
|
||||
instruction: Option<I>,
|
||||
}
|
||||
impl<I> InstructionCollector<I>{
|
||||
pub fn new(time:Time)->Self{
|
||||
impl<I> InstructionCollector<I> {
|
||||
pub fn new(time:crate::body::TIME) -> Self {
|
||||
Self{
|
||||
time,
|
||||
instruction:None
|
||||
@ -27,24 +25,24 @@ impl<I> InstructionCollector<I>{
|
||||
}
|
||||
|
||||
pub fn collect(&mut self,instruction:Option<TimedInstruction<I>>){
|
||||
match instruction{
|
||||
Some(unwrap_instruction)=>{
|
||||
match instruction {
|
||||
Some(unwrap_instruction) => {
|
||||
if unwrap_instruction.time<self.time {
|
||||
self.time=unwrap_instruction.time;
|
||||
self.instruction=Some(unwrap_instruction.instruction);
|
||||
}
|
||||
},
|
||||
None=>(),
|
||||
None => (),
|
||||
}
|
||||
}
|
||||
pub fn instruction(self)->Option<TimedInstruction<I>>{
|
||||
pub fn instruction(self) -> Option<TimedInstruction<I>> {
|
||||
//STEAL INSTRUCTION AND DESTROY INSTRUCTIONCOLLECTOR
|
||||
match self.instruction{
|
||||
match self.instruction {
|
||||
Some(instruction)=>Some(TimedInstruction{
|
||||
time:self.time,
|
||||
instruction
|
||||
}),
|
||||
None=>None,
|
||||
None => None,
|
||||
}
|
||||
}
|
||||
}
|
914
src/integer.rs
914
src/integer.rs
@ -1,914 +0,0 @@
|
||||
//integer units
|
||||
#[derive(Clone,Copy,Hash,PartialEq,PartialOrd,Debug)]
|
||||
pub struct Time(i64);
|
||||
impl Time{
|
||||
pub const ZERO:Self=Self(0);
|
||||
pub const ONE_SECOND:Self=Self(1_000_000_000);
|
||||
pub const ONE_MILLISECOND:Self=Self(1_000_000);
|
||||
pub const ONE_MICROSECOND:Self=Self(1_000);
|
||||
pub const ONE_NANOSECOND:Self=Self(1);
|
||||
#[inline]
|
||||
pub fn from_secs(num:i64)->Self{
|
||||
Self(Self::ONE_SECOND.0*num)
|
||||
}
|
||||
#[inline]
|
||||
pub fn from_millis(num:i64)->Self{
|
||||
Self(Self::ONE_MILLISECOND.0*num)
|
||||
}
|
||||
#[inline]
|
||||
pub fn from_micros(num:i64)->Self{
|
||||
Self(Self::ONE_MICROSECOND.0*num)
|
||||
}
|
||||
#[inline]
|
||||
pub fn from_nanos(num:i64)->Self{
|
||||
Self(Self::ONE_NANOSECOND.0*num)
|
||||
}
|
||||
//should I have checked subtraction? force all time variables to be positive?
|
||||
#[inline]
|
||||
pub fn nanos(&self)->i64{
|
||||
self.0
|
||||
}
|
||||
}
|
||||
impl From<Planar64> for Time{
|
||||
#[inline]
|
||||
fn from(value:Planar64)->Self{
|
||||
Time((((value.0 as i128)*1_000_000_000)>>32) as i64)
|
||||
}
|
||||
}
|
||||
impl std::fmt::Display for Time{
|
||||
#[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 std::ops::Neg for Time{
|
||||
type Output=Time;
|
||||
#[inline]
|
||||
fn neg(self)->Self::Output {
|
||||
Time(-self.0)
|
||||
}
|
||||
}
|
||||
impl std::ops::Add<Time> for Time{
|
||||
type Output=Time;
|
||||
#[inline]
|
||||
fn add(self,rhs:Self)->Self::Output {
|
||||
Time(self.0+rhs.0)
|
||||
}
|
||||
}
|
||||
impl std::ops::Sub<Time> for Time{
|
||||
type Output=Time;
|
||||
#[inline]
|
||||
fn sub(self,rhs:Self)->Self::Output {
|
||||
Time(self.0-rhs.0)
|
||||
}
|
||||
}
|
||||
impl std::ops::Mul<Time> for Time{
|
||||
type Output=Time;
|
||||
#[inline]
|
||||
fn mul(self,rhs:Time)->Self::Output{
|
||||
Self((((self.0 as i128)*(rhs.0 as i128))/1_000_000_000) as i64)
|
||||
}
|
||||
}
|
||||
impl std::ops::Div<i64> for Time{
|
||||
type Output=Time;
|
||||
#[inline]
|
||||
fn div(self,rhs:i64)->Self::Output {
|
||||
Time(self.0/rhs)
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
const fn gcd(mut a:u64,mut b:u64)->u64{
|
||||
while b!=0{
|
||||
(a,b)=(b,a.rem_euclid(b));
|
||||
};
|
||||
a
|
||||
}
|
||||
#[derive(Clone,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 fn mul_int(&self,rhs:i64)->i64{
|
||||
rhs*self.num/self.den as i64
|
||||
}
|
||||
#[inline]
|
||||
pub fn rhs_div_int(&self,rhs:i64)->i64{
|
||||
rhs*self.den as i64/self.num
|
||||
}
|
||||
#[inline]
|
||||
pub 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=>Err(Self::Error::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=>Err(Self::Error::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,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 fn new(x:Ratio64,y:Ratio64)->Self{
|
||||
Self{x,y}
|
||||
}
|
||||
#[inline]
|
||||
pub 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{
|
||||
pub const FRAC_PI_2:Self=Self(1<<30);
|
||||
pub const PI:Self=Self(-1<<31);
|
||||
#[inline]
|
||||
pub 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 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=((
|
||||
u32::from_ne_bytes(theta_max.0.to_ne_bytes())
|
||||
.wrapping_sub(u32::from_ne_bytes(theta_min.0.to_ne_bytes()))
|
||||
/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(&self)->Unit32{
|
||||
//TODO: fix this rounding towards 0
|
||||
Unit32(unsafe{((self.0 as f64*ANGLE32_TO_FLOAT64_RADIANS).cos()*UNIT32_ONE_FLOAT64).to_int_unchecked()})
|
||||
}
|
||||
#[inline]
|
||||
pub fn sin(&self)->Unit32{
|
||||
//TODO: fix this rounding towards 0
|
||||
Unit32(unsafe{((self.0 as f64*ANGLE32_TO_FLOAT64_RADIANS).sin()*UNIT32_ONE_FLOAT64).to_int_unchecked()})
|
||||
}
|
||||
*/
|
||||
}
|
||||
const ANGLE32_TO_FLOAT64_RADIANS:f64=std::f64::consts::PI/((1i64<<31) as f64);
|
||||
impl Into<f32> for Angle32{
|
||||
#[inline]
|
||||
fn into(self)->f32{
|
||||
(self.0 as f64*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))
|
||||
}
|
||||
}
|
||||
|
||||
/* 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,
|
||||
)))
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
///[-1.0,1.0] = [-2^32,2^32]
|
||||
#[derive(Clone,Copy,Hash,Eq,Ord,PartialEq,PartialOrd)]
|
||||
pub struct Planar64(i64);
|
||||
impl Planar64{
|
||||
pub const ZERO:Self=Self(0);
|
||||
pub const ONE:Self=Self(1<<32);
|
||||
#[inline]
|
||||
pub const fn int(num:i32)->Self{
|
||||
Self(Self::ONE.0*num as i64)
|
||||
}
|
||||
#[inline]
|
||||
pub const fn raw(num:i64)->Self{
|
||||
Self(num)
|
||||
}
|
||||
#[inline]
|
||||
pub const fn get(&self)->i64{
|
||||
self.0
|
||||
}
|
||||
}
|
||||
const PLANAR64_FLOAT32_ONE:f32=(1u64<<32) as f32;
|
||||
const PLANAR64_FLOAT32_MUL:f32=1.0/PLANAR64_FLOAT32_ONE;
|
||||
const PLANAR64_FLOAT64_ONE:f64=(1u64<<32) as f64;
|
||||
impl Into<f32> for Planar64{
|
||||
#[inline]
|
||||
fn into(self)->f32{
|
||||
self.0 as f32*PLANAR64_FLOAT32_MUL
|
||||
}
|
||||
}
|
||||
impl From<Ratio64> for Planar64{
|
||||
#[inline]
|
||||
fn from(ratio:Ratio64)->Self{
|
||||
Self((((ratio.num as i128)<<32)/ratio.den as i128) as i64)
|
||||
}
|
||||
}
|
||||
#[derive(Debug)]
|
||||
pub enum Planar64TryFromFloatError{
|
||||
Nan,
|
||||
Infinite,
|
||||
Subnormal,
|
||||
HighlyNegativeExponent(i16),
|
||||
HighlyPositiveExponent(i16),
|
||||
}
|
||||
#[inline]
|
||||
fn planar64_from_mes((m,e,s):(u64,i16,i8))->Result<Planar64,Planar64TryFromFloatError>{
|
||||
let e32=e+32;
|
||||
if e32<0&&(m>>-e32)==0{//shifting m will underflow to 0
|
||||
Ok(Planar64::ZERO)
|
||||
// println!("m{} e{} s{}",m,e,s);
|
||||
// println!("f={}",(m as f64)*(2.0f64.powf(e as f64))*(s as f64));
|
||||
// Err(Planar64TryFromFloatError::HighlyNegativeExponent(e))
|
||||
}else if (64-m.leading_zeros() as i16)+e32<64{//shifting m will not overflow
|
||||
if e32<0{
|
||||
Ok(Planar64((m as i64)*(s as i64)>>-e32))
|
||||
}else{
|
||||
Ok(Planar64((m as i64)*(s as i64)<<e32))
|
||||
}
|
||||
}else{//if shifting m will overflow (prev check failed)
|
||||
Err(Planar64TryFromFloatError::HighlyPositiveExponent(e))
|
||||
}
|
||||
}
|
||||
impl TryFrom<f32> for Planar64{
|
||||
type Error=Planar64TryFromFloatError;
|
||||
#[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=>Err(Self::Error::Subnormal),
|
||||
std::num::FpCategory::Normal=>planar64_from_mes(integer_decode_f32(value)),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl TryFrom<f64> for Planar64{
|
||||
type Error=Planar64TryFromFloatError;
|
||||
#[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=>Err(Self::Error::Subnormal),
|
||||
std::num::FpCategory::Normal=>planar64_from_mes(integer_decode_f64(value)),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl std::fmt::Display for Planar64{
|
||||
fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{
|
||||
write!(f,"{:.3}",
|
||||
Into::<f32>::into(*self),
|
||||
)
|
||||
}
|
||||
}
|
||||
impl std::ops::Neg for Planar64{
|
||||
type Output=Planar64;
|
||||
#[inline]
|
||||
fn neg(self)->Self::Output{
|
||||
Planar64(-self.0)
|
||||
}
|
||||
}
|
||||
impl std::ops::Add<Planar64> for Planar64{
|
||||
type Output=Planar64;
|
||||
#[inline]
|
||||
fn add(self, rhs: Self) -> Self::Output {
|
||||
Planar64(self.0+rhs.0)
|
||||
}
|
||||
}
|
||||
impl std::ops::Sub<Planar64> for Planar64{
|
||||
type Output=Planar64;
|
||||
#[inline]
|
||||
fn sub(self, rhs: Self) -> Self::Output {
|
||||
Planar64(self.0-rhs.0)
|
||||
}
|
||||
}
|
||||
impl std::ops::Mul<i64> for Planar64{
|
||||
type Output=Planar64;
|
||||
#[inline]
|
||||
fn mul(self, rhs: i64) -> Self::Output {
|
||||
Planar64(self.0*rhs)
|
||||
}
|
||||
}
|
||||
impl std::ops::Mul<Planar64> for Planar64{
|
||||
type Output=Planar64;
|
||||
#[inline]
|
||||
fn mul(self, rhs: Self) -> Self::Output {
|
||||
Planar64((((self.0 as i128)*(rhs.0 as i128))>>32) as i64)
|
||||
}
|
||||
}
|
||||
impl std::ops::Div<i64> for Planar64{
|
||||
type Output=Planar64;
|
||||
#[inline]
|
||||
fn div(self, rhs: i64) -> Self::Output {
|
||||
Planar64(self.0/rhs)
|
||||
}
|
||||
}
|
||||
impl std::ops::Div<Planar64> for Planar64{
|
||||
type Output=Planar64;
|
||||
#[inline]
|
||||
fn div(self, rhs: Planar64) -> Self::Output {
|
||||
Planar64((((self.0 as i128)<<32)/rhs.0 as i128) as i64)
|
||||
}
|
||||
}
|
||||
// impl PartialOrd<i64> for Planar64{
|
||||
// fn partial_cmp(&self, other: &i64) -> Option<std::cmp::Ordering> {
|
||||
// self.0.partial_cmp(other)
|
||||
// }
|
||||
// }
|
||||
|
||||
|
||||
///[-1.0,1.0] = [-2^32,2^32]
|
||||
#[derive(Clone,Copy,Default,Hash,Eq,PartialEq)]
|
||||
pub struct Planar64Vec3(glam::I64Vec3);
|
||||
impl Planar64Vec3{
|
||||
pub const ZERO:Self=Planar64Vec3(glam::I64Vec3::ZERO);
|
||||
pub const ONE:Self=Self::int(1,1,1);
|
||||
pub const X:Self=Self::int(1,0,0);
|
||||
pub const Y:Self=Self::int(0,1,0);
|
||||
pub const Z:Self=Self::int(0,0,1);
|
||||
pub const NEG_X:Self=Self::int(-1,0,0);
|
||||
pub const NEG_Y:Self=Self::int(0,-1,0);
|
||||
pub const NEG_Z:Self=Self::int(0,0,-1);
|
||||
pub const MIN:Self=Planar64Vec3(glam::I64Vec3::MIN);
|
||||
pub const MAX:Self=Planar64Vec3(glam::I64Vec3::MAX);
|
||||
#[inline]
|
||||
pub const fn int(x:i32,y:i32,z:i32)->Self{
|
||||
Self(glam::i64vec3((x as i64)<<32,(y as i64)<<32,(z as i64)<<32))
|
||||
}
|
||||
#[inline]
|
||||
pub fn x(&self)->Planar64{
|
||||
Planar64(self.0.x)
|
||||
}
|
||||
#[inline]
|
||||
pub fn y(&self)->Planar64{
|
||||
Planar64(self.0.y)
|
||||
}
|
||||
#[inline]
|
||||
pub fn z(&self)->Planar64{
|
||||
Planar64(self.0.z)
|
||||
}
|
||||
#[inline]
|
||||
pub fn min(&self,rhs:Self)->Self{
|
||||
Self(glam::i64vec3(
|
||||
self.0.x.min(rhs.0.x),
|
||||
self.0.y.min(rhs.0.y),
|
||||
self.0.z.min(rhs.0.z),
|
||||
))
|
||||
}
|
||||
#[inline]
|
||||
pub fn max(&self,rhs:Self)->Self{
|
||||
Self(glam::i64vec3(
|
||||
self.0.x.max(rhs.0.x),
|
||||
self.0.y.max(rhs.0.y),
|
||||
self.0.z.max(rhs.0.z),
|
||||
))
|
||||
}
|
||||
#[inline]
|
||||
pub fn midpoint(&self,rhs:Self)->Self{
|
||||
Self((self.0+rhs.0)/2)
|
||||
}
|
||||
#[inline]
|
||||
pub fn cmplt(&self,rhs:Self)->glam::BVec3{
|
||||
self.0.cmplt(rhs.0)
|
||||
}
|
||||
#[inline]
|
||||
pub fn dot(&self,rhs:Self)->Planar64{
|
||||
Planar64(((
|
||||
(self.0.x as i128)*(rhs.0.x as i128)+
|
||||
(self.0.y as i128)*(rhs.0.y as i128)+
|
||||
(self.0.z as i128)*(rhs.0.z as i128)
|
||||
)>>32) as i64)
|
||||
}
|
||||
#[inline]
|
||||
pub fn length(&self)->Planar64{
|
||||
let radicand=(self.0.x as i128)*(self.0.x as i128)+(self.0.y as i128)*(self.0.y as i128)+(self.0.z as i128)*(self.0.z as i128);
|
||||
Planar64(unsafe{(radicand as f64).sqrt().to_int_unchecked()})
|
||||
}
|
||||
#[inline]
|
||||
pub fn with_length(&self,length:Planar64)->Self{
|
||||
let radicand=(self.0.x as i128)*(self.0.x as i128)+(self.0.y as i128)*(self.0.y as i128)+(self.0.z as i128)*(self.0.z as i128);
|
||||
let self_length:i128=unsafe{(radicand as f64).sqrt().to_int_unchecked()};
|
||||
//self.0*length/self_length
|
||||
Planar64Vec3(
|
||||
glam::i64vec3(
|
||||
((self.0.x as i128)*(length.0 as i128)/self_length) as i64,
|
||||
((self.0.y as i128)*(length.0 as i128)/self_length) as i64,
|
||||
((self.0.z as i128)*(length.0 as i128)/self_length) as i64,
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
impl Into<glam::Vec3> for Planar64Vec3{
|
||||
#[inline]
|
||||
fn into(self)->glam::Vec3{
|
||||
glam::vec3(
|
||||
self.0.x as f32,
|
||||
self.0.y as f32,
|
||||
self.0.z as f32,
|
||||
)*PLANAR64_FLOAT32_MUL
|
||||
}
|
||||
}
|
||||
impl TryFrom<[f32;3]> for Planar64Vec3{
|
||||
type Error=Planar64TryFromFloatError;
|
||||
#[inline]
|
||||
fn try_from(value:[f32;3])->Result<Self,Self::Error>{
|
||||
Ok(Self(glam::i64vec3(
|
||||
Planar64::try_from(value[0])?.0,
|
||||
Planar64::try_from(value[1])?.0,
|
||||
Planar64::try_from(value[2])?.0,
|
||||
)))
|
||||
}
|
||||
}
|
||||
impl TryFrom<glam::Vec3A> for Planar64Vec3{
|
||||
type Error=Planar64TryFromFloatError;
|
||||
#[inline]
|
||||
fn try_from(value:glam::Vec3A)->Result<Self,Self::Error>{
|
||||
Ok(Self(glam::i64vec3(
|
||||
Planar64::try_from(value.x)?.0,
|
||||
Planar64::try_from(value.y)?.0,
|
||||
Planar64::try_from(value.z)?.0,
|
||||
)))
|
||||
}
|
||||
}
|
||||
impl std::fmt::Display for Planar64Vec3{
|
||||
fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{
|
||||
write!(f,"{:.3},{:.3},{:.3}",
|
||||
Into::<f32>::into(self.x()),Into::<f32>::into(self.y()),Into::<f32>::into(self.z()),
|
||||
)
|
||||
}
|
||||
}
|
||||
impl std::ops::Neg for Planar64Vec3{
|
||||
type Output=Planar64Vec3;
|
||||
#[inline]
|
||||
fn neg(self)->Self::Output{
|
||||
Planar64Vec3(-self.0)
|
||||
}
|
||||
}
|
||||
impl std::ops::Add<Planar64Vec3> for Planar64Vec3{
|
||||
type Output=Planar64Vec3;
|
||||
#[inline]
|
||||
fn add(self,rhs:Planar64Vec3) -> Self::Output {
|
||||
Planar64Vec3(self.0+rhs.0)
|
||||
}
|
||||
}
|
||||
impl std::ops::AddAssign<Planar64Vec3> for Planar64Vec3{
|
||||
#[inline]
|
||||
fn add_assign(&mut self,rhs:Planar64Vec3){
|
||||
*self=*self+rhs
|
||||
}
|
||||
}
|
||||
impl std::ops::Sub<Planar64Vec3> for Planar64Vec3{
|
||||
type Output=Planar64Vec3;
|
||||
#[inline]
|
||||
fn sub(self,rhs:Planar64Vec3) -> Self::Output {
|
||||
Planar64Vec3(self.0-rhs.0)
|
||||
}
|
||||
}
|
||||
impl std::ops::SubAssign<Planar64Vec3> for Planar64Vec3{
|
||||
#[inline]
|
||||
fn sub_assign(&mut self,rhs:Planar64Vec3){
|
||||
*self=*self-rhs
|
||||
}
|
||||
}
|
||||
impl std::ops::Mul<Planar64Vec3> for Planar64Vec3{
|
||||
type Output=Planar64Vec3;
|
||||
#[inline]
|
||||
fn mul(self, rhs: Planar64Vec3) -> Self::Output {
|
||||
Planar64Vec3(glam::i64vec3(
|
||||
(((self.0.x as i128)*(rhs.0.x as i128))>>32) as i64,
|
||||
(((self.0.y as i128)*(rhs.0.y as i128))>>32) as i64,
|
||||
(((self.0.z as i128)*(rhs.0.z as i128))>>32) as i64
|
||||
))
|
||||
}
|
||||
}
|
||||
impl std::ops::Mul<Planar64> for Planar64Vec3{
|
||||
type Output=Planar64Vec3;
|
||||
#[inline]
|
||||
fn mul(self, rhs: Planar64) -> Self::Output {
|
||||
Planar64Vec3(glam::i64vec3(
|
||||
(((self.0.x as i128)*(rhs.0 as i128))>>32) as i64,
|
||||
(((self.0.y as i128)*(rhs.0 as i128))>>32) as i64,
|
||||
(((self.0.z as i128)*(rhs.0 as i128))>>32) as i64
|
||||
))
|
||||
}
|
||||
}
|
||||
impl std::ops::Mul<i64> for Planar64Vec3{
|
||||
type Output=Planar64Vec3;
|
||||
#[inline]
|
||||
fn mul(self,rhs:i64)->Self::Output {
|
||||
Planar64Vec3(glam::i64vec3(
|
||||
self.0.x*rhs,
|
||||
self.0.y*rhs,
|
||||
self.0.z*rhs
|
||||
))
|
||||
}
|
||||
}
|
||||
impl std::ops::Mul<Time> for Planar64Vec3{
|
||||
type Output=Planar64Vec3;
|
||||
#[inline]
|
||||
fn mul(self,rhs:Time)->Self::Output{
|
||||
Planar64Vec3(glam::i64vec3(
|
||||
(((self.0.x as i128)*(rhs.0 as i128))/1_000_000_000) as i64,
|
||||
(((self.0.y as i128)*(rhs.0 as i128))/1_000_000_000) as i64,
|
||||
(((self.0.z as i128)*(rhs.0 as i128))/1_000_000_000) as i64
|
||||
))
|
||||
}
|
||||
}
|
||||
impl std::ops::Div<i64> for Planar64Vec3{
|
||||
type Output=Planar64Vec3;
|
||||
#[inline]
|
||||
fn div(self,rhs:i64)->Self::Output{
|
||||
Planar64Vec3(glam::i64vec3(
|
||||
self.0.x/rhs,
|
||||
self.0.y/rhs,
|
||||
self.0.z/rhs,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
///[-1.0,1.0] = [-2^32,2^32]
|
||||
#[derive(Clone,Copy)]
|
||||
pub struct Planar64Mat3{
|
||||
x_axis:Planar64Vec3,
|
||||
y_axis:Planar64Vec3,
|
||||
z_axis:Planar64Vec3,
|
||||
}
|
||||
impl Default for Planar64Mat3{
|
||||
#[inline]
|
||||
fn default() -> Self {
|
||||
Self{
|
||||
x_axis:Planar64Vec3::X,
|
||||
y_axis:Planar64Vec3::Y,
|
||||
z_axis:Planar64Vec3::Z,
|
||||
}
|
||||
}
|
||||
}
|
||||
impl std::ops::Mul<Planar64Vec3> for Planar64Mat3{
|
||||
type Output=Planar64Vec3;
|
||||
#[inline]
|
||||
fn mul(self,rhs:Planar64Vec3) -> Self::Output {
|
||||
self.x_axis*rhs.x()
|
||||
+self.y_axis*rhs.y()
|
||||
+self.z_axis*rhs.z()
|
||||
}
|
||||
}
|
||||
|
||||
impl Planar64Mat3{
|
||||
#[inline]
|
||||
pub fn from_cols(x_axis:Planar64Vec3,y_axis:Planar64Vec3,z_axis:Planar64Vec3)->Self{
|
||||
Self{
|
||||
x_axis,
|
||||
y_axis,
|
||||
z_axis,
|
||||
}
|
||||
}
|
||||
pub const fn int_from_cols_array(array:[i32;9])->Self{
|
||||
Self{
|
||||
x_axis:Planar64Vec3::int(array[0],array[1],array[2]),
|
||||
y_axis:Planar64Vec3::int(array[3],array[4],array[5]),
|
||||
z_axis:Planar64Vec3::int(array[6],array[7],array[8]),
|
||||
}
|
||||
}
|
||||
#[inline]
|
||||
pub fn from_rotation_y(angle:Angle32)->Self{
|
||||
let theta=angle.0 as f64*ANGLE32_TO_FLOAT64_RADIANS;
|
||||
let (s,c)=theta.sin_cos();
|
||||
let (c,s)=(c*PLANAR64_FLOAT64_ONE,s*PLANAR64_FLOAT64_ONE);
|
||||
//TODO: fix this rounding towards 0
|
||||
let (c,s):(i64,i64)=(unsafe{c.to_int_unchecked()},unsafe{s.to_int_unchecked()});
|
||||
Self::from_cols(
|
||||
Planar64Vec3(glam::i64vec3(c,0,-s)),
|
||||
Planar64Vec3::Y,
|
||||
Planar64Vec3(glam::i64vec3(s,0,c)),
|
||||
)
|
||||
}
|
||||
}
|
||||
impl Into<glam::Mat3> for Planar64Mat3{
|
||||
#[inline]
|
||||
fn into(self)->glam::Mat3{
|
||||
glam::Mat3::from_cols(
|
||||
self.x_axis.into(),
|
||||
self.y_axis.into(),
|
||||
self.z_axis.into(),
|
||||
)
|
||||
}
|
||||
}
|
||||
impl TryFrom<glam::Mat3A> for Planar64Mat3{
|
||||
type Error=Planar64TryFromFloatError;
|
||||
#[inline]
|
||||
fn try_from(value:glam::Mat3A)->Result<Self,Self::Error>{
|
||||
Ok(Self{
|
||||
x_axis:Planar64Vec3::try_from(value.x_axis)?,
|
||||
y_axis:Planar64Vec3::try_from(value.y_axis)?,
|
||||
z_axis:Planar64Vec3::try_from(value.z_axis)?,
|
||||
})
|
||||
}
|
||||
}
|
||||
impl std::fmt::Display for Planar64Mat3{
|
||||
fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{
|
||||
write!(f,"\n{:.3},{:.3},{:.3}\n{:.3},{:.3},{:.3}\n{:.3},{:.3},{:.3}",
|
||||
Into::<f32>::into(self.x_axis.x()),Into::<f32>::into(self.x_axis.y()),Into::<f32>::into(self.x_axis.z()),
|
||||
Into::<f32>::into(self.y_axis.x()),Into::<f32>::into(self.y_axis.y()),Into::<f32>::into(self.y_axis.z()),
|
||||
Into::<f32>::into(self.z_axis.x()),Into::<f32>::into(self.z_axis.y()),Into::<f32>::into(self.z_axis.z()),
|
||||
)
|
||||
}
|
||||
}
|
||||
impl std::ops::Div<i64> for Planar64Mat3{
|
||||
type Output=Planar64Mat3;
|
||||
#[inline]
|
||||
fn div(self,rhs:i64)->Self::Output{
|
||||
Planar64Mat3{
|
||||
x_axis:self.x_axis/rhs,
|
||||
y_axis:self.y_axis/rhs,
|
||||
z_axis:self.z_axis/rhs,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
///[-1.0,1.0] = [-2^32,2^32]
|
||||
#[derive(Clone,Copy,Default)]
|
||||
pub struct Planar64Affine3{
|
||||
pub matrix3:Planar64Mat3,//includes scale above 1
|
||||
pub translation:Planar64Vec3,
|
||||
}
|
||||
|
||||
impl Planar64Affine3{
|
||||
#[inline]
|
||||
pub fn new(matrix3:Planar64Mat3,translation:Planar64Vec3)->Self{
|
||||
Self{matrix3,translation}
|
||||
}
|
||||
#[inline]
|
||||
pub fn transform_point3(&self,point:Planar64Vec3) -> Planar64Vec3{
|
||||
Planar64Vec3(
|
||||
self.translation.0
|
||||
+(self.matrix3.x_axis*point.x()).0
|
||||
+(self.matrix3.y_axis*point.y()).0
|
||||
+(self.matrix3.z_axis*point.z()).0
|
||||
)
|
||||
}
|
||||
}
|
||||
impl Into<glam::Mat4> for Planar64Affine3{
|
||||
#[inline]
|
||||
fn into(self)->glam::Mat4{
|
||||
glam::Mat4::from_cols_array(&[
|
||||
self.matrix3.x_axis.0.x as f32,self.matrix3.x_axis.0.y as f32,self.matrix3.x_axis.0.z as f32,0.0,
|
||||
self.matrix3.y_axis.0.x as f32,self.matrix3.y_axis.0.y as f32,self.matrix3.y_axis.0.z as f32,0.0,
|
||||
self.matrix3.z_axis.0.x as f32,self.matrix3.z_axis.0.y as f32,self.matrix3.z_axis.0.z as f32,0.0,
|
||||
self.translation.0.x as f32,self.translation.0.y as f32,self.translation.0.z as f32,PLANAR64_FLOAT32_ONE
|
||||
])*PLANAR64_FLOAT32_MUL
|
||||
}
|
||||
}
|
||||
impl TryFrom<glam::Affine3A> for Planar64Affine3{
|
||||
type Error=Planar64TryFromFloatError;
|
||||
fn try_from(value: glam::Affine3A)->Result<Self, Self::Error> {
|
||||
Ok(Self{
|
||||
matrix3:Planar64Mat3::try_from(value.matrix3)?,
|
||||
translation:Planar64Vec3::try_from(value.translation)?
|
||||
})
|
||||
}
|
||||
}
|
||||
impl std::fmt::Display for Planar64Affine3{
|
||||
fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{
|
||||
write!(f,"translation: {:.3},{:.3},{:.3}\nmatrix3:\n{:.3},{:.3},{:.3}\n{:.3},{:.3},{:.3}\n{:.3},{:.3},{:.3}",
|
||||
Into::<f32>::into(self.translation.x()),Into::<f32>::into(self.translation.y()),Into::<f32>::into(self.translation.z()),
|
||||
Into::<f32>::into(self.matrix3.x_axis.x()),Into::<f32>::into(self.matrix3.x_axis.y()),Into::<f32>::into(self.matrix3.x_axis.z()),
|
||||
Into::<f32>::into(self.matrix3.y_axis.x()),Into::<f32>::into(self.matrix3.y_axis.y()),Into::<f32>::into(self.matrix3.y_axis.z()),
|
||||
Into::<f32>::into(self.matrix3.z_axis.x()),Into::<f32>::into(self.matrix3.z_axis.y()),Into::<f32>::into(self.matrix3.z_axis.z()),
|
||||
)
|
||||
}
|
||||
}
|
@ -1,5 +1,8 @@
|
||||
use std::todo;
|
||||
|
||||
use crate::model::{ModelData,ModelInstance};
|
||||
|
||||
use crate::primitives;
|
||||
use crate::integer::{Planar64,Planar64Vec3,Planar64Mat3,Planar64Affine3};
|
||||
|
||||
fn class_is_a(class: &str, superclass: &str) -> bool {
|
||||
if class==superclass {
|
||||
@ -31,450 +34,107 @@ fn get_texture_refs(dom:&rbx_dom_weak::WeakDom) -> Vec<rbx_dom_weak::types::Ref>
|
||||
//next class
|
||||
objects
|
||||
}
|
||||
fn planar64_affine3_from_roblox(cf:&rbx_dom_weak::types::CFrame,size:&rbx_dom_weak::types::Vector3)->Planar64Affine3{
|
||||
Planar64Affine3::new(
|
||||
Planar64Mat3::from_cols(
|
||||
Planar64Vec3::try_from([cf.orientation.x.x,cf.orientation.y.x,cf.orientation.z.x]).unwrap()
|
||||
*Planar64::try_from(size.x/2.0).unwrap(),
|
||||
Planar64Vec3::try_from([cf.orientation.x.y,cf.orientation.y.y,cf.orientation.z.y]).unwrap()
|
||||
*Planar64::try_from(size.y/2.0).unwrap(),
|
||||
Planar64Vec3::try_from([cf.orientation.x.z,cf.orientation.y.z,cf.orientation.z.z]).unwrap()
|
||||
*Planar64::try_from(size.z/2.0).unwrap(),
|
||||
),
|
||||
Planar64Vec3::try_from([cf.position.x,cf.position.y,cf.position.z]).unwrap()
|
||||
)
|
||||
}
|
||||
fn get_attributes(name:&str,can_collide:bool,velocity:Planar64Vec3,force_intersecting:bool)->crate::model::CollisionAttributes{
|
||||
let mut general=crate::model::GameMechanicAttributes::default();
|
||||
let mut intersecting=crate::model::IntersectingAttributes::default();
|
||||
let mut contacting=crate::model::ContactingAttributes::default();
|
||||
let mut force_can_collide=can_collide;
|
||||
match name{
|
||||
//"Water"=>intersecting.water=Some(crate::model::IntersectingWater{density:1.0,drag:1.0}),
|
||||
"Accelerator"=>{force_can_collide=false;intersecting.accelerator=Some(crate::model::IntersectingAccelerator{acceleration:velocity})},
|
||||
"MapFinish"=>{force_can_collide=false;general.zone=Some(crate::model::GameMechanicZone{mode_id:0,behaviour:crate::model::ZoneBehaviour::Finish})},
|
||||
"MapAnticheat"=>{force_can_collide=false;general.zone=Some(crate::model::GameMechanicZone{mode_id:0,behaviour:crate::model::ZoneBehaviour::Anitcheat})},
|
||||
"Platform"=>general.stage_element=Some(crate::model::GameMechanicStageElement{
|
||||
mode_id:0,
|
||||
stage_id:0,
|
||||
force:false,
|
||||
behaviour:crate::model::StageElementBehaviour::Platform,
|
||||
}),
|
||||
other=>{
|
||||
if let Some(captures)=lazy_regex::regex!(r"^(Force)?(Spawn|SpawnAt|Trigger|Teleport|Platform)(\d+)$")
|
||||
.captures(other){
|
||||
general.stage_element=Some(crate::model::GameMechanicStageElement{
|
||||
mode_id:0,
|
||||
stage_id:captures[3].parse::<u32>().unwrap(),
|
||||
force:match captures.get(1){
|
||||
Some(m)=>m.as_str()=="Force",
|
||||
None=>false,
|
||||
},
|
||||
behaviour:match &captures[2]{
|
||||
"Spawn"|"SpawnAt"=>crate::model::StageElementBehaviour::SpawnAt,
|
||||
"Trigger"=>{force_can_collide=false;crate::model::StageElementBehaviour::Trigger},
|
||||
"Teleport"=>{force_can_collide=false;crate::model::StageElementBehaviour::Teleport},
|
||||
"Platform"=>crate::model::StageElementBehaviour::Platform,
|
||||
_=>panic!("regex1[2] messed up bad"),
|
||||
}
|
||||
})
|
||||
}else if let Some(captures)=lazy_regex::regex!(r"^Bonus(Finish|Anticheat)(\d+)$")
|
||||
.captures(other){
|
||||
force_can_collide=false;
|
||||
match &captures[1]{
|
||||
"Finish"=>general.zone=Some(crate::model::GameMechanicZone{mode_id:captures[2].parse::<u32>().unwrap(),behaviour:crate::model::ZoneBehaviour::Finish}),
|
||||
"Anticheat"=>general.zone=Some(crate::model::GameMechanicZone{mode_id:captures[2].parse::<u32>().unwrap(),behaviour:crate::model::ZoneBehaviour::Anitcheat}),
|
||||
_=>panic!("regex2[1] messed up bad"),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
//need some way to skip this
|
||||
if velocity!=Planar64Vec3::ZERO{
|
||||
general.booster=Some(crate::model::GameMechanicBooster{velocity});
|
||||
}
|
||||
match force_can_collide{
|
||||
true=>{
|
||||
match name{
|
||||
//"Bounce"=>(),
|
||||
"Surf"=>contacting.surf=Some(crate::model::ContactingSurf{}),
|
||||
"Ladder"=>contacting.ladder=Some(crate::model::ContactingLadder{sticky:true}),
|
||||
other=>{
|
||||
//REGEX!!!!
|
||||
//Jump#
|
||||
//WormholeIn#
|
||||
}
|
||||
}
|
||||
crate::model::CollisionAttributes::Contact{contacting,general}
|
||||
},
|
||||
false=>if force_intersecting
|
||||
||general.jump_limit.is_some()
|
||||
||general.booster.is_some()
|
||||
||general.zone.is_some()
|
||||
||general.stage_element.is_some()
|
||||
||general.wormhole.is_some()
|
||||
||intersecting.water.is_some()
|
||||
||intersecting.accelerator.is_some()
|
||||
{
|
||||
crate::model::CollisionAttributes::Intersect{intersecting,general}
|
||||
}else{
|
||||
crate::model::CollisionAttributes::Decoration
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
struct RobloxAssetId(u64);
|
||||
struct RobloxAssetIdParseErr;
|
||||
impl std::str::FromStr for RobloxAssetId {
|
||||
type Err=RobloxAssetIdParseErr;
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err>{
|
||||
let regman=lazy_regex::regex!(r"(\d+)$");
|
||||
if let Some(captures) = regman.captures(s) {
|
||||
if captures.len()==2{//captures[0] is all captures concatenated, and then each individual capture
|
||||
if let Ok(id) = captures[0].parse::<u64>() {
|
||||
return Ok(Self(id));
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(RobloxAssetIdParseErr)
|
||||
}
|
||||
}
|
||||
#[derive(Clone,Copy,PartialEq)]
|
||||
struct RobloxTextureTransform{
|
||||
offset_u:f32,
|
||||
offset_v:f32,
|
||||
scale_u:f32,
|
||||
scale_v:f32,
|
||||
}
|
||||
impl std::cmp::Eq for RobloxTextureTransform{}//????
|
||||
impl std::default::Default for RobloxTextureTransform{
|
||||
fn default() -> Self {
|
||||
Self{offset_u:0.0,offset_v:0.0,scale_u:1.0,scale_v:1.0}
|
||||
}
|
||||
}
|
||||
impl std::hash::Hash for RobloxTextureTransform {
|
||||
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
||||
self.offset_u.to_ne_bytes().hash(state);
|
||||
self.offset_v.to_ne_bytes().hash(state);
|
||||
self.scale_u.to_ne_bytes().hash(state);
|
||||
self.scale_v.to_ne_bytes().hash(state);
|
||||
}
|
||||
}
|
||||
#[derive(Clone,PartialEq)]
|
||||
struct RobloxFaceTextureDescription{
|
||||
texture:u32,
|
||||
color:glam::Vec4,
|
||||
transform:RobloxTextureTransform,
|
||||
}
|
||||
impl std::cmp::Eq for RobloxFaceTextureDescription{}//????
|
||||
impl std::hash::Hash for RobloxFaceTextureDescription {
|
||||
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
||||
self.texture.hash(state);
|
||||
self.transform.hash(state);
|
||||
for &el in self.color.as_ref().iter() {
|
||||
el.to_ne_bytes().hash(state);
|
||||
type Err=RobloxAssetIdParseErr;
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err>{
|
||||
let regman=regex::Regex::new(r"(\d+)$").unwrap();
|
||||
if let Some(captures) = regman.captures(s) {
|
||||
if captures.len()==2{//captures[0] is all captures concatenated, and then each individual capture
|
||||
if let Ok(id) = captures[0].parse::<u64>() {
|
||||
return Ok(Self(id));
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(RobloxAssetIdParseErr)
|
||||
}
|
||||
}
|
||||
impl RobloxFaceTextureDescription{
|
||||
fn to_face_description(&self)->primitives::FaceDescription{
|
||||
primitives::FaceDescription{
|
||||
texture:Some(self.texture),
|
||||
transform:glam::Affine2::from_translation(
|
||||
glam::vec2(self.transform.offset_u,self.transform.offset_v)
|
||||
)
|
||||
*glam::Affine2::from_scale(
|
||||
glam::vec2(self.transform.scale_u,self.transform.scale_v)
|
||||
),
|
||||
color:self.color,
|
||||
}
|
||||
}
|
||||
}
|
||||
type RobloxPartDescription=[Option<RobloxFaceTextureDescription>;6];
|
||||
type RobloxWedgeDescription=[Option<RobloxFaceTextureDescription>;5];
|
||||
type RobloxCornerWedgeDescription=[Option<RobloxFaceTextureDescription>;5];
|
||||
#[derive(Clone,Eq,Hash,PartialEq)]
|
||||
enum RobloxBasePartDescription{
|
||||
Sphere,
|
||||
Part(RobloxPartDescription),
|
||||
Cylinder,
|
||||
Wedge(RobloxWedgeDescription),
|
||||
CornerWedge(RobloxCornerWedgeDescription),
|
||||
}
|
||||
pub fn generate_indexed_models(dom:rbx_dom_weak::WeakDom) -> crate::model::IndexedModelInstances{
|
||||
//IndexedModelInstances includes textures
|
||||
let mut spawn_point=Planar64Vec3::ZERO;
|
||||
pub fn generate_modeldatas_roblox(dom:rbx_dom_weak::WeakDom) -> Result<(Vec<ModelData>,Vec<String>,glam::Vec3), Box<dyn std::error::Error>>{
|
||||
//ModelData includes texture dds
|
||||
let mut spawn_point=glam::Vec3::ZERO;
|
||||
|
||||
let mut indexed_models=Vec::new();
|
||||
let mut model_id_from_description=std::collections::HashMap::<RobloxBasePartDescription,usize>::new();
|
||||
//TODO: generate unit Block, Wedge, etc. after based on part shape lists
|
||||
let mut modeldatas=crate::model::generate_modeldatas(primitives::the_unit_cube_lol(),ModelData::COLOR_FLOATS_WHITE);
|
||||
let unit_cube_modeldata=modeldatas[0].clone();
|
||||
|
||||
let mut texture_id_from_asset_id=std::collections::HashMap::<u64,u32>::new();
|
||||
let mut asset_id_from_texture_id=Vec::new();
|
||||
|
||||
let mut object_refs=Vec::new();
|
||||
let mut temp_objects=Vec::new();
|
||||
let mut object_refs = std::vec::Vec::new();
|
||||
let mut temp_objects = std::vec::Vec::new();
|
||||
recursive_collect_superclass(&mut object_refs, &dom, dom.root(),"BasePart");
|
||||
for object_ref in object_refs {
|
||||
if let Some(object)=dom.get_by_ref(object_ref){
|
||||
if let (
|
||||
Some(rbx_dom_weak::types::Variant::CFrame(cf)),
|
||||
Some(rbx_dom_weak::types::Variant::Vector3(size)),
|
||||
Some(rbx_dom_weak::types::Variant::Vector3(velocity)),
|
||||
Some(rbx_dom_weak::types::Variant::Float32(transparency)),
|
||||
Some(rbx_dom_weak::types::Variant::Color3uint8(color3)),
|
||||
Some(rbx_dom_weak::types::Variant::Bool(can_collide)),
|
||||
Some(rbx_dom_weak::types::Variant::Enum(shape)),
|
||||
) = (
|
||||
object.properties.get("CFrame"),
|
||||
object.properties.get("Size"),
|
||||
object.properties.get("Velocity"),
|
||||
object.properties.get("Transparency"),
|
||||
object.properties.get("Color"),
|
||||
object.properties.get("CanCollide"),
|
||||
object.properties.get("Shape"),//this will also skip unions
|
||||
)
|
||||
{
|
||||
let model_transform=planar64_affine3_from_roblox(cf,size);
|
||||
|
||||
//push TempIndexedAttributes
|
||||
let mut force_intersecting=false;
|
||||
let mut temp_indexing_attributes=Vec::new();
|
||||
if let Some(attr)=match &object.name[..]{
|
||||
"MapStart"=>{
|
||||
spawn_point=model_transform.transform_point3(Planar64Vec3::ZERO)+Planar64Vec3::Y*5/2;
|
||||
Some(crate::model::TempIndexedAttributes::Start{mode_id:0})
|
||||
},
|
||||
"UnorderedCheckpoint"=>Some(crate::model::TempIndexedAttributes::UnorderedCheckpoint{mode_id:0}),
|
||||
other=>{
|
||||
let regman=lazy_regex::regex!(r"^(BonusStart|Spawn|ForceSpawn|OrderedCheckpoint)(\d+)$");
|
||||
if let Some(captures) = regman.captures(other) {
|
||||
match &captures[1]{
|
||||
"BonusStart"=>Some(crate::model::TempIndexedAttributes::Start{mode_id:captures[2].parse::<u32>().unwrap()}),
|
||||
"Spawn"|"ForceSpawn"=>Some(crate::model::TempIndexedAttributes::Spawn{mode_id:0,stage_id:captures[2].parse::<u32>().unwrap()}),
|
||||
"OrderedCheckpoint"=>Some(crate::model::TempIndexedAttributes::OrderedCheckpoint{mode_id:0,checkpoint_id:captures[2].parse::<u32>().unwrap()}),
|
||||
_=>None,
|
||||
}
|
||||
}else{
|
||||
None
|
||||
}
|
||||
}
|
||||
}{
|
||||
force_intersecting=true;
|
||||
temp_indexing_attributes.push(attr);
|
||||
let model_instance=ModelInstance {
|
||||
transform:glam::Mat4::from_translation(
|
||||
glam::Vec3::new(cf.position.x,cf.position.y,cf.position.z)
|
||||
)
|
||||
* glam::Mat4::from_mat3(
|
||||
glam::Mat3::from_cols(
|
||||
glam::Vec3::new(cf.orientation.x.x,cf.orientation.y.x,cf.orientation.z.x),
|
||||
glam::Vec3::new(cf.orientation.x.y,cf.orientation.y.y,cf.orientation.z.y),
|
||||
glam::Vec3::new(cf.orientation.x.z,cf.orientation.y.z,cf.orientation.z.z),
|
||||
),
|
||||
)
|
||||
* glam::Mat4::from_scale(
|
||||
glam::Vec3::new(size.x,size.y,size.z)/2.0
|
||||
),
|
||||
color: glam::vec4(color3.r as f32/255f32, color3.g as f32/255f32, color3.b as f32/255f32, 1.0-*transparency),
|
||||
};
|
||||
if object.name=="MapStart"{
|
||||
spawn_point=glam::Vec4Swizzles::xyz(model_instance.transform*glam::Vec3::Y.extend(1.0))+glam::vec3(0.0,2.5,0.0);
|
||||
println!("Found MapStart{:?}",spawn_point);
|
||||
}
|
||||
if *transparency==1.0||shape.to_u32()!=1 {
|
||||
continue;
|
||||
}
|
||||
|
||||
//TODO: also detect "CylinderMesh" etc here
|
||||
let shape=match &object.class[..]{
|
||||
"Part"=>{
|
||||
if let Some(rbx_dom_weak::types::Variant::Enum(shape))=object.properties.get("Shape"){
|
||||
match shape.to_u32(){
|
||||
0=>primitives::Primitives::Sphere,
|
||||
1=>primitives::Primitives::Cube,
|
||||
2=>primitives::Primitives::Cylinder,
|
||||
3=>primitives::Primitives::Wedge,
|
||||
4=>primitives::Primitives::CornerWedge,
|
||||
_=>panic!("Funky roblox PartType={};",shape.to_u32()),
|
||||
}
|
||||
}else{
|
||||
panic!("Part has no Shape!");
|
||||
}
|
||||
},
|
||||
"WedgePart"=>primitives::Primitives::Wedge,
|
||||
"CornerWedgePart"=>primitives::Primitives::CornerWedge,
|
||||
_=>{
|
||||
println!("Unsupported BasePart ClassName={}; defaulting to cube",object.class);
|
||||
primitives::Primitives::Cube
|
||||
}
|
||||
};
|
||||
|
||||
//use the biggest one and cut it down later...
|
||||
let mut part_texture_description:RobloxPartDescription=[None,None,None,None,None,None];
|
||||
temp_objects.clear();
|
||||
recursive_collect_superclass(&mut temp_objects, &dom, object,"Decal");
|
||||
|
||||
let mut i_can_only_load_one_texture_per_model=None;
|
||||
for &decal_ref in &temp_objects{
|
||||
if let Some(decal)=dom.get_by_ref(decal_ref){
|
||||
if let (
|
||||
Some(rbx_dom_weak::types::Variant::Content(content)),
|
||||
Some(rbx_dom_weak::types::Variant::Enum(normalid)),
|
||||
Some(rbx_dom_weak::types::Variant::Color3(decal_color3)),
|
||||
Some(rbx_dom_weak::types::Variant::Float32(decal_transparency)),
|
||||
) = (
|
||||
decal.properties.get("Texture"),
|
||||
decal.properties.get("Face"),
|
||||
decal.properties.get("Color3"),
|
||||
decal.properties.get("Transparency"),
|
||||
) {
|
||||
if let Some(rbx_dom_weak::types::Variant::Content(content)) = decal.properties.get("Texture") {
|
||||
if let Ok(asset_id)=content.clone().into_string().parse::<RobloxAssetId>(){
|
||||
let texture_id=if let Some(&texture_id)=texture_id_from_asset_id.get(&asset_id.0){
|
||||
texture_id
|
||||
if let Some(&texture_id)=texture_id_from_asset_id.get(&asset_id.0){
|
||||
i_can_only_load_one_texture_per_model=Some(texture_id);
|
||||
}else{
|
||||
let texture_id=asset_id_from_texture_id.len() as u32;
|
||||
texture_id_from_asset_id.insert(asset_id.0,texture_id);
|
||||
let texture_id=asset_id_from_texture_id.len();
|
||||
texture_id_from_asset_id.insert(asset_id.0,texture_id as u32);
|
||||
asset_id_from_texture_id.push(asset_id.0);
|
||||
texture_id
|
||||
};
|
||||
let normal_id=normalid.to_u32();
|
||||
if normal_id<6{
|
||||
let (roblox_texture_color,roblox_texture_transform)=if decal.class=="Texture"{
|
||||
//generate tranform
|
||||
if let (
|
||||
Some(rbx_dom_weak::types::Variant::Float32(ox)),
|
||||
Some(rbx_dom_weak::types::Variant::Float32(oy)),
|
||||
Some(rbx_dom_weak::types::Variant::Float32(sx)),
|
||||
Some(rbx_dom_weak::types::Variant::Float32(sy)),
|
||||
) = (
|
||||
decal.properties.get("OffsetStudsU"),
|
||||
decal.properties.get("OffsetStudsV"),
|
||||
decal.properties.get("StudsPerTileU"),
|
||||
decal.properties.get("StudsPerTileV"),
|
||||
)
|
||||
{
|
||||
let (size_u,size_v)=match normal_id{
|
||||
0=>(size.z,size.y),//right
|
||||
1=>(size.x,size.z),//top
|
||||
2=>(size.x,size.y),//back
|
||||
3=>(size.z,size.y),//left
|
||||
4=>(size.x,size.z),//bottom
|
||||
5=>(size.x,size.y),//front
|
||||
_=>panic!("unreachable"),
|
||||
};
|
||||
(
|
||||
glam::vec4(decal_color3.r,decal_color3.g,decal_color3.b,1.0-*decal_transparency),
|
||||
RobloxTextureTransform{
|
||||
offset_u:*ox/(*sx),offset_v:*oy/(*sy),
|
||||
scale_u:size_u/(*sx),scale_v:size_v/(*sy),
|
||||
}
|
||||
)
|
||||
}else{
|
||||
(glam::Vec4::ONE,RobloxTextureTransform::default())
|
||||
}
|
||||
}else{
|
||||
(glam::Vec4::ONE,RobloxTextureTransform::default())
|
||||
};
|
||||
part_texture_description[normal_id as usize]=Some(RobloxFaceTextureDescription{
|
||||
texture:texture_id,
|
||||
color:roblox_texture_color,
|
||||
transform:roblox_texture_transform,
|
||||
});
|
||||
}else{
|
||||
println!("NormalId={} unsupported for shape={:?}",normal_id,shape);
|
||||
//make new model
|
||||
let mut unit_cube_texture=unit_cube_modeldata.clone();
|
||||
unit_cube_texture.texture=Some(texture_id as u32);
|
||||
modeldatas.push(unit_cube_texture);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
//obscure rust syntax "slice pattern"
|
||||
let [
|
||||
f0,//Cube::Right
|
||||
f1,//Cube::Top
|
||||
f2,//Cube::Back
|
||||
f3,//Cube::Left
|
||||
f4,//Cube::Bottom
|
||||
f5,//Cube::Front
|
||||
]=part_texture_description;
|
||||
let basepart_texture_description=match shape{
|
||||
primitives::Primitives::Sphere=>RobloxBasePartDescription::Sphere,
|
||||
primitives::Primitives::Cube=>RobloxBasePartDescription::Part([f0,f1,f2,f3,f4,f5]),
|
||||
primitives::Primitives::Cylinder=>RobloxBasePartDescription::Cylinder,
|
||||
//use front face texture first and use top face texture as a fallback
|
||||
primitives::Primitives::Wedge=>RobloxBasePartDescription::Wedge([
|
||||
f0,//Cube::Right->Wedge::Right
|
||||
if f5.is_some(){f5}else{f1},//Cube::Front|Cube::Top->Wedge::TopFront
|
||||
f2,//Cube::Back->Wedge::Back
|
||||
f3,//Cube::Left->Wedge::Left
|
||||
f4,//Cube::Bottom->Wedge::Bottom
|
||||
]),
|
||||
//TODO: fix Left+Back texture coordinates to match roblox when not overwridden by Top
|
||||
primitives::Primitives::CornerWedge=>RobloxBasePartDescription::CornerWedge([
|
||||
f0,//Cube::Right->CornerWedge::Right
|
||||
if f2.is_some(){f2}else{f1.clone()},//Cube::Back|Cube::Top->CornerWedge::TopBack
|
||||
if f3.is_some(){f3}else{f1},//Cube::Left|Cube::Top->CornerWedge::TopLeft
|
||||
f4,//Cube::Bottom->CornerWedge::Bottom
|
||||
f5,//Cube::Front->CornerWedge::Front
|
||||
]),
|
||||
};
|
||||
//make new model if unit cube has not been created before
|
||||
let model_id=if let Some(&model_id)=model_id_from_description.get(&basepart_texture_description){
|
||||
match i_can_only_load_one_texture_per_model{
|
||||
//push to existing texture model
|
||||
model_id
|
||||
}else{
|
||||
let model_id=indexed_models.len();
|
||||
model_id_from_description.insert(basepart_texture_description.clone(),model_id);//borrow checker going crazy
|
||||
indexed_models.push(match basepart_texture_description{
|
||||
RobloxBasePartDescription::Sphere=>primitives::unit_sphere(),
|
||||
RobloxBasePartDescription::Part(part_texture_description)=>{
|
||||
let mut cube_face_description=primitives::CubeFaceDescription::new();
|
||||
for (face_id,roblox_face_description) in part_texture_description.iter().enumerate(){
|
||||
cube_face_description.insert(
|
||||
match face_id{
|
||||
0=>primitives::CubeFace::Right,
|
||||
1=>primitives::CubeFace::Top,
|
||||
2=>primitives::CubeFace::Back,
|
||||
3=>primitives::CubeFace::Left,
|
||||
4=>primitives::CubeFace::Bottom,
|
||||
5=>primitives::CubeFace::Front,
|
||||
_=>panic!("unreachable"),
|
||||
},
|
||||
match roblox_face_description{
|
||||
Some(roblox_texture_transform)=>roblox_texture_transform.to_face_description(),
|
||||
None=>primitives::FaceDescription::default(),
|
||||
});
|
||||
}
|
||||
primitives::generate_partial_unit_cube(cube_face_description)
|
||||
},
|
||||
RobloxBasePartDescription::Cylinder=>primitives::unit_cylinder(),
|
||||
RobloxBasePartDescription::Wedge(wedge_texture_description)=>{
|
||||
let mut wedge_face_description=primitives::WedgeFaceDescription::new();
|
||||
for (face_id,roblox_face_description) in wedge_texture_description.iter().enumerate(){
|
||||
wedge_face_description.insert(
|
||||
match face_id{
|
||||
0=>primitives::WedgeFace::Right,
|
||||
1=>primitives::WedgeFace::TopFront,
|
||||
2=>primitives::WedgeFace::Back,
|
||||
3=>primitives::WedgeFace::Left,
|
||||
4=>primitives::WedgeFace::Bottom,
|
||||
_=>panic!("unreachable"),
|
||||
},
|
||||
match roblox_face_description{
|
||||
Some(roblox_texture_transform)=>roblox_texture_transform.to_face_description(),
|
||||
None=>primitives::FaceDescription::default(),
|
||||
});
|
||||
}
|
||||
primitives::generate_partial_unit_wedge(wedge_face_description)
|
||||
},
|
||||
RobloxBasePartDescription::CornerWedge(cornerwedge_texture_description)=>{
|
||||
let mut cornerwedge_face_description=primitives::CornerWedgeFaceDescription::new();
|
||||
for (face_id,roblox_face_description) in cornerwedge_texture_description.iter().enumerate(){
|
||||
cornerwedge_face_description.insert(
|
||||
match face_id{
|
||||
0=>primitives::CornerWedgeFace::Right,
|
||||
1=>primitives::CornerWedgeFace::TopBack,
|
||||
2=>primitives::CornerWedgeFace::TopLeft,
|
||||
3=>primitives::CornerWedgeFace::Bottom,
|
||||
4=>primitives::CornerWedgeFace::Front,
|
||||
_=>panic!("unreachable"),
|
||||
},
|
||||
match roblox_face_description{
|
||||
Some(roblox_texture_transform)=>roblox_texture_transform.to_face_description(),
|
||||
None=>primitives::FaceDescription::default(),
|
||||
});
|
||||
}
|
||||
primitives::generate_partial_unit_cornerwedge(cornerwedge_face_description)
|
||||
},
|
||||
});
|
||||
model_id
|
||||
};
|
||||
indexed_models[model_id].instances.push(crate::model::ModelInstance {
|
||||
transform:model_transform,
|
||||
color:glam::vec4(color3.r as f32/255f32, color3.g as f32/255f32, color3.b as f32/255f32, 1.0-*transparency),
|
||||
attributes:get_attributes(&object.name,*can_collide,Planar64Vec3::try_from([velocity.x,velocity.y,velocity.z]).unwrap(),force_intersecting),
|
||||
temp_indexing:temp_indexing_attributes,
|
||||
});
|
||||
Some(texture_id)=>modeldatas[(texture_id+1) as usize].instances.push(model_instance),
|
||||
//push instance to big unit cube in the sky
|
||||
None=>modeldatas[0].instances.push(model_instance),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
crate::model::IndexedModelInstances{
|
||||
textures:asset_id_from_texture_id.iter().map(|t|t.to_string()).collect(),
|
||||
models:indexed_models,
|
||||
spawn_point,
|
||||
modes:Vec::new(),
|
||||
}
|
||||
Ok((modeldatas,asset_id_from_texture_id.iter().map(|t|t.to_string()).collect(),spawn_point))
|
||||
}
|
||||
|
940
src/main.rs
940
src/main.rs
File diff suppressed because it is too large
Load Diff
292
src/model.rs
292
src/model.rs
@ -1,238 +1,70 @@
|
||||
use crate::integer::{Planar64,Planar64Vec3,Planar64Affine3};
|
||||
pub type TextureCoordinate=glam::Vec2;
|
||||
pub type Color4=glam::Vec4;
|
||||
#[derive(Clone,Hash,PartialEq,Eq)]
|
||||
pub struct IndexedVertex{
|
||||
pub pos:u32,
|
||||
pub tex:u32,
|
||||
pub normal:u32,
|
||||
pub color:u32,
|
||||
}
|
||||
pub struct IndexedPolygon{
|
||||
pub vertices:Vec<u32>,
|
||||
}
|
||||
pub struct IndexedGroup{
|
||||
pub texture:Option<u32>,//RenderPattern? material/texture/shader/flat color
|
||||
pub polys:Vec<IndexedPolygon>,
|
||||
}
|
||||
pub struct IndexedModel{
|
||||
pub unique_pos:Vec<Planar64Vec3>,
|
||||
pub unique_normal:Vec<Planar64Vec3>,
|
||||
pub unique_tex:Vec<TextureCoordinate>,
|
||||
pub unique_color:Vec<Color4>,
|
||||
pub unique_vertices:Vec<IndexedVertex>,
|
||||
pub groups: Vec<IndexedGroup>,
|
||||
pub instances:Vec<ModelInstance>,
|
||||
}
|
||||
pub struct ModelInstance{
|
||||
//pub id:u64,//this does not actually help with map fixes resimulating bots, they must always be resimulated
|
||||
pub transform:Planar64Affine3,
|
||||
pub color:Color4,//transparency is in here
|
||||
pub attributes:CollisionAttributes,
|
||||
pub temp_indexing:Vec<TempIndexedAttributes>,
|
||||
}
|
||||
impl std::default::Default for ModelInstance{
|
||||
fn default() -> Self {
|
||||
Self{
|
||||
color:Color4::ONE,
|
||||
transform:Default::default(),
|
||||
attributes:Default::default(),
|
||||
temp_indexing:Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
pub struct IndexedModelInstances{
|
||||
pub textures:Vec<String>,//RenderPattern
|
||||
pub models:Vec<IndexedModel>,
|
||||
//may make this into an object later.
|
||||
pub modes:Vec<ModeDescription>,
|
||||
pub spawn_point:Planar64Vec3,
|
||||
}
|
||||
//stage description referencing flattened ids is spooky, but the map loading is meant to be deterministic.
|
||||
pub struct ModeDescription{
|
||||
pub start:u32,//start=model_id
|
||||
pub spawns:Vec<u32>,//spawns[spawn_id]=model_id
|
||||
pub ordered_checkpoints:Vec<u32>,//ordered_checkpoints[checkpoint_id]=model_id
|
||||
pub unordered_checkpoints:Vec<u32>,//unordered_checkpoints[checkpoint_id]=model_id
|
||||
pub spawn_from_stage_id:std::collections::HashMap::<u32,usize>,
|
||||
pub ordered_checkpoint_from_checkpoint_id:std::collections::HashMap::<u32,usize>,
|
||||
}
|
||||
impl ModeDescription{
|
||||
pub fn get_spawn_model_id(&self,stage_id:u32)->Option<&u32>{
|
||||
if let Some(&spawn)=self.spawn_from_stage_id.get(&stage_id){
|
||||
self.spawns.get(spawn)
|
||||
}else{
|
||||
None
|
||||
}
|
||||
}
|
||||
pub fn get_ordered_checkpoint_model_id(&self,checkpoint_id:u32)->Option<&u32>{
|
||||
if let Some(&checkpoint)=self.ordered_checkpoint_from_checkpoint_id.get(&checkpoint_id){
|
||||
self.ordered_checkpoints.get(checkpoint)
|
||||
}else{
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
pub enum TempIndexedAttributes{
|
||||
Start{
|
||||
mode_id:u32,
|
||||
},
|
||||
Spawn{
|
||||
mode_id:u32,
|
||||
stage_id:u32,
|
||||
},
|
||||
OrderedCheckpoint{
|
||||
mode_id:u32,
|
||||
checkpoint_id:u32,
|
||||
},
|
||||
UnorderedCheckpoint{
|
||||
mode_id:u32,
|
||||
},
|
||||
use bytemuck::{Pod, Zeroable};
|
||||
#[derive(Clone, Copy, Pod, Zeroable)]
|
||||
#[repr(C)]
|
||||
pub struct Vertex {
|
||||
pub pos: [f32; 3],
|
||||
pub texture: [f32; 2],
|
||||
pub normal: [f32; 3],
|
||||
pub color: [f32; 4],
|
||||
}
|
||||
|
||||
//you have this effect while in contact
|
||||
#[derive(Clone)]
|
||||
pub struct ContactingSurf{}
|
||||
#[derive(Clone)]
|
||||
pub struct ContactingLadder{
|
||||
pub sticky:bool
|
||||
}
|
||||
//you have this effect while intersecting
|
||||
#[derive(Clone)]
|
||||
pub struct IntersectingWater{
|
||||
pub viscosity:Planar64,
|
||||
pub density:Planar64,
|
||||
pub current:Planar64Vec3,
|
||||
}
|
||||
#[derive(Clone)]
|
||||
pub struct IntersectingAccelerator{
|
||||
pub acceleration:Planar64Vec3
|
||||
}
|
||||
//All models can be given these attributes
|
||||
#[derive(Clone)]
|
||||
pub struct GameMechanicJumpLimit{
|
||||
pub count:u32,
|
||||
}
|
||||
#[derive(Clone)]
|
||||
pub struct GameMechanicBooster{
|
||||
pub velocity:Planar64Vec3,
|
||||
}
|
||||
#[derive(Clone)]
|
||||
pub enum ZoneBehaviour{
|
||||
//Start is indexed
|
||||
//Checkpoints are indexed
|
||||
Finish,
|
||||
Anitcheat,
|
||||
}
|
||||
#[derive(Clone)]
|
||||
pub struct GameMechanicZone{
|
||||
pub mode_id:u32,
|
||||
pub behaviour:ZoneBehaviour,
|
||||
}
|
||||
// enum TrapCondition{
|
||||
// FasterThan(i64),
|
||||
// SlowerThan(i64),
|
||||
// InRange(i64,i64),
|
||||
// OutsideRange(i64,i64),
|
||||
// }
|
||||
#[derive(Clone)]
|
||||
pub enum StageElementBehaviour{
|
||||
//Spawn,//The behaviour of stepping on a spawn setting the spawnid
|
||||
SpawnAt,
|
||||
Trigger,
|
||||
Teleport,
|
||||
Platform,
|
||||
//Speedtrap(TrapCondition),//Acts as a trigger with a speed condition
|
||||
}
|
||||
#[derive(Clone)]
|
||||
pub struct GameMechanicStageElement{
|
||||
pub mode_id:u32,
|
||||
pub stage_id:u32,//which spawn to send to
|
||||
pub force:bool,//allow setting to lower spawn id i.e. 7->3
|
||||
pub behaviour:StageElementBehaviour
|
||||
}
|
||||
#[derive(Clone)]
|
||||
pub struct GameMechanicWormhole{//(position,angles)*=origin.transform.inverse()*destination.transform
|
||||
pub model_id:u32,
|
||||
}
|
||||
#[derive(Default,Clone)]
|
||||
pub struct GameMechanicAttributes{
|
||||
pub jump_limit:Option<GameMechanicJumpLimit>,
|
||||
pub booster:Option<GameMechanicBooster>,
|
||||
pub zone:Option<GameMechanicZone>,
|
||||
pub stage_element:Option<GameMechanicStageElement>,
|
||||
pub wormhole:Option<GameMechanicWormhole>,//stage_element and wormhole are in conflict
|
||||
}
|
||||
#[derive(Default,Clone)]
|
||||
pub struct ContactingAttributes{
|
||||
pub elasticity:Option<u32>,//[1/2^32,1] 0=None (elasticity+1)/2^32
|
||||
//friction?
|
||||
pub surf:Option<ContactingSurf>,
|
||||
pub ladder:Option<ContactingLadder>,
|
||||
}
|
||||
#[derive(Default,Clone)]
|
||||
pub struct IntersectingAttributes{
|
||||
pub water:Option<IntersectingWater>,
|
||||
pub accelerator:Option<IntersectingAccelerator>,
|
||||
}
|
||||
//Spawn(u32) NO! spawns are indexed in the map header instead of marked with attibutes
|
||||
pub enum CollisionAttributes{
|
||||
Decoration,//visual only
|
||||
Contact{//track whether you are contacting the object
|
||||
contacting:ContactingAttributes,
|
||||
general:GameMechanicAttributes,
|
||||
},
|
||||
Intersect{//track whether you are intersecting the object
|
||||
intersecting:IntersectingAttributes,
|
||||
general:GameMechanicAttributes,
|
||||
},
|
||||
}
|
||||
impl std::default::Default for CollisionAttributes{
|
||||
fn default() -> Self {
|
||||
Self::Contact{
|
||||
contacting:ContactingAttributes::default(),
|
||||
general:GameMechanicAttributes::default()
|
||||
}
|
||||
}
|
||||
pub struct ModelInstance {
|
||||
pub transform: glam::Mat4,
|
||||
pub color: glam::Vec4,
|
||||
}
|
||||
|
||||
pub fn generate_indexed_model_list_from_obj(data:obj::ObjData,color:Color4)->Vec<IndexedModel>{
|
||||
let mut unique_vertex_index = std::collections::HashMap::<obj::IndexTuple,u32>::new();
|
||||
return data.objects.iter().map(|object|{
|
||||
unique_vertex_index.clear();
|
||||
let mut unique_vertices = Vec::new();
|
||||
let groups = object.groups.iter().map(|group|{
|
||||
IndexedGroup{
|
||||
texture:None,
|
||||
polys:group.polys.iter().map(|poly|{
|
||||
IndexedPolygon{
|
||||
vertices:poly.0.iter().map(|&tup|{
|
||||
if let Some(&i)=unique_vertex_index.get(&tup){
|
||||
i
|
||||
}else{
|
||||
let i=unique_vertices.len() as u32;
|
||||
unique_vertices.push(IndexedVertex{
|
||||
pos: tup.0 as u32,
|
||||
tex: tup.1.unwrap() as u32,
|
||||
normal: tup.2.unwrap() as u32,
|
||||
color: 0,
|
||||
});
|
||||
unique_vertex_index.insert(tup,i);
|
||||
i
|
||||
}
|
||||
}).collect()
|
||||
#[derive(Clone)]
|
||||
pub struct ModelData {
|
||||
pub instances: Vec<ModelInstance>,
|
||||
pub vertices: Vec<Vertex>,
|
||||
pub entities: Vec<Vec<u16>>,
|
||||
pub texture: Option<u32>,
|
||||
}
|
||||
|
||||
impl ModelData {
|
||||
pub const COLOR_FLOATS_WHITE: [f32;4] = [1.0,1.0,1.0,1.0];
|
||||
pub const COLOR_VEC4_WHITE: glam::Vec4 = glam::vec4(1.0,1.0,1.0,1.0);
|
||||
}
|
||||
|
||||
pub fn generate_modeldatas(data:obj::ObjData,color:[f32;4]) -> Vec<ModelData>{
|
||||
let mut modeldatas=Vec::new();
|
||||
let mut vertices = Vec::new();
|
||||
let mut vertex_index = std::collections::HashMap::<obj::IndexTuple,u16>::new();
|
||||
for object in data.objects {
|
||||
vertices.clear();
|
||||
vertex_index.clear();
|
||||
let mut entities = Vec::new();
|
||||
for group in object.groups {
|
||||
let mut indices = Vec::new();
|
||||
for poly in group.polys {
|
||||
for end_index in 2..poly.0.len() {
|
||||
for &index in &[0, end_index - 1, end_index] {
|
||||
let vert = poly.0[index];
|
||||
if let Some(&i)=vertex_index.get(&vert){
|
||||
indices.push(i);
|
||||
}else{
|
||||
let i=vertices.len() as u16;
|
||||
vertices.push(Vertex {
|
||||
pos: data.position[vert.0],
|
||||
texture: data.texture[vert.1.unwrap()],
|
||||
normal: data.normal[vert.2.unwrap()],
|
||||
color,
|
||||
});
|
||||
vertex_index.insert(vert,i);
|
||||
indices.push(i);
|
||||
}
|
||||
}
|
||||
}).collect()
|
||||
}
|
||||
}
|
||||
}).collect();
|
||||
IndexedModel{
|
||||
unique_pos: data.position.iter().map(|&v|Planar64Vec3::try_from(v).unwrap()).collect(),
|
||||
unique_tex: data.texture.iter().map(|&v|TextureCoordinate::from_array(v)).collect(),
|
||||
unique_normal: data.normal.iter().map(|&v|Planar64Vec3::try_from(v).unwrap()).collect(),
|
||||
unique_color: vec![color],
|
||||
unique_vertices,
|
||||
groups,
|
||||
instances:Vec::new(),
|
||||
entities.push(indices);
|
||||
}
|
||||
}).collect()
|
||||
}
|
||||
modeldatas.push(ModelData {
|
||||
instances: Vec::new(),
|
||||
vertices:vertices.clone(),
|
||||
entities,
|
||||
texture: None,
|
||||
});
|
||||
}
|
||||
modeldatas
|
||||
}
|
@ -1,35 +0,0 @@
|
||||
use bytemuck::{Pod, Zeroable};
|
||||
use crate::model::{IndexedVertex,IndexedPolygon};
|
||||
#[derive(Clone, Copy, Pod, Zeroable)]
|
||||
#[repr(C)]
|
||||
pub struct GraphicsVertex {
|
||||
pub pos: [f32; 3],
|
||||
pub tex: [f32; 2],
|
||||
pub normal: [f32; 3],
|
||||
pub color: [f32; 4],
|
||||
}
|
||||
pub struct IndexedGroupFixedTexture{
|
||||
pub polys:Vec<IndexedPolygon>,
|
||||
}
|
||||
pub struct IndexedModelGraphicsSingleTexture{
|
||||
pub unique_pos:Vec<[f32; 3]>,
|
||||
pub unique_tex:Vec<[f32; 2]>,
|
||||
pub unique_normal:Vec<[f32; 3]>,
|
||||
pub unique_color:Vec<[f32; 4]>,
|
||||
pub unique_vertices:Vec<IndexedVertex>,
|
||||
pub texture:Option<u32>,//RenderPattern? material/texture/shader/flat color
|
||||
pub groups: Vec<IndexedGroupFixedTexture>,
|
||||
pub instances:Vec<ModelGraphicsInstance>,
|
||||
}
|
||||
pub struct ModelGraphicsSingleTexture{
|
||||
pub instances: Vec<ModelGraphicsInstance>,
|
||||
pub vertices: Vec<GraphicsVertex>,
|
||||
pub entities: Vec<Vec<u16>>,
|
||||
pub texture: Option<u32>,
|
||||
}
|
||||
#[derive(Clone)]
|
||||
pub struct ModelGraphicsInstance{
|
||||
pub transform:glam::Mat4,
|
||||
pub normal_transform:glam::Mat3,
|
||||
pub color:glam::Vec4,
|
||||
}
|
@ -1 +0,0 @@
|
||||
//
|
1274
src/physics.rs
1274
src/physics.rs
File diff suppressed because it is too large
Load Diff
@ -1,510 +1,76 @@
|
||||
use crate::model::{Color4,TextureCoordinate,IndexedModel,IndexedPolygon,IndexedGroup,IndexedVertex};
|
||||
use crate::integer::Planar64Vec3;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Primitives{
|
||||
Sphere,
|
||||
Cube,
|
||||
Cylinder,
|
||||
Wedge,
|
||||
CornerWedge,
|
||||
}
|
||||
#[derive(Hash,PartialEq,Eq)]
|
||||
pub enum CubeFace{
|
||||
Right,
|
||||
Top,
|
||||
Back,
|
||||
Left,
|
||||
Bottom,
|
||||
Front,
|
||||
}
|
||||
const CUBE_DEFAULT_TEXTURE_COORDS:[TextureCoordinate;4]=[
|
||||
TextureCoordinate::new(0.0,0.0),
|
||||
TextureCoordinate::new(1.0,0.0),
|
||||
TextureCoordinate::new(1.0,1.0),
|
||||
TextureCoordinate::new(0.0,1.0),
|
||||
];
|
||||
const CUBE_DEFAULT_VERTICES:[Planar64Vec3;8]=[
|
||||
Planar64Vec3::int(-1,-1, 1),//0 left bottom back
|
||||
Planar64Vec3::int( 1,-1, 1),//1 right bottom back
|
||||
Planar64Vec3::int( 1, 1, 1),//2 right top back
|
||||
Planar64Vec3::int(-1, 1, 1),//3 left top back
|
||||
Planar64Vec3::int(-1, 1,-1),//4 left top front
|
||||
Planar64Vec3::int( 1, 1,-1),//5 right top front
|
||||
Planar64Vec3::int( 1,-1,-1),//6 right bottom front
|
||||
Planar64Vec3::int(-1,-1,-1),//7 left bottom front
|
||||
];
|
||||
const CUBE_DEFAULT_NORMALS:[Planar64Vec3;6]=[
|
||||
Planar64Vec3::int( 1, 0, 0),//CubeFace::Right
|
||||
Planar64Vec3::int( 0, 1, 0),//CubeFace::Top
|
||||
Planar64Vec3::int( 0, 0, 1),//CubeFace::Back
|
||||
Planar64Vec3::int(-1, 0, 0),//CubeFace::Left
|
||||
Planar64Vec3::int( 0,-1, 0),//CubeFace::Bottom
|
||||
Planar64Vec3::int( 0, 0,-1),//CubeFace::Front
|
||||
];
|
||||
const CUBE_DEFAULT_POLYS:[[[u32;3];4];6]=[
|
||||
// right (1, 0, 0)
|
||||
[
|
||||
[6,2,0],//[vertex,tex,norm]
|
||||
[5,1,0],
|
||||
[2,0,0],
|
||||
[1,3,0],
|
||||
],
|
||||
// top (0, 1, 0)
|
||||
[
|
||||
[5,3,1],
|
||||
[4,2,1],
|
||||
[3,1,1],
|
||||
[2,0,1],
|
||||
],
|
||||
// back (0, 0, 1)
|
||||
[
|
||||
[0,3,2],
|
||||
[1,2,2],
|
||||
[2,1,2],
|
||||
[3,0,2],
|
||||
],
|
||||
// left (-1, 0, 0)
|
||||
[
|
||||
[0,2,3],
|
||||
[3,1,3],
|
||||
[4,0,3],
|
||||
[7,3,3],
|
||||
],
|
||||
// bottom (0,-1, 0)
|
||||
[
|
||||
[1,1,4],
|
||||
[0,0,4],
|
||||
[7,3,4],
|
||||
[6,2,4],
|
||||
],
|
||||
// front (0, 0,-1)
|
||||
[
|
||||
[4,1,5],
|
||||
[5,0,5],
|
||||
[6,3,5],
|
||||
[7,2,5],
|
||||
],
|
||||
];
|
||||
|
||||
#[derive(Hash,PartialEq,Eq)]
|
||||
pub enum WedgeFace{
|
||||
Right,
|
||||
TopFront,
|
||||
Back,
|
||||
Left,
|
||||
Bottom,
|
||||
}
|
||||
const WEDGE_DEFAULT_NORMALS:[Planar64Vec3;5]=[
|
||||
Planar64Vec3::int( 1, 0, 0),//Wedge::Right
|
||||
Planar64Vec3::int( 0, 1,-1),//Wedge::TopFront
|
||||
Planar64Vec3::int( 0, 0, 1),//Wedge::Back
|
||||
Planar64Vec3::int(-1, 0, 0),//Wedge::Left
|
||||
Planar64Vec3::int( 0,-1, 0),//Wedge::Bottom
|
||||
];
|
||||
/*
|
||||
local cornerWedgeVerticies = {
|
||||
Vector3.new(-1/2,-1/2,-1/2),7
|
||||
Vector3.new(-1/2,-1/2, 1/2),0
|
||||
Vector3.new( 1/2,-1/2,-1/2),6
|
||||
Vector3.new( 1/2,-1/2, 1/2),1
|
||||
Vector3.new( 1/2, 1/2,-1/2),5
|
||||
}
|
||||
*/
|
||||
#[derive(Hash,PartialEq,Eq)]
|
||||
pub enum CornerWedgeFace{
|
||||
Right,
|
||||
TopBack,
|
||||
TopLeft,
|
||||
Bottom,
|
||||
Front,
|
||||
}
|
||||
const CORNERWEDGE_DEFAULT_NORMALS:[Planar64Vec3;5]=[
|
||||
Planar64Vec3::int( 1, 0, 0),//CornerWedge::Right
|
||||
Planar64Vec3::int( 0, 1, 1),//CornerWedge::BackTop
|
||||
Planar64Vec3::int(-1, 1, 0),//CornerWedge::LeftTop
|
||||
Planar64Vec3::int( 0,-1, 0),//CornerWedge::Bottom
|
||||
Planar64Vec3::int( 0, 0,-1),//CornerWedge::Front
|
||||
];
|
||||
//HashMap fits this use case perfectly but feels like using a sledgehammer to drive a nail
|
||||
pub fn unit_sphere()->crate::model::IndexedModel{
|
||||
let mut indexed_model=crate::model::generate_indexed_model_list_from_obj(obj::ObjData::load_buf(&include_bytes!("../models/suzanne.obj")[..]).unwrap(),Color4::ONE).remove(0);
|
||||
for pos in indexed_model.unique_pos.iter_mut(){
|
||||
*pos=*pos/2;
|
||||
}
|
||||
indexed_model
|
||||
}
|
||||
pub type CubeFaceDescription=std::collections::HashMap::<CubeFace,FaceDescription>;
|
||||
pub fn unit_cube()->crate::model::IndexedModel{
|
||||
let mut t=CubeFaceDescription::new();
|
||||
t.insert(CubeFace::Right,FaceDescription::default());
|
||||
t.insert(CubeFace::Top,FaceDescription::default());
|
||||
t.insert(CubeFace::Back,FaceDescription::default());
|
||||
t.insert(CubeFace::Left,FaceDescription::default());
|
||||
t.insert(CubeFace::Bottom,FaceDescription::default());
|
||||
t.insert(CubeFace::Front,FaceDescription::default());
|
||||
generate_partial_unit_cube(t)
|
||||
}
|
||||
const TEAPOT_TRANSFORM:crate::integer::Planar64Mat3=crate::integer::Planar64Mat3::int_from_cols_array([0,1,0, -1,0,0, 0,0,1]);
|
||||
pub fn unit_cylinder()->crate::model::IndexedModel{
|
||||
let mut indexed_model=crate::model::generate_indexed_model_list_from_obj(obj::ObjData::load_buf(&include_bytes!("../models/teapot.obj")[..]).unwrap(),Color4::ONE).remove(0);
|
||||
for pos in indexed_model.unique_pos.iter_mut(){
|
||||
*pos=TEAPOT_TRANSFORM*(*pos)/10;
|
||||
}
|
||||
indexed_model
|
||||
}
|
||||
pub type WedgeFaceDescription=std::collections::HashMap::<WedgeFace,FaceDescription>;
|
||||
pub fn unit_wedge()->crate::model::IndexedModel{
|
||||
let mut t=WedgeFaceDescription::new();
|
||||
t.insert(WedgeFace::Right,FaceDescription::default());
|
||||
t.insert(WedgeFace::TopFront,FaceDescription::default());
|
||||
t.insert(WedgeFace::Back,FaceDescription::default());
|
||||
t.insert(WedgeFace::Left,FaceDescription::default());
|
||||
t.insert(WedgeFace::Bottom,FaceDescription::default());
|
||||
generate_partial_unit_wedge(t)
|
||||
}
|
||||
pub type CornerWedgeFaceDescription=std::collections::HashMap::<CornerWedgeFace,FaceDescription>;
|
||||
pub fn unit_cornerwedge()->crate::model::IndexedModel{
|
||||
let mut t=CornerWedgeFaceDescription::new();
|
||||
t.insert(CornerWedgeFace::Right,FaceDescription::default());
|
||||
t.insert(CornerWedgeFace::TopBack,FaceDescription::default());
|
||||
t.insert(CornerWedgeFace::TopLeft,FaceDescription::default());
|
||||
t.insert(CornerWedgeFace::Bottom,FaceDescription::default());
|
||||
t.insert(CornerWedgeFace::Front,FaceDescription::default());
|
||||
generate_partial_unit_cornerwedge(t)
|
||||
}
|
||||
|
||||
#[derive(Copy,Clone)]
|
||||
pub struct FaceDescription{
|
||||
pub texture:Option<u32>,
|
||||
pub transform:glam::Affine2,
|
||||
pub color:Color4,
|
||||
}
|
||||
impl std::default::Default for FaceDescription{
|
||||
fn default()->Self {
|
||||
Self{
|
||||
texture:None,
|
||||
transform:glam::Affine2::IDENTITY,
|
||||
color:Color4::new(1.0,1.0,1.0,0.0),//zero alpha to hide the default texture
|
||||
}
|
||||
}
|
||||
}
|
||||
impl FaceDescription{
|
||||
pub fn new(texture:u32,transform:glam::Affine2,color:Color4)->Self{
|
||||
Self{texture:Some(texture),transform,color}
|
||||
}
|
||||
pub fn from_texture(texture:u32)->Self{
|
||||
Self{
|
||||
texture:Some(texture),
|
||||
transform:glam::Affine2::IDENTITY,
|
||||
color:Color4::ONE,
|
||||
}
|
||||
}
|
||||
}
|
||||
//TODO: it's probably better to use a shared vertex buffer between all primitives and use indexed rendering instead of generating a unique vertex buffer for each primitive.
|
||||
//implementation: put all roblox primitives into one model.groups <- this won't work but I forget why
|
||||
pub fn generate_partial_unit_cube(face_descriptions:CubeFaceDescription)->crate::model::IndexedModel{
|
||||
let mut generated_pos=Vec::new();
|
||||
let mut generated_tex=Vec::new();
|
||||
let mut generated_normal=Vec::new();
|
||||
let mut generated_color=Vec::new();
|
||||
let mut generated_vertices=Vec::new();
|
||||
let mut groups=Vec::new();
|
||||
let mut transforms=Vec::new();
|
||||
//note that on a cube every vertex is guaranteed to be unique, so there's no need to hash them against existing vertices.
|
||||
for (face,face_description) in face_descriptions.into_iter(){
|
||||
//assume that scanning short lists is faster than hashing.
|
||||
let transform_index=if let Some(transform_index)=transforms.iter().position(|&transform|transform==face_description.transform){
|
||||
transform_index
|
||||
}else{
|
||||
//create new transform_index
|
||||
let transform_index=transforms.len();
|
||||
transforms.push(face_description.transform);
|
||||
for tex in CUBE_DEFAULT_TEXTURE_COORDS{
|
||||
generated_tex.push(face_description.transform.transform_point2(tex));
|
||||
}
|
||||
transform_index
|
||||
} as u32;
|
||||
let color_index=if let Some(color_index)=generated_color.iter().position(|&color|color==face_description.color){
|
||||
color_index
|
||||
}else{
|
||||
//create new color_index
|
||||
let color_index=generated_color.len();
|
||||
generated_color.push(face_description.color);
|
||||
color_index
|
||||
} as u32;
|
||||
let face_id=match face{
|
||||
CubeFace::Right => 0,
|
||||
CubeFace::Top => 1,
|
||||
CubeFace::Back => 2,
|
||||
CubeFace::Left => 3,
|
||||
CubeFace::Bottom => 4,
|
||||
CubeFace::Front => 5,
|
||||
};
|
||||
//always push normal
|
||||
let normal_index=generated_normal.len() as u32;
|
||||
generated_normal.push(CUBE_DEFAULT_NORMALS[face_id]);
|
||||
//push vertices as they are needed
|
||||
groups.push(IndexedGroup{
|
||||
texture:face_description.texture,
|
||||
polys:vec![IndexedPolygon{
|
||||
vertices:CUBE_DEFAULT_POLYS[face_id].map(|tup|{
|
||||
let pos=CUBE_DEFAULT_VERTICES[tup[0] as usize];
|
||||
let pos_index=if let Some(pos_index)=generated_pos.iter().position(|&p|p==pos){
|
||||
pos_index
|
||||
}else{
|
||||
//create new pos_index
|
||||
let pos_index=generated_pos.len();
|
||||
generated_pos.push(pos);
|
||||
pos_index
|
||||
} as u32;
|
||||
//always push vertex
|
||||
let vertex=IndexedVertex{
|
||||
pos:pos_index,
|
||||
tex:tup[1]+4*transform_index,
|
||||
normal:normal_index,
|
||||
color:color_index,
|
||||
};
|
||||
let vert_index=generated_vertices.len();
|
||||
generated_vertices.push(vertex);
|
||||
vert_index as u32
|
||||
}).to_vec(),
|
||||
}],
|
||||
});
|
||||
}
|
||||
IndexedModel{
|
||||
unique_pos:generated_pos,
|
||||
unique_tex:generated_tex,
|
||||
unique_normal:generated_normal,
|
||||
unique_color:generated_color,
|
||||
unique_vertices:generated_vertices,
|
||||
groups,
|
||||
instances:Vec::new(),
|
||||
}
|
||||
}
|
||||
//don't think too hard about the copy paste because this is all going into the map tool eventually...
|
||||
pub fn generate_partial_unit_wedge(face_descriptions:WedgeFaceDescription)->crate::model::IndexedModel{
|
||||
let wedge_default_polys=vec![
|
||||
// right (1, 0, 0)
|
||||
vec![
|
||||
[6,2,0],//[vertex,tex,norm]
|
||||
[2,0,0],
|
||||
[1,3,0],
|
||||
pub fn the_unit_cube_lol() -> obj::ObjData{
|
||||
obj::ObjData{
|
||||
position: vec![
|
||||
[-1.,-1., 1.],//left bottom back
|
||||
[ 1.,-1., 1.],//right bottom back
|
||||
[ 1., 1., 1.],//right top back
|
||||
[-1., 1., 1.],//left top back
|
||||
[-1., 1.,-1.],//left top front
|
||||
[ 1., 1.,-1.],//right top front
|
||||
[ 1.,-1.,-1.],//right bottom front
|
||||
[-1.,-1.,-1.],//left bottom front
|
||||
],
|
||||
// FrontTop (0, 1, -1)
|
||||
vec![
|
||||
[3,1,1],
|
||||
[2,0,1],
|
||||
[6,3,1],
|
||||
[7,2,1],
|
||||
texture: vec![[0.0,0.0],[1.0,0.0],[1.0,1.0],[0.0,1.0]],
|
||||
normal: vec![
|
||||
[1.,0.,0.],//AabbFace::Right
|
||||
[0.,1.,0.],//AabbFace::Top
|
||||
[0.,0.,1.],//AabbFace::Back
|
||||
[-1.,0.,0.],//AabbFace::Left
|
||||
[0.,-1.,0.],//AabbFace::Bottom
|
||||
[0.,0.,-1.],//AabbFace::Front
|
||||
],
|
||||
// back (0, 0, 1)
|
||||
vec![
|
||||
[0,3,2],
|
||||
[1,2,2],
|
||||
[2,1,2],
|
||||
[3,0,2],
|
||||
],
|
||||
// left (-1, 0, 0)
|
||||
vec![
|
||||
[0,2,3],
|
||||
[3,1,3],
|
||||
[7,3,3],
|
||||
],
|
||||
// bottom (0,-1, 0)
|
||||
vec![
|
||||
[1,1,4],
|
||||
[0,0,4],
|
||||
[7,3,4],
|
||||
[6,2,4],
|
||||
],
|
||||
];
|
||||
let mut generated_pos=Vec::new();
|
||||
let mut generated_tex=Vec::new();
|
||||
let mut generated_normal=Vec::new();
|
||||
let mut generated_color=Vec::new();
|
||||
let mut generated_vertices=Vec::new();
|
||||
let mut groups=Vec::new();
|
||||
let mut transforms=Vec::new();
|
||||
//note that on a cube every vertex is guaranteed to be unique, so there's no need to hash them against existing vertices.
|
||||
for (face,face_description) in face_descriptions.into_iter(){
|
||||
//assume that scanning short lists is faster than hashing.
|
||||
let transform_index=if let Some(transform_index)=transforms.iter().position(|&transform|transform==face_description.transform){
|
||||
transform_index
|
||||
}else{
|
||||
//create new transform_index
|
||||
let transform_index=transforms.len();
|
||||
transforms.push(face_description.transform);
|
||||
for tex in CUBE_DEFAULT_TEXTURE_COORDS{
|
||||
generated_tex.push(face_description.transform.transform_point2(tex));
|
||||
}
|
||||
transform_index
|
||||
} as u32;
|
||||
let color_index=if let Some(color_index)=generated_color.iter().position(|&color|color==face_description.color){
|
||||
color_index
|
||||
}else{
|
||||
//create new color_index
|
||||
let color_index=generated_color.len();
|
||||
generated_color.push(face_description.color);
|
||||
color_index
|
||||
} as u32;
|
||||
let face_id=match face{
|
||||
WedgeFace::Right => 0,
|
||||
WedgeFace::TopFront => 1,
|
||||
WedgeFace::Back => 2,
|
||||
WedgeFace::Left => 3,
|
||||
WedgeFace::Bottom => 4,
|
||||
};
|
||||
//always push normal
|
||||
let normal_index=generated_normal.len() as u32;
|
||||
generated_normal.push(WEDGE_DEFAULT_NORMALS[face_id]);
|
||||
//push vertices as they are needed
|
||||
groups.push(IndexedGroup{
|
||||
texture:face_description.texture,
|
||||
polys:vec![IndexedPolygon{
|
||||
vertices:wedge_default_polys[face_id].iter().map(|tup|{
|
||||
let pos=CUBE_DEFAULT_VERTICES[tup[0] as usize];
|
||||
let pos_index=if let Some(pos_index)=generated_pos.iter().position(|&p|p==pos){
|
||||
pos_index
|
||||
}else{
|
||||
//create new pos_index
|
||||
let pos_index=generated_pos.len();
|
||||
generated_pos.push(pos);
|
||||
pos_index
|
||||
} as u32;
|
||||
//always push vertex
|
||||
let vertex=IndexedVertex{
|
||||
pos:pos_index,
|
||||
tex:tup[1]+4*transform_index,
|
||||
normal:normal_index,
|
||||
color:color_index,
|
||||
};
|
||||
let vert_index=generated_vertices.len();
|
||||
generated_vertices.push(vertex);
|
||||
vert_index as u32
|
||||
}).collect(),
|
||||
}],
|
||||
});
|
||||
objects: vec![obj::Object{
|
||||
name: "Unit Cube".to_owned(),
|
||||
groups: vec![obj::Group{
|
||||
name: "Cube Vertices".to_owned(),
|
||||
index: 0,
|
||||
material: None,
|
||||
polys: vec![
|
||||
// back (0, 0, 1)
|
||||
obj::SimplePolygon(vec![
|
||||
obj::IndexTuple(0,Some(0),Some(2)),
|
||||
obj::IndexTuple(1,Some(1),Some(2)),
|
||||
obj::IndexTuple(2,Some(2),Some(2)),
|
||||
obj::IndexTuple(3,Some(3),Some(2)),
|
||||
]),
|
||||
// front (0, 0,-1)
|
||||
obj::SimplePolygon(vec![
|
||||
obj::IndexTuple(4,Some(0),Some(5)),
|
||||
obj::IndexTuple(5,Some(1),Some(5)),
|
||||
obj::IndexTuple(6,Some(2),Some(5)),
|
||||
obj::IndexTuple(7,Some(3),Some(5)),
|
||||
]),
|
||||
// right (1, 0, 0)
|
||||
obj::SimplePolygon(vec![
|
||||
obj::IndexTuple(6,Some(0),Some(0)),
|
||||
obj::IndexTuple(5,Some(1),Some(0)),
|
||||
obj::IndexTuple(2,Some(2),Some(0)),
|
||||
obj::IndexTuple(1,Some(3),Some(0)),
|
||||
]),
|
||||
// left (-1, 0, 0)
|
||||
obj::SimplePolygon(vec![
|
||||
obj::IndexTuple(0,Some(0),Some(3)),
|
||||
obj::IndexTuple(3,Some(1),Some(3)),
|
||||
obj::IndexTuple(4,Some(2),Some(3)),
|
||||
obj::IndexTuple(7,Some(3),Some(3)),
|
||||
]),
|
||||
// top (0, 1, 0)
|
||||
obj::SimplePolygon(vec![
|
||||
obj::IndexTuple(5,Some(1),Some(1)),
|
||||
obj::IndexTuple(4,Some(0),Some(1)),
|
||||
obj::IndexTuple(3,Some(3),Some(1)),
|
||||
obj::IndexTuple(2,Some(2),Some(1)),
|
||||
]),
|
||||
// bottom (0,-1, 0)
|
||||
obj::SimplePolygon(vec![
|
||||
obj::IndexTuple(1,Some(1),Some(4)),
|
||||
obj::IndexTuple(0,Some(0),Some(4)),
|
||||
obj::IndexTuple(7,Some(3),Some(4)),
|
||||
obj::IndexTuple(6,Some(2),Some(4)),
|
||||
]),
|
||||
],
|
||||
}]
|
||||
}],
|
||||
material_libs: Vec::new(),
|
||||
}
|
||||
IndexedModel{
|
||||
unique_pos:generated_pos,
|
||||
unique_tex:generated_tex,
|
||||
unique_normal:generated_normal,
|
||||
unique_color:generated_color,
|
||||
unique_vertices:generated_vertices,
|
||||
groups,
|
||||
instances:Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn generate_partial_unit_cornerwedge(face_descriptions:CornerWedgeFaceDescription)->crate::model::IndexedModel{
|
||||
let cornerwedge_default_polys=vec![
|
||||
// right (1, 0, 0)
|
||||
vec![
|
||||
[6,2,0],//[vertex,tex,norm]
|
||||
[5,1,0],
|
||||
[1,3,0],
|
||||
],
|
||||
// BackTop (0, 1, 1)
|
||||
vec![
|
||||
[5,3,1],
|
||||
[0,1,1],
|
||||
[1,0,1],
|
||||
],
|
||||
// LeftTop (-1, 1, 0)
|
||||
vec![
|
||||
[5,3,2],
|
||||
[7,2,2],
|
||||
[0,1,2],
|
||||
],
|
||||
// bottom (0,-1, 0)
|
||||
vec![
|
||||
[1,1,3],
|
||||
[0,0,3],
|
||||
[7,3,3],
|
||||
[6,2,3],
|
||||
],
|
||||
// front (0, 0,-1)
|
||||
vec![
|
||||
[5,0,4],
|
||||
[6,3,4],
|
||||
[7,2,4],
|
||||
],
|
||||
];
|
||||
let mut generated_pos=Vec::new();
|
||||
let mut generated_tex=Vec::new();
|
||||
let mut generated_normal=Vec::new();
|
||||
let mut generated_color=Vec::new();
|
||||
let mut generated_vertices=Vec::new();
|
||||
let mut groups=Vec::new();
|
||||
let mut transforms=Vec::new();
|
||||
//note that on a cube every vertex is guaranteed to be unique, so there's no need to hash them against existing vertices.
|
||||
for (face,face_description) in face_descriptions.into_iter(){
|
||||
//assume that scanning short lists is faster than hashing.
|
||||
let transform_index=if let Some(transform_index)=transforms.iter().position(|&transform|transform==face_description.transform){
|
||||
transform_index
|
||||
}else{
|
||||
//create new transform_index
|
||||
let transform_index=transforms.len();
|
||||
transforms.push(face_description.transform);
|
||||
for tex in CUBE_DEFAULT_TEXTURE_COORDS{
|
||||
generated_tex.push(face_description.transform.transform_point2(tex));
|
||||
}
|
||||
transform_index
|
||||
} as u32;
|
||||
let color_index=if let Some(color_index)=generated_color.iter().position(|&color|color==face_description.color){
|
||||
color_index
|
||||
}else{
|
||||
//create new color_index
|
||||
let color_index=generated_color.len();
|
||||
generated_color.push(face_description.color);
|
||||
color_index
|
||||
} as u32;
|
||||
let face_id=match face{
|
||||
CornerWedgeFace::Right => 0,
|
||||
CornerWedgeFace::TopBack => 1,
|
||||
CornerWedgeFace::TopLeft => 2,
|
||||
CornerWedgeFace::Bottom => 3,
|
||||
CornerWedgeFace::Front => 4,
|
||||
};
|
||||
//always push normal
|
||||
let normal_index=generated_normal.len() as u32;
|
||||
generated_normal.push(CORNERWEDGE_DEFAULT_NORMALS[face_id]);
|
||||
//push vertices as they are needed
|
||||
groups.push(IndexedGroup{
|
||||
texture:face_description.texture,
|
||||
polys:vec![IndexedPolygon{
|
||||
vertices:cornerwedge_default_polys[face_id].iter().map(|tup|{
|
||||
let pos=CUBE_DEFAULT_VERTICES[tup[0] as usize];
|
||||
let pos_index=if let Some(pos_index)=generated_pos.iter().position(|&p|p==pos){
|
||||
pos_index
|
||||
}else{
|
||||
//create new pos_index
|
||||
let pos_index=generated_pos.len();
|
||||
generated_pos.push(pos);
|
||||
pos_index
|
||||
} as u32;
|
||||
//always push vertex
|
||||
let vertex=IndexedVertex{
|
||||
pos:pos_index,
|
||||
tex:tup[1]+4*transform_index,
|
||||
normal:normal_index,
|
||||
color:color_index,
|
||||
};
|
||||
let vert_index=generated_vertices.len();
|
||||
generated_vertices.push(vertex);
|
||||
vert_index as u32
|
||||
}).collect(),
|
||||
}],
|
||||
});
|
||||
}
|
||||
IndexedModel{
|
||||
unique_pos:generated_pos,
|
||||
unique_tex:generated_tex,
|
||||
unique_normal:generated_normal,
|
||||
unique_color:generated_color,
|
||||
unique_vertices:generated_vertices,
|
||||
groups,
|
||||
instances:Vec::new(),
|
||||
}
|
||||
}
|
||||
}
|
134
src/settings.rs
134
src/settings.rs
@ -1,134 +0,0 @@
|
||||
use crate::integer::{Ratio64,Ratio64Vec2};
|
||||
struct Ratio{
|
||||
ratio:f64,
|
||||
}
|
||||
enum DerivedFov{
|
||||
FromScreenAspect,
|
||||
FromAspect(Ratio),
|
||||
}
|
||||
enum Fov{
|
||||
Exactly{x:f64,y:f64},
|
||||
SpecifyXDeriveY{x:f64,y:DerivedFov},
|
||||
SpecifyYDeriveX{x:DerivedFov,y:f64},
|
||||
}
|
||||
impl Default for Fov{
|
||||
fn default()->Self{
|
||||
Fov::SpecifyYDeriveX{x:DerivedFov::FromScreenAspect,y:1.0}
|
||||
}
|
||||
}
|
||||
enum DerivedSensitivity{
|
||||
FromRatio(Ratio64),
|
||||
}
|
||||
enum Sensitivity{
|
||||
Exactly{x:Ratio64,y:Ratio64},
|
||||
SpecifyXDeriveY{x:Ratio64,y:DerivedSensitivity},
|
||||
SpecifyYDeriveX{x:DerivedSensitivity,y:Ratio64},
|
||||
}
|
||||
impl Default for Sensitivity{
|
||||
fn default()->Self{
|
||||
Sensitivity::SpecifyXDeriveY{x:Ratio64::ONE*524288,y:DerivedSensitivity::FromRatio(Ratio64::ONE)}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct UserSettings{
|
||||
fov:Fov,
|
||||
sensitivity:Sensitivity,
|
||||
}
|
||||
impl UserSettings{
|
||||
pub fn calculate_fov(&self,zoom:f64,screen_size:&glam::UVec2)->glam::DVec2{
|
||||
zoom*match &self.fov{
|
||||
&Fov::Exactly{x,y}=>glam::dvec2(x,y),
|
||||
Fov::SpecifyXDeriveY{x,y}=>match y{
|
||||
DerivedFov::FromScreenAspect=>glam::dvec2(*x,x*(screen_size.y as f64/screen_size.x as f64)),
|
||||
DerivedFov::FromAspect(ratio)=>glam::dvec2(*x,x*ratio.ratio),
|
||||
},
|
||||
Fov::SpecifyYDeriveX{x,y}=>match x{
|
||||
DerivedFov::FromScreenAspect=>glam::dvec2(y*(screen_size.x as f64/screen_size.y as f64),*y),
|
||||
DerivedFov::FromAspect(ratio)=>glam::dvec2(y*ratio.ratio,*y),
|
||||
},
|
||||
}
|
||||
}
|
||||
pub fn calculate_sensitivity(&self)->Ratio64Vec2{
|
||||
match &self.sensitivity{
|
||||
Sensitivity::Exactly{x,y}=>Ratio64Vec2::new(x.clone(),y.clone()),
|
||||
Sensitivity::SpecifyXDeriveY{x,y}=>match y{
|
||||
DerivedSensitivity::FromRatio(ratio)=>Ratio64Vec2::new(x.clone(),x.mul_ref(ratio)),
|
||||
}
|
||||
Sensitivity::SpecifyYDeriveX{x,y}=>match x{
|
||||
DerivedSensitivity::FromRatio(ratio)=>Ratio64Vec2::new(y.mul_ref(ratio),y.clone()),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
//sensitivity is raw input dots (i.e. dpi = dots per inch) to radians conversion factor
|
||||
sensitivity_x=0.001
|
||||
sensitivity_y_from_x_ratio=1
|
||||
Sensitivity::DeriveY{x:0.0.001,y:DerivedSensitivity{ratio:1.0}}
|
||||
*/
|
||||
|
||||
pub fn read_user_settings()->UserSettings{
|
||||
let mut cfg=configparser::ini::Ini::new();
|
||||
if let Ok(_)=cfg.load("settings.conf"){
|
||||
let (cfg_fov_x,cfg_fov_y)=(cfg.getfloat("camera","fov_x"),cfg.getfloat("camera","fov_y"));
|
||||
let fov=match(cfg_fov_x,cfg_fov_y){
|
||||
(Ok(Some(fov_x)),Ok(Some(fov_y)))=>Fov::Exactly {
|
||||
x:fov_x,
|
||||
y:fov_y
|
||||
},
|
||||
(Ok(Some(fov_x)),Ok(None))=>Fov::SpecifyXDeriveY{
|
||||
x:fov_x,
|
||||
y:if let Ok(Some(fov_y_from_x_ratio))=cfg.getfloat("camera","fov_y_from_x_ratio"){
|
||||
DerivedFov::FromAspect(Ratio{ratio:fov_y_from_x_ratio})
|
||||
}else{
|
||||
DerivedFov::FromScreenAspect
|
||||
}
|
||||
},
|
||||
(Ok(None),Ok(Some(fov_y)))=>Fov::SpecifyYDeriveX{
|
||||
x:if let Ok(Some(fov_x_from_y_ratio))=cfg.getfloat("camera","fov_x_from_y_ratio"){
|
||||
DerivedFov::FromAspect(Ratio{ratio:fov_x_from_y_ratio})
|
||||
}else{
|
||||
DerivedFov::FromScreenAspect
|
||||
},
|
||||
y:fov_y,
|
||||
},
|
||||
_=>{
|
||||
Fov::default()
|
||||
},
|
||||
};
|
||||
let (cfg_sensitivity_x,cfg_sensitivity_y)=(cfg.getfloat("camera","sensitivity_x"),cfg.getfloat("camera","sensitivity_y"));
|
||||
let sensitivity=match(cfg_sensitivity_x,cfg_sensitivity_y){
|
||||
(Ok(Some(sensitivity_x)),Ok(Some(sensitivity_y)))=>Sensitivity::Exactly {
|
||||
x:Ratio64::try_from(sensitivity_x).unwrap(),
|
||||
y:Ratio64::try_from(sensitivity_y).unwrap(),
|
||||
},
|
||||
(Ok(Some(sensitivity_x)),Ok(None))=>Sensitivity::SpecifyXDeriveY{
|
||||
x:Ratio64::try_from(sensitivity_x).unwrap(),
|
||||
y:if let Ok(Some(sensitivity_y_from_x_ratio))=cfg.getfloat("camera","sensitivity_y_from_x_ratio"){
|
||||
DerivedSensitivity::FromRatio(Ratio64::try_from(sensitivity_y_from_x_ratio).unwrap())
|
||||
}else{
|
||||
DerivedSensitivity::FromRatio(Ratio64::ONE)
|
||||
},
|
||||
},
|
||||
(Ok(None),Ok(Some(sensitivity_y)))=>Sensitivity::SpecifyYDeriveX{
|
||||
x:if let Ok(Some(sensitivity_x_from_y_ratio))=cfg.getfloat("camera","sensitivity_x_from_y_ratio"){
|
||||
DerivedSensitivity::FromRatio(Ratio64::try_from(sensitivity_x_from_y_ratio).unwrap())
|
||||
}else{
|
||||
DerivedSensitivity::FromRatio(Ratio64::ONE)
|
||||
},
|
||||
y:Ratio64::try_from(sensitivity_y).unwrap(),
|
||||
},
|
||||
_=>{
|
||||
Sensitivity::default()
|
||||
},
|
||||
};
|
||||
UserSettings{
|
||||
fov,
|
||||
sensitivity,
|
||||
}
|
||||
}else{
|
||||
UserSettings::default()
|
||||
}
|
||||
}
|
@ -5,8 +5,8 @@ struct Camera {
|
||||
proj_inv: mat4x4<f32>,
|
||||
// from world to camera
|
||||
view: mat4x4<f32>,
|
||||
// from camera to world
|
||||
view_inv: mat4x4<f32>,
|
||||
// camera position
|
||||
cam_pos: vec4<f32>,
|
||||
};
|
||||
|
||||
//group 0 is the camera
|
||||
@ -31,7 +31,8 @@ fn vs_sky(@builtin(vertex_index) vertex_index: u32) -> SkyOutput {
|
||||
1.0
|
||||
);
|
||||
|
||||
let inv_model_view = mat3x3<f32>(camera.view_inv[0].xyz, camera.view_inv[1].xyz, camera.view_inv[2].xyz);
|
||||
// transposition = inversion for this orthonormal matrix
|
||||
let inv_model_view = transpose(mat3x3<f32>(camera.view[0].xyz, camera.view[1].xyz, camera.view[2].xyz));
|
||||
let unprojected = camera.proj_inv * pos;
|
||||
|
||||
var result: SkyOutput;
|
||||
@ -42,20 +43,20 @@ fn vs_sky(@builtin(vertex_index) vertex_index: u32) -> SkyOutput {
|
||||
|
||||
struct ModelInstance{
|
||||
transform:mat4x4<f32>,
|
||||
normal_transform:mat3x3<f32>,
|
||||
//texture_transform:mat3x3<f32>,
|
||||
color:vec4<f32>,
|
||||
}
|
||||
//my fancy idea is to create a megatexture for each model that includes all the textures each intance will need
|
||||
//the texture transform then maps the texture coordinates to the location of the specific texture
|
||||
//group 1 is the model
|
||||
const MAX_MODEL_INSTANCES=4096;
|
||||
@group(2)
|
||||
@group(1)
|
||||
@binding(0)
|
||||
var<uniform> model_instances: array<ModelInstance, MAX_MODEL_INSTANCES>;
|
||||
@group(2)
|
||||
@group(1)
|
||||
@binding(1)
|
||||
var model_texture: texture_2d<f32>;
|
||||
@group(2)
|
||||
@group(1)
|
||||
@binding(2)
|
||||
var model_sampler: sampler;
|
||||
|
||||
@ -65,7 +66,6 @@ struct EntityOutputTexture {
|
||||
@location(2) normal: vec3<f32>,
|
||||
@location(3) view: vec3<f32>,
|
||||
@location(4) color: vec4<f32>,
|
||||
@location(5) @interpolate(flat) model_color: vec4<f32>,
|
||||
};
|
||||
@vertex
|
||||
fn vs_entity_texture(
|
||||
@ -77,26 +77,25 @@ fn vs_entity_texture(
|
||||
) -> EntityOutputTexture {
|
||||
var position: vec4<f32> = model_instances[instance].transform * vec4<f32>(pos, 1.0);
|
||||
var result: EntityOutputTexture;
|
||||
result.normal = model_instances[instance].normal_transform * normal;
|
||||
result.texture = texture;
|
||||
result.color = color;
|
||||
result.model_color = model_instances[instance].color;
|
||||
result.view = position.xyz - camera.view_inv[3].xyz;//col(3)
|
||||
result.normal = (model_instances[instance].transform * vec4<f32>(normal, 0.0)).xyz;
|
||||
result.texture=texture;//(model_instances[instance].texture_transform * vec3<f32>(texture, 1.0)).xy;
|
||||
result.color=model_instances[instance].color * color;
|
||||
result.view = position.xyz - camera.cam_pos.xyz;
|
||||
result.position = camera.proj * camera.view * position;
|
||||
return result;
|
||||
}
|
||||
|
||||
//group 2 is the skybox texture
|
||||
@group(1)
|
||||
@group(2)
|
||||
@binding(0)
|
||||
var cube_texture: texture_cube<f32>;
|
||||
@group(1)
|
||||
@group(2)
|
||||
@binding(1)
|
||||
var cube_sampler: sampler;
|
||||
|
||||
@fragment
|
||||
fn fs_sky(vertex: SkyOutput) -> @location(0) vec4<f32> {
|
||||
return textureSample(cube_texture, cube_sampler, vertex.sampledir);
|
||||
return textureSample(cube_texture, model_sampler, vertex.sampledir);
|
||||
}
|
||||
|
||||
@fragment
|
||||
@ -108,5 +107,5 @@ fn fs_entity_texture(vertex: EntityOutputTexture) -> @location(0) vec4<f32> {
|
||||
|
||||
let fragment_color = textureSample(model_texture, model_sampler, vertex.texture)*vertex.color;
|
||||
let reflected_color = textureSample(cube_texture, cube_sampler, reflected).rgb;
|
||||
return mix(vec4<f32>(vec3<f32>(0.05) + 0.2 * reflected_color,1.0),mix(vertex.model_color,vec4<f32>(fragment_color.rgb,1.0),fragment_color.a),0.5+0.5*abs(d));
|
||||
return mix(vec4<f32>(vec3<f32>(0.1) + 0.5 * reflected_color,1.0),fragment_color,1.0-pow(1.0-abs(d),2.0));
|
||||
}
|
||||
|
107
src/worker.rs
107
src/worker.rs
@ -1,107 +0,0 @@
|
||||
use std::thread;
|
||||
use std::sync::{mpsc,Arc};
|
||||
use parking_lot::Mutex;
|
||||
|
||||
//The goal here is to have a worker thread that parks itself when it runs out of work.
|
||||
//The worker thread publishes the result of its work back to the worker object for every item in the work queue.
|
||||
//The physics (target use case) knows when it has not changed the body, so not updating the value is also an option.
|
||||
|
||||
pub struct Worker<Task:Send,Value:Clone> {
|
||||
sender: mpsc::Sender<Task>,
|
||||
value:Arc<Mutex<Value>>,
|
||||
}
|
||||
|
||||
impl<Task:Send+'static,Value:Clone+Send+'static> Worker<Task,Value> {
|
||||
pub fn new<F:FnMut(Task)->Value+Send+'static>(value:Value,mut f:F) -> Self {
|
||||
let (sender, receiver) = mpsc::channel::<Task>();
|
||||
let ret=Self {
|
||||
sender,
|
||||
value:Arc::new(Mutex::new(value)),
|
||||
};
|
||||
let value=ret.value.clone();
|
||||
thread::spawn(move || {
|
||||
loop {
|
||||
match receiver.recv() {
|
||||
Ok(task) => {
|
||||
let v=f(task);//make sure function is evaluated before lock is acquired
|
||||
*value.lock()=v;
|
||||
}
|
||||
Err(_) => {
|
||||
println!("Worker stopping.",);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
ret
|
||||
}
|
||||
|
||||
pub fn send(&self,task:Task)->Result<(), mpsc::SendError<Task>>{
|
||||
self.sender.send(task)
|
||||
}
|
||||
|
||||
pub fn grab_clone(&self)->Value{
|
||||
self.value.lock().clone()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct CompatWorker<Task,Value:Clone,F>{
|
||||
data:std::marker::PhantomData<Task>,
|
||||
f:F,
|
||||
value:Value,
|
||||
}
|
||||
|
||||
impl<Task,Value:Clone,F:FnMut(Task)->Value> CompatWorker<Task,Value,F> {
|
||||
pub fn new(value:Value,f:F) -> Self {
|
||||
Self {
|
||||
f,
|
||||
value,
|
||||
data:std::marker::PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn send(&mut self,task:Task)->Result<(),()>{
|
||||
self.value=(self.f)(task);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn grab_clone(&self)->Value{
|
||||
self.value.clone()
|
||||
}
|
||||
}
|
||||
|
||||
#[test]//How to run this test with printing: cargo test --release -- --nocapture
|
||||
fn test_worker() {
|
||||
println!("hiiiii");
|
||||
// Create the worker thread
|
||||
let worker = Worker::new(crate::physics::Body::with_pva(crate::integer::Planar64Vec3::ZERO,crate::integer::Planar64Vec3::ZERO,crate::integer::Planar64Vec3::ZERO),
|
||||
|_|crate::physics::Body::with_pva(crate::integer::Planar64Vec3::ONE,crate::integer::Planar64Vec3::ONE,crate::integer::Planar64Vec3::ONE)
|
||||
);
|
||||
|
||||
// Send tasks to the worker
|
||||
for _ in 0..5 {
|
||||
let task = crate::instruction::TimedInstruction{
|
||||
time:crate::integer::Time::ZERO,
|
||||
instruction:crate::physics::PhysicsInstruction::StrafeTick,
|
||||
};
|
||||
worker.send(task).unwrap();
|
||||
}
|
||||
|
||||
// Optional: Signal the worker to stop (in a real-world scenario)
|
||||
// sender.send("STOP".to_string()).unwrap();
|
||||
|
||||
// Sleep to allow the worker thread to finish processing
|
||||
thread::sleep(std::time::Duration::from_secs(2));
|
||||
|
||||
// Send a new task
|
||||
let task = crate::instruction::TimedInstruction{
|
||||
time:crate::integer::Time::ZERO,
|
||||
instruction:crate::physics::PhysicsInstruction::StrafeTick,
|
||||
};
|
||||
worker.send(task).unwrap();
|
||||
|
||||
println!("value={:?}",worker.grab_clone());
|
||||
|
||||
// wait long enough to see print from final task
|
||||
thread::sleep(std::time::Duration::from_secs(1));
|
||||
}
|
@ -1,30 +1,26 @@
|
||||
//find roots of polynomials
|
||||
use crate::integer::Planar64;
|
||||
|
||||
#[inline]
|
||||
pub fn zeroes2(a0:Planar64,a1:Planar64,a2:Planar64) -> Vec<Planar64>{
|
||||
if a2==Planar64::ZERO{
|
||||
pub fn zeroes2(a0:f32,a1:f32,a2:f32) -> Vec<f32>{
|
||||
if a2==0f32{
|
||||
return zeroes1(a0, a1);
|
||||
}
|
||||
let mut radicand=a1.get() as i128*a1.get() as i128-a2.get() as i128*a0.get() as i128*4;
|
||||
if 0<radicand {
|
||||
//start with f64 sqrt
|
||||
let planar_radicand=Planar64::raw(unsafe{(radicand as f64).sqrt().to_int_unchecked()});
|
||||
//TODO: one or two newtons
|
||||
if Planar64::ZERO<a2 {
|
||||
return vec![(-a1-planar_radicand)/(a2*2),(-a1+planar_radicand)/(a2*2)];
|
||||
let mut radicand=a1*a1-4f32*a2*a0;
|
||||
if 0f32<radicand {
|
||||
radicand=radicand.sqrt();
|
||||
if 0f32<a2 {
|
||||
return vec![(-a1-radicand)/(2f32*a2),(-a1+radicand)/(2f32*a2)];
|
||||
} else {
|
||||
return vec![(-a1+planar_radicand)/(a2*2),(-a1-planar_radicand)/(a2*2)];
|
||||
return vec![(-a1+radicand)/(2f32*a2),(-a1-radicand)/(2f32*a2)];
|
||||
}
|
||||
} else if radicand==0 {
|
||||
return vec![a1/(a2*-2)];
|
||||
} else if radicand==0f32 {
|
||||
return vec![-a1/(2f32*a2)];
|
||||
} else {
|
||||
return vec![];
|
||||
}
|
||||
}
|
||||
#[inline]
|
||||
pub fn zeroes1(a0:Planar64,a1:Planar64) -> Vec<Planar64> {
|
||||
if a1==Planar64::ZERO{
|
||||
pub fn zeroes1(a0:f32,a1:f32) -> Vec<f32> {
|
||||
if a1==0f32{
|
||||
return vec![];
|
||||
} else {
|
||||
return vec![-a0/a1];
|
||||
|
Reference in New Issue
Block a user