Compare commits

..

6 Commits

Author SHA1 Message Date
f0479181f6 do not step physics on mouse input, only update pos (overwriting previous pos) 2023-09-21 16:39:51 -07:00
971aa9e287 fix angles 2023-09-21 16:39:51 -07:00
6a79c4ec24 accumulate deltas 2023-09-21 16:39:51 -07:00
9025bea5ef implement jump() + remove jump_trying + prevent air jumping 2023-09-21 16:39:51 -07:00
6ed71073f6 wip 2 2023-09-21 16:39:51 -07:00
91bfa70f05 wip 2023-09-21 16:38:47 -07:00
14 changed files with 715 additions and 2546 deletions

1
.gitignore vendored

@ -1,2 +1 @@
/target
/textures

50
Cargo.lock generated

@ -834,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"
@ -1465,20 +1442,6 @@ dependencies = [
"thiserror",
]
[[package]]
name = "rbx_xml"
version = "0.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4bc65b70827519fdc4ab47416d1085b912f087fadab9ed415471b6daba635574"
dependencies = [
"base64",
"log",
"rbx_dom_weak",
"rbx_reflection",
"rbx_reflection_database",
"xml-rs",
]
[[package]]
name = "redox_syscall"
version = "0.3.5"
@ -1490,9 +1453,9 @@ dependencies = [
[[package]]
name = "regex"
version = "1.9.5"
version = "1.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "697061221ea1b4a94a624f67d0ae2bfe4e22b8a17b6a192afb11046542cc8c47"
checksum = "12de2eff854e5fa4b1295edd650e227e9d8fb0c9e90b12e7f36d6a6811791a29"
dependencies = [
"aho-corasick",
"memchr",
@ -1502,9 +1465,9 @@ dependencies = [
[[package]]
name = "regex-automata"
version = "0.3.8"
version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c2f401f4955220693b56f8ec66ee9c78abffd8d1c4f23dc41a23839eb88f0795"
checksum = "49530408a136e16e5b486e883fbb6ba058e8e4e8ae6621a77b048b314336e629"
dependencies = [
"aho-corasick",
"memchr",
@ -1682,22 +1645,19 @@ checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
[[package]]
name = "strafe-client"
version = "0.7.0"
version = "0.5.0"
dependencies = [
"async-executor",
"bytemuck",
"ddsfile",
"env_logger",
"glam",
"lazy-regex",
"log",
"obj",
"parking_lot",
"pollster",
"rbx_binary",
"rbx_dom_weak",
"rbx_reflection_database",
"rbx_xml",
"wgpu",
"winit",
]

@ -1,6 +1,6 @@
[package]
name = "strafe-client"
version = "0.7.0"
version = "0.5.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
@ -11,19 +11,16 @@ bytemuck = { version = "1.13.1", features = ["derive"] }
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"
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

@ -12,7 +12,7 @@ pub enum PhysicsInstruction {
// bool,//true = Trigger; false = teleport
// bool,//true = Force
// )
//InputInstructions conditionally activate RefreshWalkTarget (by doing what SetWalkTargetVelocity used to do and then flagging it)
//Both of these conditionally activate RefreshWalkTarget (by doing what SetWalkTargetVelocity used to do and then flagging it)
Input(InputInstruction),
}
#[derive(Debug)]
@ -27,12 +27,8 @@ pub enum InputInstruction {
Jump(bool),
Zoom(bool),
Reset,
Idle,
//Idle: there were no input events, but the simulation is safe to advance to this timestep
//for interpolation / networking / playback reasons, most playback heads will always want
//to be 1 instruction ahead to generate the next state for interpolation.
}
#[derive(Clone)]
pub struct Body {
position: glam::Vec3,//I64 where 2^32 = 1 u
velocity: glam::Vec3,//I64 where 2^32 = 1 u/s
@ -43,20 +39,20 @@ trait MyHash{
fn hash(&self) -> u64;
}
impl MyHash for Body {
fn hash(&self) -> u64 {
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());
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 {
@ -78,9 +74,9 @@ impl InputState {
}
impl crate::instruction::InstructionEmitter<InputInstruction> for InputState{
fn next_instruction(&self, time_limit:crate::body::TIME) -> Option<TimedInstruction<InputInstruction>> {
//this is polled by PhysicsState for actions like Jump
//no, it has to be the other way around. physics is run up until the jump instruction, and then the jump instruction is pushed.
self.queue.get(0)
//this is polled by PhysicsState for actions like Jump
//no, it has to be the other way around. physics is run up until the jump instruction, and then the jump instruction is pushed.
self.queue.get(0)
}
}
impl crate::instruction::InstructionConsumer<InputInstruction> for InputState{
@ -91,33 +87,47 @@ impl crate::instruction::InstructionConsumer<InputInstruction> for InputState{
}
*/
//hey dumbass just use a delta
#[derive(Clone)]
pub struct MouseState {
pub pos: glam::IVec2,
pub time: TIME,
enum MouseInterpolation {
First,//just checks the last value
Lerp,//lerps between
}
impl Default for MouseState{
fn default() -> Self {
pub struct MouseInterpolationState {
interpolation: MouseInterpolation,
time0: TIME,
time1: TIME,
mouse0: glam::IVec2,
mouse1: glam::IVec2,
}
impl MouseInterpolationState {
pub fn new() -> Self {
Self {
time:0,
pos:glam::IVec2::ZERO,
interpolation:MouseInterpolation::First,
time0:0,
time1:1,//ONE NANOSECOND!!!! avoid divide by zero
mouse0:glam::IVec2::ZERO,
mouse1:glam::IVec2::ZERO,
}
}
}
impl MouseState {
pub fn move_mouse(&mut self,pos:glam::IVec2,time:TIME){
self.time=time;
self.pos=pos;
pub fn move_mouse(&mut self,time:TIME,delta:glam::IVec2){
self.time0=self.time1;
self.mouse0=self.mouse1;
self.time1=time;
self.mouse1=self.mouse1+delta;
}
pub fn lerp(&self,target:&MouseState,time:TIME)->glam::IVec2 {
let m0=self.pos.as_i64vec2();
let m1=target.pos.as_i64vec2();
//these are deltas
let t1t=(target.time-time) as i64;
let tt0=(time-self.time) as i64;
let dt=(target.time-self.time) as i64;
((m0*t1t+m1*tt0)/dt).as_ivec2()
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()
}
}
}
}
@ -140,168 +150,126 @@ impl WalkState {
}
}
#[derive(Clone)]
pub struct PhysicsCamera {
// Note: we use the Y=up coordinate space in this example.
pub struct Camera {
offset: glam::Vec3,
angles: glam::DVec2,//YAW AND THEN PITCH
//punch: glam::Vec3,
//punch_velocity: glam::Vec3,
fov: glam::Vec2,//slope
sensitivity: glam::DVec2,
mouse:MouseState,
time: TIME,
}
#[inline]
fn mat3_from_rotation_y_f64(angle: f64) -> glam::Mat3 {
let (sina, cosa) = angle.sin_cos();
glam::Mat3::from_cols(
glam::Vec3::new(cosa as f32, 0.0, -sina as f32),
glam::Vec3::Y,
glam::Vec3::new(sina as f32, 0.0, cosa as f32),
let (sina, cosa) = angle.sin_cos();
glam::Mat3::from_cols(
glam::Vec3::new(cosa as f32, 0.0, -sina as f32),
glam::Vec3::Y,
glam::Vec3::new(sina as f32, 0.0, cosa as f32),
)
}
#[inline]
fn perspective_rh(fov_x_slope: f32, fov_y_slope: f32, z_near: f32, z_far: f32) -> glam::Mat4 {
//glam_assert!(z_near > 0.0 && z_far > 0.0);
let r = z_far / (z_near - z_far);
glam::Mat4::from_cols(
glam::Vec4::new(1.0/fov_x_slope, 0.0, 0.0, 0.0),
glam::Vec4::new(0.0, 1.0/fov_y_slope, 0.0, 0.0),
glam::Vec4::new(0.0, 0.0, r, -1.0),
glam::Vec4::new(0.0, 0.0, r * z_near, 0.0),
)
}
impl PhysicsCamera {
pub fn from_offset(offset:glam::Vec3) -> Self {
impl Camera {
pub fn from_offset(offset:glam::Vec3,aspect:f32) -> Self {
Self{
offset,
angles: glam::DVec2::ZERO,
sensitivity: glam::dvec2(1.0/16384.0,1.0/16384.0),
mouse:MouseState{pos:glam::IVec2::ZERO,time:-1},//escape initialization hell divide by zero
offset,
angles: glam::DVec2::ZERO,
fov: glam::vec2(aspect,1.0),
sensitivity: glam::dvec2(1.0/2048.0,1.0/2048.0),
time: 0,
}
}
pub fn simulate_move_angles(&self, mouse_pos: glam::IVec2) -> glam::DVec2 {
let mut a=self.angles-self.sensitivity*(mouse_pos-self.mouse.pos).as_dvec2();
a.y=a.y.clamp(-std::f64::consts::FRAC_PI_2, std::f64::consts::FRAC_PI_2);
fn simulate_move_angles(&self, delta: glam::IVec2) -> glam::DVec2 {
let mut a=self.angles-self.sensitivity*delta.as_dvec2();
a.y=a.y.clamp(-std::f64::consts::PI, std::f64::consts::PI);
return a
}
fn simulate_move_rotation_y(&self, mouse_pos_x: i32) -> glam::Mat3 {
mat3_from_rotation_y_f64(self.angles.x-self.sensitivity.x*((mouse_pos_x-self.mouse.pos.x) as f64))
fn simulate_move_rotation_y(&self, delta_x: i32) -> glam::Mat3 {
mat3_from_rotation_y_f64(self.angles.x-self.sensitivity.x*(delta_x as f64))
}
pub fn proj(&self)->glam::Mat4{
perspective_rh(self.fov.x, self.fov.y, 0.5, 1000.0)
}
pub fn view(&self,pos:glam::Vec3)->glam::Mat4{
//f32 good enough for view matrix
glam::Mat4::from_translation(pos+self.offset) * glam::Mat4::from_euler(glam::EulerRot::YXZ, self.angles.x as f32, self.angles.y as f32, 0f32)
}
pub fn set_fov_aspect(&mut self,fov:f32,aspect:f32){
self.fov.x=fov*aspect;
self.fov.y=fov;
}
}
pub struct GameMechanicsState{
pub stage_id:u32,
//jump_counts:HashMap<u32,u32>,
}
impl std::default::Default for GameMechanicsState{
fn default() -> Self {
Self{
stage_id:0,
}
const CONTROL_MOVEFORWARD:u32 = 0b00000001;
const CONTROL_MOVEBACK:u32 = 0b00000010;
const CONTROL_MOVERIGHT:u32 = 0b00000100;
const CONTROL_MOVELEFT:u32 = 0b00001000;
const CONTROL_MOVEUP:u32 = 0b00010000;
const CONTROL_MOVEDOWN:u32 = 0b00100000;
const CONTROL_JUMP:u32 = 0b01000000;
const CONTROL_ZOOM:u32 = 0b10000000;
const FORWARD_DIR:glam::Vec3 = glam::Vec3::new(0.0,0.0,-1.0);
const RIGHT_DIR:glam::Vec3 = glam::Vec3::new(1.0,0.0,0.0);
const UP_DIR:glam::Vec3 = glam::Vec3::new(0.0,1.0,0.0);
fn get_control_dir(controls: u32) -> glam::Vec3{
//don't get fancy just do it
let mut control_dir:glam::Vec3 = glam::Vec3::new(0.0,0.0,0.0);
if controls & CONTROL_MOVEFORWARD == CONTROL_MOVEFORWARD {
control_dir+=FORWARD_DIR;
}
}
pub struct WorldState{}
pub struct StyleModifiers{
pub controls_mask:u32,//controls which are unable to be activated
pub controls_held:u32,//controls which must be active to be able to strafe
pub mv:f32,
pub walkspeed:f32,
pub friction:f32,
pub walk_accel:f32,
pub gravity:glam::Vec3,
pub strafe_tick_num:TIME,
pub strafe_tick_den:TIME,
pub hitbox_halfsize:glam::Vec3,
}
impl std::default::Default for StyleModifiers{
fn default() -> Self {
Self{
controls_mask: !0,//&!(Self::CONTROL_MOVEUP|Self::CONTROL_MOVEDOWN),
controls_held: 0,
strafe_tick_num: 100,//100t
strafe_tick_den: 1_000_000_000,
gravity: glam::vec3(0.0,-100.0,0.0),
friction: 1.2,
walk_accel: 90.0,
mv: 2.7,
walkspeed: 18.0,
hitbox_halfsize: glam::vec3(1.0,2.5,1.0),
}
if controls & CONTROL_MOVEBACK == CONTROL_MOVEBACK {
control_dir+=-FORWARD_DIR;
}
}
impl StyleModifiers{
const CONTROL_MOVEFORWARD:u32 = 0b00000001;
const CONTROL_MOVEBACK:u32 = 0b00000010;
const CONTROL_MOVERIGHT:u32 = 0b00000100;
const CONTROL_MOVELEFT:u32 = 0b00001000;
const CONTROL_MOVEUP:u32 = 0b00010000;
const CONTROL_MOVEDOWN:u32 = 0b00100000;
const CONTROL_JUMP:u32 = 0b01000000;
const CONTROL_ZOOM:u32 = 0b10000000;
const FORWARD_DIR:glam::Vec3 = glam::Vec3::NEG_Z;
const RIGHT_DIR:glam::Vec3 = glam::Vec3::X;
const UP_DIR:glam::Vec3 = glam::Vec3::Y;
fn get_control(&self,control:u32,controls:u32)->bool{
controls&self.controls_mask&control==control
if controls & CONTROL_MOVELEFT == CONTROL_MOVELEFT {
control_dir+=-RIGHT_DIR;
}
fn get_control_dir(&self,controls:u32)->glam::Vec3{
//don't get fancy just do it
let mut control_dir:glam::Vec3 = glam::Vec3::ZERO;
//Disallow strafing if held controls are not held
if controls&self.controls_held!=self.controls_held{
return control_dir;
}
//Apply mask after held check so you can require non-allowed keys to be held for some reason
let controls=controls&self.controls_mask;
if controls & Self::CONTROL_MOVEFORWARD == Self::CONTROL_MOVEFORWARD {
control_dir+=Self::FORWARD_DIR;
}
if controls & Self::CONTROL_MOVEBACK == Self::CONTROL_MOVEBACK {
control_dir+=-Self::FORWARD_DIR;
}
if controls & Self::CONTROL_MOVELEFT == Self::CONTROL_MOVELEFT {
control_dir+=-Self::RIGHT_DIR;
}
if controls & Self::CONTROL_MOVERIGHT == Self::CONTROL_MOVERIGHT {
control_dir+=Self::RIGHT_DIR;
}
if controls & Self::CONTROL_MOVEUP == Self::CONTROL_MOVEUP {
control_dir+=Self::UP_DIR;
}
if controls & Self::CONTROL_MOVEDOWN == Self::CONTROL_MOVEDOWN {
control_dir+=-Self::UP_DIR;
}
return control_dir
if controls & CONTROL_MOVERIGHT == CONTROL_MOVERIGHT {
control_dir+=RIGHT_DIR;
}
if controls & CONTROL_MOVEUP == CONTROL_MOVEUP {
control_dir+=UP_DIR;
}
if controls & CONTROL_MOVEDOWN == CONTROL_MOVEDOWN {
control_dir+=-UP_DIR;
}
return control_dir
}
pub struct PhysicsState{
pub time:TIME,
pub body:Body,
pub world:WorldState,//currently there is only one state the world can be in
pub game:GameMechanicsState,
pub style:StyleModifiers,
pub contacts:std::collections::HashMap::<u32,RelativeCollision>,
pub intersects:std::collections::HashMap::<u32,RelativeCollision>,
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>,
//camera must exist in state because wormholes modify the camera, also camera punch
pub camera:PhysicsCamera,
pub next_mouse:MouseState,//Where is the mouse headed next
pub controls:u32,
pub walk:WalkState,
pub grounded:bool,
//all models
pub models:Vec<ModelPhysics>,
pub modes:Vec<crate::model::ModeDescription>,
pub mode_from_mode_id:std::collections::HashMap::<u32,usize>,
//the spawn point is where you spawn when you load into the map.
//This is not the same as Reset which teleports you to Spawn0
pub spawn_point:glam::Vec3,
}
#[derive(Clone)]
pub struct PhysicsOutputState{
camera:PhysicsCamera,
body:Body,
}
impl PhysicsOutputState{
pub fn adjust_mouse(&self,mouse:&MouseState)->(glam::Vec3,glam::Vec2){
(self.body.extrapolated_position(mouse.time),self.camera.simulate_move_angles(mouse.pos).as_vec2())
}
pub camera: Camera,
pub mouse_interpolation: MouseInterpolationState,
pub controls: u32,
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,
}
#[derive(Debug,Clone,Copy,Hash,Eq,PartialEq)]
@ -313,7 +281,7 @@ pub enum AabbFace{
Bottom,
Front,
}
#[derive(Clone)]
pub struct Aabb {
min: glam::Vec3,
max: glam::Vec3,
@ -413,55 +381,31 @@ impl Aabb {
type TreyMeshFace = AabbFace;
type TreyMesh = Aabb;
enum PhysicsCollisionAttributes{
Contact{//track whether you are contacting the object
contacting:crate::model::ContactingAttributes,
general:crate::model::GameMechanicAttributes,
},
Intersect{//track whether you are intersecting the object
intersecting:crate::model::IntersectingAttributes,
general:crate::model::GameMechanicAttributes,
},
}
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.
mesh: TreyMesh,
transform:glam::Affine3A,
attributes:PhysicsCollisionAttributes,
transform: glam::Mat4,
}
impl ModelPhysics {
fn from_model_transform_attributes(model:&crate::model::IndexedModel,transform:&glam::Affine3A,attributes:PhysicsCollisionAttributes)->Self{
let mut aabb=Aabb::new();
for indexed_vertex in &model.unique_vertices {
aabb.grow(transform.transform_point3(glam::Vec3::from_array(model.unique_pos[indexed_vertex.pos as usize])));
}
Self{
mesh:aabb,
attributes,
transform:transform.clone(),
}
}
pub fn from_model(model:&crate::model::IndexedModel,instance:&crate::model::ModelInstance) -> Option<Self> {
match &instance.attributes{
crate::model::CollisionAttributes::Contact{contacting,general}=>Some(ModelPhysics::from_model_transform_attributes(model,&instance.transform,PhysicsCollisionAttributes::Contact{contacting:contacting.clone(),general:general.clone()})),
crate::model::CollisionAttributes::Intersect{intersecting,general}=>Some(ModelPhysics::from_model_transform_attributes(model,&instance.transform,PhysicsCollisionAttributes::Intersect{intersecting:intersecting.clone(),general:general.clone()})),
crate::model::CollisionAttributes::Decoration=>None,
}
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 {
return &self.mesh;
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.clone();
let mut aabb=self.mesh();
//in this implementation face = worldspace aabb face
match face {
AabbFace::Right => aabb.min.x=aabb.max.x,
@ -474,7 +418,7 @@ impl ModelPhysics {
return aabb;
}
pub fn face_normal(&self,face:TreyMeshFace) -> glam::Vec3 {
Aabb::normal(face)//this is wrong for scale
glam::Vec4Swizzles::xyz(Aabb::normal(face).extend(0.0))//this is wrong for scale
}
}
@ -487,14 +431,11 @@ pub struct RelativeCollision {
}
impl RelativeCollision {
pub fn model<'a>(&self,models:&'a Vec<ModelPhysics>)->Option<&'a ModelPhysics>{
models.get(self.model as usize)
}
pub fn mesh(&self,models:&Vec<ModelPhysics>) -> TreyMesh {
return self.model(models).unwrap().face_mesh(self.face).clone()
return models.get(self.model as usize).unwrap().face_mesh(self.face)
}
pub fn normal(&self,models:&Vec<ModelPhysics>) -> glam::Vec3 {
return self.model(models).unwrap().face_normal(self.face)
return models.get(self.model as usize).unwrap().face_normal(self.face)
}
}
@ -524,183 +465,7 @@ impl Body {
}
}
impl Default for PhysicsState{
fn default() -> Self {
Self{
spawn_point:glam::vec3(0.0,50.0,0.0),
body: Body::with_pva(glam::vec3(0.0,50.0,0.0),glam::vec3(0.0,0.0,0.0),glam::vec3(0.0,-100.0,0.0)),
time: 0,
style:StyleModifiers::default(),
grounded: false,
contacts: std::collections::HashMap::new(),
intersects: std::collections::HashMap::new(),
models: Vec::new(),
walk: WalkState::new(),
camera: PhysicsCamera::from_offset(glam::vec3(0.0,4.5-2.5,0.0)),
next_mouse: MouseState::default(),
controls: 0,
world:WorldState{},
game:GameMechanicsState::default(),
modes:Vec::new(),
mode_from_mode_id:std::collections::HashMap::new(),
}
}
}
impl PhysicsState {
pub fn clear(&mut self){
self.models.clear();
self.modes.clear();
self.contacts.clear();
self.intersects.clear();
}
pub fn into_worker(mut self)->crate::worker::Worker<TimedInstruction<InputInstruction>,PhysicsOutputState>{
let mut last_time=0;
//last_time: this indicates the last time the mouse position was known.
//Only used to generate a MouseState right before mouse movement
//to finalize a long period of no movement and avoid interpolating from a long out-of-date MouseState.
let mut mouse_blocking=true;//waiting for next_mouse to be written
let mut timeline=std::collections::VecDeque::new();
crate::worker::Worker::new(self.output(),move |ins:TimedInstruction<InputInstruction>|{
let run_queue=match &ins.instruction{
InputInstruction::MoveMouse(_)=>{
//I FORGOT TO EDIT THE MOVE MOUSE TIMESTAMPS
if !mouse_blocking{
//mouse has not been moving for a while.
//make sure not to interpolate between two distant MouseStates.
//generate a mouse instruction with no movement timestamped at last InputInstruction
//Idle instructions are CRITICAL to keeping this value up to date
//interpolate normally (now that prev mouse pos is up to date)
// timeline.push_back(TimedInstruction{
// time:last_time,
// instruction:InputInstruction::MoveMouse(self.next_mouse.pos),
// });
}
mouse_blocking=true;//block physics until the next mouse event or mouse event timeout.
true//empty queue
},
_=>{
if mouse_blocking{
//maybe I can turn this inside out by making this anotehr state machine where 50_000_000 is an instruction timestamp
//check if last mouse move is within 50ms
if ins.time-self.next_mouse.time<50_000_000{
false//do not empty queue
}else{
mouse_blocking=false;
// timeline.push_back(TimedInstruction{
// time:ins.time,
// instruction:InputInstruction::MoveMouse(self.next_mouse.pos),
// });
true
}
}else{
true
}
},
};
last_time=ins.time;
timeline.push_back(ins);
if run_queue{
//empty queue
while let Some(instruction)=timeline.pop_front(){
self.run(instruction.time);
self.process_instruction(TimedInstruction{
time:instruction.time,
instruction:PhysicsInstruction::Input(instruction.instruction),
});
}
}
self.output()
})
}
pub fn output(&self)->PhysicsOutputState{
PhysicsOutputState{
body:self.body.clone(),
camera:self.camera.clone(),
}
}
pub fn generate_models(&mut self,indexed_models:&crate::model::IndexedModelInstances){
let mut starts=Vec::new();
let mut spawns=Vec::new();
let mut ordered_checkpoints=Vec::new();
let mut unordered_checkpoints=Vec::new();
for model in &indexed_models.models{
//make aabb and run vertices to get realistic bounds
for model_instance in &model.instances{
if let Some(model_physics)=ModelPhysics::from_model(model,model_instance){
let model_id=self.models.len() as u32;
self.models.push(model_physics);
for attr in &model_instance.temp_indexing{
match attr{
crate::model::TempIndexedAttributes::Start{mode_id}=>starts.push((*mode_id,model_id)),
crate::model::TempIndexedAttributes::Spawn{mode_id,stage_id}=>spawns.push((*mode_id,model_id,*stage_id)),
crate::model::TempIndexedAttributes::OrderedCheckpoint{mode_id,checkpoint_id}=>ordered_checkpoints.push((*mode_id,model_id,*checkpoint_id)),
crate::model::TempIndexedAttributes::UnorderedCheckpoint{mode_id}=>unordered_checkpoints.push((*mode_id,model_id)),
}
}
}
}
}
//I don't wanna write structs for temporary structures
//this code builds ModeDescriptions from the unsorted lists at the top of the function
starts.sort_by_key(|tup|tup.0);
let mut eshmep=std::collections::HashMap::new();
let mut modedatas:Vec<(u32,Vec<(u32,u32)>,Vec<(u32,u32)>,Vec<u32>)>=starts.into_iter().enumerate().map(|(i,tup)|{
eshmep.insert(tup.0,i);
(tup.1,Vec::new(),Vec::new(),Vec::new())
}).collect();
for tup in spawns{
if let Some(mode_id)=eshmep.get(&tup.0){
if let Some(modedata)=modedatas.get_mut(*mode_id){
modedata.1.push((tup.2,tup.1));
}
}
}
for tup in ordered_checkpoints{
if let Some(mode_id)=eshmep.get(&tup.0){
if let Some(modedata)=modedatas.get_mut(*mode_id){
modedata.2.push((tup.2,tup.1));
}
}
}
for tup in unordered_checkpoints{
if let Some(mode_id)=eshmep.get(&tup.0){
if let Some(modedata)=modedatas.get_mut(*mode_id){
modedata.3.push(tup.1);
}
}
}
let num_modes=self.modes.len();
for (mode_id,mode) in eshmep{
self.mode_from_mode_id.insert(mode_id,num_modes+mode);
}
self.modes.append(&mut modedatas.into_iter().map(|mut tup|{
tup.1.sort_by_key(|tup|tup.0);
tup.2.sort_by_key(|tup|tup.0);
let mut eshmep1=std::collections::HashMap::new();
let mut eshmep2=std::collections::HashMap::new();
crate::model::ModeDescription{
start:tup.0,
spawns:tup.1.into_iter().enumerate().map(|(i,tup)|{eshmep1.insert(tup.0,i);tup.1}).collect(),
ordered_checkpoints:tup.2.into_iter().enumerate().map(|(i,tup)|{eshmep2.insert(tup.0,i);tup.1}).collect(),
unordered_checkpoints:tup.3,
spawn_from_stage_id:eshmep1,
ordered_checkpoint_from_checkpoint_id:eshmep2,
}
}).collect());
println!("Physics Objects: {}",self.models.len());
}
pub fn get_mode(&self,mode_id:u32)->Option<&crate::model::ModeDescription>{
if let Some(&mode)=self.mode_from_mode_id.get(&mode_id){
self.modes.get(mode)
}else{
None
}
}
//tickless gaming
pub fn run(&mut self, time_limit:TIME){
//prepare is ommitted - everything is done via instructions.
@ -727,7 +492,7 @@ impl PhysicsState {
}
fn contact_constrain_velocity(&self,velocity:&mut glam::Vec3){
for (_,contact) in &self.contacts {
for contact in self.contacts.iter() {
let n=contact.normal(&self.models);
let d=velocity.dot(n);
if d<0f32{
@ -736,7 +501,7 @@ impl PhysicsState {
}
}
fn contact_constrain_acceleration(&self,acceleration:&mut glam::Vec3){
for (_,contact) in &self.contacts {
for contact in self.contacts.iter() {
let n=contact.normal(&self.models);
let d=acceleration.dot(n);
if d<0f32{
@ -746,7 +511,7 @@ impl PhysicsState {
}
fn next_strafe_instruction(&self) -> Option<TimedInstruction<PhysicsInstruction>> {
return Some(TimedInstruction{
time:(self.time*self.style.strafe_tick_num/self.style.strafe_tick_den+1)*self.style.strafe_tick_den/self.style.strafe_tick_num,
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
});
@ -796,7 +561,7 @@ impl PhysicsState {
self.body.acceleration=a;
self.walk.state=WalkEnum::Reached;
}else{
let accel=self.style.walk_accel.min(self.style.gravity.length()*self.style.friction);
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);
@ -825,7 +590,7 @@ impl PhysicsState {
fn mesh(&self) -> TreyMesh {
let mut aabb=Aabb::new();
for vertex in Aabb::unit_vertices(){
aabb.grow(self.body.position+self.style.hitbox_halfsize*vertex);
aabb.grow(self.body.position+self.hitbox_halfsize*vertex);
}
aabb
}
@ -1109,19 +874,12 @@ impl crate::instruction::InstructionEmitter<PhysicsInstruction> for PhysicsState
//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 {
for collision_data in self.contacts.iter() {
collector.collect(self.predict_collision_end(self.time,time_limit,collision_data));
}
// for collision_data in &self.intersects{
// collector.collect(self.predict_collision_end2(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() {
let i=i as u32;
if self.contacts.contains_key(&i)||self.intersects.contains_key(&i){
continue;
}
collector.collect(self.predict_collision_start(self.time,time_limit,i));
collector.collect(self.predict_collision_start(self.time,time_limit,i as u32));
}
if self.grounded {
//walk maintenance
@ -1137,116 +895,59 @@ impl crate::instruction::InstructionEmitter<PhysicsInstruction> for PhysicsState
impl crate::instruction::InstructionConsumer<PhysicsInstruction> for PhysicsState {
fn process_instruction(&mut self, ins:TimedInstruction<PhysicsInstruction>) {
match &ins.instruction {
PhysicsInstruction::Input(InputInstruction::Idle)
|PhysicsInstruction::Input(InputInstruction::MoveMouse(_))
|PhysicsInstruction::StrafeTick => (),
_=>println!("{:?}",ins),
PhysicsInstruction::StrafeTick => (),
PhysicsInstruction::Input(InputInstruction::MoveMouse(_)) => (),
_=>println!("{:?}",ins),
}
//selectively update body
match &ins.instruction {
//PhysicsInstruction::Input(InputInstruction::MoveMouse(_)) => (),//dodge time for mouse movement
PhysicsInstruction::Input(_)
|PhysicsInstruction::ReachWalkTargetVelocity
|PhysicsInstruction::CollisionStart(_)
|PhysicsInstruction::CollisionEnd(_)
|PhysicsInstruction::StrafeTick => self.advance_time(ins.time),
PhysicsInstruction::Input(InputInstruction::MoveMouse(_)) => (),//dodge time for mouse movement
PhysicsInstruction::Input(_)
|PhysicsInstruction::ReachWalkTargetVelocity
|PhysicsInstruction::CollisionStart(_)
|PhysicsInstruction::CollisionEnd(_)
|PhysicsInstruction::StrafeTick => self.advance_time(ins.time),
}
match ins.instruction {
PhysicsInstruction::CollisionStart(c) => {
let model=c.model(&self.models).unwrap();
match &model.attributes{
PhysicsCollisionAttributes::Contact{contacting,general}=>{
match &contacting.surf{
Some(surf)=>println!("I'm surfing!"),
None=>match &c.face {
AabbFace::Top => {
//ground
self.grounded=true;
},
_ => (),
},
}
//check ground
self.contacts.insert(c.model,c);
match &general.stage_element{
Some(stage_element)=>{
if stage_element.force||self.game.stage_id<stage_element.stage_id{
self.game.stage_id=stage_element.stage_id;
}
match stage_element.behaviour{
crate::model::StageElementBehaviour::SpawnAt=>(),
crate::model::StageElementBehaviour::Trigger
|crate::model::StageElementBehaviour::Teleport=>{
//TODO make good
if let Some(mode)=self.get_mode(stage_element.mode_id){
if let Some(&spawn)=mode.get_spawn_model_id(self.game.stage_id){
if let Some(model)=self.models.get(spawn as usize){
self.body.position=model.transform.transform_point3(glam::Vec3::Y)+glam::Vec3::Y*(self.style.hitbox_halfsize.y+0.1);
//manual clear //for c in self.contacts{process_instruction(CollisionEnd(c))}
self.contacts.clear();
self.intersects.clear();
self.body.acceleration=self.style.gravity;
self.walk.state=WalkEnum::Reached;
self.grounded=false;
}else{println!("bad1");}
}else{println!("bad2");}
}else{println!("bad3");}
},
crate::model::StageElementBehaviour::Platform=>(),
}
},
None=>(),
}
//flatten v
let mut v=self.body.velocity;
self.contact_constrain_velocity(&mut v);
match &general.booster{
Some(booster)=>{
v+=booster.velocity;
self.contact_constrain_velocity(&mut v);
},
None=>(),
}
self.body.velocity=v;
if self.grounded&&self.style.get_control(StyleModifiers::CONTROL_JUMP,self.controls){
self.jump();
}
self.refresh_walk_target();
},
PhysicsCollisionAttributes::Intersect{intersecting,general}=>{
//I think that setting the velocity to 0 was preventing surface contacts from entering an infinite loop
self.intersects.insert(c.model,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;
if self.grounded&&self.controls&CONTROL_JUMP!=0{
self.jump();
}
self.refresh_walk_target();
},
PhysicsInstruction::CollisionEnd(c) => {
let model=c.model(&self.models).unwrap();
match &model.attributes{
PhysicsCollisionAttributes::Contact{contacting,general}=>{
self.contacts.remove(&c.model);//remove contact before calling contact_constrain_acceleration
let mut a=self.style.gravity;
self.contact_constrain_acceleration(&mut a);
self.body.acceleration=a;
//check ground
match &c.face {
AabbFace::Top => {
self.grounded=false;
},
_ => (),
}
self.refresh_walk_target();
},
PhysicsCollisionAttributes::Intersect{intersecting,general}=>{
self.intersects.remove(&c.model);
},
}
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;
//check ground
match &c.face {
AabbFace::Top => {
self.grounded=false;
},
_ => (),
}
self.refresh_walk_target();
},
PhysicsInstruction::StrafeTick => {
let camera_mat=self.camera.simulate_move_rotation_y(self.camera.mouse.lerp(&self.next_mouse,self.time).x);
let control_dir=camera_mat*self.style.get_control_dir(self.controls);
let camera_mat=self.camera.simulate_move_rotation_y(self.mouse_interpolation.interpolated_position(self.time).x-self.mouse_interpolation.mouse0.x);
let control_dir=camera_mat*get_control_dir(self.controls);
let d=self.body.velocity.dot(control_dir);
if d<self.style.mv {
let mut v=self.body.velocity+(self.style.mv-d)*control_dir;
if d<self.mv {
let mut v=self.body.velocity+(self.mv-d)*control_dir;
self.contact_constrain_velocity(&mut v);
self.body.velocity=v;
}
@ -1262,51 +963,55 @@ impl crate::instruction::InstructionConsumer<PhysicsInstruction> for PhysicsStat
self.walk.state=WalkEnum::Reached;
},
PhysicsInstruction::Input(input_instruction) => {
let mut refresh_walk_target=true;
let mut refresh_walk_target_velocity=true;
let mut refresh_walk_target=false;
match input_instruction{
InputInstruction::MoveMouse(m) => {
self.camera.angles=self.camera.simulate_move_angles(self.next_mouse.pos);
self.camera.mouse.move_mouse(self.next_mouse.pos,self.next_mouse.time);
self.next_mouse.move_mouse(m,self.time);
self.camera.angles=self.camera.simulate_move_angles(self.mouse_interpolation.mouse1-self.mouse_interpolation.mouse0);
self.mouse_interpolation.move_mouse(self.time,m);
refresh_walk_target=true;
},
InputInstruction::MoveForward(s) => {
self.set_control(CONTROL_MOVEFORWARD,s);
refresh_walk_target=true;
},
InputInstruction::MoveLeft(s) => {
self.set_control(CONTROL_MOVELEFT,s);
refresh_walk_target=true;
},
InputInstruction::MoveBack(s) => {
self.set_control(CONTROL_MOVEBACK,s);
refresh_walk_target=true;
},
InputInstruction::MoveRight(s) => {
self.set_control(CONTROL_MOVERIGHT,s);
refresh_walk_target=true;
},
InputInstruction::MoveUp(s) => {
self.set_control(CONTROL_MOVEUP,s);
refresh_walk_target=true;
},
InputInstruction::MoveDown(s) => {
self.set_control(CONTROL_MOVEDOWN,s);
refresh_walk_target=true;
},
InputInstruction::MoveForward(s) => self.set_control(StyleModifiers::CONTROL_MOVEFORWARD,s),
InputInstruction::MoveLeft(s) => self.set_control(StyleModifiers::CONTROL_MOVELEFT,s),
InputInstruction::MoveBack(s) => self.set_control(StyleModifiers::CONTROL_MOVEBACK,s),
InputInstruction::MoveRight(s) => self.set_control(StyleModifiers::CONTROL_MOVERIGHT,s),
InputInstruction::MoveUp(s) => self.set_control(StyleModifiers::CONTROL_MOVEUP,s),
InputInstruction::MoveDown(s) => self.set_control(StyleModifiers::CONTROL_MOVEDOWN,s),
InputInstruction::Jump(s) => {
self.set_control(StyleModifiers::CONTROL_JUMP,s);
self.set_control(CONTROL_JUMP,s);
refresh_walk_target=true;
if self.grounded{
self.jump();
}
refresh_walk_target_velocity=false;
},
InputInstruction::Zoom(s) => {
self.set_control(StyleModifiers::CONTROL_ZOOM,s);
refresh_walk_target=false;
self.set_control(CONTROL_ZOOM,s);
},
InputInstruction::Reset => {
//temp
self.body.position=self.spawn_point;
self.body.velocity=glam::Vec3::ZERO;
//manual clear //for c in self.contacts{process_instruction(CollisionEnd(c))}
self.contacts.clear();
self.body.acceleration=self.style.gravity;
self.walk.state=WalkEnum::Reached;
self.grounded=false;
refresh_walk_target=false;
},
InputInstruction::Idle => {refresh_walk_target=false;},//literally idle!
InputInstruction::Reset => println!("reset"),
}
//calculate control dir
let camera_mat=self.camera.simulate_move_rotation_y(self.mouse_interpolation.interpolated_position(self.time).x-self.mouse_interpolation.mouse0.x);
let control_dir=camera_mat*get_control_dir(self.controls);
//calculate walk target velocity
if refresh_walk_target{
//calculate walk target velocity
if refresh_walk_target_velocity{
let camera_mat=self.camera.simulate_move_rotation_y(self.camera.mouse.lerp(&self.next_mouse,self.time).x);
let control_dir=camera_mat*self.style.get_control_dir(self.controls);
self.walk.target_velocity=self.style.walkspeed*control_dir;
}
self.walk.target_velocity=self.walkspeed*control_dir;
self.refresh_walk_target();
}
},

@ -51,9 +51,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, event: WindowEvent);
fn device_event(&mut self, event: DeviceEvent);
fn render(
&mut self,
view: &wgpu::TextureView,
@ -180,41 +179,9 @@ async fn setup<E: Example>(title: &str) -> Setup {
(size, surface)
};
let adapter;
let optional_features = E::optional_features();
let required_features = E::required_features();
//no helper function smh gotta write it myself
let adapters = instance.enumerate_adapters(backends);
let mut chosen_adapter = None;
let mut chosen_adapter_score=0;
for adapter in adapters {
if !adapter.is_surface_supported(&surface) {
continue;
}
let score=match adapter.get_info().device_type{
wgpu::DeviceType::IntegratedGpu=>3,
wgpu::DeviceType::DiscreteGpu=>4,
wgpu::DeviceType::VirtualGpu=>2,
wgpu::DeviceType::Other|wgpu::DeviceType::Cpu=>1,
};
let adapter_features = adapter.features();
if chosen_adapter_score<score&&adapter_features.contains(required_features) {
chosen_adapter_score=score;
chosen_adapter=Some(adapter);
}
}
if let Some(maybe_chosen_adapter) = chosen_adapter{
adapter=maybe_chosen_adapter;
}else{
panic!("No suitable GPU adapters found on the system!");
}
let adapter = wgpu::util::initialize_adapter_from_env_or_default(&instance, Some(&surface))
.await
.expect("No suitable GPU adapters found on the system!");
#[cfg(not(target_arch = "wasm32"))]
{
@ -222,6 +189,15 @@ async fn setup<E: Example>(title: &str) -> Setup {
println!("Using {} ({:?})", adapter_info.name, adapter_info.backend);
}
let optional_features = E::optional_features();
let required_features = E::required_features();
let adapter_features = adapter.features();
assert!(
adapter_features.contains(required_features),
"Adapter does not support required features for this example: {:?}",
required_features - adapter_features
);
let required_downlevel_capabilities = E::required_downlevel_capabilities();
let downlevel_capabilities = adapter.get_downlevel_capabilities();
assert!(
@ -245,7 +221,7 @@ async fn setup<E: Example>(title: &str) -> Setup {
.request_device(
&wgpu::DeviceDescriptor {
label: None,
features: (optional_features & adapter.features()) | required_features,
features: (optional_features & adapter_features) | required_features,
limits: needed_limits,
},
trace_dir.ok().as_ref().map(std::path::Path::new),
@ -359,7 +335,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 +344,14 @@ fn start<E: Example>(
println!("{:#?}", instance.generate_report());
}
_ => {
example.update(&window,&device,&queue,event);
example.update(&device,event);
}
},
event::Event::DeviceEvent {
event,
..
} => {
example.device_event(&window,event);
example.device_event(event);
},
event::Event::RedrawRequested(_) => {

@ -1,11 +1,11 @@
#[derive(Debug)]
pub struct TimedInstruction<I> {
pub time: crate::physics::TIME,
pub time: crate::body::TIME,
pub instruction: I,
}
pub trait InstructionEmitter<I> {
fn next_instruction(&self, time_limit:crate::physics::TIME) -> Option<TimedInstruction<I>>;
fn next_instruction(&self, time_limit:crate::body::TIME) -> Option<TimedInstruction<I>>;
}
pub trait InstructionConsumer<I> {
fn process_instruction(&mut self, instruction:TimedInstruction<I>);
@ -13,11 +13,11 @@ pub trait InstructionConsumer<I> {
//PROPER PRIVATE FIELDS!!!
pub struct InstructionCollector<I> {
time: crate::physics::TIME,
time: crate::body::TIME,
instruction: Option<I>,
}
impl<I> InstructionCollector<I> {
pub fn new(time:crate::physics::TIME) -> Self {
pub fn new(time:crate::body::TIME) -> Self {
Self{
time,
instruction:None

5
src/lib.rs Normal file

@ -0,0 +1,5 @@
pub mod framework;
pub mod body;
pub mod zeroes;
pub mod instruction;
pub mod load_roblox;

@ -1,5 +1,3 @@
use crate::primitives;
fn class_is_a(class: &str, superclass: &str) -> bool {
if class==superclass {
return true
@ -12,458 +10,18 @@ fn class_is_a(class: &str, superclass: &str) -> bool {
}
return false
}
fn recursive_collect_superclass(objects: &mut std::vec::Vec<rbx_dom_weak::types::Ref>,dom: &rbx_dom_weak::WeakDom, instance: &rbx_dom_weak::Instance, superclass: &str){
for &referent in instance.children() {
if let Some(c) = dom.get_by_ref(referent) {
if class_is_a(c.class.as_str(), superclass) {
objects.push(c.referent());//copy ref
}
recursive_collect_superclass(objects,dom,c,superclass);
pub fn get_objects<R: std::io::Read>(buf_thing: R, superclass: &str) -> Result<std::vec::Vec<rbx_dom_weak::Instance>, Box<dyn std::error::Error>> {
// Using buffered I/O is recommended with rbx_binary
let dom = rbx_binary::from_reader(buf_thing)?;
let mut objects = std::vec::Vec::<rbx_dom_weak::Instance>::new();
//move matching instances into objects
let (_,mut instances) = dom.into_raw();
for (_,instance) in instances.drain() {
if class_is_a(instance.class.as_str(), superclass) {
objects.push(instance);
}
}
}
fn get_texture_refs(dom:&rbx_dom_weak::WeakDom) -> Vec<rbx_dom_weak::types::Ref>{
let mut objects = std::vec::Vec::new();
recursive_collect_superclass(&mut objects, dom, dom.root(),"Decal");
//get ids
//clear vec
//next class
objects
}
fn get_attributes(name:&str,can_collide:bool,velocity:glam::Vec3,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();
match name{
//"Water"=>intersecting.water=Some(crate::model::IntersectingWater{density:1.0,drag:1.0}),
"Accelerator"=>intersecting.accelerator=Some(crate::model::IntersectingAccelerator{acceleration:velocity}),
"MapFinish"=>general.zone=Some(crate::model::GameMechanicZone{mode_id:0,behaviour:crate::model::ZoneBehaviour::Finish}),
"MapAnticheat"=>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"=>crate::model::StageElementBehaviour::Trigger,
"Teleport"=>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){
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!=glam::Vec3::ZERO{
general.booster=Some(crate::model::GameMechanicBooster{velocity});
}
match 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);
}
}
}
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>;4];
#[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=glam::Vec3::ZERO;
let mut indexed_models=Vec::new();
let mut model_id_from_description=std::collections::HashMap::<RobloxBasePartDescription,usize>::new();
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();
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)),
) = (
object.properties.get("CFrame"),
object.properties.get("Size"),
object.properties.get("Velocity"),
object.properties.get("Transparency"),
object.properties.get("Color"),
object.properties.get("CanCollide"),
)
{
let model_transform=glam::Affine3A::from_translation(
glam::Vec3::new(cf.position.x,cf.position.y,cf.position.z)
)
* glam::Affine3A::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::Affine3A::from_scale(
glam::Vec3::new(size.x,size.y,size.z)/2.0
);
//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(glam::Vec3::ZERO)+glam::vec3(0.0,2.5,0.0);
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);
}
//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");
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 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
}else{
let texture_id=asset_id_from_texture_id.len() as u32;
texture_id_from_asset_id.insert(asset_id.0,texture_id);
asset_id_from_texture_id.push(asset_id.0);
texture_id
};
let normal_id=normalid.to_u32();
if normal_id<6{
let mut roblox_texture_transform=RobloxTextureTransform::default();
let mut roblox_texture_color=glam::Vec4::ONE;
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"),
};
roblox_texture_transform=RobloxTextureTransform{
offset_u:*ox/(*sx),offset_v:*oy/(*sy),
scale_u:size_u/(*sx),scale_v:size_v/(*sy),
};
roblox_texture_color=glam::vec4(decal_color3.r,decal_color3.g,decal_color3.b,1.0-*decal_transparency);
}
}
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);
}
}
}
}
}
//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
]),
primitives::Primitives::CornerWedge=>RobloxBasePartDescription::CornerWedge([
f0,//Cube::Right->CornerWedge::Right
f1,//Cube::Top->CornerWedge::Top
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){
//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::Top,
1=>primitives::CornerWedgeFace::Right,
2=>primitives::CornerWedgeFace::Bottom,
3=>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,glam::vec3(velocity.x,velocity.y,velocity.z),force_intersecting),
temp_indexing:temp_indexing_attributes,
});
}
}
}
crate::model::IndexedModelInstances{
textures:asset_id_from_texture_id.iter().map(|t|t.to_string()).collect(),
models:indexed_models,
spawn_point,
modes:Vec::new(),
}
return Ok(objects)
}

File diff suppressed because it is too large Load Diff

@ -1,269 +0,0 @@
use bytemuck::{Pod, Zeroable};
#[derive(Clone, Copy, Pod, Zeroable)]
#[repr(C)]
pub struct Vertex {
pub pos: [f32; 3],
pub tex: [f32; 2],
pub normal: [f32; 3],
pub color: [f32; 4],
}
#[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<[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 groups: Vec<IndexedGroup>,
pub instances:Vec<ModelInstance>,
}
pub struct IndexedGroupFixedTexture{
pub polys:Vec<IndexedPolygon>,
}
pub struct IndexedModelSingleTexture{
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 ModelSingleTexture{
pub instances: Vec<ModelGraphicsInstance>,
pub vertices: Vec<Vertex>,
pub entities: Vec<Vec<u16>>,
pub texture: Option<u32>,
}
#[derive(Clone)]
pub struct ModelGraphicsInstance{
pub transform:glam::Mat4,
pub normal_transform:glam::Mat4,
pub color:glam::Vec4,
}
pub struct ModelInstance{
//pub id:u64,//this does not actually help with map fixes resimulating bots, they must always be resimulated
pub transform:glam::Affine3A,
pub color:glam::Vec4,//transparency is in here
pub attributes:CollisionAttributes,
pub temp_indexing:Vec<TempIndexedAttributes>,
}
impl std::default::Default for ModelInstance{
fn default() -> Self {
Self{
color:glam::Vec4::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:glam::Vec3,
}
//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,
},
}
//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:i64,
pub density:i64,
pub current:glam::Vec3,
}
#[derive(Clone)]
pub struct IntersectingAccelerator{
pub acceleration:glam::Vec3
}
//All models can be given these attributes
#[derive(Clone)]
pub struct GameMechanicJumpLimit{
pub count:u32,
}
#[derive(Clone)]
pub struct GameMechanicBooster{
pub velocity:glam::Vec3,
}
#[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 fn generate_indexed_model_list_from_obj(data:obj::ObjData,color:[f32;4]) -> 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()
}
}).collect()
}
}).collect();
IndexedModel{
unique_pos: data.position.clone(),
unique_tex: data.texture.clone(),
unique_normal: data.normal.clone(),
unique_color: vec![color],
unique_vertices,
groups,
instances:Vec::new(),
}
}).collect()
}

@ -1,503 +0,0 @@
use crate::model::{IndexedModel, IndexedPolygon, IndexedGroup, IndexedVertex};
#[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:[[f32;2];4]=[[0.0,0.0],[1.0,0.0],[1.0,1.0],[0.0,1.0]];
const CUBE_DEFAULT_VERTICES:[[f32;3];8]=[
[-1.,-1., 1.],//0 left bottom back
[ 1.,-1., 1.],//1 right bottom back
[ 1., 1., 1.],//2 right top back
[-1., 1., 1.],//3 left top back
[-1., 1.,-1.],//4 left top front
[ 1., 1.,-1.],//5 right top front
[ 1.,-1.,-1.],//6 right bottom front
[-1.,-1.,-1.],//7 left bottom front
];
const CUBE_DEFAULT_NORMALS:[[f32;3];6]=[
[ 1., 0., 0.],//CubeFace::Right
[ 0., 1., 0.],//CubeFace::Top
[ 0., 0., 1.],//CubeFace::Back
[-1., 0., 0.],//CubeFace::Left
[ 0.,-1., 0.],//CubeFace::Bottom
[ 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:[[f32;3];5]=[
[ 1., 0., 0.],//Wedge::Right
[ 0., 1.,-1.],//Wedge::TopFront
[ 0., 0., 1.],//Wedge::Back
[-1., 0., 0.],//Wedge::Left
[ 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{
Top,
Right,
Bottom,
Front,
}
const CORNERWEDGE_DEFAULT_NORMALS:[[f32;3];5]=[
[ 1., 0., 0.],//CornerWedge::Right
[ 0., 1., 1.],//CornerWedge::BackTop
[-1., 1., 0.],//CornerWedge::LeftTop
[ 0.,-1., 0.],//CornerWedge::Bottom
[ 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(),*glam::Vec4::ONE.as_ref()).remove(0);
for pos in indexed_model.unique_pos.iter_mut(){
pos[0]=pos[0]*0.5;
pos[1]=pos[1]*0.5;
pos[2]=pos[2]*0.5;
}
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:glam::Mat3=glam::mat3(glam::vec3(0.0,0.1,0.0),glam::vec3(-0.1,0.0,0.0),glam::vec3(0.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(),*glam::Vec4::ONE.as_ref()).remove(0);
for pos in indexed_model.unique_pos.iter_mut(){
[pos[0],pos[1],pos[2]]=*(TEAPOT_TRANSFORM*glam::Vec3::from_array(*pos)).as_ref();
}
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::Top,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:glam::Vec4,
}
impl std::default::Default for FaceDescription{
fn default()->Self {
Self{
texture:None,
transform:glam::Affine2::IDENTITY,
color:glam::vec4(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:glam::Vec4)->Self{
Self{texture:Some(texture),transform,color}
}
pub fn from_texture(texture:u32)->Self{
Self{
texture:Some(texture),
transform:glam::Affine2::IDENTITY,
color:glam::Vec4::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::<[f32;3]>::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(glam::Vec2::from_array(tex)).as_ref());
}
transform_index
} as u32;
let color_index=if let Some(color_index)=generated_color.iter().position(|color|color==face_description.color.as_ref()){
color_index
}else{
//create new color_index
let color_index=generated_color.len();
generated_color.push(*face_description.color.as_ref());
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],
],
// FrontTop (0, 1, -1)
vec![
[3,1,1],
[2,0,1],
[6,3,1],
[7,2,1],
],
// 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::<[f32;3]>::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(glam::Vec2::from_array(tex)).as_ref());
}
transform_index
} as u32;
let color_index=if let Some(color_index)=generated_color.iter().position(|color|color==face_description.color.as_ref()){
color_index
}else{
//create new color_index
let color_index=generated_color.len();
generated_color.push(*face_description.color.as_ref());
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(),
}],
});
}
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::<[f32;3]>::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(glam::Vec2::from_array(tex)).as_ref());
}
transform_index
} as u32;
let color_index=if let Some(color_index)=generated_color.iter().position(|color|color==face_description.color.as_ref()){
color_index
}else{
//create new color_index
let color_index=generated_color.len();
generated_color.push(*face_description.color.as_ref());
color_index
} as u32;
let face_id=match face{
CornerWedgeFace::Right => 0,
CornerWedgeFace::Top => 1,
CornerWedgeFace::Bottom => 2,
CornerWedgeFace::Front => 3,
};
//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(),
}
}

@ -43,20 +43,19 @@ fn vs_sky(@builtin(vertex_index) vertex_index: u32) -> SkyOutput {
struct ModelInstance{
transform:mat4x4<f32>,
normal_transform:mat4x4<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)
var<storage> model_instances: array<ModelInstance>;
@group(1)
@binding(1)
var model_texture: texture_2d<f32>;
@group(2)
@group(1)
@binding(2)
var model_sampler: sampler;
@ -66,7 +65,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(
@ -78,26 +76,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 * vec4<f32>(normal, 1.0)).xyz;
result.texture = texture;
result.color = color;
result.model_color = model_instances[instance].color;
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
@ -109,5 +106,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),1.0-pow(1.0-abs(d),2.0));
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));
}

@ -1,82 +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()
}
}
#[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(glam::Vec3::ZERO,glam::Vec3::ZERO,glam::Vec3::ZERO),
|_|crate::physics::Body::with_pva(glam::Vec3::ONE,glam::Vec3::ONE,glam::Vec3::ONE)
);
// Send tasks to the worker
for i in 0..5 {
let task = crate::instruction::TimedInstruction{
time:0,
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:0,
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,5 +1,4 @@
//find roots of polynomials
#[inline]
pub fn zeroes2(a0:f32,a1:f32,a2:f32) -> Vec<f32>{
if a2==0f32{
return zeroes1(a0, a1);