diff --git a/Cargo.lock b/Cargo.lock index 70a6c92..c95721e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -82,21 +82,6 @@ dependencies = [ "libc", ] -[[package]] -name = "approx" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f2a05fd1bd10b2527e20a2cd32d8873d115b8b39fe219ee25f42a8aca6ba278" -dependencies = [ - "num-traits", -] - -[[package]] -name = "array-init" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d62b7694a562cdf5a74227903507c56ab2cc8bdd1f781ed5cb4cf9c9f810bfc" - [[package]] name = "arrayref" version = "0.3.7" @@ -142,30 +127,6 @@ version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" -[[package]] -name = "binrw" -version = "0.13.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "173901312e9850391d4d7c1318c4e099fdc037d61870fca427429830efdb4e5f" -dependencies = [ - "array-init", - "binrw_derive", - "bytemuck", -] - -[[package]] -name = "binrw_derive" -version = "0.13.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb515fdd6f8d3a357c8e19b8ec59ef53880807864329b1cb1cba5c53bf76557e" -dependencies = [ - "either", - "owo-colors", - "proc-macro2", - "quote", - "syn 1.0.109", -] - [[package]] name = "bit-set" version = "0.5.3" @@ -237,15 +198,6 @@ version = "3.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec" -[[package]] -name = "bv" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8834bb1d8ee5dc048ee3124f2c7c1afcc6bc9aed03f11e9dfd8c69470a5db340" -dependencies = [ - "feature-probe", -] - [[package]] name = "bytemuck" version = "1.14.1" @@ -266,12 +218,6 @@ dependencies = [ "syn 2.0.48", ] -[[package]] -name = "byteorder" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fc10e8cc6b2580fda3f36eb6dc5316657f812a3df879a44a66fc9f0fdbc4855" - [[package]] name = "byteorder" version = "1.5.0" @@ -338,16 +284,6 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e" -[[package]] -name = "cgmath" -version = "0.18.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a98d30140e3296250832bbaaff83b27dcd6fa3cc70fb6f1f3e5c9c0023b5317" -dependencies = [ - "approx", - "num-traits", -] - [[package]] name = "codespan-reporting" version = "0.11.1" @@ -460,30 +396,6 @@ dependencies = [ "libc", ] -[[package]] -name = "crc" -version = "3.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86ec7a15cbe22e59248fc7eadb1907dab5ba09372595da4d73dd805ed4417dfe" -dependencies = [ - "crc-catalog", -] - -[[package]] -name = "crc-catalog" -version = "2.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" - -[[package]] -name = "crc32fast" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" -dependencies = [ - "cfg-if", -] - [[package]] name = "crossbeam-utils" version = "0.8.19" @@ -514,7 +426,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "479dfe1e6737aa9e96c6ac7b69689dc4c32da8383f2c12744739d76afa8b66c4" dependencies = [ "bitflags 2.4.2", - "byteorder 1.5.0", + "byteorder", "enum-primitive-derive", "num-traits", ] @@ -540,12 +452,6 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ea835d29036a4087793836fa931b08837ad5e957da9e23886b29586fb9b6650" -[[package]] -name = "either" -version = "1.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" - [[package]] name = "enum-primitive-derive" version = "0.2.2" @@ -573,12 +479,6 @@ dependencies = [ "windows-sys 0.52.0", ] -[[package]] -name = "feature-probe" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "835a3dc7d1ec9e75e2b5fb4ba75396837112d2060b03f7d43bc1897c7f7211da" - [[package]] name = "foreign-types" version = "0.5.0" @@ -759,6 +659,16 @@ dependencies = [ "objc2", ] +[[package]] +name = "id" +version = "0.1.0" +source = "git+https://git.itzana.me/Quaternions/id?rev=1f710976cc786c8853dab73d6e1cee53158deeb0#1f710976cc786c8853dab73d6e1cee53158deeb0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.48", +] + [[package]] name = "indexmap" version = "2.2.1" @@ -769,15 +679,6 @@ dependencies = [ "hashbrown", ] -[[package]] -name = "itertools" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" -dependencies = [ - "either", -] - [[package]] name = "jni" version = "0.21.1" @@ -943,25 +844,6 @@ dependencies = [ "libc", ] -[[package]] -name = "lzma" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "782ba3f542e8bc1349386c15e9dc3119ae6da96479f96b3863cc7a88bbdfd4e4" -dependencies = [ - "byteorder 0.5.3", -] - -[[package]] -name = "lzma-rs" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "297e814c836ae64db86b36cf2a557ba54368d03f6afcd7d947c266692f71115e" -dependencies = [ - "byteorder 1.5.0", - "crc", -] - [[package]] name = "malloc_buf" version = "0.0.6" @@ -1081,12 +963,6 @@ dependencies = [ "syn 2.0.48", ] -[[package]] -name = "obj" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "059c95245738cdc7b40078cdd51a23200252a4c0a0a6dd005136152b3f467a4a" - [[package]] name = "objc" version = "0.2.7" @@ -1152,12 +1028,6 @@ dependencies = [ "ttf-parser", ] -[[package]] -name = "owo-colors" -version = "3.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1b04fb49957986fdce4d6ee7a65027d55d4b6d2265e5848bbb507b58ccfdb6f" - [[package]] name = "parking_lot" version = "0.12.1" @@ -1266,9 +1136,9 @@ dependencies = [ [[package]] name = "profiling-procmacros" -version = "1.0.13" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b322d7d65c1ab449be3c890fcbd0db6e1092d0dd05d79dba2dd28032cebeb05" +checksum = "ce97fecd27bc49296e5e20518b5a1bb54a14f7d5fe6228bc9686ee2a74915cc8" dependencies = [ "quote", "syn 2.0.48", @@ -1470,7 +1340,7 @@ version = "0.8.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f9860a6cc38ed1da53456442089b4dfa35e7cedaa326df63017af88385e6b20" dependencies = [ - "byteorder 1.5.0", + "byteorder", "num-traits", "paste", ] @@ -1481,7 +1351,7 @@ version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bffea85eea980d8a74453e5d02a8d93028f3c34725de143085a844ebe953258a" dependencies = [ - "byteorder 1.5.0", + "byteorder", "rmp", "serde", ] @@ -1640,17 +1510,12 @@ dependencies = [ "configparser", "ddsfile", "glam", - "lazy-regex", - "obj", + "id", "parking_lot", "pollster", - "rbx_binary", - "rbx_dom_weak", - "rbx_reflection_database", - "rbx_xml", "strafesnet_common", - "vbsp", - "vmdl", + "strafesnet_rbx_loader", + "strafesnet_texture_loader", "wgpu", "winit", ] @@ -1658,9 +1523,33 @@ dependencies = [ [[package]] name = "strafesnet_common" version = "0.1.0" -source = "git+https://git.itzana.me/StrafesNET/common?rev=434ca29aef7e3015c9ca1ed45de8fef42e33fdfb#434ca29aef7e3015c9ca1ed45de8fef42e33fdfb" +source = "git+https://git.itzana.me/StrafesNET/common?rev=47cdea0c8a5d10a2440ca6270a975d560aa3642d#47cdea0c8a5d10a2440ca6270a975d560aa3642d" dependencies = [ "glam", + "id", +] + +[[package]] +name = "strafesnet_rbx_loader" +version = "0.1.0" +source = "git+https://git.itzana.me/StrafesNET/rbx_loader?rev=93bc4dc0fb2175af8ded235c7a4b47af7235790a#93bc4dc0fb2175af8ded235c7a4b47af7235790a" +dependencies = [ + "glam", + "lazy-regex", + "rbx_binary", + "rbx_dom_weak", + "rbx_reflection_database", + "rbx_xml", + "strafesnet_common", +] + +[[package]] +name = "strafesnet_texture_loader" +version = "0.1.0" +source = "git+https://git.itzana.me/StrafesNET/texture_loader?rev=c182e46001a095b245fe80e5c9912e58f51b0276#c182e46001a095b245fe80e5c9912e58f51b0276" +dependencies = [ + "lazy-regex", + "strafesnet_common", ] [[package]] @@ -1691,17 +1580,6 @@ dependencies = [ "unicode-ident", ] -[[package]] -name = "syn_util" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6754c4559b79657554e9d8a0d56e65e490c76d382b9c23108364ec4125dea23c" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", -] - [[package]] name = "termcolor" version = "1.4.1" @@ -1780,29 +1658,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" dependencies = [ "pin-project-lite", - "tracing-attributes", "tracing-core", ] -[[package]] -name = "tracing-attributes" -version = "0.1.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.48", -] - [[package]] name = "tracing-core" version = "0.1.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" -dependencies = [ - "once_cell", -] [[package]] name = "ttf-parser" @@ -1834,61 +1697,12 @@ version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" -[[package]] -name = "vbsp" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9267540dab0c93bb5201c40ba3b2d027e2717bf355a8f9bf25377b06a5b32f6" -dependencies = [ - "ahash", - "arrayvec", - "binrw", - "bitflags 2.4.2", - "bv", - "cgmath", - "itertools", - "lzma-rs", - "num_enum", - "static_assertions", - "thiserror", - "vbsp-derive", - "zip-lzma", -] - -[[package]] -name = "vbsp-derive" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ade687fadf34b1b7502387fc9eb7b4032ddc9b93022d31356e9984c957abaad" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", - "syn_util", -] - [[package]] name = "version_check" version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" -[[package]] -name = "vmdl" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "892922743c4c107372331efd8f67c57282590f8c18c26b4465c4b0e1e6678664" -dependencies = [ - "arrayvec", - "bitflags 2.4.2", - "bytemuck", - "cgmath", - "itertools", - "static_assertions", - "thiserror", - "tracing", -] - [[package]] name = "walkdir" version = "2.4.0" @@ -2599,15 +2413,3 @@ dependencies = [ "quote", "syn 2.0.48", ] - -[[package]] -name = "zip-lzma" -version = "0.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b480cb31fccfb2786565c0e0712865fd6f1ea0ea850c50316f643c3948196e63" -dependencies = [ - "byteorder 1.5.0", - "crc32fast", - "crossbeam-utils", - "lzma", -] diff --git a/Cargo.toml b/Cargo.toml index 02d986d..d2aa88f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,17 +10,12 @@ bytemuck = { version = "1.13.1", features = ["derive"] } configparser = "3.0.2" ddsfile = "0.5.1" glam = "0.25.0" -lazy-regex = "3.0.2" -obj = "0.10.2" +id = { git = "https://git.itzana.me/Quaternions/id", rev = "1f710976cc786c8853dab73d6e1cee53158deeb0" } 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" -strafesnet_common = { git = "https://git.itzana.me/StrafesNET/common", rev = "434ca29aef7e3015c9ca1ed45de8fef42e33fdfb" } -vbsp = "0.5.0" -vmdl = "0.1.1" +strafesnet_common = { git = "https://git.itzana.me/StrafesNET/common", rev = "47cdea0c8a5d10a2440ca6270a975d560aa3642d" } +strafesnet_rbx_loader = { git = "https://git.itzana.me/StrafesNET/rbx_loader", rev = "93bc4dc0fb2175af8ded235c7a4b47af7235790a" } +strafesnet_texture_loader = { git = "https://git.itzana.me/StrafesNET/texture_loader", rev = "c182e46001a095b245fe80e5c9912e58f51b0276", features = ["legacy"] } wgpu = "0.19.0" winit = "0.29.2" diff --git a/src/graphics.rs b/src/graphics.rs index 9239b2e..e3ee6ad 100644 --- a/src/graphics.rs +++ b/src/graphics.rs @@ -1,7 +1,10 @@ use std::borrow::Cow; +use std::collections::{HashSet,HashMap}; +use strafesnet_common::map; use strafesnet_common::integer; +use strafesnet_common::model::{self, ColorId, NormalId, PolygonIter, PositionId, RenderConfigId, TextureCoordinateId, VertexId}; use wgpu::{util::DeviceExt,AstcBlock,AstcChannel}; -use crate::model_graphics::{GraphicsVertex,GraphicsModelColor4,GraphicsModelInstance,GraphicsModelSingleTexture,IndexedGraphicsModelSingleTexture,IndexedGroupFixedTexture}; +use crate::model_graphics::{self,IndexedGraphicsMeshOwnedRenderConfig,IndexedGraphicsMeshOwnedRenderConfigId,GraphicsMeshOwnedRenderConfig,GraphicsModelColor4,GraphicsModelOwned,GraphicsVertex}; #[derive(Clone)] pub struct GraphicsModelUpdate{ @@ -9,84 +12,83 @@ pub struct GraphicsModelUpdate{ color:Option, } -struct Entity{ - index_count:u32, - index_buf:wgpu::Buffer, +struct Indices{ + count:u32, + buf:wgpu::Buffer, + format:wgpu::IndexFormat, } -fn create_entities(device:&wgpu::Device,entities:&Vec>)->Vec{ - entities.iter().map(|indices|{ - let index_buf=device.create_buffer_init(&wgpu::util::BufferInitDescriptor{ - label:Some("Index"), - contents:bytemuck::cast_slice(indices), - usage:wgpu::BufferUsages::INDEX, - }); - Entity{ - index_buf, - index_count:indices.len() as u32, +impl Indices{ + fn new(device:&wgpu::Device,indices:&Vec,format:wgpu::IndexFormat)->Self{ + Self{ + buf:device.create_buffer_init(&wgpu::util::BufferInitDescriptor{ + label:Some("Index"), + contents:bytemuck::cast_slice(indices), + usage:wgpu::BufferUsages::INDEX, + }), + count:indices.len() as u32, + format, } - }).collect() + } } - struct GraphicsModel{ - entities:Vec, + indices:Indices, model_buf:wgpu::Buffer, vertex_buf:wgpu::Buffer, bind_group:wgpu::BindGroup, - index_format:wgpu::IndexFormat, - instances:Vec, + instance_count:u32, } -pub struct GraphicsSamplers{ - repeat: wgpu::Sampler, +struct GraphicsSamplers{ + repeat:wgpu::Sampler, } -pub struct GraphicsBindGroupLayouts{ - model: wgpu::BindGroupLayout, +struct GraphicsBindGroupLayouts{ + model:wgpu::BindGroupLayout, } -pub struct GraphicsBindGroups { - camera: wgpu::BindGroup, - skybox_texture: wgpu::BindGroup, +struct GraphicsBindGroups{ + camera:wgpu::BindGroup, + skybox_texture:wgpu::BindGroup, } -pub struct GraphicsPipelines{ - skybox: wgpu::RenderPipeline, - model: wgpu::RenderPipeline, +struct GraphicsPipelines{ + skybox:wgpu::RenderPipeline, + model:wgpu::RenderPipeline, } -pub struct GraphicsCamera{ - screen_size: glam::UVec2, - fov: glam::Vec2,//slope +struct GraphicsCamera{ + screen_size:glam::UVec2, + fov:glam::Vec2,//slope //camera angles and such are extrapolated and passed in every time } #[inline] -fn perspective_rh(fov_x_slope: f32, fov_y_slope: f32, z_near: f32, z_far: f32) -> glam::Mat4 { +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); + 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), + 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 GraphicsCamera{ pub fn proj(&self)->glam::Mat4{ - perspective_rh(self.fov.x, self.fov.y, 0.5, 2000.0) + perspective_rh(self.fov.x,self.fov.y,0.5,2000.0) } pub fn world(&self,pos:glam::Vec3,angles:glam::Vec2)->glam::Mat4{ //f32 good enough for view matrix - glam::Mat4::from_translation(pos) * glam::Mat4::from_euler(glam::EulerRot::YXZ, angles.x, angles.y, 0f32) + glam::Mat4::from_translation(pos) * glam::Mat4::from_euler(glam::EulerRot::YXZ,angles.x,angles.y,0f32) } - pub fn to_uniform_data(&self,(pos,angles): (glam::Vec3,glam::Vec2)) -> [f32; 16 * 4] { + pub fn to_uniform_data(&self,(pos,angles):(glam::Vec3,glam::Vec2))->[f32; 16 * 4]{ let proj=self.proj(); - let proj_inv = proj.inverse(); + let proj_inv=proj.inverse(); let view_inv=self.world(pos,angles); let view=view_inv.inverse(); - let mut raw = [0f32; 16 * 4]; + let mut raw=[0f32; 16 * 4]; raw[..16].copy_from_slice(&AsRef::<[f32; 16]>::as_ref(&proj)[..]); raw[16..32].copy_from_slice(&AsRef::<[f32; 16]>::as_ref(&proj_inv)[..]); raw[32..48].copy_from_slice(&AsRef::<[f32; 16]>::as_ref(&view)[..]); @@ -104,37 +106,37 @@ impl std::default::Default for GraphicsCamera{ } pub struct GraphicsState{ - pipelines: GraphicsPipelines, - bind_groups: GraphicsBindGroups, - bind_group_layouts: GraphicsBindGroupLayouts, - samplers: GraphicsSamplers, + pipelines:GraphicsPipelines, + bind_groups:GraphicsBindGroups, + bind_group_layouts:GraphicsBindGroupLayouts, + samplers:GraphicsSamplers, camera:GraphicsCamera, - camera_buf: wgpu::Buffer, - temp_squid_texture_view: wgpu::TextureView, - models: Vec, - depth_view: wgpu::TextureView, - staging_belt: wgpu::util::StagingBelt, + camera_buf:wgpu::Buffer, + temp_squid_texture_view:wgpu::TextureView, + models:Vec, + depth_view:wgpu::TextureView, + staging_belt:wgpu::util::StagingBelt, } impl GraphicsState{ - const DEPTH_FORMAT: wgpu::TextureFormat=wgpu::TextureFormat::Depth24Plus; + const DEPTH_FORMAT:wgpu::TextureFormat=wgpu::TextureFormat::Depth24Plus; fn create_depth_texture( - config: &wgpu::SurfaceConfiguration, - device: &wgpu::Device, - ) -> wgpu::TextureView { - let depth_texture=device.create_texture(&wgpu::TextureDescriptor { - size: wgpu::Extent3d { - width: config.width, - height: config.height, - depth_or_array_layers: 1, + config:&wgpu::SurfaceConfiguration, + device:&wgpu::Device, + )->wgpu::TextureView{ + let depth_texture=device.create_texture(&wgpu::TextureDescriptor{ + size:wgpu::Extent3d{ + width:config.width, + height:config.height, + depth_or_array_layers:1, }, - mip_level_count: 1, - sample_count: 1, - dimension: wgpu::TextureDimension::D2, - format: Self::DEPTH_FORMAT, - usage: wgpu::TextureUsages::RENDER_ATTACHMENT, - label: None, - view_formats: &[], + mip_level_count:1, + sample_count:1, + dimension:wgpu::TextureDimension::D2, + format:Self::DEPTH_FORMAT, + usage:wgpu::TextureUsages::RENDER_ATTACHMENT, + label:None, + view_formats:&[], }); depth_texture.create_view(&wgpu::TextureViewDescriptor::default()) @@ -145,148 +147,151 @@ impl GraphicsState{ pub fn load_user_settings(&mut self,user_settings:&crate::settings::UserSettings){ self.camera.fov=user_settings.calculate_fov(1.0,&self.camera.screen_size).as_vec2(); } - pub fn generate_models(&mut self,device:&wgpu::Device,queue:&wgpu::Queue,indexed_models:crate::model::IndexedModelInstances){ + pub fn generate_models(&mut self,device:&wgpu::Device,queue:&wgpu::Queue,map:&map::CompleteMap,textures:strafesnet_texture_loader::texture_loader::Textures){ //generate texture view per texture - - //idk how to do this gooder lol - let mut double_map=std::collections::HashMap::::new(); - let mut texture_loading_threads=Vec::new(); - let num_textures=indexed_models.textures.len(); - for (i,texture_id) in indexed_models.textures.into_iter().enumerate(){ - let path=std::path::PathBuf::from(format!("textures/{}.dds",texture_id)); - if let Ok(mut file)=std::fs::File::open(path.as_path()){ - double_map.insert(i as u32, texture_loading_threads.len() as u32); - texture_loading_threads.push((texture_id,std::thread::spawn(move ||{ - ddsfile::Dds::read(&mut file).unwrap() - }))); - }else{ - //println!("missing texture path={:?}",path); - } - } - - let texture_views:Vec=texture_loading_threads.into_iter().map(|(texture_id,thread)|{ - let image=thread.join().unwrap(); + let texture_views:Vec=textures.into_iter().map(|(texture_id,texture_data)|{ + let image=ddsfile::Dds::read(std::io::Cursor::new(texture_data)).unwrap(); let (mut width,mut height)=(image.get_width(),image.get_height()); let format=match image.header10.unwrap().dxgi_format{ - ddsfile::DxgiFormat::R8G8B8A8_UNorm_sRGB => wgpu::TextureFormat::Rgba8UnormSrgb, - ddsfile::DxgiFormat::BC7_UNorm_sRGB => { - //floor(w,4), should be ceil(w,4) + ddsfile::DxgiFormat::R8G8B8A8_UNorm_sRGB=>wgpu::TextureFormat::Rgba8UnormSrgb, + ddsfile::DxgiFormat::BC7_UNorm_sRGB =>{ + //floor(w,4),should be ceil(w,4) width=width/4*4; height=height/4*4; wgpu::TextureFormat::Bc7RgbaUnormSrgb }, - other=>panic!("unsupported format {:?}",other), + other=>panic!("unsupported format{:?}",other), }; - let size = wgpu::Extent3d { + let size=wgpu::Extent3d{ width, height, - depth_or_array_layers: 1, + depth_or_array_layers:1, }; - let layer_size = wgpu::Extent3d { - depth_or_array_layers: 1, + let layer_size=wgpu::Extent3d{ + depth_or_array_layers:1, ..size }; - let max_mips = layer_size.max_mips(wgpu::TextureDimension::D2); + let max_mips=layer_size.max_mips(wgpu::TextureDimension::D2); - let texture = device.create_texture_with_data( + let texture=device.create_texture_with_data( queue, - &wgpu::TextureDescriptor { + &wgpu::TextureDescriptor{ size, - mip_level_count: max_mips, - sample_count: 1, - dimension: wgpu::TextureDimension::D2, + mip_level_count:max_mips, + sample_count:1, + dimension:wgpu::TextureDimension::D2, format, - usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST, - label: Some(format!("Texture{}",texture_id).as_str()), - view_formats: &[], + usage:wgpu::TextureUsages::TEXTURE_BINDING|wgpu::TextureUsages::COPY_DST, + label:Some(format!("Texture{}",texture_id.get()).as_str()), + view_formats:&[], }, wgpu::util::TextureDataOrder::LayerMajor, &image.data, ); - texture.create_view(&wgpu::TextureViewDescriptor { - label: Some(format!("Texture{} View",texture_id).as_str()), - dimension: Some(wgpu::TextureViewDimension::D2), + texture.create_view(&wgpu::TextureViewDescriptor{ + label:Some(format!("Texture{} View",texture_id.get()).as_str()), + dimension:Some(wgpu::TextureViewDimension::D2), ..wgpu::TextureViewDescriptor::default() }) }).collect(); + let num_textures=texture_views.len(); //split groups with different textures into separate models - //the models received here are supposed to be tightly packed, i.e. no code needs to check if two models are using the same groups. - let indexed_models_len=indexed_models.models.len(); - let mut unique_texture_models=Vec::with_capacity(indexed_models_len); - for model in indexed_models.models.into_iter(){ - //convert ModelInstance into GraphicsModelInstance - let instances:Vec=model.instances.into_iter().filter_map(|instance|{ - if instance.color.w==0.0&&!model.groups.iter().any(|g|g.texture.is_some()){ - None - }else{ - Some(GraphicsModelInstance{ - transform: instance.transform.into(), - normal_transform: Into::::into(instance.transform.matrix3).inverse().transpose(), - color:GraphicsModelColor4::from(instance.color), - }) + //the models received here are supposed to be tightly packed,i.e. no code needs to check if two models are using the same groups. + let indexed_models_len=map.models.len(); + //models split into graphics_group.RenderConfigId + let mut owned_mesh_id_from_mesh_id_render_config_id:HashMap>=HashMap::new(); + let mut unique_render_config_models:Vec=Vec::with_capacity(indexed_models_len); + for model in &map.models{ + //wow + let instance=GraphicsModelOwned{ + transform:model.transform.into(), + normal_transform:Into::::into(model.transform.matrix3).inverse().transpose(), + color:GraphicsModelColor4::new(model.color), + }; + //get or create owned mesh map + if let Some(owned_mesh_map)=owned_mesh_id_from_mesh_id_render_config_id.get(&model.mesh){ + //the mesh has already been split into a set of unique renderconfig meshes + //simply add one instance to each of them + for owned_mesh_id in owned_mesh_map.values(){ + let owned_mesh=unique_render_config_models.get_mut(owned_mesh_id.get() as usize).unwrap(); + owned_mesh.instances.push(instance.clone()); } - }).collect(); - //skip pushing a model if all instances are invisible - if instances.len()==0{ - continue; - } - //check each group, if it's using a new texture then make a new clone of the model - let id=unique_texture_models.len(); - let mut unique_textures=Vec::new(); - for group in model.groups.into_iter(){ - //ignore zero copy optimization for now - let texture_index=if let Some(texture_index)=unique_textures.iter().position(|&texture|texture==group.texture){ - texture_index - }else{ - //create new texture_index - let texture_index=unique_textures.len(); - unique_textures.push(group.texture); - unique_texture_models.push(IndexedGraphicsModelSingleTexture{ - unique_pos:model.unique_pos.iter().map(|&v|*Into::::into(v).as_ref()).collect(), - unique_tex:model.unique_tex.iter().map(|v|*v.as_ref()).collect(), - unique_normal:model.unique_normal.iter().map(|&v|*Into::::into(v).as_ref()).collect(), - unique_color:model.unique_color.iter().map(|v|*v.as_ref()).collect(), - unique_vertices:model.unique_vertices.clone(), - texture:group.texture, - groups:Vec::new(), - instances:instances.clone(), - }); - texture_index - }; - unique_texture_models[id+texture_index].groups.push(IndexedGroupFixedTexture{ - polys:group.polys, - }); - } + }else{ + let mut owned_mesh_map=HashMap::new(); + //add mesh if renderid never before seen for this model + //add instance + //convert Model into GraphicsModelOwned + //check each group, if it's using a new render config then make a new clone of the model + if let Some(mesh)=map.meshes.get(model.mesh.get() as usize){ + for graphics_group in mesh.graphics_groups.iter(){ + let render_config=&map.render_configs[graphics_group.render.get() as usize]; + if model.color.w==0.0&&render_config.texture.is_none(){ + continue; + } + //get or create owned mesh + let owned_mesh_id=if let Some(&owned_mesh_id)=owned_mesh_map.get(&graphics_group.render){ + owned_mesh_id + }else{ + //create + let owned_mesh_id=IndexedGraphicsMeshOwnedRenderConfigId::new(unique_render_config_models.len() as u32); + owned_mesh_map.insert(graphics_group.render,owned_mesh_id); + unique_render_config_models.push(IndexedGraphicsMeshOwnedRenderConfig{ + unique_pos:mesh.unique_pos.iter().map(|&v|*Into::::into(v).as_ref()).collect(), + unique_tex:mesh.unique_tex.iter().map(|v|*v.as_ref()).collect(), + unique_normal:mesh.unique_normal.iter().map(|&v|*Into::::into(v).as_ref()).collect(), + unique_color:mesh.unique_color.iter().map(|v|*v.as_ref()).collect(), + unique_vertices:mesh.unique_vertices.clone(), + render_config:graphics_group.render, + polys:model::PolygonGroup::PolygonList(model::PolygonList::new(Vec::new())), + instances:vec![instance.clone()], + }); + owned_mesh_id + }; + let owned_mesh=unique_render_config_models.get_mut(owned_mesh_id.get() as usize).unwrap(); + match &mut owned_mesh.polys{ + model::PolygonGroup::PolygonList(polygon_list)=>polygon_list.extend( + graphics_group.groups.iter().flat_map(|polygon_group_id|{ + mesh.polygon_groups[polygon_group_id.get() as usize].polys() + }) + .map(|vertex_id_slice| + vertex_id_slice.to_vec() + ) + ), + } + } + } + owned_mesh_id_from_mesh_id_render_config_id.insert(model.mesh,owned_mesh_map); + }; } - //check every model to see if it's using the same (texture,color) but has few instances, if it is combine it into one model - //1. collect unique instances of texture and color, note model id - //2. for each model id, check if removing it from the pool decreases both the model count and instance count by more than one + //check every model to see if it's using the same (texture,color) but has few instances,if it is combine it into one model + //1. collect unique instances of texture and color,note model id + //2. for each model id,check if removing it from the pool decreases both the model count and instance count by more than one //3. transpose all models that stay in the set - //best plan: benchmark set_bind_group, set_vertex_buffer, set_index_buffer and draw_indexed + //best plan:benchmark set_bind_group,set_vertex_buffer,set_index_buffer and draw_indexed //check if the estimated render performance is better by transposing multiple model instances into one model instance - //for now: just deduplicate single models... + //for now:just deduplicate single models... let mut deduplicated_models=Vec::with_capacity(indexed_models_len);//use indexed_models_len because the list will likely get smaller instead of bigger - let mut unique_texture_color=std::collections::HashMap::new();//texture->color->vec![(model_id,instance_id)] - for (model_id,model) in unique_texture_models.iter().enumerate(){ - //for now: filter out models with more than one instance + let mut unique_texture_color=HashMap::new();//texture->color->vec![(model_id,instance_id)] + for (model_id,model) in unique_render_config_models.iter().enumerate(){ + //for now:filter out models with more than one instance if 1=model.unique_pos.iter().map(|untransformed_pos|{ + let map_pos_id:Vec=model.unique_pos.iter().map(|untransformed_pos|{ let pos=instance.transform.transform_point3(glam::Vec3::from_array(untransformed_pos.clone())).to_array(); - let h=pos.map(|v|bytemuck::cast::(v)); - (if let Some(&pos_id)=pos_id_from.get(&h){ + let h=bytemuck::cast::<[f32;3],[u32;3]>(pos); + PositionId::new((if let Some(&pos_id)=pos_id_from.get(&h){ pos_id }else{ let pos_id=unique_pos.len(); - unique_pos.push(pos.clone()); + unique_pos.push(pos); pos_id_from.insert(h,pos_id); pos_id - }) as u32 + }) as u32) }).collect(); - let map_tex_id:Vec=model.unique_tex.iter().map(|tex|{ - let h=tex.map(|v|bytemuck::cast::(v)); - (if let Some(&tex_id)=tex_id_from.get(&h){ + let map_tex_id:Vec=model.unique_tex.iter().map(|&tex|{ + let h=bytemuck::cast::<[f32;2],[u32;2]>(tex); + TextureCoordinateId::new((if let Some(&tex_id)=tex_id_from.get(&h){ tex_id }else{ let tex_id=unique_tex.len(); - unique_tex.push(tex.clone()); + unique_tex.push(tex); tex_id_from.insert(h,tex_id); tex_id - }) as u32 + }) as u32) }).collect(); - let map_normal_id:Vec=model.unique_normal.iter().map(|untransformed_normal|{ + let map_normal_id:Vec=model.unique_normal.iter().map(|untransformed_normal|{ let normal=(instance.normal_transform*glam::Vec3::from_array(untransformed_normal.clone())).to_array(); - let h=normal.map(|v|bytemuck::cast::(v)); - (if let Some(&normal_id)=normal_id_from.get(&h){ + let h=bytemuck::cast::<[f32;3],[u32;3]>(normal); + NormalId::new((if let Some(&normal_id)=normal_id_from.get(&h){ normal_id }else{ let normal_id=unique_normal.len(); - unique_normal.push(normal.clone()); + unique_normal.push(normal); normal_id_from.insert(h,normal_id); normal_id - }) as u32 + }) as u32) }).collect(); - let map_color_id:Vec=model.unique_color.iter().map(|color|{ - let h=color.map(|v|bytemuck::cast::(v)); - (if let Some(&color_id)=color_id_from.get(&h){ + let map_color_id:Vec=model.unique_color.iter().map(|&color|{ + let h=bytemuck::cast::<[f32;4],[u32;4]>(color); + ColorId::new((if let Some(&color_id)=color_id_from.get(&h){ color_id }else{ let color_id=unique_color.len(); - unique_color.push(color.clone()); + unique_color.push(color); color_id_from.insert(h,color_id); color_id - }) as u32 + }) as u32) }).collect(); //map the indexed vertices onto new indices //creating the vertex map is slightly different because the vertices are directly hashable - let map_vertex_id:Vec=model.unique_vertices.iter().map(|unmapped_vertex|{ - let vertex=crate::model::IndexedVertex{ - pos:map_pos_id[unmapped_vertex.pos as usize], - tex:map_tex_id[unmapped_vertex.tex as usize], - normal:map_normal_id[unmapped_vertex.normal as usize], - color:map_color_id[unmapped_vertex.color as usize], + let map_vertex_id:Vec=model.unique_vertices.iter().map(|unmapped_vertex|{ + let vertex=model::IndexedVertex{ + pos:map_pos_id[unmapped_vertex.pos.get() as usize], + tex:map_tex_id[unmapped_vertex.tex.get() as usize], + normal:map_normal_id[unmapped_vertex.normal.get() as usize], + color:map_color_id[unmapped_vertex.color.get() as usize], }; - (if let Some(&vertex_id)=vertex_id_from.get(&vertex){ + VertexId::new((if let Some(&vertex_id)=vertex_id_from.get(&vertex){ vertex_id }else{ let vertex_id=unique_vertices.len(); unique_vertices.push(vertex.clone()); vertex_id_from.insert(vertex,vertex_id); vertex_id - }) as u32 + }) as u32) }).collect(); - for group in &model.groups{ - for poly in &group.polys{ - polys.push(crate::model::IndexedPolygon{vertices:poly.vertices.iter().map(|&vertex_id|map_vertex_id[vertex_id as usize]).collect()}); - } - } + polys.extend(model.polys.polys().map(|poly| + poly.iter().map(|vertex_id| + map_vertex_id[vertex_id.get() as usize] + ).collect() + )); } //push model into dedup - deduplicated_models.push(IndexedGraphicsModelSingleTexture{ + deduplicated_models.push(IndexedGraphicsMeshOwnedRenderConfig{ unique_pos, unique_tex, unique_normal, unique_color, unique_vertices, - texture, - groups:vec![IndexedGroupFixedTexture{ - polys - }], - instances:vec![GraphicsModelInstance{ + render_config, + polys:model::PolygonGroup::PolygonList(model::PolygonList::new(polys)), + instances:vec![GraphicsModelOwned{ transform:glam::Mat4::IDENTITY, normal_transform:glam::Mat3::IDENTITY, color @@ -421,7 +424,7 @@ impl GraphicsState{ } } //fill untouched models - for (model_id,model) in unique_texture_models.into_iter().enumerate(){ + for (model_id,model) in unique_render_config_models.into_iter().enumerate(){ if !selected_model_instances.contains(&model_id){ deduplicated_models.push(model); } @@ -429,45 +432,43 @@ impl GraphicsState{ //de-index models let deduplicated_models_len=deduplicated_models.len(); - let models:Vec=deduplicated_models.into_iter().map(|model|{ - let mut vertices = Vec::new(); - let mut index_from_vertex = std::collections::HashMap::new();//:: + let models:Vec=deduplicated_models.into_iter().map(|model|{ + let mut vertices=Vec::new(); + let mut index_from_vertex=HashMap::new();//:: //this mut be combined in a more complex way if the models use different render patterns per group - let mut indices = Vec::new(); - for group in model.groups { - for poly in group.polys { - for end_index in 2..poly.vertices.len() { - for &index in &[0, end_index - 1, end_index] { - let vertex_index = poly.vertices[index]; - if let Some(&i)=index_from_vertex.get(&vertex_index){ - indices.push(i); - }else{ - let i=vertices.len(); - let vertex=&model.unique_vertices[vertex_index as usize]; - vertices.push(GraphicsVertex{ - pos: model.unique_pos[vertex.pos as usize], - tex: model.unique_tex[vertex.tex as usize], - normal: model.unique_normal[vertex.normal as usize], - color:model.unique_color[vertex.color as usize], - }); - index_from_vertex.insert(vertex_index,i); - indices.push(i); - } + let mut indices=Vec::new(); + for poly in model.polys.polys(){ + for end_index in 2..poly.len(){ + for index in [0,end_index-1,end_index]{ + let vertex_index=poly[index]; + if let Some(&i)=index_from_vertex.get(&vertex_index){ + indices.push(i); + }else{ + let i=vertices.len(); + let vertex=&model.unique_vertices[vertex_index.get() as usize]; + vertices.push(GraphicsVertex{ + pos:model.unique_pos[vertex.pos.get() as usize], + tex:model.unique_tex[vertex.tex.get() as usize], + normal:model.unique_normal[vertex.normal.get() as usize], + color:model.unique_color[vertex.color.get() as usize], + }); + index_from_vertex.insert(vertex_index,i); + indices.push(i); } } } } - GraphicsModelSingleTexture{ + GraphicsMeshOwnedRenderConfig{ instances:model.instances, - entities:if (u32::MAX as usize){ - match double_map.get(&texture_id){ - Some(&mapped_texture_id)=>&texture_views[mapped_texture_id as usize], - None=>&self.temp_squid_texture_view, - } - }, + let render_config=&map.render_configs[model.render_config.get() as usize]; + let texture_view=match render_config.texture{ + Some(texture_id)=>&texture_views[texture_id.get() as usize], None=>&self.temp_squid_texture_view, }; - let model_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { - layout: &self.bind_group_layouts.model, - entries: &[ - wgpu::BindGroupEntry { - binding: 0, - resource: model_buf.as_entire_binding(), + let bind_group=device.create_bind_group(&wgpu::BindGroupDescriptor{ + layout:&self.bind_group_layouts.model, + entries:&[ + wgpu::BindGroupEntry{ + binding:0, + resource:model_buf.as_entire_binding(), }, - wgpu::BindGroupEntry { - binding: 1, - resource: wgpu::BindingResource::TextureView(texture_view), + wgpu::BindGroupEntry{ + binding:1, + resource:wgpu::BindingResource::TextureView(texture_view), }, - wgpu::BindGroupEntry { - binding: 2, - resource: wgpu::BindingResource::Sampler(&self.samplers.repeat), + wgpu::BindGroupEntry{ + binding:2, + resource:wgpu::BindingResource::Sampler(&self.samplers.repeat), }, ], - label: Some(format!("Model{} Bind Group",model_count).as_str()), + label:Some(format!("Model{} Bind Group",model_count).as_str()), }); - let vertex_buf = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { - label: Some("Vertex"), - contents: bytemuck::cast_slice(&model.vertices), - usage: wgpu::BufferUsages::VERTEX, + let vertex_buf=device.create_buffer_init(&wgpu::util::BufferInitDescriptor{ + label:Some("Vertex"), + contents:bytemuck::cast_slice(&model.vertices), + usage:wgpu::BufferUsages::VERTEX, }); //all of these are being moved here self.models.push(GraphicsModel{ - instances:instances_chunk.to_vec(), + instance_count:instances_chunk.len() as u32, vertex_buf, - index_format:match &model.entities{ - crate::model_graphics::Entities::U32(_)=>wgpu::IndexFormat::Uint32, - crate::model_graphics::Entities::U16(_)=>wgpu::IndexFormat::Uint16, + indices:match &model.indices{ + model_graphics::Indices::U32(indices)=>Indices::new(device,indices,wgpu::IndexFormat::Uint32), + model_graphics::Indices::U16(indices)=>Indices::new(device,indices,wgpu::IndexFormat::Uint16), }, - entities:match &model.entities{ - crate::model_graphics::Entities::U32(entities)=>create_entities(device,entities), - crate::model_graphics::Entities::U16(entities)=>create_entities(device,entities), - }, - bind_group: model_bind_group, + bind_group, model_buf, }); } @@ -539,8 +532,8 @@ impl GraphicsState{ println!("Textures Loaded={}",texture_views.len()); println!("Indexed Models={}",indexed_models_len); println!("Deduplicated Models={}",deduplicated_models_len); - println!("Graphics Objects: {}",self.models.len()); - println!("Graphics Instances: {}",instance_count); + println!("Graphics Objects:{}",self.models.len()); + println!("Graphics Instances:{}",instance_count); } pub fn new( @@ -548,325 +541,325 @@ impl GraphicsState{ queue:&wgpu::Queue, config:&wgpu::SurfaceConfiguration, )->Self{ - let camera_bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { - label: None, - entries: &[ - wgpu::BindGroupLayoutEntry { - binding: 0, - visibility: wgpu::ShaderStages::VERTEX, - ty: wgpu::BindingType::Buffer { - ty: wgpu::BufferBindingType::Uniform, - has_dynamic_offset: false, - min_binding_size: None, + let camera_bind_group_layout=device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor{ + label:None, + entries:&[ + wgpu::BindGroupLayoutEntry{ + binding:0, + visibility:wgpu::ShaderStages::VERTEX, + ty:wgpu::BindingType::Buffer{ + ty:wgpu::BufferBindingType::Uniform, + has_dynamic_offset:false, + min_binding_size:None, }, - count: None, + count:None, }, ], }); - let skybox_texture_bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { - label: Some("Skybox Texture Bind Group Layout"), - entries: &[ - wgpu::BindGroupLayoutEntry { - binding: 0, - visibility: wgpu::ShaderStages::FRAGMENT, - ty: wgpu::BindingType::Texture { - sample_type: wgpu::TextureSampleType::Float { filterable: true }, - multisampled: false, - view_dimension: wgpu::TextureViewDimension::Cube, + let skybox_texture_bind_group_layout=device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor{ + label:Some("Skybox Texture Bind Group Layout"), + entries:&[ + wgpu::BindGroupLayoutEntry{ + binding:0, + visibility:wgpu::ShaderStages::FRAGMENT, + ty:wgpu::BindingType::Texture{ + sample_type:wgpu::TextureSampleType::Float{filterable:true}, + multisampled:false, + view_dimension:wgpu::TextureViewDimension::Cube, }, - count: None, + count:None, }, - wgpu::BindGroupLayoutEntry { - binding: 1, - visibility: wgpu::ShaderStages::FRAGMENT, - ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering), - count: None, + wgpu::BindGroupLayoutEntry{ + binding:1, + visibility:wgpu::ShaderStages::FRAGMENT, + ty:wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering), + count:None, }, ], }); - let model_bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { - label: Some("Model Bind Group Layout"), - entries: &[ - wgpu::BindGroupLayoutEntry { - binding: 0, - visibility: wgpu::ShaderStages::VERTEX, - ty: wgpu::BindingType::Buffer { - ty: wgpu::BufferBindingType::Uniform, - has_dynamic_offset: false, - min_binding_size: None, + let model_bind_group_layout=device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor{ + label:Some("Model Bind Group Layout"), + entries:&[ + wgpu::BindGroupLayoutEntry{ + binding:0, + visibility:wgpu::ShaderStages::VERTEX, + ty:wgpu::BindingType::Buffer{ + ty:wgpu::BufferBindingType::Uniform, + has_dynamic_offset:false, + min_binding_size:None, }, - count: None, + count:None, }, - wgpu::BindGroupLayoutEntry { - binding: 1, - visibility: wgpu::ShaderStages::FRAGMENT, - ty: wgpu::BindingType::Texture { - sample_type: wgpu::TextureSampleType::Float { filterable: true }, - multisampled: false, - view_dimension: wgpu::TextureViewDimension::D2, + wgpu::BindGroupLayoutEntry{ + binding:1, + visibility:wgpu::ShaderStages::FRAGMENT, + ty:wgpu::BindingType::Texture{ + sample_type:wgpu::TextureSampleType::Float{filterable:true}, + multisampled:false, + view_dimension:wgpu::TextureViewDimension::D2, }, - count: None, + count:None, }, - wgpu::BindGroupLayoutEntry { - binding: 2, - visibility: wgpu::ShaderStages::FRAGMENT, - ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering), - count: None, + wgpu::BindGroupLayoutEntry{ + binding:2, + visibility:wgpu::ShaderStages::FRAGMENT, + ty:wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering), + count:None, }, ], }); - let clamp_sampler = device.create_sampler(&wgpu::SamplerDescriptor { - label: Some("Clamp Sampler"), - address_mode_u: wgpu::AddressMode::ClampToEdge, - address_mode_v: wgpu::AddressMode::ClampToEdge, - address_mode_w: wgpu::AddressMode::ClampToEdge, - mag_filter: wgpu::FilterMode::Linear, - min_filter: wgpu::FilterMode::Linear, - mipmap_filter: wgpu::FilterMode::Linear, + let clamp_sampler=device.create_sampler(&wgpu::SamplerDescriptor{ + label:Some("Clamp Sampler"), + address_mode_u:wgpu::AddressMode::ClampToEdge, + address_mode_v:wgpu::AddressMode::ClampToEdge, + address_mode_w:wgpu::AddressMode::ClampToEdge, + mag_filter:wgpu::FilterMode::Linear, + min_filter:wgpu::FilterMode::Linear, + mipmap_filter:wgpu::FilterMode::Linear, ..Default::default() }); - let repeat_sampler = device.create_sampler(&wgpu::SamplerDescriptor { - label: Some("Repeat Sampler"), - address_mode_u: wgpu::AddressMode::Repeat, - address_mode_v: wgpu::AddressMode::Repeat, - address_mode_w: wgpu::AddressMode::Repeat, - mag_filter: wgpu::FilterMode::Linear, - min_filter: wgpu::FilterMode::Linear, - mipmap_filter: wgpu::FilterMode::Linear, + let repeat_sampler=device.create_sampler(&wgpu::SamplerDescriptor{ + label:Some("Repeat Sampler"), + address_mode_u:wgpu::AddressMode::Repeat, + address_mode_v:wgpu::AddressMode::Repeat, + address_mode_w:wgpu::AddressMode::Repeat, + mag_filter:wgpu::FilterMode::Linear, + min_filter:wgpu::FilterMode::Linear, + mipmap_filter:wgpu::FilterMode::Linear, anisotropy_clamp:16, ..Default::default() }); // Create the render pipeline - let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor { - label: None, - source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(include_str!("shader.wgsl"))), + let shader=device.create_shader_module(wgpu::ShaderModuleDescriptor{ + label:None, + source:wgpu::ShaderSource::Wgsl(Cow::Borrowed(include_str!("shader.wgsl"))), }); //load textures - let device_features = device.features(); + let device_features=device.features(); let skybox_texture_view={ - let skybox_format = if device_features.contains(wgpu::Features::TEXTURE_COMPRESSION_ASTC) { + let skybox_format=if device_features.contains(wgpu::Features::TEXTURE_COMPRESSION_ASTC){ println!("Using ASTC"); - wgpu::TextureFormat::Astc { - block: AstcBlock::B4x4, - channel: AstcChannel::UnormSrgb, + wgpu::TextureFormat::Astc{ + block:AstcBlock::B4x4, + channel:AstcChannel::UnormSrgb, } - } else if device_features.contains(wgpu::Features::TEXTURE_COMPRESSION_ETC2) { + }else if device_features.contains(wgpu::Features::TEXTURE_COMPRESSION_ETC2){ println!("Using ETC2"); wgpu::TextureFormat::Etc2Rgb8UnormSrgb - } else if device_features.contains(wgpu::Features::TEXTURE_COMPRESSION_BC) { + }else if device_features.contains(wgpu::Features::TEXTURE_COMPRESSION_BC){ println!("Using BC"); wgpu::TextureFormat::Bc1RgbaUnormSrgb - } else { + }else{ println!("Using plain"); wgpu::TextureFormat::Bgra8UnormSrgb }; - let bytes = match skybox_format { - wgpu::TextureFormat::Astc { - block: AstcBlock::B4x4, - channel: AstcChannel::UnormSrgb, - } => &include_bytes!("../images/astc.dds")[..], - wgpu::TextureFormat::Etc2Rgb8UnormSrgb => &include_bytes!("../images/etc2.dds")[..], - wgpu::TextureFormat::Bc1RgbaUnormSrgb => &include_bytes!("../images/bc1.dds")[..], - wgpu::TextureFormat::Bgra8UnormSrgb => &include_bytes!("../images/bgra.dds")[..], - _ => unreachable!(), + let bytes=match skybox_format{ + wgpu::TextureFormat::Astc{ + block:AstcBlock::B4x4, + channel:AstcChannel::UnormSrgb, + }=>&include_bytes!("../images/astc.dds")[..], + wgpu::TextureFormat::Etc2Rgb8UnormSrgb=>&include_bytes!("../images/etc2.dds")[..], + wgpu::TextureFormat::Bc1RgbaUnormSrgb=>&include_bytes!("../images/bc1.dds")[..], + wgpu::TextureFormat::Bgra8UnormSrgb=>&include_bytes!("../images/bgra.dds")[..], + _=>unreachable!(), }; - let skybox_image = ddsfile::Dds::read(&mut std::io::Cursor::new(bytes)).unwrap(); + let skybox_image=ddsfile::Dds::read(&mut std::io::Cursor::new(bytes)).unwrap(); - let size = wgpu::Extent3d { - width: skybox_image.get_width(), - height: skybox_image.get_height(), - depth_or_array_layers: 6, + let size=wgpu::Extent3d{ + width:skybox_image.get_width(), + height:skybox_image.get_height(), + depth_or_array_layers:6, }; - let layer_size = wgpu::Extent3d { - depth_or_array_layers: 1, + let layer_size=wgpu::Extent3d{ + depth_or_array_layers:1, ..size }; - let max_mips = layer_size.max_mips(wgpu::TextureDimension::D2); + let max_mips=layer_size.max_mips(wgpu::TextureDimension::D2); - let skybox_texture = device.create_texture_with_data( + let skybox_texture=device.create_texture_with_data( queue, - &wgpu::TextureDescriptor { + &wgpu::TextureDescriptor{ size, - mip_level_count: max_mips, - sample_count: 1, - dimension: wgpu::TextureDimension::D2, - format: skybox_format, - usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST, - label: Some("Skybox Texture"), - view_formats: &[], + mip_level_count:max_mips, + sample_count:1, + dimension:wgpu::TextureDimension::D2, + format:skybox_format, + usage:wgpu::TextureUsages::TEXTURE_BINDING|wgpu::TextureUsages::COPY_DST, + label:Some("Skybox Texture"), + view_formats:&[], }, wgpu::util::TextureDataOrder::LayerMajor, &skybox_image.data, ); - skybox_texture.create_view(&wgpu::TextureViewDescriptor { - label: Some("Skybox Texture View"), - dimension: Some(wgpu::TextureViewDimension::Cube), + skybox_texture.create_view(&wgpu::TextureViewDescriptor{ + label:Some("Skybox Texture View"), + dimension:Some(wgpu::TextureViewDimension::Cube), ..wgpu::TextureViewDescriptor::default() }) }; //squid let squid_texture_view={ - let bytes = include_bytes!("../images/squid.dds"); + let bytes=include_bytes!("../images/squid.dds"); - let image = ddsfile::Dds::read(&mut std::io::Cursor::new(bytes)).unwrap(); + let image=ddsfile::Dds::read(&mut std::io::Cursor::new(bytes)).unwrap(); - let size = wgpu::Extent3d { - width: image.get_width(), - height: image.get_height(), - depth_or_array_layers: 1, + let size=wgpu::Extent3d{ + width:image.get_width(), + height:image.get_height(), + depth_or_array_layers:1, }; - let layer_size = wgpu::Extent3d { - depth_or_array_layers: 1, + let layer_size=wgpu::Extent3d{ + depth_or_array_layers:1, ..size }; - let max_mips = layer_size.max_mips(wgpu::TextureDimension::D2); + let max_mips=layer_size.max_mips(wgpu::TextureDimension::D2); - let texture = device.create_texture_with_data( + let texture=device.create_texture_with_data( queue, - &wgpu::TextureDescriptor { + &wgpu::TextureDescriptor{ size, - mip_level_count: max_mips, - sample_count: 1, - dimension: wgpu::TextureDimension::D2, - format: wgpu::TextureFormat::Bc7RgbaUnorm, - usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST, - label: Some("Squid Texture"), - view_formats: &[], + mip_level_count:max_mips, + sample_count:1, + dimension:wgpu::TextureDimension::D2, + format:wgpu::TextureFormat::Bc7RgbaUnorm, + usage:wgpu::TextureUsages::TEXTURE_BINDING|wgpu::TextureUsages::COPY_DST, + label:Some("Squid Texture"), + view_formats:&[], }, wgpu::util::TextureDataOrder::LayerMajor, &image.data, ); - texture.create_view(&wgpu::TextureViewDescriptor { - label: Some("Squid Texture View"), - dimension: Some(wgpu::TextureViewDimension::D2), + texture.create_view(&wgpu::TextureViewDescriptor{ + label:Some("Squid Texture View"), + dimension:Some(wgpu::TextureViewDimension::D2), ..wgpu::TextureViewDescriptor::default() }) }; - let model_pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { - label: None, - bind_group_layouts: &[ + let model_pipeline_layout=device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor{ + label:None, + bind_group_layouts:&[ &camera_bind_group_layout, &skybox_texture_bind_group_layout, &model_bind_group_layout, ], - push_constant_ranges: &[], + push_constant_ranges:&[], }); - let sky_pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { - label: None, - bind_group_layouts: &[ + let sky_pipeline_layout=device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor{ + label:None, + bind_group_layouts:&[ &camera_bind_group_layout, &skybox_texture_bind_group_layout, ], - push_constant_ranges: &[], + push_constant_ranges:&[], }); // Create the render pipelines - let sky_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { - label: Some("Sky Pipeline"), - layout: Some(&sky_pipeline_layout), - vertex: wgpu::VertexState { - module: &shader, - entry_point: "vs_sky", - buffers: &[], + let sky_pipeline=device.create_render_pipeline(&wgpu::RenderPipelineDescriptor{ + label:Some("Sky Pipeline"), + layout:Some(&sky_pipeline_layout), + vertex:wgpu::VertexState{ + module:&shader, + entry_point:"vs_sky", + buffers:&[], }, - fragment: Some(wgpu::FragmentState { - module: &shader, - entry_point: "fs_sky", - targets: &[Some(config.view_formats[0].into())], + fragment:Some(wgpu::FragmentState{ + module:&shader, + entry_point:"fs_sky", + targets:&[Some(config.view_formats[0].into())], }), - primitive: wgpu::PrimitiveState { - front_face: wgpu::FrontFace::Cw, + primitive:wgpu::PrimitiveState{ + front_face:wgpu::FrontFace::Cw, ..Default::default() }, - depth_stencil: Some(wgpu::DepthStencilState { - format: Self::DEPTH_FORMAT, - depth_write_enabled: false, - depth_compare: wgpu::CompareFunction::LessEqual, - stencil: wgpu::StencilState::default(), - bias: wgpu::DepthBiasState::default(), + depth_stencil:Some(wgpu::DepthStencilState{ + format:Self::DEPTH_FORMAT, + depth_write_enabled:false, + depth_compare:wgpu::CompareFunction::LessEqual, + stencil:wgpu::StencilState::default(), + bias:wgpu::DepthBiasState::default(), }), - multisample: wgpu::MultisampleState::default(), - multiview: None, + multisample:wgpu::MultisampleState::default(), + multiview:None, }); - let model_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { - label: Some("Model Pipeline"), - layout: Some(&model_pipeline_layout), - vertex: wgpu::VertexState { - module: &shader, - entry_point: "vs_entity_texture", - buffers: &[wgpu::VertexBufferLayout { - array_stride: std::mem::size_of::() as wgpu::BufferAddress, - step_mode: wgpu::VertexStepMode::Vertex, - attributes: &wgpu::vertex_attr_array![0 => Float32x3, 1 => Float32x2, 2 => Float32x3, 3 => Float32x4], + let model_pipeline=device.create_render_pipeline(&wgpu::RenderPipelineDescriptor{ + label:Some("Model Pipeline"), + layout:Some(&model_pipeline_layout), + vertex:wgpu::VertexState{ + module:&shader, + entry_point:"vs_entity_texture", + buffers:&[wgpu::VertexBufferLayout{ + array_stride:std::mem::size_of::() as wgpu::BufferAddress, + step_mode:wgpu::VertexStepMode::Vertex, + attributes:&wgpu::vertex_attr_array![0=>Float32x3,1=>Float32x2,2=>Float32x3,3=>Float32x4], }], }, - fragment: Some(wgpu::FragmentState { - module: &shader, - entry_point: "fs_entity_texture", - targets: &[Some(config.view_formats[0].into())], + fragment:Some(wgpu::FragmentState{ + module:&shader, + entry_point:"fs_entity_texture", + targets:&[Some(config.view_formats[0].into())], }), - primitive: wgpu::PrimitiveState { - front_face: wgpu::FrontFace::Cw, + primitive:wgpu::PrimitiveState{ + front_face:wgpu::FrontFace::Cw, cull_mode:Some(wgpu::Face::Front), ..Default::default() }, - depth_stencil: Some(wgpu::DepthStencilState { - format: Self::DEPTH_FORMAT, - depth_write_enabled: true, - depth_compare: wgpu::CompareFunction::LessEqual, - stencil: wgpu::StencilState::default(), - bias: wgpu::DepthBiasState::default(), + depth_stencil:Some(wgpu::DepthStencilState{ + format:Self::DEPTH_FORMAT, + depth_write_enabled:true, + depth_compare:wgpu::CompareFunction::LessEqual, + stencil:wgpu::StencilState::default(), + bias:wgpu::DepthBiasState::default(), }), - multisample: wgpu::MultisampleState::default(), - multiview: None, + multisample:wgpu::MultisampleState::default(), + multiview:None, }); let camera=GraphicsCamera::default(); - let camera_uniforms = camera.to_uniform_data(crate::physics::PhysicsOutputState::default().extrapolate(glam::IVec2::ZERO,integer::Time::ZERO)); - let camera_buf = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { - label: Some("Camera"), - contents: bytemuck::cast_slice(&camera_uniforms), - usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST, + let camera_uniforms=camera.to_uniform_data(crate::physics::PhysicsOutputState::default().extrapolate(glam::IVec2::ZERO,integer::Time::ZERO)); + let camera_buf=device.create_buffer_init(&wgpu::util::BufferInitDescriptor{ + label:Some("Camera"), + contents:bytemuck::cast_slice(&camera_uniforms), + usage:wgpu::BufferUsages::UNIFORM|wgpu::BufferUsages::COPY_DST, }); - let camera_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { - layout: &camera_bind_group_layout, - entries: &[ - wgpu::BindGroupEntry { - binding: 0, - resource: camera_buf.as_entire_binding(), + let camera_bind_group=device.create_bind_group(&wgpu::BindGroupDescriptor{ + layout:&camera_bind_group_layout, + entries:&[ + wgpu::BindGroupEntry{ + binding:0, + resource:camera_buf.as_entire_binding(), }, ], - label: Some("Camera"), + label:Some("Camera"), }); - let skybox_texture_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { - layout: &skybox_texture_bind_group_layout, - entries: &[ - wgpu::BindGroupEntry { - binding: 0, - resource: wgpu::BindingResource::TextureView(&skybox_texture_view), + let skybox_texture_bind_group=device.create_bind_group(&wgpu::BindGroupDescriptor{ + layout:&skybox_texture_bind_group_layout, + entries:&[ + wgpu::BindGroupEntry{ + binding:0, + resource:wgpu::BindingResource::TextureView(&skybox_texture_view), }, - wgpu::BindGroupEntry { - binding: 1, - resource: wgpu::BindingResource::Sampler(&clamp_sampler), + wgpu::BindGroupEntry{ + binding:1, + resource:wgpu::BindingResource::Sampler(&clamp_sampler), }, ], - label: Some("Sky Texture"), + label:Some("Sky Texture"), }); - let depth_view = Self::create_depth_texture(config, device); + let depth_view=Self::create_depth_texture(config,device); Self{ pipelines:GraphicsPipelines{ @@ -879,12 +872,12 @@ impl GraphicsState{ }, camera, camera_buf, - models: Vec::new(), + models:Vec::new(), depth_view, - staging_belt: wgpu::util::StagingBelt::new(0x100), - bind_group_layouts: GraphicsBindGroupLayouts { model: model_bind_group_layout }, - samplers: GraphicsSamplers { repeat: repeat_sampler }, - temp_squid_texture_view: squid_texture_view, + staging_belt:wgpu::util::StagingBelt::new(0x100), + bind_group_layouts:GraphicsBindGroupLayouts{model:model_bind_group_layout}, + samplers:GraphicsSamplers{repeat:repeat_sampler}, + temp_squid_texture_view:squid_texture_view, } } pub fn resize( @@ -892,7 +885,7 @@ impl GraphicsState{ device:&wgpu::Device, config:&wgpu::SurfaceConfiguration, user_settings:&crate::settings::UserSettings, - ) { + ){ self.depth_view=Self::create_depth_texture(config,device); self.camera.screen_size=glam::uvec2(config.width,config.height); self.load_user_settings(user_settings); @@ -905,14 +898,13 @@ impl GraphicsState{ physics_output:crate::physics::PhysicsOutputState, predicted_time:integer::Time, mouse_pos:glam::IVec2, - ) { - //TODO: use scheduled frame times to create beautiful smoothing simulation physics extrapolation assuming no input + ){ + //TODO:use scheduled frame times to create beautiful smoothing simulation physics extrapolation assuming no input - let mut encoder = - device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None }); + let mut encoder=device.create_command_encoder(&wgpu::CommandEncoderDescriptor{label:None}); // update rotation - let camera_uniforms = self.camera.to_uniform_data(physics_output.extrapolate(mouse_pos,predicted_time)); + let camera_uniforms=self.camera.to_uniform_data(physics_output.extrapolate(mouse_pos,predicted_time)); self.staging_belt .write_buffer( &mut encoder, @@ -924,8 +916,8 @@ impl GraphicsState{ .copy_from_slice(bytemuck::cast_slice(&camera_uniforms)); //This code only needs to run when the uniforms change /* - for model in self.models.iter() { - let model_uniforms = get_instances_buffer_data(&model.instances); + for model in self.models.iter(){ + let model_uniforms=get_instances_buffer_data(&model.instances); self.staging_belt .write_buffer( &mut encoder, @@ -940,49 +932,47 @@ impl GraphicsState{ self.staging_belt.finish(); { - let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { - label: None, - color_attachments: &[Some(wgpu::RenderPassColorAttachment { + let mut rpass=encoder.begin_render_pass(&wgpu::RenderPassDescriptor{ + label:None, + color_attachments:&[Some(wgpu::RenderPassColorAttachment{ view, - resolve_target: None, - ops: wgpu::Operations { - load: wgpu::LoadOp::Clear(wgpu::Color { - r: 0.1, - g: 0.2, - b: 0.3, - a: 1.0, + resolve_target:None, + ops:wgpu::Operations{ + load:wgpu::LoadOp::Clear(wgpu::Color{ + r:0.1, + g:0.2, + b:0.3, + a:1.0, }), store:wgpu::StoreOp::Store, }, })], - depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment { - view: &self.depth_view, - depth_ops: Some(wgpu::Operations { - load: wgpu::LoadOp::Clear(1.0), + depth_stencil_attachment:Some(wgpu::RenderPassDepthStencilAttachment{ + view:&self.depth_view, + depth_ops:Some(wgpu::Operations{ + load:wgpu::LoadOp::Clear(1.0), store:wgpu::StoreOp::Discard, }), - stencil_ops: None, + stencil_ops:None, }), timestamp_writes:Default::default(), occlusion_query_set:Default::default(), }); - rpass.set_bind_group(0, &self.bind_groups.camera, &[]); - rpass.set_bind_group(1, &self.bind_groups.skybox_texture, &[]); + rpass.set_bind_group(0,&self.bind_groups.camera,&[]); + rpass.set_bind_group(1,&self.bind_groups.skybox_texture,&[]); rpass.set_pipeline(&self.pipelines.model); - for model in self.models.iter() { - rpass.set_bind_group(2, &model.bind_group, &[]); - rpass.set_vertex_buffer(0, model.vertex_buf.slice(..)); - - for entity in model.entities.iter(){ - rpass.set_index_buffer(entity.index_buf.slice(..),model.index_format); - rpass.draw_indexed(0..entity.index_count,0,0..model.instances.len() as u32); - } + for model in &self.models{ + rpass.set_bind_group(2,&model.bind_group,&[]); + rpass.set_vertex_buffer(0,model.vertex_buf.slice(..)); + rpass.set_index_buffer(model.indices.buf.slice(..),model.indices.format); + //TODO: loop over triangle strips + rpass.draw_indexed(0..model.indices.count,0,0..model.instance_count); } rpass.set_pipeline(&self.pipelines.skybox); - rpass.draw(0..3, 0..1); + rpass.draw(0..3,0..1); } queue.submit(std::iter::once(encoder.finish())); @@ -992,10 +982,9 @@ impl GraphicsState{ } const MODEL_BUFFER_SIZE:usize=4*4 + 12 + 4;//let size=std::mem::size_of::(); const MODEL_BUFFER_SIZE_BYTES:usize=MODEL_BUFFER_SIZE*4; -fn get_instances_buffer_data(instances:&[GraphicsModelInstance]) -> Vec { - let mut raw = Vec::with_capacity(MODEL_BUFFER_SIZE*instances.len()); - for (i,mi) in instances.iter().enumerate(){ - let mut v = raw.split_off(MODEL_BUFFER_SIZE*i); +fn get_instances_buffer_data(instances:&[GraphicsModelOwned])->Vec{ + let mut raw=Vec::with_capacity(MODEL_BUFFER_SIZE*instances.len()); + for mi in instances{ //model transform raw.extend_from_slice(&AsRef::<[f32; 4*4]>::as_ref(&mi.transform)[..]); //normal transform @@ -1007,7 +996,6 @@ fn get_instances_buffer_data(instances:&[GraphicsModelInstance]) -> Vec { raw.extend_from_slice(&[0.0]); //color raw.extend_from_slice(AsRef::<[f32; 4]>::as_ref(&mi.color.get())); - raw.append(&mut v); } raw } diff --git a/src/graphics_worker.rs b/src/graphics_worker.rs index 378269c..4f7e8d9 100644 --- a/src/graphics_worker.rs +++ b/src/graphics_worker.rs @@ -4,7 +4,7 @@ pub enum Instruction{ Render(crate::physics::PhysicsOutputState,integer::Time,glam::IVec2), //UpdateModel(crate::graphics::GraphicsModelUpdate), Resize(winit::dpi::PhysicalSize,crate::settings::UserSettings), - GenerateModels(crate::model::IndexedModelInstances), + GenerateModels(strafesnet_common::map::CompleteMap,strafesnet_texture_loader::texture_loader::Textures), ClearModels, } @@ -27,8 +27,8 @@ pub fn new<'a>( let mut resize=None; crate::compat_worker::INWorker::new(move |ins:Instruction|{ match ins{ - Instruction::GenerateModels(indexed_model_instances)=>{ - graphics.generate_models(&device,&queue,indexed_model_instances); + Instruction::GenerateModels(map,textures)=>{ + graphics.generate_models(&device,&queue,&map,textures); }, Instruction::ClearModels=>{ graphics.clear(); diff --git a/src/load_bsp.rs b/src/load_bsp.rs deleted file mode 100644 index 840886c..0000000 --- a/src/load_bsp.rs +++ /dev/null @@ -1,232 +0,0 @@ -use strafesnet_common::integer; - -const VALVE_SCALE:f32=1.0/16.0; -fn valve_transform(v:[f32;3])->integer::Planar64Vec3{ - integer::Planar64Vec3::try_from([v[0]*VALVE_SCALE,v[2]*VALVE_SCALE,-v[1]*VALVE_SCALE]).unwrap() -} -pub fn generate_indexed_models(input:&mut R)->Result{ - let mut s=Vec::new(); - - match input.read_to_end(&mut s){ - Ok(_)=>(), - Err(e)=>println!("load_bsp::generate_indexed_models read_to_end failed: {:?}",e), - } - - match vbsp::Bsp::read(s.as_slice()){ - Ok(bsp)=>{ - let mut spawn_point=integer::Planar64Vec3::ZERO; - - let mut name_from_texture_id=Vec::new(); - let mut texture_id_from_name=std::collections::HashMap::new(); - - let mut models=bsp.models().map(|world_model|{ - //non-deduplicated - let mut spam_pos=Vec::new(); - let mut spam_tex=Vec::new(); - let mut spam_normal=Vec::new(); - let mut spam_vertices=Vec::new(); - let groups=world_model.faces() - .filter(|face| face.is_visible())//TODO: look at this - .map(|face|{ - let face_texture=face.texture(); - let face_texture_data=face_texture.texture_data(); - let (texture_u,texture_v)=(glam::Vec3A::from_slice(&face_texture.texture_transforms_u[0..3]),glam::Vec3A::from_slice(&face_texture.texture_transforms_v[0..3])); - let texture_offset=glam::vec2(face_texture.texture_transforms_u[3],face_texture.texture_transforms_v[3]); - let texture_size=glam::vec2(face_texture_data.width as f32,face_texture_data.height as f32); - - //texture - let texture_id=if let Some(&texture_id)=texture_id_from_name.get(face_texture_data.name()){ - texture_id - }else{ - let texture_id=name_from_texture_id.len() as u32; - texture_id_from_name.insert(face_texture_data.name().to_string(),texture_id); - name_from_texture_id.push(face_texture_data.name().to_string()); - texture_id - }; - - //normal - let normal=face.normal(); - let normal_idx=spam_normal.len() as u32; - spam_normal.push(valve_transform(<[f32;3]>::from(normal))); - let mut vertices:Vec=face.vertex_positions().map(|vertex_pos|{ - let vertex_xyz=<[f32;3]>::from(vertex_pos); - let pos=glam::Vec3A::from_array(vertex_xyz); - let pos_idx=spam_pos.len(); - spam_pos.push(valve_transform(vertex_xyz)); - - //calculate texture coordinates - let tex=(glam::vec2(pos.dot(texture_u),pos.dot(texture_v))+texture_offset)/texture_size; - let tex_idx=spam_tex.len() as u32; - spam_tex.push(tex); - - let i=spam_vertices.len() as u32; - spam_vertices.push(crate::model::IndexedVertex{ - pos: pos_idx as u32, - tex: tex_idx as u32, - normal: normal_idx, - color: 0, - }); - i - }).collect(); - vertices.reverse(); - crate::model::IndexedGroup{ - texture:Some(texture_id), - polys:vec![crate::model::IndexedPolygon{vertices}], - } - }).collect(); - crate::model::IndexedModel{ - unique_pos:spam_pos, - unique_tex:spam_tex, - unique_normal:spam_normal, - unique_color:vec![glam::Vec4::ONE], - unique_vertices:spam_vertices, - groups, - instances:vec![crate::model::ModelInstance{ - attributes:crate::model::CollisionAttributes::Decoration, - transform:integer::Planar64Affine3::new( - integer::Planar64Mat3::default(), - valve_transform(<[f32;3]>::from(world_model.origin)) - ), - ..Default::default() - }], - } - }).collect(); - - //dedupe prop models - let mut model_dedupe=std::collections::HashSet::new(); - for prop in bsp.static_props(){ - model_dedupe.insert(prop.model()); - } - - //generate unique meshes - let mut model_map=std::collections::HashMap::with_capacity(model_dedupe.len()); - let mut prop_models=Vec::new(); - for model_name in model_dedupe{ - let model_name_lower=model_name.to_lowercase(); - //.mdl, .vvd, .dx90.vtx - let mut path=std::path::PathBuf::from(model_name_lower.as_str()); - let file_name=std::path::PathBuf::from(path.file_stem().unwrap()); - path.pop(); - path.push(file_name); - let mut vvd_path=path.clone(); - let mut vtx_path=path.clone(); - vvd_path.set_extension("vvd"); - vtx_path.set_extension("dx90.vtx"); - match (bsp.pack.get(model_name_lower.as_str()),bsp.pack.get(vvd_path.as_os_str().to_str().unwrap()),bsp.pack.get(vtx_path.as_os_str().to_str().unwrap())){ - (Ok(Some(mdl_file)),Ok(Some(vvd_file)),Ok(Some(vtx_file)))=>{ - match (vmdl::mdl::Mdl::read(mdl_file.as_ref()),vmdl::vvd::Vvd::read(vvd_file.as_ref()),vmdl::vtx::Vtx::read(vtx_file.as_ref())){ - (Ok(mdl),Ok(vvd),Ok(vtx))=>{ - let model=vmdl::Model::from_parts(mdl,vtx,vvd); - let texture_paths=model.texture_directories(); - if texture_paths.len()!=1{ - println!("WARNING: multiple texture paths"); - } - let skin=model.skin_tables().nth(0).unwrap(); - - let mut spam_pos=Vec::with_capacity(model.vertices().len()); - let mut spam_normal=Vec::with_capacity(model.vertices().len()); - let mut spam_tex=Vec::with_capacity(model.vertices().len()); - let mut spam_vertices=Vec::with_capacity(model.vertices().len()); - for (i,vertex) in model.vertices().iter().enumerate(){ - spam_pos.push(valve_transform(<[f32;3]>::from(vertex.position))); - spam_normal.push(valve_transform(<[f32;3]>::from(vertex.normal))); - spam_tex.push(glam::Vec2::from_array(vertex.texture_coordinates)); - spam_vertices.push(crate::model::IndexedVertex{ - pos:i as u32, - tex:i as u32, - normal:i as u32, - color:0, - }); - } - - let model_id=prop_models.len(); - model_map.insert(model_name,model_id); - prop_models.push(crate::model::IndexedModel{ - unique_pos:spam_pos, - unique_normal:spam_normal, - unique_tex:spam_tex, - unique_color:vec![glam::Vec4::ONE], - unique_vertices:spam_vertices, - groups:model.meshes().map(|mesh|{ - let texture=if let (Some(texture_path),Some(texture_name))=(texture_paths.get(0),skin.texture(mesh.material_index())){ - let mut path=std::path::PathBuf::from(texture_path.as_str()); - path.push(texture_name); - let texture_location=path.as_os_str().to_str().unwrap(); - let texture_id=if let Some(&texture_id)=texture_id_from_name.get(texture_location){ - texture_id - }else{ - println!("texture! {}",texture_location); - let texture_id=name_from_texture_id.len() as u32; - texture_id_from_name.insert(texture_location.to_string(),texture_id); - name_from_texture_id.push(texture_location.to_string()); - texture_id - }; - Some(texture_id) - }else{ - None - }; - - crate::model::IndexedGroup{ - texture, - polys:{ - //looking at the code, it would seem that the strips are pre-deindexed into triangle lists when calling this function - mesh.vertex_strip_indices().map(|strip|{ - strip.collect::>().chunks(3).map(|tri|{ - crate::model::IndexedPolygon{vertices:vec![tri[0] as u32,tri[1] as u32,tri[2] as u32]} - }).collect::>() - }).flatten().collect() - }, - } - }).collect(), - instances:Vec::new(), - }); - }, - _=>println!("model_name={} error",model_name), - } - }, - _=>println!("no model name={}",model_name), - } - } - - //generate model instances - for prop in bsp.static_props(){ - let placement=prop.as_prop_placement(); - if let Some(&model_index)=model_map.get(placement.model){ - prop_models[model_index].instances.push(crate::model::ModelInstance{ - transform:integer::Planar64Affine3::new( - integer::Planar64Mat3::try_from( - glam::Mat3A::from_diagonal(glam::Vec3::splat(placement.scale)) - //TODO: figure this out - *glam::Mat3A::from_quat(glam::Quat::from_xyzw( - placement.rotation.v.x,//b - placement.rotation.v.y,//c - placement.rotation.v.z,//d - placement.rotation.s,//a - )) - ).unwrap(), - valve_transform(<[f32;3]>::from(placement.origin)), - ), - attributes:crate::model::CollisionAttributes::Decoration, - ..Default::default() - }); - }else{ - //println!("model not found {}",placement.model); - } - } - - //actually add the prop models - prop_models.append(&mut models); - - Ok(crate::model::IndexedModelInstances{ - textures:name_from_texture_id, - models:prop_models, - spawn_point, - modes:Vec::new(), - }) - }, - Err(e)=>{ - println!("rotten {:?}",e); - Err(e) - }, - } -} diff --git a/src/load_roblox.rs b/src/load_roblox.rs deleted file mode 100644 index 211208d..0000000 --- a/src/load_roblox.rs +++ /dev/null @@ -1,523 +0,0 @@ -use crate::primitives; -use strafesnet_common::integer::{Planar64,Planar64Vec3,Planar64Mat3,Planar64Affine3}; - -fn class_is_a(class: &str, superclass: &str) -> bool { - if class==superclass { - return true - } - let class_descriptor=rbx_reflection_database::get().classes.get(class); - if let Some(descriptor) = &class_descriptor { - if let Some(class_super) = &descriptor.superclass { - return class_is_a(&class_super, superclass) - } - } - return false -} -fn recursive_collect_superclass(objects: &mut std::vec::Vec,dom: &rbx_dom_weak::WeakDom, instance: &rbx_dom_weak::Instance, superclass: &str){ - let mut stack=vec![instance]; - while let Some(item)=stack.pop(){ - for &referent in item.children(){ - if let Some(c)=dom.get_by_ref(referent){ - if class_is_a(c.class.as_str(),superclass){ - objects.push(c.referent());//copy ref - } - stack.push(c); - } - } - } -} -fn planar64_affine3_from_roblox(cf:&rbx_dom_weak::types::CFrame,size:&rbx_dom_weak::types::Vector3)->Planar64Affine3{ - Planar64Affine3::new( - Planar64Mat3::from_cols( - 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"=>{ - force_can_collide=false; - //TODO: read stupid CustomPhysicalProperties - intersecting.water=Some(crate::model::IntersectingWater{density:Planar64::ONE,viscosity:Planar64::ONE/10,velocity}); - }, - "Accelerator"=>{ - //although the new game supports collidable accelerators, this is a roblox compatability map loader - force_can_collide=false; - general.accelerator=Some(crate::model::GameMechanicAccelerator{acceleration:velocity}); - }, - // "UnorderedCheckpoint"=>general.teleport_behaviour=Some(crate::model::TeleportBehaviour::StageElement(crate::model::GameMechanicStageElement{ - // mode_id:0, - // stage_id:0, - // force:false, - // behaviour:crate::model::StageElementBehaviour::Unordered - // })), - "SetVelocity"=>general.trajectory=Some(crate::model::GameMechanicSetTrajectory::Velocity(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.teleport_behaviour=Some(crate::model::TeleportBehaviour::StageElement(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.teleport_behaviour=Some(crate::model::TeleportBehaviour::StageElement(crate::model::GameMechanicStageElement{ - mode_id:0, - stage_id:captures[3].parse::().unwrap(), - force:match captures.get(1){ - Some(m)=>m.as_str()=="Force", - None=>false, - }, - behaviour:match &captures[2]{ - "Spawn"|"SpawnAt"=>crate::model::StageElementBehaviour::SpawnAt, - //cancollide false so you don't hit the side - //NOT a decoration - "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"^(Force)?(Jump)(\d+)$") - .captures(other){ - general.teleport_behaviour=Some(crate::model::TeleportBehaviour::StageElement(crate::model::GameMechanicStageElement{ - mode_id:0, - stage_id:0, - force:match captures.get(1){ - Some(m)=>m.as_str()=="Force", - None=>false, - }, - behaviour:match &captures[2]{ - "Jump"=>crate::model::StageElementBehaviour::JumpLimit(captures[3].parse::().unwrap()), - _=>panic!("regex4[1] 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::().unwrap(),behaviour:crate::model::ZoneBehaviour::Finish}), - "Anticheat"=>general.zone=Some(crate::model::GameMechanicZone{mode_id:captures[2].parse::().unwrap(),behaviour:crate::model::ZoneBehaviour::Anitcheat}), - _=>panic!("regex2[1] messed up bad"), - } - }else if let Some(captures)=lazy_regex::regex!(r"^(WormholeIn)(\d+)$") - .captures(other){ - force_can_collide=false; - match &captures[1]{ - "WormholeIn"=>general.teleport_behaviour=Some(crate::model::TeleportBehaviour::Wormhole(crate::model::GameMechanicWormhole{destination_model_id:captures[2].parse::().unwrap()})), - _=>panic!("regex3[1] messed up bad"), - } - } - // else if let Some(captures)=lazy_regex::regex!(r"^(OrderedCheckpoint)(\d+)$") - // .captures(other){ - // match &captures[1]{ - // "OrderedCheckpoint"=>general.checkpoint=Some(crate::model::GameMechanicCheckpoint::Ordered{mode_id:0,checkpoint_id:captures[2].parse::().unwrap()}), - // _=>panic!("regex3[1] messed up bad"), - // } - // } - } - } - //need some way to skip this - if velocity!=Planar64Vec3::ZERO{ - general.booster=Some(crate::model::GameMechanicBooster::Velocity(velocity)); - } - match force_can_collide{ - true=>{ - match name{ - "Bounce"=>contacting.contact_behaviour=Some(crate::model::ContactingBehaviour::Elastic(u32::MAX)), - "Surf"=>contacting.contact_behaviour=Some(crate::model::ContactingBehaviour::Surf), - "Ladder"=>contacting.contact_behaviour=Some(crate::model::ContactingBehaviour::Ladder(crate::model::ContactingLadder{sticky:true})), - _=>(), - } - crate::model::CollisionAttributes::Contact{contacting,general} - }, - false=>if force_intersecting - ||general.any() - ||intersecting.any() - { - 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{ - 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::() { - 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(&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(&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;6]; -type RobloxWedgeDescription=[Option;5]; -type RobloxCornerWedgeDescription=[Option;5]; -#[derive(Clone,Eq,Hash,PartialEq)] -enum RobloxBasePartDescription{ - Sphere(RobloxPartDescription), - Part(RobloxPartDescription), - Cylinder(RobloxPartDescription), - 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; - - let mut indexed_models=Vec::new(); - let mut model_id_from_description=std::collections::HashMap::::new(); - - let mut texture_id_from_asset_id=std::collections::HashMap::::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=planar64_affine3_from_roblox(cf,size); - - if model_transform.matrix3.determinant()==Planar64::ZERO{ - let mut parent_ref=object.parent(); - let mut full_path=object.name.clone(); - while let Some(parent)=dom.get_by_ref(parent_ref){ - full_path=format!("{}.{}",parent.name,full_path); - parent_ref=parent.parent(); - } - println!("Zero determinant CFrame at location {}",full_path); - println!("matrix3:{}",model_transform.matrix3); - continue; - } - - //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(crate::model::TempAttrStart{mode_id:0})) - }, - other=>{ - let regman=lazy_regex::regex!(r"^(BonusStart|Spawn|ForceSpawn|WormholeOut)(\d+)$"); - if let Some(captures) = regman.captures(other) { - match &captures[1]{ - "BonusStart"=>Some(crate::model::TempIndexedAttributes::Start(crate::model::TempAttrStart{mode_id:captures[2].parse::().unwrap()})), - "Spawn"|"ForceSpawn"=>Some(crate::model::TempIndexedAttributes::Spawn(crate::model::TempAttrSpawn{mode_id:0,stage_id:captures[2].parse::().unwrap()})), - "WormholeOut"=>Some(crate::model::TempIndexedAttributes::Wormhole(crate::model::TempAttrWormhole{wormhole_id:captures[2].parse::().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!"); - } - }, - "TrussPart"=>primitives::Primitives::Cube, - "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::(){ - 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 (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); - } - } - } - } - } - //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([f0,f1,f2,f3,f4,f5]), - primitives::Primitives::Cube=>RobloxBasePartDescription::Part([f0,f1,f2,f3,f4,f5]), - primitives::Primitives::Cylinder=>RobloxBasePartDescription::Cylinder([f0,f1,f2,f3,f4,f5]), - //use front face texture first and use top face texture as a fallback - primitives::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){ - //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(part_texture_description) - |RobloxBasePartDescription::Cylinder(part_texture_description) - |RobloxBasePartDescription::Part(part_texture_description)=>{ - let mut cube_face_description=primitives::CubeFaceDescription::default(); - for (face_id,roblox_face_description) in part_texture_description.iter().enumerate(){ - cube_face_description.insert( - match face_id{ - 0=>primitives::CubeFace::Right, - 1=>primitives::CubeFace::Top, - 2=>primitives::CubeFace::Back, - 3=>primitives::CubeFace::Left, - 4=>primitives::CubeFace::Bottom, - 5=>primitives::CubeFace::Front, - _=>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::Wedge(wedge_texture_description)=>{ - let mut wedge_face_description=primitives::WedgeFaceDescription::default(); - for (face_id,roblox_face_description) in wedge_texture_description.iter().enumerate(){ - wedge_face_description.insert( - match face_id{ - 0=>primitives::WedgeFace::Right, - 1=>primitives::WedgeFace::TopFront, - 2=>primitives::WedgeFace::Back, - 3=>primitives::WedgeFace::Left, - 4=>primitives::WedgeFace::Bottom, - _=>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::default(); - for (face_id,roblox_face_description) in cornerwedge_texture_description.iter().enumerate(){ - cornerwedge_face_description.insert( - match face_id{ - 0=>primitives::CornerWedgeFace::Right, - 1=>primitives::CornerWedgeFace::TopBack, - 2=>primitives::CornerWedgeFace::TopLeft, - 3=>primitives::CornerWedgeFace::Bottom, - 4=>primitives::CornerWedgeFace::Front, - _=>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, - }); - } - } - } - crate::model::IndexedModelInstances{ - textures:asset_id_from_texture_id.iter().map(|t|t.to_string()).collect(), - models:indexed_models, - spawn_point, - modes:Vec::new(), - } -} diff --git a/src/main.rs b/src/main.rs index 3f4300b..627528e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,15 +1,9 @@ -use strafesnet_common::integer; - -mod model; mod setup; mod window; mod worker; mod physics; mod graphics; mod settings; -mod primitives; -mod load_bsp; -mod load_roblox; mod face_crawler; mod compat_worker; mod model_physics; @@ -17,102 +11,6 @@ mod model_graphics; mod physics_worker; mod graphics_worker; -fn load_file(path: std::path::PathBuf)->Option{ - println!("Loading file: {:?}", &path); - //oh boy! let's load the map! - if let Ok(file)=std::fs::File::open(path){ - let mut input = std::io::BufReader::new(file); - let mut first_8=[0u8;8]; - //.rbxm roblox binary = "{ - match match &first_8[4..8]{ - b"lox!"=>rbx_binary::from_reader(input).map_err(|e|format!("{:?}",e)), - b"lox "=>rbx_xml::from_reader(input,rbx_xml::DecodeOptions::default()).map_err(|e|format!("{:?}",e)), - other=>Err(format!("Unknown Roblox file type {:?}",other)), - }{ - Ok(dom)=>Some(load_roblox::generate_indexed_models(dom)), - Err(e)=>{ - println!("Error loading roblox file:{:?}",e); - None - }, - } - }, - b"VBSP"=>load_bsp::generate_indexed_models(&mut input).ok(), - //b"SNFM"=>Some(sniffer::generate_indexed_models(input)), - //b"SNFB"=>Some(sniffer::load_bot(input)), - other=>{ - println!("loser file {:?}",other); - None - }, - } - }else{ - println!("Failed to read first 8 bytes and seek back to beginning of file."); - None - } - }else{ - println!("Could not open file"); - None - } -} - -pub fn default_models()->model::IndexedModelInstances{ - let mut indexed_models = Vec::new(); - indexed_models.push(primitives::unit_sphere()); - indexed_models.push(primitives::unit_cylinder()); - indexed_models.push(primitives::unit_cube()); - println!("models.len = {:?}", indexed_models.len()); - //quad monkeys - indexed_models[0].instances.push(model::ModelInstance{ - transform:integer::Planar64Affine3::try_from(glam::Affine3A::from_translation(glam::vec3(10.,5.,10.))).unwrap(), - ..Default::default() - }); - indexed_models[0].instances.push(model::ModelInstance{ - transform:integer::Planar64Affine3::try_from(glam::Affine3A::from_translation(glam::vec3(20.,5.,10.))).unwrap(), - color:glam::vec4(1.0,0.0,0.0,1.0), - ..Default::default() - }); - indexed_models[0].instances.push(model::ModelInstance{ - transform:integer::Planar64Affine3::try_from(glam::Affine3A::from_translation(glam::vec3(10.,5.,20.))).unwrap(), - color:glam::vec4(0.0,1.0,0.0,1.0), - ..Default::default() - }); - indexed_models[0].instances.push(model::ModelInstance{ - transform:integer::Planar64Affine3::try_from(glam::Affine3A::from_translation(glam::vec3(20.,5.,20.))).unwrap(), - color:glam::vec4(0.0,0.0,1.0,1.0), - ..Default::default() - }); - //decorative monkey - indexed_models[0].instances.push(model::ModelInstance{ - transform:integer::Planar64Affine3::try_from(glam::Affine3A::from_translation(glam::vec3(15.,10.,15.))).unwrap(), - color:glam::vec4(0.5,0.5,0.5,0.5), - attributes:model::CollisionAttributes::Decoration, - ..Default::default() - }); - //teapot - indexed_models[1].instances.push(model::ModelInstance{ - transform:integer::Planar64Affine3::try_from(glam::Affine3A::from_scale_rotation_translation(glam::vec3(0.5, 1.0, 0.2),glam::quat(-0.22248298016985793,-0.839457167990537,-0.05603504040830783,-0.49261857546227916),glam::vec3(-10.,7.,10.))).unwrap(), - ..Default::default() - }); - //ground - indexed_models[2].instances.push(model::ModelInstance{ - transform:integer::Planar64Affine3::try_from(glam::Affine3A::from_translation(glam::vec3(0.,0.,0.))*glam::Affine3A::from_scale(glam::vec3(160.0, 1.0, 160.0))).unwrap(), - ..Default::default() - }); - model::IndexedModelInstances{ - textures:Vec::new(), - models:indexed_models, - spawn_point:integer::Planar64Vec3::Y*50, - modes:Vec::new(), - } -} - fn main(){ setup::setup_and_start(format!("Strafe Client v{}",env!("CARGO_PKG_VERSION"))); } diff --git a/src/model.rs b/src/model.rs deleted file mode 100644 index ddfc3d9..0000000 --- a/src/model.rs +++ /dev/null @@ -1,321 +0,0 @@ -use strafesnet_common::integer::{Time,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, -} -pub struct IndexedGroup{ - pub texture:Option,//RenderPattern? material/texture/shader/flat color - pub polys:Vec, -} -pub struct IndexedModel{ - pub unique_pos:Vec, - pub unique_normal:Vec, - pub unique_tex:Vec, - pub unique_color:Vec, - pub unique_vertices:Vec, - pub groups: Vec, - pub instances:Vec, -} -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, -} -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,//RenderPattern - pub models:Vec, - //may make this into an object later. - pub modes:Vec, - pub spawn_point:Planar64Vec3, -} -//stage description referencing flattened ids is spooky, but the map loading is meant to be deterministic. -pub struct ModeDescription{ - //TODO: put "default" style modifiers in mode - //pub style:StyleModifiers, - pub start:usize,//start=model_id - pub spawns:Vec,//spawns[spawn_id]=model_id - pub spawn_from_stage_id:std::collections::HashMap::, - pub ordered_checkpoint_from_checkpoint_id:std::collections::HashMap::, -} -impl ModeDescription{ - pub fn get_spawn_model_id(&self,stage_id:u32)->Option<&usize>{ - self.spawns.get(*self.spawn_from_stage_id.get(&stage_id)?) - } -} -//I don't want this code to exist! -#[derive(Clone)] -pub struct TempAttrStart{ - pub mode_id:u32, -} -#[derive(Clone)] -pub struct TempAttrSpawn{ - pub mode_id:u32, - pub stage_id:u32, -} -#[derive(Clone)] -pub struct TempAttrWormhole{ - pub wormhole_id:u32, -} -pub enum TempIndexedAttributes{ - Start(TempAttrStart), - Spawn(TempAttrSpawn), - Wormhole(TempAttrWormhole), -} - -//you have this effect while in contact -#[derive(Clone,Hash,Eq,PartialEq)] -pub struct ContactingLadder{ - pub sticky:bool -} -#[derive(Clone,Hash,Eq,PartialEq)] -pub enum ContactingBehaviour{ - Surf, - Cling,//usable as a zipline, or other weird and wonderful things - Ladder(ContactingLadder), - Elastic(u32),//[1/2^32,1] 0=None (elasticity+1)/2^32 -} -//you have this effect while intersecting -#[derive(Clone,Hash,Eq,PartialEq)] -pub struct IntersectingWater{ - pub viscosity:Planar64, - pub density:Planar64, - pub velocity:Planar64Vec3, -} -//All models can be given these attributes -#[derive(Clone,Hash,Eq,PartialEq)] -pub struct GameMechanicAccelerator{ - pub acceleration:Planar64Vec3 -} -#[derive(Clone,Hash,Eq,PartialEq)] -pub enum GameMechanicBooster{ - Affine(Planar64Affine3),//capable of SetVelocity,DotVelocity,normal booster,bouncy part,redirect velocity, and much more - Velocity(Planar64Vec3),//straight up boost velocity adds to your current velocity - Energy{direction:Planar64Vec3,energy:Planar64},//increase energy in direction -} -#[derive(Clone,Hash,Eq,PartialEq)] -pub enum TrajectoryChoice{ - HighArcLongDuration,//underhand lob at target: less horizontal speed and more air time - LowArcShortDuration,//overhand throw at target: more horizontal speed and less air time -} -#[derive(Clone,Hash,Eq,PartialEq)] -pub enum GameMechanicSetTrajectory{ - //Speed-type SetTrajectory - AirTime(Time),//air time (relative to gravity direction) is invariant across mass and gravity changes - Height(Planar64),//boost height (relative to gravity direction) is invariant across mass and gravity changes - DotVelocity{direction:Planar64Vec3,dot:Planar64},//set your velocity in a specific direction without touching other directions - //Velocity-type SetTrajectory - TargetPointTime{//launch on a trajectory that will land at a target point in a set amount of time - target_point:Planar64Vec3, - time:Time,//short time = fast and direct, long time = launch high in the air, negative time = wrong way - }, - TargetPointSpeed{//launch at a fixed speed and land at a target point - target_point:Planar64Vec3, - speed:Planar64,//if speed is too low this will fail to reach the target. The closest-passing trajectory will be chosen instead - trajectory_choice:TrajectoryChoice, - }, - Velocity(Planar64Vec3),//SetVelocity -} -impl GameMechanicSetTrajectory{ - fn is_velocity(&self)->bool{ - match self{ - GameMechanicSetTrajectory::AirTime(_) - |GameMechanicSetTrajectory::Height(_) - |GameMechanicSetTrajectory::DotVelocity{direction:_,dot:_}=>false, - GameMechanicSetTrajectory::TargetPointTime{target_point:_,time:_} - |GameMechanicSetTrajectory::TargetPointSpeed{target_point:_,speed:_,trajectory_choice:_} - |GameMechanicSetTrajectory::Velocity(_)=>true, - } - } -} -#[derive(Clone,Hash,Eq,PartialEq)] -pub enum ZoneBehaviour{ - //Start is indexed - //Checkpoints are indexed - Finish, - Anitcheat, -} -#[derive(Clone,Hash,Eq,PartialEq)] -pub struct GameMechanicZone{ - pub mode_id:u32, - pub behaviour:ZoneBehaviour, -} -// enum TrapCondition{ -// FasterThan(Planar64), -// SlowerThan(Planar64), -// InRange(Planar64,Planar64), -// OutsideRange(Planar64,Planar64), -// } -#[derive(Clone,Hash,Eq,PartialEq)] -pub enum StageElementBehaviour{ - //Spawn,//The behaviour of stepping on a spawn setting the spawnid - SpawnAt,//must be standing on top to get effect. except cancollide false - Trigger, - Teleport, - Platform, - //Checkpoint acts like a trigger if you haven't hit all the checkpoints yet. - //Note that all stage elements act like this for the next stage. - Checkpoint, - //OrderedCheckpoint. You must pass through all of these in ascending order. - //If you hit them out of order it acts like a trigger. - //Do not support backtracking at all for now. - Ordered{ - checkpoint_id:u32, - }, - //UnorderedCheckpoint. You must pass through all of these in any order. - Unordered, - //If you get reset by a jump limit - JumpLimit(u32), - //Speedtrap(TrapCondition),//Acts as a trigger with a speed condition -} -#[derive(Clone,Hash,Eq,PartialEq)] -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,Hash,Eq,PartialEq)] -pub struct GameMechanicWormhole{ - //destination does not need to be another wormhole - //this defines a one way portal to a destination model transform - //two of these can create a two way wormhole - pub destination_model_id:u32, - //(position,angles)*=origin.transform.inverse()*destination.transform -} -#[derive(Clone,Hash,Eq,PartialEq)] -pub enum TeleportBehaviour{ - StageElement(GameMechanicStageElement), - Wormhole(GameMechanicWormhole), -} -//attributes listed in order of handling -#[derive(Default,Clone,Hash,Eq,PartialEq)] -pub struct GameMechanicAttributes{ - pub zone:Option, - pub booster:Option, - pub trajectory:Option, - pub teleport_behaviour:Option, - pub accelerator:Option, -} -impl GameMechanicAttributes{ - pub fn any(&self)->bool{ - self.zone.is_some() - ||self.booster.is_some() - ||self.trajectory.is_some() - ||self.teleport_behaviour.is_some() - ||self.accelerator.is_some() - } - pub fn is_wrcp(&self,current_mode_id:u32)->bool{ - self.trajectory.as_ref().map_or(false,|t|t.is_velocity()) - &&match &self.teleport_behaviour{ - Some(TeleportBehaviour::StageElement( - GameMechanicStageElement{ - mode_id, - stage_id:_, - force:true, - behaviour:StageElementBehaviour::Trigger|StageElementBehaviour::Teleport - } - ))=>current_mode_id==*mode_id, - _=>false, - } - } -} -#[derive(Default,Clone,Hash,Eq,PartialEq)] -pub struct ContactingAttributes{ - //friction? - pub contact_behaviour:Option, -} -impl ContactingAttributes{ - pub fn any(&self)->bool{ - self.contact_behaviour.is_some() - } -} -#[derive(Default,Clone,Hash,Eq,PartialEq)] -pub struct IntersectingAttributes{ - pub water:Option, -} -impl IntersectingAttributes{ - pub fn any(&self)->bool{ - self.water.is_some() - } -} -//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:Color4)->Vec{ - let mut unique_vertex_index = std::collections::HashMap::::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.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(), - } - }).collect() -} diff --git a/src/model_graphics.rs b/src/model_graphics.rs index 8e871e7..2468cda 100644 --- a/src/model_graphics.rs +++ b/src/model_graphics.rs @@ -1,50 +1,39 @@ -use bytemuck::{Pod, Zeroable}; -use crate::model::{IndexedVertex,IndexedPolygon}; -#[derive(Clone, Copy, Pod, Zeroable)] +use bytemuck::{Pod,Zeroable}; +use strafesnet_common::model::{IndexedVertex,PolygonGroup,RenderConfigId}; +#[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 GraphicsVertex{ + pub pos:[f32;3], + pub tex:[f32;2], + pub normal:[f32;3], + pub color:[f32;4], } -pub struct IndexedGroupFixedTexture{ - pub polys:Vec, -} -pub struct IndexedGraphicsModelSingleTexture{ - pub unique_pos:Vec<[f32; 3]>, - pub unique_tex:Vec<[f32; 2]>, - pub unique_normal:Vec<[f32; 3]>, - pub unique_color:Vec<[f32; 4]>, +#[derive(Clone,Copy,id::Id)] +pub struct IndexedGraphicsMeshOwnedRenderConfigId(u32); +pub struct IndexedGraphicsMeshOwnedRenderConfig{ + 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, - pub texture:Option,//RenderPattern? material/texture/shader/flat color - pub groups: Vec, - pub instances:Vec, + pub render_config:RenderConfigId, + pub polys:PolygonGroup, + pub instances:Vec, } -pub enum Entities{ - U32(Vec>), - U16(Vec>), +pub enum Indices{ + U32(Vec), + U16(Vec), } -pub struct GraphicsModelSingleTexture{ - pub instances:Vec, +pub struct GraphicsMeshOwnedRenderConfig{ pub vertices:Vec, - pub entities:Entities, - pub texture:Option, + pub indices:Indices, + pub render_config:RenderConfigId, + pub instances:Vec, } -#[derive(Clone,PartialEq)] +#[derive(Clone,Copy,PartialEq,id::Id)] pub struct GraphicsModelColor4(glam::Vec4); -impl GraphicsModelColor4{ - pub const fn get(&self)->glam::Vec4{ - self.0 - } -} -impl From for GraphicsModelColor4{ - fn from(value:glam::Vec4)->Self{ - Self(value) - } -} impl std::hash::Hash for GraphicsModelColor4{ - fn hash(&self,state:&mut H) { + fn hash(&self,state:&mut H) { for &f in self.0.as_ref(){ bytemuck::cast::(f).hash(state); } @@ -52,7 +41,7 @@ impl std::hash::Hash for GraphicsModelColor4{ } impl Eq for GraphicsModelColor4{} #[derive(Clone)] -pub struct GraphicsModelInstance{ +pub struct GraphicsModelOwned{ pub transform:glam::Mat4, pub normal_transform:glam::Mat3, pub color:GraphicsModelColor4, diff --git a/src/model_physics.rs b/src/model_physics.rs index 7e31ac0..4a0595a 100644 --- a/src/model_physics.rs +++ b/src/model_physics.rs @@ -1,21 +1,13 @@ use std::borrow::{Borrow,Cow}; +use std::collections::{HashSet,HashMap}; +use strafesnet_common::model::{self,MeshId,PolygonIter}; use strafesnet_common::zeroes; use strafesnet_common::integer::{self,Planar64,Planar64Vec3}; -#[derive(Debug,Clone,Copy,Hash,Eq,PartialEq)] -pub struct VertId(usize); -#[derive(Debug,Clone,Copy,Hash,Eq,PartialEq)] -pub struct EdgeId(usize); pub trait UndirectedEdge{ type DirectedEdge:Copy+DirectedEdge; fn as_directed(&self,parity:bool)->Self::DirectedEdge; } -impl UndirectedEdge for EdgeId{ - type DirectedEdge=DirectedEdgeId; - fn as_directed(&self,parity:bool)->DirectedEdgeId{ - DirectedEdgeId(self.0|((parity as usize)<<(usize::BITS-1))) - } -} pub trait DirectedEdge{ type UndirectedEdge:Copy+UndirectedEdge; fn as_undirected(&self)->Self::UndirectedEdge; @@ -25,20 +17,37 @@ pub trait DirectedEdge{ self.as_undirected().as_directed(!self.parity()) } } + +#[derive(Debug,Clone,Copy,Hash,id::Id,Eq,PartialEq)] +pub struct MeshVertId(u32); +#[derive(Debug,Clone,Copy,Hash,id::Id,Eq,PartialEq)] +pub struct MeshFaceId(u32); + +#[derive(Debug,Clone,Copy,Hash,id::Id,Eq,PartialEq)] +pub struct SubmeshVertId(u32); +#[derive(Debug,Clone,Copy,Hash,id::Id,Eq,PartialEq)] +pub struct SubmeshEdgeId(u32); /// DirectedEdgeId refers to an EdgeId when undirected. -#[derive(Debug,Clone,Copy,Hash,Eq,PartialEq)] -pub struct DirectedEdgeId(usize); -impl DirectedEdge for DirectedEdgeId{ - type UndirectedEdge=EdgeId; - fn as_undirected(&self)->EdgeId{ - EdgeId(self.0&!(1<<(usize::BITS-1))) - } - fn parity(&self)->bool{ - self.0&(1<<(usize::BITS-1))!=0 +#[derive(Debug,Clone,Copy,Hash,id::Id,Eq,PartialEq)] +pub struct SubmeshDirectedEdgeId(u32); +#[derive(Debug,Clone,Copy,Hash,id::Id,Eq,PartialEq)] +pub struct SubmeshFaceId(u32); + +impl UndirectedEdge for SubmeshEdgeId{ + type DirectedEdge=SubmeshDirectedEdgeId; + fn as_directed(&self,parity:bool)->SubmeshDirectedEdgeId{ + SubmeshDirectedEdgeId(self.0|((parity as u32)<<(u32::BITS-1))) + } +} +impl DirectedEdge for SubmeshDirectedEdgeId{ + type UndirectedEdge=SubmeshEdgeId; + fn as_undirected(&self)->SubmeshEdgeId{ + SubmeshEdgeId(self.0&!(1<<(u32::BITS-1))) + } + fn parity(&self)->bool{ + self.0&(1<<(u32::BITS-1))!=0 } } -#[derive(Debug,Clone,Copy,Hash,Eq,PartialEq)] -pub struct FaceId(usize); //Vertex <-> Edge <-> Face -> Collide pub enum FEV{ @@ -48,6 +57,7 @@ pub enum FEV{ } //use Unit32 #[repr(C)] for map files +#[derive(Clone,Hash,Eq,PartialEq)] struct Face{ normal:Planar64Vec3, dot:Planar64, @@ -71,34 +81,170 @@ pub trait MeshQuery{ fn vert_faces(&self,vert_id:VERT)->Cow>; } struct FaceRefs{ - edges:Vec, + edges:Vec, //verts:Vec, } struct EdgeRefs{ - faces:[FaceId;2],//left, right - verts:[VertId;2],//bottom, top + faces:[SubmeshFaceId;2],//left, right + verts:[SubmeshVertId;2],//bottom, top } struct VertRefs{ - faces:Vec, - edges:Vec, + faces:Vec, + edges:Vec, } -pub struct PhysicsMesh{ - faces:Vec, - verts:Vec, +pub struct PhysicsMeshData{ + //this contains all real and virtual faces used in both the complete mesh and convex submeshes + //faces are sorted such that all faces that belong to the complete mesh appear first, and then + //all remaining faces are virtual to operate internal logic of the face crawler + //and cannot be part of a physics collision + //virtual faces are only used in convex submeshes. + faces:Vec,//MeshFaceId indexes this list + verts:Vec,//MeshVertId indexes this list +} +pub struct PhysicsMeshTopology{ + //mapping of local ids to PhysicsMeshData ids + faces:Vec,//SubmeshFaceId indexes this list + verts:Vec,//SubmeshVertId indexes this list + //all ids here are local to this object face_topology:Vec, edge_topology:Vec, vert_topology:Vec, } +#[derive(Clone,Copy,Hash,id::Id,Eq,PartialEq)] +pub struct PhysicsMeshId(u32); +impl Into for PhysicsMeshId{ + fn into(self)->MeshId{ + MeshId::new(self.0) + } +} +impl From for PhysicsMeshId{ + fn from(value:MeshId)->Self{ + Self::new(value.get()) + } +} +#[derive(Debug,Default,Clone,Copy,Hash,id::Id,Eq,PartialEq)] +pub struct PhysicsSubmeshId(u32); +pub struct PhysicsMesh{ + data:PhysicsMeshData, + complete_mesh:PhysicsMeshTopology, + //Most objects in roblox maps are already convex, so the list length is 0 + //as soon as the mesh is divided into 2 submeshes, the list length jumps to 2. + //length 1 is unnecessary since the complete mesh would be a duplicate of the only submesh, but would still function properly + submeshes:Vec, +} +impl PhysicsMesh{ + pub fn unit_cube()->Self{ + //go go gadget debug print mesh + let data=PhysicsMeshData{ + faces:vec![ + Face{normal:Planar64Vec3::raw( 4294967296, 0, 0),dot:Planar64::raw(4294967296)}, + Face{normal:Planar64Vec3::raw( 0, 4294967296, 0),dot:Planar64::raw(4294967296)}, + Face{normal:Planar64Vec3::raw( 0, 0, 4294967296),dot:Planar64::raw(4294967296)}, + Face{normal:Planar64Vec3::raw(-4294967296, 0, 0),dot:Planar64::raw(4294967296)}, + Face{normal:Planar64Vec3::raw( 0,-4294967296, 0),dot:Planar64::raw(4294967296)}, + Face{normal:Planar64Vec3::raw( 0, 0,-4294967296),dot:Planar64::raw(4294967296)} + ], + verts:vec![ + Vert(Planar64Vec3::raw( 4294967296,-4294967296,-4294967296)), + Vert(Planar64Vec3::raw( 4294967296, 4294967296,-4294967296)), + Vert(Planar64Vec3::raw( 4294967296, 4294967296, 4294967296)), + Vert(Planar64Vec3::raw( 4294967296,-4294967296, 4294967296)), + Vert(Planar64Vec3::raw(-4294967296, 4294967296,-4294967296)), + Vert(Planar64Vec3::raw(-4294967296, 4294967296, 4294967296)), + Vert(Planar64Vec3::raw(-4294967296,-4294967296, 4294967296)), + Vert(Planar64Vec3::raw(-4294967296,-4294967296,-4294967296)) + ] + }; + let mesh_topology=PhysicsMeshTopology{ + faces:(0..data.faces.len() as u32).map(MeshFaceId::new).collect(), + verts:(0..data.verts.len() as u32).map(MeshVertId::new).collect(), + face_topology:vec![ + FaceRefs{edges:vec![SubmeshDirectedEdgeId((9223372036854775808u64-(1<<63)+(1<<31)) as u32),SubmeshDirectedEdgeId((9223372036854775809u64-(1<<63)+(1<<31)) as u32),SubmeshDirectedEdgeId((9223372036854775810u64-(1<<63)+(1<<31)) as u32),SubmeshDirectedEdgeId(3)]}, + FaceRefs{edges:vec![SubmeshDirectedEdgeId((9223372036854775812u64-(1<<63)+(1<<31)) as u32),SubmeshDirectedEdgeId((9223372036854775813u64-(1<<63)+(1<<31)) as u32),SubmeshDirectedEdgeId(6),SubmeshDirectedEdgeId(1)]}, + FaceRefs{edges:vec![SubmeshDirectedEdgeId(7),SubmeshDirectedEdgeId(2),SubmeshDirectedEdgeId((9223372036854775814u64-(1<<63)+(1<<31)) as u32),SubmeshDirectedEdgeId((9223372036854775816u64-(1<<63)+(1<<31)) as u32)]}, + FaceRefs{edges:vec![SubmeshDirectedEdgeId(8),SubmeshDirectedEdgeId(5),SubmeshDirectedEdgeId((9223372036854775817u64-(1<<63)+(1<<31)) as u32),SubmeshDirectedEdgeId(10)]}, + FaceRefs{edges:vec![SubmeshDirectedEdgeId((9223372036854775815u64-(1<<63)+(1<<31)) as u32),SubmeshDirectedEdgeId((9223372036854775818u64-(1<<63)+(1<<31)) as u32),SubmeshDirectedEdgeId(11),SubmeshDirectedEdgeId((9223372036854775811u64-(1<<63)+(1<<31)) as u32)]}, + FaceRefs{edges:vec![SubmeshDirectedEdgeId(4),SubmeshDirectedEdgeId(0),SubmeshDirectedEdgeId((9223372036854775819u64-(1<<63)+(1<<31)) as u32),SubmeshDirectedEdgeId(9)]} + ], + edge_topology:vec![ + EdgeRefs{faces:[SubmeshFaceId(0),SubmeshFaceId(5)],verts:[SubmeshVertId(0),SubmeshVertId(1)]}, + EdgeRefs{faces:[SubmeshFaceId(0),SubmeshFaceId(1)],verts:[SubmeshVertId(1),SubmeshVertId(2)]}, + EdgeRefs{faces:[SubmeshFaceId(0),SubmeshFaceId(2)],verts:[SubmeshVertId(2),SubmeshVertId(3)]}, + EdgeRefs{faces:[SubmeshFaceId(4),SubmeshFaceId(0)],verts:[SubmeshVertId(0),SubmeshVertId(3)]}, + EdgeRefs{faces:[SubmeshFaceId(1),SubmeshFaceId(5)],verts:[SubmeshVertId(1),SubmeshVertId(4)]}, + EdgeRefs{faces:[SubmeshFaceId(1),SubmeshFaceId(3)],verts:[SubmeshVertId(4),SubmeshVertId(5)]}, + EdgeRefs{faces:[SubmeshFaceId(2),SubmeshFaceId(1)],verts:[SubmeshVertId(2),SubmeshVertId(5)]}, + EdgeRefs{faces:[SubmeshFaceId(4),SubmeshFaceId(2)],verts:[SubmeshVertId(3),SubmeshVertId(6)]}, + EdgeRefs{faces:[SubmeshFaceId(2),SubmeshFaceId(3)],verts:[SubmeshVertId(5),SubmeshVertId(6)]}, + EdgeRefs{faces:[SubmeshFaceId(3),SubmeshFaceId(5)],verts:[SubmeshVertId(4),SubmeshVertId(7)]}, + EdgeRefs{faces:[SubmeshFaceId(4),SubmeshFaceId(3)],verts:[SubmeshVertId(6),SubmeshVertId(7)]}, + EdgeRefs{faces:[SubmeshFaceId(5),SubmeshFaceId(4)],verts:[SubmeshVertId(0),SubmeshVertId(7)]} + ], + vert_topology:vec![ + VertRefs{faces:vec![SubmeshFaceId(0),SubmeshFaceId(4),SubmeshFaceId(5)],edges:vec![SubmeshDirectedEdgeId((9223372036854775811u64-(1<<63)+(1<<31)) as u32),SubmeshDirectedEdgeId((9223372036854775819u64-(1<<63)+(1<<31)) as u32),SubmeshDirectedEdgeId((9223372036854775808u64-(1<<63)+(1<<31)) as u32)]}, + VertRefs{faces:vec![SubmeshFaceId(0),SubmeshFaceId(5),SubmeshFaceId(1)],edges:vec![SubmeshDirectedEdgeId((9223372036854775812u64-(1<<63)+(1<<31)) as u32),SubmeshDirectedEdgeId(0),SubmeshDirectedEdgeId((9223372036854775809u64-(1<<63)+(1<<31)) as u32)]}, + VertRefs{faces:vec![SubmeshFaceId(0),SubmeshFaceId(2),SubmeshFaceId(1)],edges:vec![SubmeshDirectedEdgeId(1),SubmeshDirectedEdgeId((9223372036854775810u64-(1<<63)+(1<<31)) as u32),SubmeshDirectedEdgeId((9223372036854775814u64-(1<<63)+(1<<31)) as u32)]}, + VertRefs{faces:vec![SubmeshFaceId(0),SubmeshFaceId(2),SubmeshFaceId(4)],edges:vec![SubmeshDirectedEdgeId(2),SubmeshDirectedEdgeId(3),SubmeshDirectedEdgeId((9223372036854775815u64-(1<<63)+(1<<31)) as u32)]}, + VertRefs{faces:vec![SubmeshFaceId(3),SubmeshFaceId(5),SubmeshFaceId(1)],edges:vec![SubmeshDirectedEdgeId(4),SubmeshDirectedEdgeId((9223372036854775817u64-(1<<63)+(1<<31)) as u32),SubmeshDirectedEdgeId((9223372036854775813u64-(1<<63)+(1<<31)) as u32)]}, + VertRefs{faces:vec![SubmeshFaceId(2),SubmeshFaceId(3),SubmeshFaceId(1)],edges:vec![SubmeshDirectedEdgeId(5),SubmeshDirectedEdgeId(6),SubmeshDirectedEdgeId((9223372036854775816u64-(1<<63)+(1<<31)) as u32)]}, + VertRefs{faces:vec![SubmeshFaceId(2),SubmeshFaceId(3),SubmeshFaceId(4)],edges:vec![SubmeshDirectedEdgeId(7),SubmeshDirectedEdgeId(8),SubmeshDirectedEdgeId((9223372036854775818u64-(1<<63)+(1<<31)) as u32)]}, + VertRefs{faces:vec![SubmeshFaceId(4),SubmeshFaceId(3),SubmeshFaceId(5)],edges:vec![SubmeshDirectedEdgeId(10),SubmeshDirectedEdgeId(11),SubmeshDirectedEdgeId(9)]} + ] + }; + Self{ + data, + complete_mesh:mesh_topology, + submeshes:Vec::new(), + } + } + pub fn unit_cylinder()->Self{ + Self::unit_cube() + } + #[inline] + pub const fn complete_mesh(&self)->&PhysicsMeshTopology{ + &self.complete_mesh + } + #[inline] + pub const fn complete_mesh_view(&self)->PhysicsMeshView{ + PhysicsMeshView{ + data:&self.data, + topology:self.complete_mesh(), + } + } + #[inline] + pub fn submeshes(&self)->&[PhysicsMeshTopology]{ + //the complete mesh is already a convex mesh when len()==0, len()==1 is invalid but will still work + if self.submeshes.len()==0{ + std::slice::from_ref(&self.complete_mesh) + }else{ + &self.submeshes.as_slice() + } + } + #[inline] + pub fn submesh_view(&self,submesh_id:PhysicsSubmeshId)->PhysicsMeshView{ + PhysicsMeshView{ + data:&self.data, + topology:&self.submeshes()[submesh_id.get() as usize], + } + } + pub fn submesh_views(&self)->impl Iterator{ + self.submeshes().iter().map(|topology|PhysicsMeshView{ + data:&self.data, + topology, + }) + } +} +//mesh builder code #[derive(Default,Clone)] struct VertRefGuy{ - edges:std::collections::HashSet, - faces:std::collections::HashSet, + edges:HashSet, + faces:HashSet, } #[derive(Clone,Hash,Eq,PartialEq)] -struct EdgeRefVerts([VertId;2]); +struct EdgeRefVerts([SubmeshVertId;2]); impl EdgeRefVerts{ - fn new(v0:VertId,v1:VertId)->(Self,bool){ + const fn new(v0:SubmeshVertId,v1:SubmeshVertId)->(Self,bool){ (if v0.0Self{ - Self([FaceId(0);2]) + const fn new()->Self{ + Self([SubmeshFaceId(0);2]) } - fn push(&mut self,i:usize,face_id:FaceId){ + fn push(&mut self,i:usize,face_id:SubmeshFaceId){ self.0[i]=face_id; } } -struct FaceRefEdges(Vec); +struct FaceRefEdges(Vec); #[derive(Default)] struct EdgePool{ edge_guys:Vec<(EdgeRefVerts,EdgeRefFaces)>, - edge_id_from_guy:std::collections::HashMap, + edge_id_from_guy:HashMap, } impl EdgePool{ - fn push(&mut self,edge_ref_verts:EdgeRefVerts)->(&mut EdgeRefFaces,EdgeId){ + fn push(&mut self,edge_ref_verts:EdgeRefVerts)->(&mut EdgeRefFaces,SubmeshEdgeId){ let edge_id=if let Some(&edge_id)=self.edge_id_from_guy.get(&edge_ref_verts){ edge_id }else{ - let edge_id=self.edge_guys.len(); + let edge_id=SubmeshEdgeId::new(self.edge_guys.len() as u32); self.edge_guys.push((edge_ref_verts.clone(),EdgeRefFaces::new())); self.edge_id_from_guy.insert(edge_ref_verts,edge_id); edge_id }; - (&mut unsafe{self.edge_guys.get_unchecked_mut(edge_id)}.1,EdgeId(edge_id)) + (&mut unsafe{self.edge_guys.get_unchecked_mut(edge_id.get() as usize)}.1,edge_id) } } -impl From<&crate::model::IndexedModel> for PhysicsMesh{ - fn from(indexed_model:&crate::model::IndexedModel)->Self{ - assert!(indexed_model.unique_pos.len()!=0,"Mesh cannot have 0 vertices"); - let verts=indexed_model.unique_pos.iter().map(|v|Vert(v.clone())).collect(); - let mut vert_ref_guys=vec![VertRefGuy::default();indexed_model.unique_pos.len()]; - let mut edge_pool=EdgePool::default(); - let mut face_i=0; +impl From<&model::Mesh> for PhysicsMesh{ + fn from(mesh:&model::Mesh)->Self{ + assert!(mesh.unique_pos.len()!=0,"Mesh cannot have 0 vertices"); + let verts=mesh.unique_pos.iter().copied().map(Vert).collect(); + //TODO: fix submeshes + //flat map mesh.physics_groups[$1].groups.polys()[$2] as face_id + //lower face_id points to upper face_id + //the same face is not allowed to be in multiple polygon groups let mut faces=Vec::new(); - let mut face_ref_guys=Vec::new(); - for group in indexed_model.groups.iter(){for poly in group.polys.iter(){ - let face_id=FaceId(face_i); - //one face per poly - let mut normal=Planar64Vec3::ZERO; - let len=poly.vertices.len(); - let face_edges=poly.vertices.iter().enumerate().map(|(i,&vert_id)|{ - let vert0_id=indexed_model.unique_vertices[vert_id as usize].pos as usize; - let vert1_id=indexed_model.unique_vertices[poly.vertices[(i+1)%len] as usize].pos as usize; - //https://www.khronos.org/opengl/wiki/Calculating_a_Surface_Normal (Newell's Method) - let v0=indexed_model.unique_pos[vert0_id]; - let v1=indexed_model.unique_pos[vert1_id]; - normal+=Planar64Vec3::new( - (v0.y()-v1.y())*(v0.z()+v1.z()), - (v0.z()-v1.z())*(v0.x()+v1.x()), - (v0.x()-v1.x())*(v0.y()+v1.y()), - ); - //get/create edge and push face into it - let (edge_ref_verts,is_sorted)=EdgeRefVerts::new(VertId(vert0_id),VertId(vert1_id)); - let (edge_ref_faces,edge_id)=edge_pool.push(edge_ref_verts); - //polygon vertices as assumed to be listed clockwise - //populate the edge face on the left or right depending on how the edge vertices got sorted - edge_ref_faces.push(!is_sorted as usize,face_id); - //index edges & face into vertices - { - let vert_ref_guy=unsafe{vert_ref_guys.get_unchecked_mut(vert0_id)}; - vert_ref_guy.edges.insert(edge_id.as_directed(is_sorted)); - vert_ref_guy.faces.insert(face_id); - unsafe{vert_ref_guys.get_unchecked_mut(vert1_id)}.edges.insert(edge_id.as_directed(!is_sorted)); + let mut face_id_from_face=HashMap::new(); + let mut mesh_topologies:Vec=mesh.physics_groups.iter().map(|physics_group|{ + //construct submesh + let mut submesh_faces=Vec::new();//these contain a map from submeshId->meshId + let mut submesh_verts=Vec::new(); + let mut submesh_vert_id_from_mesh_vert_id=HashMap::::new(); + //lazy closure + let mut get_submesh_vert_id=|vert_id:MeshVertId|{ + if let Some(&submesh_vert_id)=submesh_vert_id_from_mesh_vert_id.get(&vert_id){ + submesh_vert_id + }else{ + let submesh_vert_id=SubmeshVertId::new(submesh_verts.len() as u32); + submesh_verts.push(vert_id); + submesh_vert_id_from_mesh_vert_id.insert(vert_id,submesh_vert_id); + submesh_vert_id + } + }; + let mut edge_pool=EdgePool::default(); + let mut vert_ref_guys=vec![VertRefGuy::default();mesh.unique_pos.len()]; + let mut face_ref_guys=Vec::new(); + for polygon_group_id in &physics_group.groups{ + let polygon_group=&mesh.polygon_groups[polygon_group_id.get() as usize]; + for poly_vertices in polygon_group.polys(){ + let submesh_face_id=SubmeshFaceId::new(submesh_faces.len() as u32); + //one face per poly + let mut normal=Planar64Vec3::ZERO; + let len=poly_vertices.len(); + let face_edges=poly_vertices.into_iter().enumerate().map(|(i,vert_id)|{ + let vert0_id=MeshVertId::new(mesh.unique_vertices[vert_id.get() as usize].pos.get() as u32); + let vert1_id=MeshVertId::new(mesh.unique_vertices[poly_vertices[(i+1)%len].get() as usize].pos.get() as u32); + //index submesh verts + let submesh_vert0_id=get_submesh_vert_id(vert0_id); + let submesh_vert1_id=get_submesh_vert_id(vert1_id); + //https://www.khronos.org/opengl/wiki/Calculating_a_Surface_Normal (Newell's Method) + let v0=mesh.unique_pos[vert0_id.get() as usize]; + let v1=mesh.unique_pos[vert1_id.get() as usize]; + normal+=Planar64Vec3::new( + (v0.y()-v1.y())*(v0.z()+v1.z()), + (v0.z()-v1.z())*(v0.x()+v1.x()), + (v0.x()-v1.x())*(v0.y()+v1.y()), + ); + //get/create edge and push face into it + let (edge_ref_verts,is_sorted)=EdgeRefVerts::new(submesh_vert0_id,submesh_vert1_id); + let (edge_ref_faces,edge_id)=edge_pool.push(edge_ref_verts); + //polygon vertices as assumed to be listed clockwise + //populate the edge face on the left or right depending on how the edge vertices got sorted + edge_ref_faces.push(!is_sorted as usize,submesh_face_id); + //index edges & face into vertices + { + let vert_ref_guy=unsafe{vert_ref_guys.get_unchecked_mut(submesh_vert0_id.get() as usize)}; + vert_ref_guy.edges.insert(edge_id.as_directed(is_sorted)); + vert_ref_guy.faces.insert(submesh_face_id); + unsafe{vert_ref_guys.get_unchecked_mut(submesh_vert1_id.get() as usize)}.edges.insert(edge_id.as_directed(!is_sorted)); + } + //return directed_edge_id + edge_id.as_directed(is_sorted) + }).collect(); + //choose precision loss randomly idk + normal=normal/len as i64; + let mut dot=Planar64::ZERO; + for &v in poly_vertices{ + dot+=normal.dot(mesh.unique_pos[mesh.unique_vertices[v.get() as usize].pos.get() as usize]); + } + //assume face hash is stable, and there are no flush faces... + let face=Face{normal,dot:dot/len as i64}; + let face_id=match face_id_from_face.get(&face){ + Some(&face_id)=>face_id, + None=>{ + let face_id=MeshFaceId::new(faces.len() as u32); + face_id_from_face.insert(face.clone(),face_id); + faces.push(face); + face_id + } + }; + submesh_faces.push(face_id); + face_ref_guys.push(FaceRefEdges(face_edges)); } - //return directed_edge_id - edge_id.as_directed(is_sorted) - }).collect(); - //choose precision loss randomly idk - normal=normal/len as i64; - let mut dot=Planar64::ZERO; - for &v in poly.vertices.iter(){ - dot+=normal.dot(indexed_model.unique_pos[indexed_model.unique_vertices[v as usize].pos as usize]); } - faces.push(Face{normal,dot:dot/len as i64}); - face_ref_guys.push(FaceRefEdges(face_edges)); - face_i+=1; - }} - //conceivably faces, edges, and vertices exist now + PhysicsMeshTopology{ + faces:submesh_faces, + verts:submesh_verts, + face_topology:face_ref_guys.into_iter().map(|face_ref_guy|{ + FaceRefs{edges:face_ref_guy.0} + }).collect(), + edge_topology:edge_pool.edge_guys.into_iter().map(|(edge_ref_verts,edge_ref_faces)| + EdgeRefs{faces:edge_ref_faces.0,verts:edge_ref_verts.0} + ).collect(), + vert_topology:vert_ref_guys.into_iter().map(|vert_ref_guy| + VertRefs{ + edges:vert_ref_guy.edges.into_iter().collect(), + faces:vert_ref_guy.faces.into_iter().collect(), + } + ).collect(), + } + }).collect(); Self{ - faces, - verts, - face_topology:face_ref_guys.into_iter().map(|face_ref_guy|{ - FaceRefs{edges:face_ref_guy.0} - }).collect(), - edge_topology:edge_pool.edge_guys.into_iter().map(|(edge_ref_verts,edge_ref_faces)| - EdgeRefs{faces:edge_ref_faces.0,verts:edge_ref_verts.0} - ).collect(), - vert_topology:vert_ref_guys.into_iter().map(|vert_ref_guy| - VertRefs{ - edges:vert_ref_guy.edges.into_iter().collect(), - faces:vert_ref_guy.faces.into_iter().collect(), - } - ).collect(), + data:PhysicsMeshData{ + faces, + verts, + }, + complete_mesh:mesh_topologies.pop().unwrap(), + submeshes:mesh_topologies, } } } -impl PhysicsMesh{ - pub fn verts<'a>(&'a self)->impl Iterator+'a{ - self.verts.iter().map(|Vert(pos)|*pos) - } +pub struct PhysicsMeshView<'a>{ + data:&'a PhysicsMeshData, + topology:&'a PhysicsMeshTopology, } -impl MeshQuery for PhysicsMesh{ - fn face_nd(&self,face_id:FaceId)->(Planar64Vec3,Planar64){ - (self.faces[face_id.0].normal,self.faces[face_id.0].dot) +impl MeshQuery for PhysicsMeshView<'_>{ + fn face_nd(&self,face_id:SubmeshFaceId)->(Planar64Vec3,Planar64){ + let face_idx=self.topology.faces[face_id.get() as usize].get() as usize; + (self.data.faces[face_idx].normal,self.data.faces[face_idx].dot) } //ideally I never calculate the vertex position, but I have to for the graphical meshes... - fn vert(&self,vert_id:VertId)->Planar64Vec3{ - self.verts[vert_id.0].0 + fn vert(&self,vert_id:SubmeshVertId)->Planar64Vec3{ + let vert_idx=self.topology.verts[vert_id.get() as usize].get() as usize; + self.data.verts[vert_idx].0 } - fn face_edges(&self,face_id:FaceId)->Cow>{ - Cow::Borrowed(&self.face_topology[face_id.0].edges) + fn face_edges(&self,face_id:SubmeshFaceId)->Cow>{ + Cow::Borrowed(&self.topology.face_topology[face_id.get() as usize].edges) } - fn edge_faces(&self,edge_id:EdgeId)->Cow<[FaceId;2]>{ - Cow::Borrowed(&self.edge_topology[edge_id.0].faces) + fn edge_faces(&self,edge_id:SubmeshEdgeId)->Cow<[SubmeshFaceId;2]>{ + Cow::Borrowed(&self.topology.edge_topology[edge_id.get() as usize].faces) } - fn edge_verts(&self,edge_id:EdgeId)->Cow<[VertId;2]>{ - Cow::Borrowed(&self.edge_topology[edge_id.0].verts) + fn edge_verts(&self,edge_id:SubmeshEdgeId)->Cow<[SubmeshVertId;2]>{ + Cow::Borrowed(&self.topology.edge_topology[edge_id.get() as usize].verts) } - fn vert_edges(&self,vert_id:VertId)->Cow>{ - Cow::Borrowed(&self.vert_topology[vert_id.0].edges) + fn vert_edges(&self,vert_id:SubmeshVertId)->Cow>{ + Cow::Borrowed(&self.topology.vert_topology[vert_id.get() as usize].edges) } - fn vert_faces(&self,vert_id:VertId)->Cow>{ - Cow::Borrowed(&self.vert_topology[vert_id.0].faces) + fn vert_faces(&self,vert_id:SubmeshVertId)->Cow>{ + Cow::Borrowed(&self.topology.vert_topology[vert_id.get() as usize].faces) + } +} + +pub struct PhysicsMeshTransform{ + pub vertex:integer::Planar64Affine3, + pub normal:integer::Planar64Mat3, + pub det:Planar64, +} +impl PhysicsMeshTransform{ + pub const fn new(transform:integer::Planar64Affine3)->Self{ + Self{ + normal:transform.matrix3.inverse_times_det().transpose(), + det:transform.matrix3.determinant(), + vertex:transform, + } } } pub struct TransformedMesh<'a>{ - mesh:&'a PhysicsMesh, - transform:&'a integer::Planar64Affine3, - normal_transform:&'a integer::Planar64Mat3, - transform_det:Planar64, + view:PhysicsMeshView<'a>, + transform:&'a PhysicsMeshTransform, } impl TransformedMesh<'_>{ pub fn new<'a>( - mesh:&'a PhysicsMesh, - transform:&'a integer::Planar64Affine3, - normal_transform:&'a integer::Planar64Mat3, - transform_det:Planar64, - )->TransformedMesh<'a>{ + view:PhysicsMeshView<'a>, + transform:&'a PhysicsMeshTransform, + )->TransformedMesh<'a>{ TransformedMesh{ - mesh, + view, transform, - normal_transform, - transform_det, } } - fn farthest_vert(&self,dir:Planar64Vec3)->VertId{ + pub fn verts<'a>(&'a self)->impl Iterator+'a{ + self.view.data.verts.iter().map(|&Vert(pos)|self.transform.vertex.transform_point3(pos)) + } + fn farthest_vert(&self,dir:Planar64Vec3)->SubmeshVertId{ let mut best_dot=Planar64::MIN; - let mut best_vert=VertId(0); - for (i,vert) in self.mesh.verts.iter().enumerate(){ - let p=self.transform.transform_point3(vert.0); + let mut best_vert=SubmeshVertId(0); + //this happens to be well-defined. there are no virtual virtices + for (i,vert_id) in self.view.topology.verts.iter().enumerate(){ + let p=self.transform.vertex.transform_point3(self.view.data.verts[vert_id.get() as usize].0); let d=dir.dot(p); if best_dot for TransformedMesh<'_>{ - fn face_nd(&self,face_id:FaceId)->(Planar64Vec3,Planar64){ - let (n,d)=self.mesh.face_nd(face_id); - let transformed_n=*self.normal_transform*n; - let transformed_d=d+transformed_n.dot(self.transform.translation)/self.transform_det; - (transformed_n/self.transform_det,transformed_d) +impl MeshQuery for TransformedMesh<'_>{ + fn face_nd(&self,face_id:SubmeshFaceId)->(Planar64Vec3,Planar64){ + let (n,d)=self.view.face_nd(face_id); + let transformed_n=self.transform.normal*n; + let transformed_d=d+transformed_n.dot(self.transform.vertex.translation)/self.transform.det; + (transformed_n/self.transform.det,transformed_d) } - fn vert(&self,vert_id:VertId)->Planar64Vec3{ - self.transform.transform_point3(self.mesh.vert(vert_id)) + fn vert(&self,vert_id:SubmeshVertId)->Planar64Vec3{ + self.transform.vertex.transform_point3(self.view.vert(vert_id)) } #[inline] - fn face_edges(&self,face_id:FaceId)->Cow>{ - self.mesh.face_edges(face_id) + fn face_edges(&self,face_id:SubmeshFaceId)->Cow>{ + self.view.face_edges(face_id) } #[inline] - fn edge_faces(&self,edge_id:EdgeId)->Cow<[FaceId;2]>{ - self.mesh.edge_faces(edge_id) + fn edge_faces(&self,edge_id:SubmeshEdgeId)->Cow<[SubmeshFaceId;2]>{ + self.view.edge_faces(edge_id) } #[inline] - fn edge_verts(&self,edge_id:EdgeId)->Cow<[VertId;2]>{ - self.mesh.edge_verts(edge_id) + fn edge_verts(&self,edge_id:SubmeshEdgeId)->Cow<[SubmeshVertId;2]>{ + self.view.edge_verts(edge_id) } #[inline] - fn vert_edges(&self,vert_id:VertId)->Cow>{ - self.mesh.vert_edges(vert_id) + fn vert_edges(&self,vert_id:SubmeshVertId)->Cow>{ + self.view.vert_edges(vert_id) } #[inline] - fn vert_faces(&self,vert_id:VertId)->Cow>{ - self.mesh.vert_faces(vert_id) + fn vert_faces(&self,vert_id:SubmeshVertId)->Cow>{ + self.view.vert_faces(vert_id) } } @@ -307,12 +511,12 @@ impl MeshQuery for TransformedMesh<'_>{ //(vertex,face) #[derive(Clone,Copy)] pub enum MinkowskiVert{ - VertVert(VertId,VertId), + VertVert(SubmeshVertId,SubmeshVertId), } #[derive(Clone,Copy)] pub enum MinkowskiEdge{ - VertEdge(VertId,EdgeId), - EdgeVert(EdgeId,VertId), + VertEdge(SubmeshVertId,SubmeshEdgeId), + EdgeVert(SubmeshEdgeId,SubmeshVertId), //EdgeEdge when edges are parallel } impl UndirectedEdge for MinkowskiEdge{ @@ -326,8 +530,8 @@ impl UndirectedEdge for MinkowskiEdge{ } #[derive(Clone,Copy)] pub enum MinkowskiDirectedEdge{ - VertEdge(VertId,DirectedEdgeId), - EdgeVert(DirectedEdgeId,VertId), + VertEdge(SubmeshVertId,SubmeshDirectedEdgeId), + EdgeVert(SubmeshDirectedEdgeId,SubmeshVertId), //EdgeEdge when edges are parallel } impl DirectedEdge for MinkowskiDirectedEdge{ @@ -347,17 +551,17 @@ impl DirectedEdge for MinkowskiDirectedEdge{ } #[derive(Debug,Clone,Copy,Hash,Eq,PartialEq)] pub enum MinkowskiFace{ - VertFace(VertId,FaceId), - EdgeEdge(EdgeId,EdgeId,bool), - FaceVert(FaceId,VertId), + VertFace(SubmeshVertId,SubmeshFaceId), + EdgeEdge(SubmeshEdgeId,SubmeshEdgeId,bool), + FaceVert(SubmeshFaceId,SubmeshVertId), //EdgeFace //FaceEdge //FaceFace } pub struct MinkowskiMesh<'a>{ - mesh0:&'a TransformedMesh<'a>, - mesh1:&'a TransformedMesh<'a>, + mesh0:TransformedMesh<'a>, + mesh1:TransformedMesh<'a>, } //infinity fev algorithm state transition @@ -371,7 +575,7 @@ enum EV{ } impl MinkowskiMesh<'_>{ - pub fn minkowski_sum<'a>(mesh0:&'a TransformedMesh,mesh1:&'a TransformedMesh)->MinkowskiMesh<'a>{ + pub fn minkowski_sum<'a>(mesh0:TransformedMesh<'a>,mesh1:TransformedMesh<'a>)->MinkowskiMesh<'a>{ MinkowskiMesh{ mesh0, mesh1, @@ -739,7 +943,6 @@ fn test_is_empty_volume(){ #[test] fn build_me_a_cube(){ - let unit_cube=crate::primitives::unit_cube(); - let mesh=PhysicsMesh::from(&unit_cube); + let mesh=PhysicsMesh::unit_cube(); //println!("mesh={:?}",mesh); } \ No newline at end of file diff --git a/src/physics.rs b/src/physics.rs index cccb4f3..a15704c 100644 --- a/src/physics.rs +++ b/src/physics.rs @@ -1,8 +1,16 @@ -use crate::model_physics::{PhysicsMesh,TransformedMesh,MeshQuery}; +use std::collections::HashMap; +use std::collections::HashSet; +use crate::model_physics::{self,PhysicsMesh,PhysicsMeshTransform,TransformedMesh,MeshQuery,PhysicsMeshId,PhysicsSubmeshId}; use strafesnet_common::bvh; +use strafesnet_common::map; use strafesnet_common::aabb; +use strafesnet_common::gameplay_modes::{self,StageId}; +use strafesnet_common::gameplay_attributes::{self,CollisionAttributesId}; +use strafesnet_common::model::{MeshId,ModelId}; +use strafesnet_common::gameplay_style::{self,StyleModifiers}; use strafesnet_common::instruction::{self,InstructionEmitter,InstructionConsumer,TimedInstruction}; -use strafesnet_common::integer::{self,Time,Planar64,Planar64Vec3,Planar64Mat3,Angle32,Ratio64,Ratio64Vec2}; +use strafesnet_common::integer::{self,Time,Planar64,Planar64Vec3,Planar64Mat3,Angle32,Ratio64Vec2}; +use gameplay::ModeState; #[derive(Debug)] pub enum PhysicsInstruction { @@ -143,91 +151,36 @@ impl WalkState{ } } -struct Modes{ - modes:Vec, - mode_from_mode_id:std::collections::HashMap::, -} -impl Modes{ - fn clear(&mut self){ - self.modes.clear(); - self.mode_from_mode_id.clear(); - } - fn get_mode(&self,mode_id:u32)->Option<&crate::model::ModeDescription>{ - self.modes.get(*self.mode_from_mode_id.get(&mode_id)?) - } - fn insert(&mut self,temp_map_mode_id:u32,mode:crate::model::ModeDescription){ - let mode_id=self.modes.len(); - self.mode_from_mode_id.insert(temp_map_mode_id,mode_id); - self.modes.push(mode); - } -} -impl Default for Modes{ - fn default() -> Self { - Self{ - modes:Vec::new(), - mode_from_mode_id:std::collections::HashMap::new(), - } - } -} - #[derive(Default)] struct PhysicsModels{ - meshes:Vec, - models:Vec, + meshes:HashMap, + models:HashMap, //separate models into Contacting and Intersecting? //wrap model id with ContactingModelId and IntersectingModelId //attributes can be split into contacting and intersecting (this also saves a bit of memory) //can go even further and deduplicate General attributes separately, reconstructing it when queried - attributes:Vec, - model_id_from_wormhole_id:std::collections::HashMap::, + attributes:HashMap, } impl PhysicsModels{ fn clear(&mut self){ self.meshes.clear(); self.models.clear(); self.attributes.clear(); - self.model_id_from_wormhole_id.clear(); - } - fn aabb_list(&self)->Vec{ - self.models.iter().map(|model|{ - let mut aabb=aabb::Aabb::default(); - for pos in self.meshes[model.mesh_id].verts(){ - aabb.grow(model.transform.transform_point3(pos)); - } - aabb - }).collect() } //TODO: "statically" verify the maps don't refer to any nonexistant data, if they do delete the references. //then I can make these getter functions unchecked. - fn mesh(&self,model_id:usize)->TransformedMesh{ + fn mesh(&self,convex_mesh_id:ConvexMeshId)->TransformedMesh{ + let model=&self.models[&convex_mesh_id.model_id]; TransformedMesh::new( - &self.meshes[self.models[model_id].mesh_id], - &self.models[model_id].transform, - &self.models[model_id].normal_transform, - self.models[model_id].transform_det, + self.meshes[&model.mesh_id].submesh_view(convex_mesh_id.submesh_id), + &model.transform ) } - fn model(&self,model_id:usize)->&PhysicsModel{ - &self.models[model_id] + fn model(&self,model_id:PhysicsModelId)->&PhysicsModel{ + &self.models[&model_id] } - fn attr(&self,model_id:usize)->&PhysicsCollisionAttributes{ - &self.attributes[self.models[model_id].attr_id] - } - fn get_wormhole_model(&self,wormhole_id:u32)->Option<&PhysicsModel>{ - self.models.get(*self.model_id_from_wormhole_id.get(&wormhole_id)?) - } - fn push_mesh(&mut self,mesh:PhysicsMesh){ - self.meshes.push(mesh); - } - fn push_model(&mut self,model:PhysicsModel)->usize{ - let model_id=self.models.len(); - self.models.push(model); - model_id - } - fn push_attr(&mut self,attr:PhysicsCollisionAttributes)->usize{ - let attr_id=self.attributes.len(); - self.attributes.push(attr); - attr_id + fn attr(&self,model_id:PhysicsModelId)->&PhysicsCollisionAttributes{ + &self.attributes[&self.models[&model_id].attr_id] } } @@ -238,8 +191,6 @@ pub struct PhysicsCamera{ sensitivity:Ratio64Vec2,//dots to Angle32 ratios mouse:MouseState,//last seen absolute mouse pos clamped_mouse_pos:glam::IVec2,//angles are calculated from this cumulative value - angle_pitch_lower_limit:Angle32, - angle_pitch_upper_limit:Angle32, //angle limits could be an enum + struct that defines whether it's limited and selects clamp or wrap depending // enum AngleLimit{ // Unlimited, @@ -249,12 +200,14 @@ pub struct PhysicsCamera{ //yaw_limit:AngleLimit, } -impl PhysicsCamera { +impl PhysicsCamera{ + const ANGLE_PITCH_LOWER_LIMIT:Angle32=Angle32::NEG_FRAC_PI_2; + const ANGLE_PITCH_UPPER_LIMIT:Angle32=Angle32::FRAC_PI_2; pub fn move_mouse(&mut self,mouse_pos:glam::IVec2){ - let mut unclamped_mouse_pos=self.clamped_mouse_pos+mouse_pos-self.mouse.pos; + let mut unclamped_mouse_pos=mouse_pos-self.mouse.pos+self.clamped_mouse_pos; unclamped_mouse_pos.y=unclamped_mouse_pos.y.clamp( - self.sensitivity.y.rhs_div_int(self.angle_pitch_lower_limit.get() as i64) as i32, - self.sensitivity.y.rhs_div_int(self.angle_pitch_upper_limit.get() as i64) as i32, + self.sensitivity.y.rhs_div_int(Self::ANGLE_PITCH_LOWER_LIMIT.get() as i64) as i32, + self.sensitivity.y.rhs_div_int(Self::ANGLE_PITCH_UPPER_LIMIT.get() as i64) as i32, ); self.clamped_mouse_pos=unclamped_mouse_pos; } @@ -263,7 +216,7 @@ impl PhysicsCamera { let ax=Angle32::wrap_from_i64(a.x); let ay=Angle32::clamp_from_i64(a.y) //clamp to actual vertical cam limit - .clamp(self.angle_pitch_lower_limit,self.angle_pitch_upper_limit); + .clamp(Self::ANGLE_PITCH_LOWER_LIMIT,Self::ANGLE_PITCH_UPPER_LIMIT); return glam::vec2(ax.into(),ay.into()); } fn simulate_move_rotation(&self,mouse_pos:glam::IVec2)->Planar64Mat3{ @@ -271,7 +224,7 @@ impl PhysicsCamera { let ax=Angle32::wrap_from_i64(a.x); let ay=Angle32::clamp_from_i64(a.y) //clamp to actual vertical cam limit - .clamp(self.angle_pitch_lower_limit,self.angle_pitch_upper_limit); + .clamp(Self::ANGLE_PITCH_LOWER_LIMIT,Self::ANGLE_PITCH_UPPER_LIMIT); Planar64Mat3::from_rotation_yx(ax,ay) } fn simulate_move_rotation_y(&self,mouse_pos_x:i32)->Planar64Mat3{ @@ -286,335 +239,129 @@ impl std::default::Default for PhysicsCamera{ sensitivity:Ratio64Vec2::ONE*200_000, mouse:MouseState::default(),//t=0 does not cause divide by zero because it's immediately replaced clamped_mouse_pos:glam::IVec2::ZERO, - angle_pitch_lower_limit:-Angle32::FRAC_PI_2, - angle_pitch_upper_limit:Angle32::FRAC_PI_2, } } } - -pub struct GameMechanicsState{ - stage_id:u32, - jump_counts:std::collections::HashMap,//model_id -> jump count - next_ordered_checkpoint_id:u32,//which OrderedCheckpoint model_id you must pass next (if 0 you haven't passed OrderedCheckpoint0) - unordered_checkpoints:std::collections::HashSet,//hashset of UnorderedCheckpoint model ids -} -impl std::default::Default for GameMechanicsState{ - fn default()->Self{ - Self{ - stage_id:0, - next_ordered_checkpoint_id:0, - unordered_checkpoints:std::collections::HashSet::new(), - jump_counts:std::collections::HashMap::new(), +mod gameplay{ + use super::{gameplay_modes,HashSet,HashMap,ModelId}; + pub struct ModeState{ + mode_id:gameplay_modes::ModeId, + stage_id:gameplay_modes::StageId, + next_ordered_checkpoint_id:gameplay_modes::CheckpointId,//which OrderedCheckpoint model_id you must pass next (if 0 you haven't passed OrderedCheckpoint0) + unordered_checkpoints:HashSet, + jump_counts:HashMap,//model_id -> jump count + } + impl ModeState{ + pub const fn get_mode_id(&self)->gameplay_modes::ModeId{ + self.mode_id + } + pub const fn get_stage_id(&self)->gameplay_modes::StageId{ + self.stage_id + } + pub const fn get_next_ordered_checkpoint_id(&self)->gameplay_modes::CheckpointId{ + self.next_ordered_checkpoint_id + } + pub fn get_jump_count(&self,model_id:ModelId)->Option{ + self.jump_counts.get(&model_id).copied() + } + pub const fn ordered_checkpoint_count(&self)->u32{ + self.next_ordered_checkpoint_id.get() + } + pub fn unordered_checkpoint_count(&self)->u32{ + self.unordered_checkpoints.len() as u32 + } + pub fn set_mode_id(&mut self,mode_id:gameplay_modes::ModeId){ + self.clear(); + self.mode_id=mode_id; + } + pub fn set_stage_id(&mut self,stage_id:gameplay_modes::StageId){ + self.clear_checkpoints(); + self.stage_id=stage_id; + } + pub fn accumulate_ordered_checkpoint(&mut self,stage:&gameplay_modes::Stage,model_id:ModelId){ + if stage.is_next_ordered_checkpoint(self.get_next_ordered_checkpoint_id(),model_id){ + self.next_ordered_checkpoint_id=gameplay_modes::CheckpointId::new(self.next_ordered_checkpoint_id.get()+1); + } + } + pub fn accumulate_unordered_checkpoint(&mut self,stage:&gameplay_modes::Stage,model_id:ModelId){ + if stage.is_unordered_checkpoint(model_id){ + self.unordered_checkpoints.insert(model_id); + } + } + pub fn clear(&mut self){ + self.clear_jump_counts(); + self.clear_checkpoints(); + } + pub fn clear_jump_counts(&mut self){ + self.jump_counts.clear(); + } + pub fn clear_checkpoints(&mut self){ + self.next_ordered_checkpoint_id=gameplay_modes::CheckpointId::FIRST; + self.unordered_checkpoints.clear(); + } + } + impl std::default::Default for ModeState{ + fn default()->Self{ + Self{ + mode_id:gameplay_modes::ModeId::MAIN, + stage_id:gameplay_modes::StageId::FIRST, + next_ordered_checkpoint_id:gameplay_modes::CheckpointId::FIRST, + unordered_checkpoints:HashSet::new(), + jump_counts:HashMap::new(), + } } } } struct WorldState{} -enum JumpCalculation{ - Capped,//roblox - Energy,//new - Linear,//source -} - -enum JumpImpulse{ - FromTime(Time),//jump time is invariant across mass and gravity changes - FromHeight(Planar64),//jump height is invariant across mass and gravity changes - FromDeltaV(Planar64),//jump velocity is invariant across mass and gravity changes - FromEnergy(Planar64),// :) -} -//Jumping acts on dot(walks_state.normal,body.velocity) -//Capped means it increases the dot to the cap -//Energy means it adds energy -//Linear means it linearly adds on - -enum EnableStrafe{ - Always, - MaskAny(u32),//hsw, shsw - MaskAll(u32), - //Function(Boxbool>), -} - -struct StrafeSettings{ - enable:EnableStrafe, - air_accel_limit:Option, - tick_rate:Ratio64, -} - -struct Hitbox{ +struct HitboxMesh{ halfsize:Planar64Vec3, mesh:PhysicsMesh, - transform:integer::Planar64Affine3, - normal_transform:Planar64Mat3, - transform_det:Planar64, + transform:PhysicsMeshTransform, } -impl Hitbox{ +impl HitboxMesh{ fn new(mesh:PhysicsMesh,transform:integer::Planar64Affine3)->Self{ //calculate extents let mut aabb=aabb::Aabb::default(); - for vert in mesh.verts(){ - aabb.grow(transform.transform_point3(vert)); + let transform=PhysicsMeshTransform::new(transform); + let transformed_mesh=TransformedMesh::new(mesh.complete_mesh_view(),&transform); + for vert in transformed_mesh.verts(){ + aabb.grow(vert); } Self{ halfsize:aabb.size()/2, mesh, transform, - normal_transform:transform.matrix3.inverse_times_det().transpose(), - transform_det:transform.matrix3.determinant(), } } - fn from_mesh_scale(mesh:PhysicsMesh,scale:Planar64Vec3)->Self{ - let matrix3=Planar64Mat3::from_diagonal(scale); - Self{ - halfsize:scale, - mesh, - normal_transform:matrix3.inverse_times_det().transpose(), - transform:integer::Planar64Affine3::new(matrix3,Planar64Vec3::ZERO), - transform_det:matrix3.determinant(),//scale.x*scale.y*scale.z but whatever - } - } - fn from_mesh_scale_offset(mesh:PhysicsMesh,scale:Planar64Vec3,offset:Planar64Vec3)->Self{ - let matrix3=Planar64Mat3::from_diagonal(scale); - Self{ - halfsize:scale, - mesh, - normal_transform:matrix3.inverse_times_det().transpose(), - transform:integer::Planar64Affine3::new(matrix3,offset), - transform_det:matrix3.determinant(), - } - } - fn roblox()->Self{ - Self::from_mesh_scale(PhysicsMesh::from(&crate::primitives::unit_cylinder()),Planar64Vec3::int(2,5,2)/2) - } - fn source()->Self{ - Self::from_mesh_scale(PhysicsMesh::from(&crate::primitives::unit_cube()),Planar64Vec3::raw(33<<28,73<<28,33<<28)/2) - } #[inline] fn transformed_mesh(&self)->TransformedMesh{ - TransformedMesh::new(&self.mesh,&self.transform,&self.normal_transform,self.transform_det) + TransformedMesh::new(self.mesh.complete_mesh_view(),&self.transform) } } -struct StyleModifiers{ - controls_used:u32,//controls which are allowed to pass into gameplay - controls_mask:u32,//controls which are masked from control state (e.g. jump in scroll style) - strafe:Option, - jump_impulse:JumpImpulse, - jump_calculation:JumpCalculation, - static_friction:Planar64, - kinetic_friction:Planar64, - walk_speed:Planar64, - walk_accel:Planar64, - ladder_speed:Planar64, - ladder_accel:Planar64, - ladder_dot:Planar64, - swim_speed:Planar64, - mass:Planar64, - mv:Planar64, - surf_slope:Option, - rocket_force:Option, - gravity:Planar64Vec3, - hitbox:Hitbox, - camera_offset:Planar64Vec3, +trait StyleHelper{ + fn get_control(&self,control:u32,controls:u32)->bool; + fn allow_strafe(&self,controls:u32)->bool; + fn get_control_dir(&self,controls:u32)->Planar64Vec3; + //fn get_jump_time(&self)->Planar64; + //fn get_jump_height(&self)->Planar64; + //fn get_jump_energy(&self)->Planar64; + fn get_jump_deltav(&self)->Planar64; + fn get_walk_target_velocity(&self,camera:&PhysicsCamera,controls:u32,next_mouse:&MouseState,time:Time,normal:&Planar64Vec3)->Planar64Vec3; + fn get_ladder_target_velocity(&self,camera:&PhysicsCamera,controls:u32,next_mouse:&MouseState,time:Time,normal:&Planar64Vec3)->Planar64Vec3; + fn get_propulsion_control_dir(&self,camera:&PhysicsCamera,controls:u32,next_mouse:&MouseState,time:Time)->Planar64Vec3; + fn calculate_mesh(&self)->HitboxMesh; } -impl std::default::Default for StyleModifiers{ - fn default()->Self{ - Self::roblox_bhop() - } -} -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 RIGHT_DIR:Planar64Vec3=Planar64Vec3::X; - const UP_DIR:Planar64Vec3=Planar64Vec3::Y; - const FORWARD_DIR:Planar64Vec3=Planar64Vec3::NEG_Z; - - fn neo()->Self{ - Self{ - controls_used:!0, - controls_mask:!0,//&!(Self::CONTROL_MOVEUP|Self::CONTROL_MOVEDOWN), - strafe:Some(StrafeSettings{ - enable:EnableStrafe::Always, - air_accel_limit:None, - tick_rate:Ratio64::new(64,Time::ONE_SECOND.nanos() as u64).unwrap(), - }), - jump_impulse:JumpImpulse::FromEnergy(Planar64::int(512)), - jump_calculation:JumpCalculation::Energy, - gravity:Planar64Vec3::int(0,-80,0), - static_friction:Planar64::int(2), - kinetic_friction:Planar64::int(3),//unrealistic: kinetic friction is typically lower than static - mass:Planar64::int(1), - mv:Planar64::int(3), - rocket_force:None, - walk_speed:Planar64::int(16), - walk_accel:Planar64::int(80), - ladder_speed:Planar64::int(16), - ladder_accel:Planar64::int(160), - ladder_dot:(Planar64::int(1)/2).sqrt(), - swim_speed:Planar64::int(12), - surf_slope:Some(Planar64::raw(7)/8), - hitbox:Hitbox::roblox(), - camera_offset:Planar64Vec3::int(0,2,0),//4.5-2.5=2 - } - } - - fn roblox_bhop()->Self{ - Self{ - controls_used:!0, - controls_mask:!0,//&!(Self::CONTROL_MOVEUP|Self::CONTROL_MOVEDOWN), - strafe:Some(StrafeSettings{ - enable:EnableStrafe::Always, - air_accel_limit:None, - tick_rate:Ratio64::new(100,Time::ONE_SECOND.nanos() as u64).unwrap(), - }), - jump_impulse:JumpImpulse::FromTime(Time::from_micros(715_588)), - jump_calculation:JumpCalculation::Capped, - gravity:Planar64Vec3::int(0,-100,0), - static_friction:Planar64::int(2), - kinetic_friction:Planar64::int(3),//unrealistic: kinetic friction is typically lower than static - mass:Planar64::int(1), - mv:Planar64::int(27)/10, - rocket_force:None, - walk_speed:Planar64::int(18), - walk_accel:Planar64::int(90), - ladder_speed:Planar64::int(18), - ladder_accel:Planar64::int(180), - ladder_dot:(Planar64::int(1)/2).sqrt(), - swim_speed:Planar64::int(12), - surf_slope:Some(Planar64::raw(3787805118)),// normal.y=0.75 - hitbox:Hitbox::roblox(), - camera_offset:Planar64Vec3::int(0,2,0),//4.5-2.5=2 - } - } - fn roblox_surf()->Self{ - Self{ - controls_used:!0, - controls_mask:!0,//&!(Self::CONTROL_MOVEUP|Self::CONTROL_MOVEDOWN), - strafe:Some(StrafeSettings{ - enable:EnableStrafe::Always, - air_accel_limit:None, - tick_rate:Ratio64::new(100,Time::ONE_SECOND.nanos() as u64).unwrap(), - }), - jump_impulse:JumpImpulse::FromTime(Time::from_micros(715_588)), - jump_calculation:JumpCalculation::Capped, - gravity:Planar64Vec3::int(0,-50,0), - static_friction:Planar64::int(2), - kinetic_friction:Planar64::int(3),//unrealistic: kinetic friction is typically lower than static - mass:Planar64::int(1), - mv:Planar64::int(27)/10, - rocket_force:None, - walk_speed:Planar64::int(18), - walk_accel:Planar64::int(90), - ladder_speed:Planar64::int(18), - ladder_accel:Planar64::int(180), - ladder_dot:(Planar64::int(1)/2).sqrt(), - swim_speed:Planar64::int(12), - surf_slope:Some(Planar64::raw(3787805118)),// normal.y=0.75 - hitbox:Hitbox::roblox(), - camera_offset:Planar64Vec3::int(0,2,0),//4.5-2.5=2 - } - } - - fn source_bhop()->Self{ - Self{ - controls_used:!0, - controls_mask:!0,//&!(Self::CONTROL_MOVEUP|Self::CONTROL_MOVEDOWN), - strafe:Some(StrafeSettings{ - enable:EnableStrafe::Always, - air_accel_limit:Some(Planar64::raw(150<<28)*100), - tick_rate:Ratio64::new(100,Time::ONE_SECOND.nanos() as u64).unwrap(), - }), - jump_impulse:JumpImpulse::FromHeight(Planar64::raw(52<<28)), - jump_calculation:JumpCalculation::Linear, - gravity:Planar64Vec3::raw(0,-800<<28,0), - static_friction:Planar64::int(2),//? - kinetic_friction:Planar64::int(3),//? - mass:Planar64::int(1), - mv:Planar64::raw(30<<28), - rocket_force:None, - walk_speed:Planar64::int(18),//? - walk_accel:Planar64::int(90),//? - ladder_speed:Planar64::int(18),//? - ladder_accel:Planar64::int(180),//? - ladder_dot:(Planar64::int(1)/2).sqrt(),//? - swim_speed:Planar64::int(12),//? - surf_slope:Some(Planar64::raw(3787805118)),// normal.y=0.75 - hitbox:Hitbox::source(), - camera_offset:Planar64Vec3::raw(0,(64<<28)-(73<<27),0), - } - } - fn source_surf()->Self{ - Self{ - controls_used:!0, - controls_mask:!0,//&!(Self::CONTROL_MOVEUP|Self::CONTROL_MOVEDOWN), - strafe:Some(StrafeSettings{ - enable:EnableStrafe::Always, - air_accel_limit:Some(Planar64::raw(150<<28)*66), - tick_rate:Ratio64::new(66,Time::ONE_SECOND.nanos() as u64).unwrap(), - }), - jump_impulse:JumpImpulse::FromHeight(Planar64::raw(52<<28)), - jump_calculation:JumpCalculation::Linear, - gravity:Planar64Vec3::raw(0,-800<<28,0), - static_friction:Planar64::int(2),//? - kinetic_friction:Planar64::int(3),//? - mass:Planar64::int(1), - mv:Planar64::raw(30<<28), - rocket_force:None, - walk_speed:Planar64::int(18),//? - walk_accel:Planar64::int(90),//? - ladder_speed:Planar64::int(18),//? - ladder_accel:Planar64::int(180),//? - ladder_dot:(Planar64::int(1)/2).sqrt(),//? - swim_speed:Planar64::int(12),//? - surf_slope:Some(Planar64::raw(3787805118)),// normal.y=0.75 - hitbox:Hitbox::source(), - camera_offset:Planar64Vec3::raw(0,(64<<28)-(73<<27),0), - } - } - fn roblox_rocket()->Self{ - Self{ - controls_used:!0, - controls_mask:!0, - strafe:None, - jump_impulse:JumpImpulse::FromTime(Time::from_micros(715_588)), - jump_calculation:JumpCalculation::Capped, - gravity:Planar64Vec3::int(0,-100,0), - static_friction:Planar64::int(2), - kinetic_friction:Planar64::int(3),//unrealistic: kinetic friction is typically lower than static - mass:Planar64::int(1), - mv:Planar64::int(27)/10, - rocket_force:Some(Planar64::int(200)), - walk_speed:Planar64::int(18), - walk_accel:Planar64::int(90), - ladder_speed:Planar64::int(18), - ladder_accel:Planar64::int(180), - ladder_dot:(Planar64::int(1)/2).sqrt(), - swim_speed:Planar64::int(12), - surf_slope:Some(Planar64::raw(3787805118)),// normal.y=0.75 - hitbox:Hitbox::roblox(), - camera_offset:Planar64Vec3::int(0,2,0),//4.5-2.5=2 - } - } - +impl StyleHelper for StyleModifiers{ fn get_control(&self,control:u32,controls:u32)->bool{ controls&self.controls_mask&control==control } fn allow_strafe(&self,controls:u32)->bool{ //disable strafing according to strafe settings - match &self.strafe{ - Some(StrafeSettings{enable:EnableStrafe::Always,air_accel_limit:_,tick_rate:_})=>true, - &Some(StrafeSettings{enable:EnableStrafe::MaskAny(mask),air_accel_limit:_,tick_rate:_})=>mask&controls!=0, - &Some(StrafeSettings{enable:EnableStrafe::MaskAll(mask),air_accel_limit:_,tick_rate:_})=>mask&controls==mask, - None=>false, - } + self.strafe.as_ref().is_some_and(|s|s.mask(controls)) } fn get_control_dir(&self,controls:u32)->Planar64Vec3{ @@ -648,10 +395,10 @@ impl StyleModifiers{ //fn get_jump_energy(&self)->Planar64 fn get_jump_deltav(&self)->Planar64{ match &self.jump_impulse{ - &JumpImpulse::FromTime(time)=>self.gravity.length()*(time/2), - &JumpImpulse::FromHeight(height)=>(self.gravity.length()*height*2).sqrt(), - &JumpImpulse::FromDeltaV(deltav)=>deltav, - &JumpImpulse::FromEnergy(energy)=>(energy*2/self.mass).sqrt(), + &gameplay_style::JumpImpulse::FromTime(time)=>self.gravity.length()*(time/2), + &gameplay_style::JumpImpulse::FromHeight(height)=>(self.gravity.length()*height*2).sqrt(), + &gameplay_style::JumpImpulse::FromDeltaV(deltav)=>deltav, + &gameplay_style::JumpImpulse::FromEnergy(energy)=>(energy*2/self.mass).sqrt(), } } @@ -712,9 +459,13 @@ impl StyleModifiers{ let camera_mat=camera.simulate_move_rotation(camera.mouse.lerp(&next_mouse,time)); camera_mat*self.get_control_dir(controls) } - #[inline] - fn mesh(&self)->TransformedMesh{ - self.hitbox.transformed_mesh() + fn calculate_mesh(&self)->HitboxMesh{ + let mesh=match self.hitbox.mesh{ + gameplay_style::HitboxMesh::Box=>PhysicsMesh::unit_cube(), + gameplay_style::HitboxMesh::Cylinder=>PhysicsMesh::unit_cylinder(), + }; + let transform=integer::Planar64Affine3::new(Planar64Mat3::from_diagonal(self.hitbox.halfsize),Planar64Vec3::ZERO); + HitboxMesh::new(mesh,transform) } } @@ -725,26 +476,6 @@ enum MoveState{ Ladder(WalkState), } -pub struct PhysicsState{ - time:Time, - body:Body, - world:WorldState,//currently there is only one state the world can be in - game:GameMechanicsState, - style:StyleModifiers, - touching:TouchingState, - //camera must exist in state because wormholes modify the camera, also camera punch - camera:PhysicsCamera, - pub next_mouse:MouseState,//Where is the mouse headed next - controls:u32, - move_state:MoveState, - models:PhysicsModels, - bvh:bvh::BvhNode, - - modes:Modes, - //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 - spawn_point:Planar64Vec3, -} #[derive(Clone,Default)] pub struct PhysicsOutputState{ body:Body, @@ -760,56 +491,82 @@ impl PhysicsOutputState{ #[derive(Clone,Hash,Eq,PartialEq)] enum PhysicsCollisionAttributes{ Contact{//track whether you are contacting the object - contacting:crate::model::ContactingAttributes, - general:crate::model::GameMechanicAttributes, + contacting:gameplay_attributes::ContactingAttributes, + general:gameplay_attributes::GeneralAttributes, }, Intersect{//track whether you are intersecting the object - intersecting:crate::model::IntersectingAttributes, - general:crate::model::GameMechanicAttributes, + intersecting:gameplay_attributes::IntersectingAttributes, + general:gameplay_attributes::GeneralAttributes, }, } struct NonPhysicsError; -impl TryFrom<&crate::model::CollisionAttributes> for PhysicsCollisionAttributes{ +impl TryFrom<&gameplay_attributes::CollisionAttributes> for PhysicsCollisionAttributes{ type Error=NonPhysicsError; - fn try_from(value:&crate::model::CollisionAttributes)->Result{ + fn try_from(value:&gameplay_attributes::CollisionAttributes)->Result{ match value{ - crate::model::CollisionAttributes::Decoration=>Err(NonPhysicsError), - crate::model::CollisionAttributes::Contact{contacting,general}=>Ok(Self::Contact{contacting:contacting.clone(),general:general.clone()}), - crate::model::CollisionAttributes::Intersect{intersecting,general}=>Ok(Self::Intersect{intersecting:intersecting.clone(),general:general.clone()}), + gameplay_attributes::CollisionAttributes::Decoration=>Err(NonPhysicsError), + gameplay_attributes::CollisionAttributes::Contact{contacting,general}=>Ok(Self::Contact{contacting:contacting.clone(),general:general.clone()}), + gameplay_attributes::CollisionAttributes::Intersect{intersecting,general}=>Ok(Self::Intersect{intersecting:intersecting.clone(),general:general.clone()}), } } } - +#[derive(Clone,Copy,Hash,id::Id,Eq,PartialEq)] +struct PhysicsAttributesId(u32); +impl Into for PhysicsAttributesId{ + fn into(self)->CollisionAttributesId{ + CollisionAttributesId::new(self.0) + } +} +impl From for PhysicsAttributesId{ + fn from(value:CollisionAttributesId)->Self{ + Self::new(value.get()) + } +} +//unique physics meshes indexed by this +#[derive(Debug,Default,Clone,Copy,Eq,Hash,PartialEq)] +struct ConvexMeshId{ + model_id:PhysicsModelId, + submesh_id:PhysicsSubmeshId, +} +#[derive(Debug,Default,Clone,Copy,Hash,id::Id,Eq,PartialEq)] +struct PhysicsModelId(u32); +impl Into for PhysicsModelId{ + fn into(self)->ModelId{ + ModelId::new(self.0) + } +} +impl From for PhysicsModelId{ + fn from(value:ModelId)->Self{ + Self::new(value.get()) + } +} pub struct PhysicsModel{ //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_id:usize, - attr_id:usize, - transform:integer::Planar64Affine3, - normal_transform:integer::Planar64Mat3, - transform_det:Planar64, + mesh_id:PhysicsMeshId, + //put these up on the Model (data normalization) + attr_id:PhysicsAttributesId, + transform:PhysicsMeshTransform, } impl PhysicsModel{ - pub fn new(mesh_id:usize,attr_id:usize,transform:integer::Planar64Affine3)->Self{ + const fn new(mesh_id:PhysicsMeshId,attr_id:PhysicsAttributesId,transform:PhysicsMeshTransform)->Self{ Self{ mesh_id, attr_id, transform, - normal_transform:transform.matrix3.inverse_times_det().transpose(), - transform_det:transform.matrix3.determinant(), } } } #[derive(Debug,Clone,Eq,Hash,PartialEq)] struct ContactCollision{ - face_id:crate::model_physics::MinkowskiFace, - model_id:usize,//using id to avoid lifetimes + face_id:model_physics::MinkowskiFace, + convex_mesh_id:ConvexMeshId, } #[derive(Debug,Clone,Eq,Hash,PartialEq)] struct IntersectCollision{ - model_id:usize, + convex_mesh_id:ConvexMeshId, } #[derive(Debug,Clone,Eq,Hash,PartialEq)] enum Collision{ @@ -817,23 +574,23 @@ enum Collision{ Intersect(IntersectCollision), } impl Collision{ - fn model_id(&self)->usize{ + const fn convex_mesh_id(&self)->ConvexMeshId{ match self{ - &Collision::Contact(ContactCollision{model_id,face_id:_}) - |&Collision::Intersect(IntersectCollision{model_id})=>model_id, + &Collision::Contact(ContactCollision{convex_mesh_id,face_id:_}) + |&Collision::Intersect(IntersectCollision{convex_mesh_id})=>convex_mesh_id, } } - fn face_id(&self)->Option{ + const fn face_id(&self)->Option{ match self{ - &Collision::Contact(ContactCollision{model_id:_,face_id})=>Some(face_id), - &Collision::Intersect(IntersectCollision{model_id:_})=>None, + &Collision::Contact(ContactCollision{convex_mesh_id:_,face_id})=>Some(face_id), + &Collision::Intersect(IntersectCollision{convex_mesh_id:_})=>None, } } } #[derive(Default)] struct TouchingState{ - contacts:std::collections::HashSet::, - intersects:std::collections::HashSet::, + contacts:HashSet::, + intersects:HashSet::, } impl TouchingState{ fn clear(&mut self){ @@ -859,7 +616,7 @@ impl TouchingState{ } //add accelerators for contact in &self.contacts{ - match models.attr(contact.model_id){ + match models.attr(contact.convex_mesh_id.model_id){ PhysicsCollisionAttributes::Contact{contacting,general}=>{ match &general.accelerator{ Some(accelerator)=>a+=accelerator.acceleration, @@ -870,7 +627,7 @@ impl TouchingState{ } } for intersect in &self.intersects{ - match models.attr(intersect.model_id){ + match models.attr(intersect.convex_mesh_id.model_id){ PhysicsCollisionAttributes::Intersect{intersecting,general}=>{ match &general.accelerator{ Some(accelerator)=>a+=accelerator.acceleration, @@ -883,55 +640,54 @@ impl TouchingState{ //add water../? a } - fn constrain_velocity(&self,models:&PhysicsModels,style_mesh:&TransformedMesh,velocity:&mut Planar64Vec3){ + fn constrain_velocity(&self,models:&PhysicsModels,hitbox_mesh:&HitboxMesh,velocity:&mut Planar64Vec3){ //TODO: trey push solve for contact in &self.contacts{ - let n=contact_normal(models,style_mesh,contact); + let n=contact_normal(models,hitbox_mesh,contact); let d=n.dot128(*velocity); if d<0{ *velocity-=n*Planar64::raw(((d<<32)/n.dot128(n)) as i64); } } } - fn constrain_acceleration(&self,models:&PhysicsModels,style_mesh:&TransformedMesh,acceleration:&mut Planar64Vec3){ + fn constrain_acceleration(&self,models:&PhysicsModels,hitbox_mesh:&HitboxMesh,acceleration:&mut Planar64Vec3){ //TODO: trey push solve for contact in &self.contacts{ - let n=contact_normal(models,style_mesh,contact); + let n=contact_normal(models,hitbox_mesh,contact); let d=n.dot128(*acceleration); if d<0{ *acceleration-=n*Planar64::raw(((d<<32)/n.dot128(n)) as i64); } } } - fn get_move_state(&self,body:&Body,models:&PhysicsModels,style:&StyleModifiers,camera:&PhysicsCamera,controls:u32,next_mouse:&MouseState,time:Time)->(MoveState,Planar64Vec3){ + fn get_move_state(&self,body:&Body,models:&PhysicsModels,style:&StyleModifiers,hitbox_mesh:&HitboxMesh,camera:&PhysicsCamera,controls:u32,next_mouse:&MouseState,time:Time)->(MoveState,Planar64Vec3){ //check current move conditions and use heuristics to determine //which ladder to climb on, which ground to walk on, etc //collect move state affecting objects from contacts (accelerator,water,ladder,ground) - let style_mesh=style.mesh(); let gravity=self.base_acceleration(models,style,camera,controls,next_mouse,time); let mut move_state=MoveState::Air; let mut a=gravity; for contact in &self.contacts{ - match models.attr(contact.model_id){ + match models.attr(contact.convex_mesh_id.model_id){ PhysicsCollisionAttributes::Contact{contacting,general}=>{ - let normal=contact_normal(models,&style_mesh,contact); + let normal=contact_normal(models,hitbox_mesh,contact); match &contacting.contact_behaviour{ - Some(crate::model::ContactingBehaviour::Ladder(_))=>{ + Some(gameplay_attributes::ContactingBehaviour::Ladder(_))=>{ //ladder walkstate let mut target_velocity=style.get_ladder_target_velocity(camera,controls,next_mouse,time,&normal); - self.constrain_velocity(models,&style_mesh,&mut target_velocity); + self.constrain_velocity(models,hitbox_mesh,&mut target_velocity); let (walk_state,mut acceleration)=WalkState::ladder(body,style,gravity,target_velocity,contact.clone(),&normal); move_state=MoveState::Ladder(walk_state); - self.constrain_acceleration(models,&style_mesh,&mut acceleration); + self.constrain_acceleration(models,hitbox_mesh,&mut acceleration); a=acceleration; }, None=>if style.surf_slope.map_or(true,|s|normal.walkable(s,Planar64Vec3::Y)){ //check ground let mut target_velocity=style.get_walk_target_velocity(camera,controls,next_mouse,time,&normal); - self.constrain_velocity(models,&style_mesh,&mut target_velocity); + self.constrain_velocity(models,hitbox_mesh,&mut target_velocity); let (walk_state,mut acceleration)=WalkState::ground(body,style,gravity,target_velocity,contact.clone(),&normal); move_state=MoveState::Walk(walk_state); - self.constrain_acceleration(models,&style_mesh,&mut acceleration); + self.constrain_acceleration(models,hitbox_mesh,&mut acceleration); a=acceleration; }, _=>(), @@ -943,33 +699,33 @@ impl TouchingState{ for intersect in &self.intersects{ //water } - self.constrain_acceleration(models,&style_mesh,&mut a); + self.constrain_acceleration(models,hitbox_mesh,&mut a); (move_state,a) } - fn predict_collision_end(&self,collector:&mut instruction::InstructionCollector,models:&PhysicsModels,style_mesh:&TransformedMesh,body:&Body,time:Time){ + fn predict_collision_end(&self,collector:&mut instruction::InstructionCollector,models:&PhysicsModels,hitbox_mesh:&HitboxMesh,body:&Body,time:Time){ let relative_body=VirtualBody::relative(&Body::default(),body).body(time); for contact in &self.contacts{ //detect face slide off - let model_mesh=models.mesh(contact.model_id); - let minkowski=crate::model_physics::MinkowskiMesh::minkowski_sum(&model_mesh,&style_mesh); - collector.collect(minkowski.predict_collision_face_out(&relative_body,collector.time(),contact.face_id).map(|(face,time)|{ + let model_mesh=models.mesh(contact.convex_mesh_id); + let minkowski=model_physics::MinkowskiMesh::minkowski_sum(model_mesh,hitbox_mesh.transformed_mesh()); + collector.collect(minkowski.predict_collision_face_out(&relative_body,collector.time(),contact.face_id).map(|(_face,time)|{ TimedInstruction{ time, instruction:PhysicsInstruction::CollisionEnd( - Collision::Contact(ContactCollision{model_id:contact.model_id,face_id:contact.face_id}) + Collision::Contact(ContactCollision{convex_mesh_id:contact.convex_mesh_id,face_id:contact.face_id}) ), } })); } for intersect in &self.intersects{ //detect model collision in reverse - let model_mesh=models.mesh(intersect.model_id); - let minkowski=crate::model_physics::MinkowskiMesh::minkowski_sum(&model_mesh,&style_mesh); - collector.collect(minkowski.predict_collision_out(&relative_body,collector.time()).map(|(face,time)|{ + let model_mesh=models.mesh(intersect.convex_mesh_id); + let minkowski=model_physics::MinkowskiMesh::minkowski_sum(model_mesh,hitbox_mesh.transformed_mesh()); + collector.collect(minkowski.predict_collision_out(&relative_body,collector.time()).map(|(_face,time)|{ TimedInstruction{ time, instruction:PhysicsInstruction::CollisionEnd( - Collision::Intersect(IntersectCollision{model_id:intersect.model_id}) + Collision::Intersect(IntersectCollision{convex_mesh_id:intersect.convex_mesh_id}) ), } })); @@ -978,7 +734,7 @@ impl TouchingState{ } impl Body{ - pub fn new(position:Planar64Vec3,velocity:Planar64Vec3,acceleration:Planar64Vec3,time:Time)->Self{ + pub const fn new(position:Planar64Vec3,velocity:Planar64Vec3,acceleration:Planar64Vec3,time:Time)->Self{ Self{ position, velocity, @@ -1047,7 +803,7 @@ struct VirtualBody<'a>{ body1:&'a Body, } impl VirtualBody<'_>{ - fn relative<'a>(body0:&'a Body,body1:&'a Body)->VirtualBody<'a>{ + const fn relative<'a>(body0:&'a Body,body1:&'a Body)->VirtualBody<'a>{ //(p0,v0,a0,t0) //(p1,v1,a1,t1) VirtualBody{ @@ -1069,33 +825,63 @@ impl VirtualBody<'_>{ } } +pub struct PhysicsState{ + time:Time, + body:Body, + world:WorldState,//currently there is only one state the world can be in + touching:TouchingState, + //camera must exist in state because wormholes modify the camera, also camera punch + camera:PhysicsCamera, + //input_state + pub next_mouse:MouseState,//Where is the mouse headed next + controls:u32,//TODO this should be a struct + //style + style:StyleModifiers,//mode style with custom style updates applied + //gameplay_state + mode_state:ModeState, + move_state:MoveState, +} +//random collection of contextual data that doesn't belong in PhysicsState +pub struct PhysicsData{ + //permanent map data + bvh:bvh::BvhNode, + //transient map/environment data (open world loads/unloads parts of this data) + models:PhysicsModels, + //semi-transient data + modes:gameplay_modes::Modes, + //cached calculations + hitbox_mesh:HitboxMesh, +} impl Default for PhysicsState{ fn default()->Self{ Self{ - spawn_point:Planar64Vec3::int(0,50,0), body:Body::new(Planar64Vec3::int(0,50,0),Planar64Vec3::int(0,0,0),Planar64Vec3::int(0,-100,0),Time::ZERO), time:Time::ZERO, style:StyleModifiers::default(), touching:TouchingState::default(), - models:PhysicsModels::default(), - bvh:bvh::BvhNode::default(), move_state: MoveState::Air, camera:PhysicsCamera::default(), next_mouse:MouseState::default(), controls:0, world:WorldState{}, - game:GameMechanicsState::default(), - modes:Modes::default(), + mode_state:ModeState::default(), + } + } +} +impl Default for PhysicsData{ + fn default()->Self{ + Self{ + bvh:bvh::BvhNode::default(), + models:Default::default(), + modes:Default::default(), + hitbox_mesh:StyleModifiers::default().calculate_mesh(), } } } impl PhysicsState { pub fn clear(&mut self){ - self.models.clear(); - self.modes.clear(); self.touching.clear(); - self.bvh=bvh::BvhNode::default(); } pub fn output(&self)->PhysicsOutputState{ @@ -1106,91 +892,10 @@ impl PhysicsState { } } - pub fn spawn(&mut self,spawn_point:Planar64Vec3){ - self.game.stage_id=0; - self.spawn_point=spawn_point; - self.process_instruction(instruction::TimedInstruction{ - time:self.time, - instruction: PhysicsInstruction::Input(PhysicsInputInstruction::Reset), - }); - } - - pub fn generate_models(&mut self,indexed_models:&crate::model::IndexedModelInstances){ - let mut starts=Vec::new(); - let mut spawns=Vec::new(); - let mut attr_hash=std::collections::HashMap::new(); - for model in &indexed_models.models{ - let mesh_id=self.models.meshes.len(); - let mut make_mesh=false; - for model_instance in &model.instances{ - if let Ok(physics_attributes)=PhysicsCollisionAttributes::try_from(&model_instance.attributes){ - let attr_id=if let Some(&attr_id)=attr_hash.get(&physics_attributes){ - attr_id - }else{ - let attr_id=self.models.push_attr(physics_attributes.clone()); - attr_hash.insert(physics_attributes,attr_id); - attr_id - }; - let model_physics=PhysicsModel::new(mesh_id,attr_id,model_instance.transform); - make_mesh=true; - let model_id=self.models.push_model(model_physics); - for attr in &model_instance.temp_indexing{ - match attr{ - crate::model::TempIndexedAttributes::Start(s)=>starts.push((model_id,s.clone())), - crate::model::TempIndexedAttributes::Spawn(s)=>spawns.push((model_id,s.clone())), - crate::model::TempIndexedAttributes::Wormhole(s)=>{self.models.model_id_from_wormhole_id.insert(s.wormhole_id,model_id);}, - } - } - } - } - if make_mesh{ - self.models.push_mesh(PhysicsMesh::from(model)); - } - } - self.bvh=bvh::generate_bvh(self.models.aabb_list()); - //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.1.mode_id); - let mut mode_id_from_map_mode_id=std::collections::HashMap::new(); - let mut modedatas:Vec<(usize,Vec<(u32,usize)>,u32)>=starts.into_iter().enumerate().map(|(i,(model_id,s))|{ - mode_id_from_map_mode_id.insert(s.mode_id,i); - (model_id,Vec::new(),s.mode_id) - }).collect(); - for (model_id,s) in spawns{ - if let Some(mode_id)=mode_id_from_map_mode_id.get(&s.mode_id){ - if let Some(modedata)=modedatas.get_mut(*mode_id){ - modedata.1.push((s.stage_id,model_id)); - } - } - } - for mut tup in modedatas.into_iter(){ - tup.1.sort_by_key(|tup|tup.0); - let mut eshmep1=std::collections::HashMap::new(); - let mut eshmep2=std::collections::HashMap::new(); - self.modes.insert(tup.2,crate::model::ModeDescription{ - start:tup.0, - spawns:tup.1.into_iter().enumerate().map(|(i,tup)|{eshmep1.insert(tup.0,i);tup.1}).collect(), - spawn_from_stage_id:eshmep1, - ordered_checkpoint_from_checkpoint_id:eshmep2, - }); - } - println!("Physics Objects: {}",self.models.models.len()); - } - pub fn load_user_settings(&mut self,user_settings:&crate::settings::UserSettings){ self.camera.sensitivity=user_settings.calculate_sensitivity(); } - //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 - //process - self.process_instruction(instruction); - //write hash lol - } - } - pub fn advance_time(&mut self, time: Time){ self.body.advance_time(time); self.time=time; @@ -1203,7 +908,7 @@ impl PhysicsState { fn next_strafe_instruction(&self)->Option>{ self.style.strafe.as_ref().map(|strafe|{ TimedInstruction{ - time:Time::from_nanos(strafe.tick_rate.rhs_div_int(strafe.tick_rate.mul_int(self.time.nanos())+1)), + time:strafe.next_tick(self.time), //only poll the physics if there is a before and after mouse event instruction:PhysicsInstruction::StrafeTick } @@ -1241,32 +946,6 @@ impl PhysicsState { // }); // } - fn refresh_walk_target(&mut self)->Planar64Vec3{ - match &mut self.move_state{ - MoveState::Air|MoveState::Water=>self.touching.base_acceleration(&self.models,&self.style,&self.camera,self.controls,&self.next_mouse,self.time), - MoveState::Walk(WalkState{state,contact,jump_direction:_})=>{ - let style_mesh=self.style.mesh(); - let n=contact_normal(&self.models,&style_mesh,contact); - let gravity=self.touching.base_acceleration(&self.models,&self.style,&self.camera,self.controls,&self.next_mouse,self.time); - let mut a; - let mut v=self.style.get_walk_target_velocity(&self.camera,self.controls,&self.next_mouse,self.time,&n); - self.touching.constrain_velocity(&self.models,&style_mesh,&mut v); - let normal_accel=-n.dot(gravity)/n.length(); - (*state,a)=WalkEnum::with_target_velocity(&self.body,&self.style,v,&n,self.style.walk_speed,normal_accel); - a - }, - MoveState::Ladder(WalkState{state,contact,jump_direction:_})=>{ - let style_mesh=self.style.mesh(); - let n=contact_normal(&self.models,&style_mesh,contact); - let gravity=self.touching.base_acceleration(&self.models,&self.style,&self.camera,self.controls,&self.next_mouse,self.time); - let mut a; - let mut v=self.style.get_ladder_target_velocity(&self.camera,self.controls,&self.next_mouse,self.time,&n); - self.touching.constrain_velocity(&self.models,&style_mesh,&mut v); - (*state,a)=WalkEnum::with_target_velocity(&self.body,&self.style,v,&n,self.style.ladder_speed,self.style.ladder_accel); - a - }, - } - } fn next_move_instruction(&self)->Option>{ //check if you have a valid walk state and create an instruction match &self.move_state{ @@ -1282,41 +961,156 @@ impl PhysicsState { } } } - -impl instruction::InstructionEmitter for PhysicsState{ +#[derive(Default)] +pub struct PhysicsContext{ + pub state:PhysicsState,//this captures the entire state of the physics. + data:PhysicsData,//data currently loaded into memory which is needded for physics to run, but is not part of the state. +} +impl instruction::InstructionConsumer for PhysicsContext{ + fn process_instruction(&mut self,ins:TimedInstruction){ + atomic_state_update(&mut self.state,&self.data,ins) + } +} +impl instruction::InstructionEmitter for PhysicsContext{ //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>{ + literally_next_instruction_but_with_context(&self.state,&self.data,time_limit) + } +} +impl PhysicsContext{ + pub fn spawn(&mut self){ + self.process_instruction(instruction::TimedInstruction{ + time:self.state.time, + instruction: PhysicsInstruction::Input(PhysicsInputInstruction::Reset), + }); + } + + pub fn generate_models(&mut self,map:&map::CompleteMap){ + self.data.modes=map.modes.clone(); + let mut used_attributes=Vec::new(); + let mut physics_attr_id_from_model_attr_id=HashMap::::new(); + let mut used_meshes=Vec::new(); + let mut physics_mesh_id_from_model_mesh_id=HashMap::::new(); + self.data.models.models=map.models.iter().enumerate().filter_map(|(model_id,model)|{ + let attr_id=if let Some(&attr_id)=physics_attr_id_from_model_attr_id.get(&model.attributes){ + attr_id + }else{ + //check if it's real + match map.attributes.get(model.attributes.get() as usize).and_then(|m_attr|{ + PhysicsCollisionAttributes::try_from(m_attr).map_or(None,|p_attr|{ + let attr_id=PhysicsAttributesId::new(used_attributes.len() as u32); + used_attributes.push(p_attr); + physics_attr_id_from_model_attr_id.insert(model.attributes,attr_id); + Some(attr_id) + }) + }){ + Some(attr_id)=>attr_id, + None=>return None, + } + }; + let mesh_id=if let Some(&mesh_id)=physics_mesh_id_from_model_mesh_id.get(&model.mesh){ + mesh_id + }else{ + match map.meshes.get(model.mesh.get() as usize).and_then(|mesh|{ + let mesh_id=PhysicsMeshId::new(used_meshes.len() as u32); + used_meshes.push(PhysicsMesh::from(mesh)); + physics_mesh_id_from_model_mesh_id.insert(model.mesh,mesh_id); + Some(mesh_id) + }){ + Some(mesh_id)=>mesh_id, + None=>return None, + } + }; + Some((PhysicsModelId::new(model_id as u32),PhysicsModel::new(mesh_id,attr_id,PhysicsMeshTransform::new(model.transform)))) + }).collect(); + self.data.models.attributes=used_attributes.into_iter().enumerate().map(|(attr_id,attr)|(PhysicsAttributesId::new(attr_id as u32),attr)).collect(); + self.data.models.meshes=used_meshes.into_iter().enumerate().map(|(mesh_id,mesh)|(PhysicsMeshId::new(mesh_id as u32),mesh)).collect(); + let convex_mesh_aabb_list=self.data.models.models.iter() + .flat_map(|(&model_id,model)|{ + self.data.models.meshes[&model.mesh_id].submesh_views() + .enumerate().map(move|(submesh_id,view)|{ + let mut aabb=aabb::Aabb::default(); + let transformed_mesh=TransformedMesh::new(view,&model.transform); + for v in transformed_mesh.verts(){ + aabb.grow(v); + } + (ConvexMeshId{ + model_id, + submesh_id:PhysicsSubmeshId::new(submesh_id as u32), + },aabb) + }) + }).collect(); + self.data.bvh=bvh::generate_bvh(convex_mesh_aabb_list); + println!("Physics Objects: {}",self.data.models.models.len()); + } + + //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 + //process + self.process_instruction(instruction); + //write hash lol + } + } +} + + //TODO get rid of this trash + fn refresh_walk_target(s:&mut PhysicsState,data:&PhysicsData)->Planar64Vec3{ + match &mut s.move_state{ + MoveState::Air|MoveState::Water=>s.touching.base_acceleration(&data.models,&s.style,&s.camera,s.controls,&s.next_mouse,s.time), + MoveState::Walk(WalkState{state,contact,jump_direction:_})=>{ + let n=contact_normal(&data.models,&data.hitbox_mesh,contact); + let gravity=s.touching.base_acceleration(&data.models,&s.style,&s.camera,s.controls,&s.next_mouse,s.time); + let a; + let mut v=s.style.get_walk_target_velocity(&s.camera,s.controls,&s.next_mouse,s.time,&n); + s.touching.constrain_velocity(&data.models,&data.hitbox_mesh,&mut v); + let normal_accel=-n.dot(gravity)/n.length(); + (*state,a)=WalkEnum::with_target_velocity(&s.body,&s.style,v,&n,s.style.walk_speed,normal_accel); + a + }, + MoveState::Ladder(WalkState{state,contact,jump_direction:_})=>{ + let n=contact_normal(&data.models,&data.hitbox_mesh,contact); + //let gravity=s.touching.base_acceleration(&data.models,&s.style,&s.camera,s.controls,&s.next_mouse,s.time); + let a; + let mut v=s.style.get_ladder_target_velocity(&s.camera,s.controls,&s.next_mouse,s.time,&n); + s.touching.constrain_velocity(&data.models,&data.hitbox_mesh,&mut v); + (*state,a)=WalkEnum::with_target_velocity(&s.body,&s.style,v,&n,s.style.ladder_speed,s.style.ladder_accel); + a + }, + } + } + + fn literally_next_instruction_but_with_context(state:&PhysicsState,data:&PhysicsData,time_limit:Time)->Option>{ //JUST POLLING!!! NO MUTATION let mut collector = instruction::InstructionCollector::new(time_limit); - collector.collect(self.next_move_instruction()); + collector.collect(state.next_move_instruction()); - let style_mesh=self.style.mesh(); //check for collision ends - self.touching.predict_collision_end(&mut collector,&self.models,&style_mesh,&self.body,self.time); + state.touching.predict_collision_end(&mut collector,&data.models,&data.hitbox_mesh,&state.body,state.time); //check for collision starts let mut aabb=aabb::Aabb::default(); - self.body.grow_aabb(&mut aabb,self.time,collector.time()); - aabb.inflate(self.style.hitbox.halfsize); + state.body.grow_aabb(&mut aabb,state.time,collector.time()); + aabb.inflate(data.hitbox_mesh.halfsize); //common body - let relative_body=VirtualBody::relative(&Body::default(),&self.body).body(self.time); - self.bvh.the_tester(&aabb,&mut |id|{ + let relative_body=VirtualBody::relative(&Body::default(),&state.body).body(state.time); + data.bvh.the_tester(&aabb,&mut |convex_mesh_id|{ //no checks are needed because of the time limits. - let model_mesh=self.models.mesh(id); - let minkowski=crate::model_physics::MinkowskiMesh::minkowski_sum(&model_mesh,&style_mesh); + let model_mesh=data.models.mesh(convex_mesh_id); + let minkowski=model_physics::MinkowskiMesh::minkowski_sum(model_mesh,data.hitbox_mesh.transformed_mesh()); collector.collect(minkowski.predict_collision_in(&relative_body,collector.time()) //temp (?) code to avoid collision loops - .map_or(None,|(face,time)|if time==self.time{None}else{Some((face,time))}) + .map_or(None,|(face,time)|if time==state.time{None}else{Some((face,time))}) .map(|(face,time)|{ - TimedInstruction{time,instruction:PhysicsInstruction::CollisionStart(match self.models.attr(id){ - PhysicsCollisionAttributes::Contact{contacting:_,general:_}=>Collision::Contact(ContactCollision{model_id:id,face_id:face}), - PhysicsCollisionAttributes::Intersect{intersecting:_,general:_}=>Collision::Intersect(IntersectCollision{model_id:id}), + TimedInstruction{time,instruction:PhysicsInstruction::CollisionStart(match data.models.attr(convex_mesh_id.model_id){ + PhysicsCollisionAttributes::Contact{contacting:_,general:_}=>Collision::Contact(ContactCollision{convex_mesh_id,face_id:face}), + PhysicsCollisionAttributes::Intersect{intersecting:_,general:_}=>Collision::Intersect(IntersectCollision{convex_mesh_id}), })} })); }); collector.instruction() } -} fn get_walk_state(move_state:&MoveState)->Option<&WalkState>{ match move_state{ @@ -1325,17 +1119,17 @@ fn get_walk_state(move_state:&MoveState)->Option<&WalkState>{ } } -fn jumped_velocity(models:&PhysicsModels,style:&StyleModifiers,walk_state:&WalkState,v:&mut Planar64Vec3){ +fn jumped_velocity(models:&PhysicsModels,style:&StyleModifiers,hitbox_mesh:&HitboxMesh,walk_state:&WalkState,v:&mut Planar64Vec3){ let jump_dir=match &walk_state.jump_direction{ - JumpDirection::FromContactNormal=>contact_normal(models,&style.mesh(),&walk_state.contact), + JumpDirection::FromContactNormal=>contact_normal(models,hitbox_mesh,&walk_state.contact), &JumpDirection::Exactly(dir)=>dir, }; *v=*v+jump_dir*(style.get_jump_deltav()/jump_dir.length()); } -fn contact_normal(models:&PhysicsModels,style_mesh:&TransformedMesh,contact:&ContactCollision)->Planar64Vec3{ - let model_mesh=models.mesh(contact.model_id); - let minkowski=crate::model_physics::MinkowskiMesh::minkowski_sum(&model_mesh,style_mesh); +fn contact_normal(models:&PhysicsModels,hitbox_mesh:&HitboxMesh,contact:&ContactCollision)->Planar64Vec3{ + let model_mesh=models.mesh(contact.convex_mesh_id); + let minkowski=model_physics::MinkowskiMesh::minkowski_sum(model_mesh,hitbox_mesh.transformed_mesh()); minkowski.face_nd(contact.face_id).0 } @@ -1349,11 +1143,11 @@ fn set_position(body:&mut Body,touching:&mut TouchingState,point:Planar64Vec3)-> //touching.recalculate(body); point } -fn set_velocity_cull(body:&mut Body,touching:&mut TouchingState,models:&PhysicsModels,style_mesh:&TransformedMesh,v:Planar64Vec3)->bool{ +fn set_velocity_cull(body:&mut Body,touching:&mut TouchingState,models:&PhysicsModels,hitbox_mesh:&HitboxMesh,v:Planar64Vec3)->bool{ //This is not correct but is better than what I have let mut culled=false; touching.contacts.retain(|contact|{ - let n=contact_normal(models,style_mesh,contact); + let n=contact_normal(models,hitbox_mesh,contact); let r=n.dot(v)<=Planar64::ZERO; if !r{ culled=true; @@ -1361,19 +1155,19 @@ fn set_velocity_cull(body:&mut Body,touching:&mut TouchingState,models:&PhysicsM } r }); - set_velocity(body,touching,models,style_mesh,v); + set_velocity(body,touching,models,hitbox_mesh,v); culled } -fn set_velocity(body:&mut Body,touching:&TouchingState,models:&PhysicsModels,style_mesh:&TransformedMesh,mut v:Planar64Vec3)->Planar64Vec3{ - touching.constrain_velocity(models,style_mesh,&mut v); +fn set_velocity(body:&mut Body,touching:&TouchingState,models:&PhysicsModels,hitbox_mesh:&HitboxMesh,mut v:Planar64Vec3)->Planar64Vec3{ + touching.constrain_velocity(models,hitbox_mesh,&mut v); body.velocity=v; v } -fn set_acceleration_cull(body:&mut Body,touching:&mut TouchingState,models:&PhysicsModels,style_mesh:&TransformedMesh,a:Planar64Vec3)->bool{ +fn set_acceleration_cull(body:&mut Body,touching:&mut TouchingState,models:&PhysicsModels,hitbox_mesh:&HitboxMesh,a:Planar64Vec3)->bool{ //This is not correct but is better than what I have let mut culled=false; touching.contacts.retain(|contact|{ - let n=contact_normal(models,style_mesh,contact); + let n=contact_normal(models,hitbox_mesh,contact); let r=n.dot(a)<=Planar64::ZERO; if !r{ culled=true; @@ -1381,93 +1175,87 @@ fn set_acceleration_cull(body:&mut Body,touching:&mut TouchingState,models:&Phys } r }); - set_acceleration(body,touching,models,style_mesh,a); + set_acceleration(body,touching,models,hitbox_mesh,a); culled } -fn set_acceleration(body:&mut Body,touching:&TouchingState,models:&PhysicsModels,style_mesh:&TransformedMesh,mut a:Planar64Vec3)->Planar64Vec3{ - touching.constrain_acceleration(models,style_mesh,&mut a); +fn set_acceleration(body:&mut Body,touching:&TouchingState,models:&PhysicsModels,hitbox_mesh:&HitboxMesh,mut a:Planar64Vec3)->Planar64Vec3{ + touching.constrain_acceleration(models,hitbox_mesh,&mut a); body.acceleration=a; a } -fn teleport(body:&mut Body,touching:&mut TouchingState,models:&PhysicsModels,style:&StyleModifiers,point:Planar64Vec3)->MoveState{ +fn teleport(body:&mut Body,touching:&mut TouchingState,models:&PhysicsModels,style:&StyleModifiers,hitbox_mesh:&HitboxMesh,point:Planar64Vec3)->MoveState{ set_position(body,touching,point); - set_acceleration(body,touching,models,&style.mesh(),style.gravity); + set_acceleration(body,touching,models,hitbox_mesh,style.gravity); MoveState::Air } -fn teleport_to_spawn(body:&mut Body,touching:&mut TouchingState,style:&StyleModifiers,mode:&crate::model::ModeDescription,models:&PhysicsModels,stage_id:u32)->Option{ - let model=models.model(*mode.get_spawn_model_id(stage_id)? as usize); - let point=model.transform.transform_point3(Planar64Vec3::Y)+Planar64Vec3::Y*(style.hitbox.halfsize.y()+Planar64::ONE/16); - Some(teleport(body,touching,models,style,point)) +fn teleport_to_spawn(body:&mut Body,touching:&mut TouchingState,style:&StyleModifiers,hitbox_mesh:&HitboxMesh,mode:&gameplay_modes::Mode,models:&PhysicsModels,stage_id:gameplay_modes::StageId)->Option{ + let model=models.model(mode.get_spawn_model_id(stage_id)?.into()); + let point=model.transform.vertex.transform_point3(Planar64Vec3::Y)+Planar64Vec3::Y*(style.hitbox.halfsize.y()+Planar64::ONE/16); + Some(teleport(body,touching,models,style,hitbox_mesh,point)) } -fn run_teleport_behaviour(teleport_behaviour:&Option,game:&mut GameMechanicsState,models:&PhysicsModels,modes:&Modes,style:&StyleModifiers,touching:&mut TouchingState,body:&mut Body,model_id:usize)->Option{ +fn run_teleport_behaviour(wormhole:&Option,models:&PhysicsModels,mode:&gameplay_modes::Mode,style:&StyleModifiers,hitbox_mesh:&HitboxMesh,mode_state:&mut ModeState,touching:&mut TouchingState,body:&mut Body,convex_mesh_id:ConvexMeshId)->Option{ //TODO: jump count and checkpoints are always reset on teleport. //Map makers are expected to use tools to prevent //multi-boosting on JumpLimit boosters such as spawning into a SetVelocity - match teleport_behaviour{ - Some(crate::model::TeleportBehaviour::StageElement(stage_element))=>{ - if stage_element.force||game.stage_idNone, - crate::model::StageElementBehaviour::Trigger - |crate::model::StageElementBehaviour::Teleport=>{ - //I guess this is correct behaviour when trying to teleport to a non-existent spawn but it's still weird - teleport_to_spawn(body,touching,style,modes.get_mode(stage_element.mode_id)?,models,game.stage_id) - }, - crate::model::StageElementBehaviour::Platform=>None, - &crate::model::StageElementBehaviour::Checkpoint=>{ - // let mode=modes.get_mode(stage_element.mode_id)?; - // if mode.ordered_checkpoint_id.map_or(true,|id|id{ - if checkpoint_id(), + gameplay_modes::StageElementBehaviour::Trigger + |gameplay_modes::StageElementBehaviour::Teleport=>{ + //I guess this is correct behaviour when trying to teleport to a non-existent spawn but it's still weird + return teleport_to_spawn(body,touching,style,hitbox_mesh,mode,models,mode_state.get_stage_id()); }, - crate::model::StageElementBehaviour::Unordered=>{ - //count model id in accumulated unordered checkpoints - game.unordered_checkpoints.insert(model_id); - None - }, - &crate::model::StageElementBehaviour::JumpLimit(jump_limit)=>{ - //let count=game.jump_counts.get(&model.id); - //TODO - None + gameplay_modes::StageElementBehaviour::Platform=>(), + gameplay_modes::StageElementBehaviour::Check=>(),//this is to run the checkpoint check behaviour without any other side effects + gameplay_modes::StageElementBehaviour::Checkpoint=>{ + //each of these checks if the model is actually a valid respective checkpoint object + //accumulate sequential ordered checkpoints + mode_state.accumulate_ordered_checkpoint(&stage,convex_mesh_id.model_id.into()); + //insert model id in accumulated unordered checkpoints + mode_state.accumulate_unordered_checkpoint(&stage,convex_mesh_id.model_id.into()); }, } - }, - Some(crate::model::TeleportBehaviour::Wormhole(wormhole))=>{ - let origin_model=models.model(model_id); - let destination_model=models.get_wormhole_model(wormhole.destination_model_id)?; + } + } + match wormhole{ + &Some(gameplay_attributes::Wormhole{destination_model})=>{ + let origin_model=models.model(convex_mesh_id.model_id); + let destination_model=models.model(destination_model.into()); //ignore the transform for now - Some(teleport(body,touching,models,style,body.position-origin_model.transform.translation+destination_model.transform.translation)) + Some(teleport(body,touching,models,style,hitbox_mesh,body.position-origin_model.transform.vertex.translation+destination_model.transform.vertex.translation)) } None=>None, } } -impl instruction::InstructionConsumer for PhysicsState { - fn process_instruction(&mut self, ins:TimedInstruction) { + fn atomic_state_update(state:&mut PhysicsState,data:&PhysicsData,ins:TimedInstruction){ match &ins.instruction{ PhysicsInstruction::Input(PhysicsInputInstruction::Idle) |PhysicsInstruction::Input(PhysicsInputInstruction::SetNextMouse(_)) @@ -1477,416 +1265,421 @@ impl instruction::InstructionConsumer for PhysicsState { } //selectively update body match &ins.instruction{ - PhysicsInstruction::Input(PhysicsInputInstruction::Idle)=>self.time=ins.time,//idle simply updates time + PhysicsInstruction::Input(PhysicsInputInstruction::Idle)=>state.time=ins.time,//idle simply updates time PhysicsInstruction::Input(_) |PhysicsInstruction::ReachWalkTargetVelocity |PhysicsInstruction::CollisionStart(_) |PhysicsInstruction::CollisionEnd(_) - |PhysicsInstruction::StrafeTick=>self.advance_time(ins.time), + |PhysicsInstruction::StrafeTick=>state.advance_time(ins.time), } match ins.instruction{ PhysicsInstruction::CollisionStart(c)=>{ - let style_mesh=self.style.mesh(); - let model_id=c.model_id(); - match (self.models.attr(model_id),&c){ + let convex_mesh_id=c.convex_mesh_id(); + match (data.models.attr(convex_mesh_id.model_id),&c){ (PhysicsCollisionAttributes::Contact{contacting,general},Collision::Contact(contact))=>{ - let mut v=self.body.velocity; - let normal=contact_normal(&self.models,&style_mesh,contact); + let mut v=state.body.velocity; + let normal=contact_normal(&data.models,&data.hitbox_mesh,contact); match &contacting.contact_behaviour{ - Some(crate::model::ContactingBehaviour::Surf)=>println!("I'm surfing!"), - Some(crate::model::ContactingBehaviour::Cling)=>println!("Unimplemented!"), - &Some(crate::model::ContactingBehaviour::Elastic(elasticity))=>{ + Some(gameplay_attributes::ContactingBehaviour::Surf)=>println!("I'm surfing!"), + Some(gameplay_attributes::ContactingBehaviour::Cling)=>println!("Unimplemented!"), + &Some(gameplay_attributes::ContactingBehaviour::Elastic(elasticity))=>{ //velocity and normal are facing opposite directions so this is inherently negative. let d=normal.dot(v)*(Planar64::ONE+Planar64::raw(elasticity as i64+1)); v+=normal*(d/normal.dot(normal)); }, - Some(crate::model::ContactingBehaviour::Ladder(contacting_ladder))=>{ + Some(gameplay_attributes::ContactingBehaviour::Ladder(contacting_ladder))=>{ if contacting_ladder.sticky{ //kill v //actually you could do this with a booster attribute :thinking: v=Planar64Vec3::ZERO;//model.velocity } //ladder walkstate - let gravity=self.touching.base_acceleration(&self.models,&self.style,&self.camera,self.controls,&self.next_mouse,self.time); - let mut target_velocity=self.style.get_ladder_target_velocity(&self.camera,self.controls,&self.next_mouse,self.time,&normal); - self.touching.constrain_velocity(&self.models,&style_mesh,&mut target_velocity); - let (walk_state,a)=WalkState::ladder(&self.body,&self.style,gravity,target_velocity,contact.clone(),&normal); - self.move_state=MoveState::Ladder(walk_state); - set_acceleration(&mut self.body,&self.touching,&self.models,&style_mesh,a); - } - None=>if self.style.surf_slope.map_or(true,|s|contact_normal(&self.models,&style_mesh,contact).walkable(s,Planar64Vec3::Y)){ + let gravity=state.touching.base_acceleration(&data.models,&state.style,&state.camera,state.controls,&state.next_mouse,state.time); + let mut target_velocity=state.style.get_ladder_target_velocity(&state.camera,state.controls,&state.next_mouse,state.time,&normal); + state.touching.constrain_velocity(&data.models,&data.hitbox_mesh,&mut target_velocity); + let (walk_state,a)=WalkState::ladder(&state.body,&state.style,gravity,target_velocity,contact.clone(),&normal); + state.move_state=MoveState::Ladder(walk_state); + set_acceleration(&mut state.body,&state.touching,&data.models,&data.hitbox_mesh,a); + }, + Some(gameplay_attributes::ContactingBehaviour::NoJump)=>todo!("nyi"), + None=>if state.style.surf_slope.map_or(true,|s|contact_normal(&data.models,&data.hitbox_mesh,contact).walkable(s,Planar64Vec3::Y)){ //ground - let gravity=self.touching.base_acceleration(&self.models,&self.style,&self.camera,self.controls,&self.next_mouse,self.time); - let mut target_velocity=self.style.get_walk_target_velocity(&self.camera,self.controls,&self.next_mouse,self.time,&normal); - self.touching.constrain_velocity(&self.models,&style_mesh,&mut target_velocity); - let (walk_state,a)=WalkState::ground(&self.body,&self.style,gravity,target_velocity,contact.clone(),&normal); - self.move_state=MoveState::Walk(walk_state); - set_acceleration(&mut self.body,&self.touching,&self.models,&style_mesh,a); + let gravity=state.touching.base_acceleration(&data.models,&state.style,&state.camera,state.controls,&state.next_mouse,state.time); + let mut target_velocity=state.style.get_walk_target_velocity(&state.camera,state.controls,&state.next_mouse,state.time,&normal); + state.touching.constrain_velocity(&data.models,&data.hitbox_mesh,&mut target_velocity); + let (walk_state,a)=WalkState::ground(&state.body,&state.style,gravity,target_velocity,contact.clone(),&normal); + state.move_state=MoveState::Walk(walk_state); + set_acceleration(&mut state.body,&state.touching,&data.models,&data.hitbox_mesh,a); }, } //check ground - self.touching.insert(c); + state.touching.insert(c); //I love making functions with 10 arguments to dodge the borrow checker - run_teleport_behaviour(&general.teleport_behaviour,&mut self.game,&self.models,&self.modes,&self.style,&mut self.touching,&mut self.body,model_id); + run_teleport_behaviour(&general.wormhole,&data.models,&data.modes.get_mode(state.mode_state.get_mode_id()).unwrap(),&state.style,&data.hitbox_mesh,&mut state.mode_state,&mut state.touching,&mut state.body,convex_mesh_id); //flatten v - self.touching.constrain_velocity(&self.models,&style_mesh,&mut v); + state.touching.constrain_velocity(&data.models,&data.hitbox_mesh,&mut v); match &general.booster{ Some(booster)=>{ //DELETE THIS when boosters get converted to height machines match booster{ - &crate::model::GameMechanicBooster::Affine(transform)=>v=transform.transform_point3(v), - &crate::model::GameMechanicBooster::Velocity(velocity)=>v+=velocity, - &crate::model::GameMechanicBooster::Energy{direction: _,energy: _}=>todo!(), + //&gameplay_attributes::Booster::Affine(transform)=>v=transform.transform_point3(v), + &gameplay_attributes::Booster::Velocity(velocity)=>v+=velocity, + &gameplay_attributes::Booster::Energy{direction: _,energy: _}=>todo!(), } }, None=>(), } - let calc_move=if self.style.get_control(StyleModifiers::CONTROL_JUMP,self.controls){ - if let Some(walk_state)=get_walk_state(&self.move_state){ - jumped_velocity(&self.models,&self.style,walk_state,&mut v); - set_velocity_cull(&mut self.body,&mut self.touching,&self.models,&style_mesh,v) + let calc_move=if state.style.get_control(StyleModifiers::CONTROL_JUMP,state.controls){ + if let Some(walk_state)=get_walk_state(&state.move_state){ + jumped_velocity(&data.models,&state.style,&data.hitbox_mesh,walk_state,&mut v); + set_velocity_cull(&mut state.body,&mut state.touching,&data.models,&data.hitbox_mesh,v) }else{false} }else{false}; match &general.trajectory{ Some(trajectory)=>{ match trajectory{ - crate::model::GameMechanicSetTrajectory::AirTime(_) => todo!(), - crate::model::GameMechanicSetTrajectory::Height(_) => todo!(), - crate::model::GameMechanicSetTrajectory::TargetPointTime { target_point: _, time: _ } => todo!(), - crate::model::GameMechanicSetTrajectory::TargetPointSpeed { target_point: _, speed: _, trajectory_choice: _ } => todo!(), - &crate::model::GameMechanicSetTrajectory::Velocity(velocity)=>v=velocity, - crate::model::GameMechanicSetTrajectory::DotVelocity { direction: _, dot: _ } => todo!(), + gameplay_attributes::SetTrajectory::AirTime(_)=>todo!(), + gameplay_attributes::SetTrajectory::Height(_)=>todo!(), + gameplay_attributes::SetTrajectory::TargetPointTime { target_point: _, time: _ }=>todo!(), + gameplay_attributes::SetTrajectory::TargetPointSpeed { target_point: _, speed: _, trajectory_choice: _ }=>todo!(), + &gameplay_attributes::SetTrajectory::Velocity(velocity)=>v=velocity, + gameplay_attributes::SetTrajectory::DotVelocity { direction: _, dot: _ }=>todo!(), } }, None=>(), } - set_velocity(&mut self.body,&self.touching,&self.models,&style_mesh,v); + set_velocity(&mut state.body,&state.touching,&data.models,&data.hitbox_mesh,v); //not sure if or is correct here if calc_move||Planar64::ZERO{ //I think that setting the velocity to 0 was preventing surface contacts from entering an infinite loop - self.touching.insert(c); - run_teleport_behaviour(&general.teleport_behaviour,&mut self.game,&self.models,&self.modes,&self.style,&mut self.touching,&mut self.body,model_id); + state.touching.insert(c); + run_teleport_behaviour(&general.wormhole,&data.models,&data.modes.get_mode(state.mode_state.get_mode_id()).unwrap(),&state.style,&data.hitbox_mesh,&mut state.mode_state,&mut state.touching,&mut state.body,convex_mesh_id); }, _=>panic!("invalid pair"), } }, - PhysicsInstruction::CollisionEnd(c) => { - match self.models.attr(c.model_id()){ + PhysicsInstruction::CollisionEnd(c)=>{ + match data.models.attr(c.convex_mesh_id().model_id){ PhysicsCollisionAttributes::Contact{contacting:_,general:_}=>{ - self.touching.remove(&c);//remove contact before calling contact_constrain_acceleration + state.touching.remove(&c);//remove contact before calling contact_constrain_acceleration //check ground - (self.move_state,self.body.acceleration)=self.touching.get_move_state(&self.body,&self.models,&self.style,&self.camera,self.controls,&self.next_mouse,self.time); + (state.move_state,state.body.acceleration)=state.touching.get_move_state(&state.body,&data.models,&state.style,&data.hitbox_mesh,&state.camera,state.controls,&state.next_mouse,state.time); }, PhysicsCollisionAttributes::Intersect{intersecting:_,general:_}=>{ - self.touching.remove(&c); + state.touching.remove(&c); }, } }, - PhysicsInstruction::StrafeTick => { - let control_dir=self.style.get_control_dir(self.controls); + PhysicsInstruction::StrafeTick=>{ + let control_dir=state.style.get_control_dir(state.controls); if control_dir!=Planar64Vec3::ZERO{ - let camera_mat=self.camera.simulate_move_rotation_y(self.camera.mouse.lerp(&self.next_mouse,self.time).x); + let camera_mat=state.camera.simulate_move_rotation_y(state.camera.mouse.lerp(&state.next_mouse,state.time).x); let control_dir=camera_mat*control_dir; //normalize but careful for zero - let d=self.body.velocity.dot(control_dir); - if d { - match &mut self.move_state{ + PhysicsInstruction::ReachWalkTargetVelocity=>{ + match &mut state.move_state{ MoveState::Air|MoveState::Water=>(), MoveState::Walk(walk_state)|MoveState::Ladder(walk_state)=>{ match &mut walk_state.state{ WalkEnum::Reached=>(), WalkEnum::Transient(walk_target)=>{ - let style_mesh=self.style.mesh(); //precisely set velocity let a=Planar64Vec3::ZERO;//ignore gravity for now. - set_acceleration(&mut self.body,&self.touching,&self.models,&style_mesh,a); + set_acceleration(&mut state.body,&state.touching,&data.models,&data.hitbox_mesh,a); let v=walk_target.velocity; - set_velocity(&mut self.body,&self.touching,&self.models,&style_mesh,v); + set_velocity(&mut state.body,&state.touching,&data.models,&data.hitbox_mesh,v); walk_state.state=WalkEnum::Reached; }, } } } }, - PhysicsInstruction::Input(input_instruction) => { - let mut refresh_walk_target=true; + PhysicsInstruction::Input(input_instruction)=>{ + let mut b_refresh_walk_target=true; match input_instruction{ - PhysicsInputInstruction::SetNextMouse(m) => { - self.camera.move_mouse(self.next_mouse.pos); - (self.camera.mouse,self.next_mouse)=(self.next_mouse.clone(),m); + PhysicsInputInstruction::SetNextMouse(m)=>{ + state.camera.move_mouse(state.next_mouse.pos); + (state.camera.mouse,state.next_mouse)=(state.next_mouse.clone(),m); }, - PhysicsInputInstruction::ReplaceMouse(m0,m1) => { - self.camera.move_mouse(m0.pos); - (self.camera.mouse,self.next_mouse)=(m0,m1); + PhysicsInputInstruction::ReplaceMouse(m0,m1)=>{ + state.camera.move_mouse(m0.pos); + (state.camera.mouse,state.next_mouse)=(m0,m1); }, - PhysicsInputInstruction::SetMoveForward(s) => self.set_control(StyleModifiers::CONTROL_MOVEFORWARD,s), - PhysicsInputInstruction::SetMoveLeft(s) => self.set_control(StyleModifiers::CONTROL_MOVELEFT,s), - PhysicsInputInstruction::SetMoveBack(s) => self.set_control(StyleModifiers::CONTROL_MOVEBACK,s), - PhysicsInputInstruction::SetMoveRight(s) => self.set_control(StyleModifiers::CONTROL_MOVERIGHT,s), - PhysicsInputInstruction::SetMoveUp(s) => self.set_control(StyleModifiers::CONTROL_MOVEUP,s), - PhysicsInputInstruction::SetMoveDown(s) => self.set_control(StyleModifiers::CONTROL_MOVEDOWN,s), - PhysicsInputInstruction::SetJump(s) => { - self.set_control(StyleModifiers::CONTROL_JUMP,s); - if let Some(walk_state)=get_walk_state(&self.move_state){ - let mut v=self.body.velocity; - jumped_velocity(&self.models,&self.style,walk_state,&mut v); - if set_velocity_cull(&mut self.body,&mut self.touching,&self.models,&self.style.mesh(),v){ - (self.move_state,self.body.acceleration)=self.touching.get_move_state(&self.body,&self.models,&self.style,&self.camera,self.controls,&self.next_mouse,self.time); + PhysicsInputInstruction::SetMoveForward(s)=>state.set_control(StyleModifiers::CONTROL_MOVEFORWARD,s), + PhysicsInputInstruction::SetMoveLeft(s)=>state.set_control(StyleModifiers::CONTROL_MOVELEFT,s), + PhysicsInputInstruction::SetMoveBack(s)=>state.set_control(StyleModifiers::CONTROL_MOVEBACK,s), + PhysicsInputInstruction::SetMoveRight(s)=>state.set_control(StyleModifiers::CONTROL_MOVERIGHT,s), + PhysicsInputInstruction::SetMoveUp(s)=>state.set_control(StyleModifiers::CONTROL_MOVEUP,s), + PhysicsInputInstruction::SetMoveDown(s)=>state.set_control(StyleModifiers::CONTROL_MOVEDOWN,s), + PhysicsInputInstruction::SetJump(s)=>{ + state.set_control(StyleModifiers::CONTROL_JUMP,s); + if let Some(walk_state)=get_walk_state(&state.move_state){ + let mut v=state.body.velocity; + jumped_velocity(&data.models,&state.style,&data.hitbox_mesh,walk_state,&mut v); + if set_velocity_cull(&mut state.body,&mut state.touching,&data.models,&data.hitbox_mesh,v){ + (state.move_state,state.body.acceleration)=state.touching.get_move_state(&state.body,&data.models,&state.style,&data.hitbox_mesh,&state.camera,state.controls,&state.next_mouse,state.time); } } - refresh_walk_target=false; + b_refresh_walk_target=false; }, - PhysicsInputInstruction::SetZoom(s) => { - self.set_control(StyleModifiers::CONTROL_ZOOM,s); - refresh_walk_target=false; + PhysicsInputInstruction::SetZoom(s)=>{ + state.set_control(StyleModifiers::CONTROL_ZOOM,s); + b_refresh_walk_target=false; }, - PhysicsInputInstruction::Reset => { + PhysicsInputInstruction::Reset=>{ //it matters which of these runs first, but I have not thought it through yet as it doesn't matter yet - set_position(&mut self.body,&mut self.touching,self.spawn_point); - set_velocity(&mut self.body,&self.touching,&self.models,&self.style.mesh(),Planar64Vec3::ZERO); - (self.move_state,self.body.acceleration)=self.touching.get_move_state(&self.body,&self.models,&self.style,&self.camera,self.controls,&self.next_mouse,self.time); - refresh_walk_target=false; + state.mode_state.clear(); + state.mode_state.set_stage_id(gameplay_modes::StageId::FIRST); + let spawn_point=data.modes.get_mode(state.mode_state.get_mode_id()).and_then(|mode| + //TODO: spawn at the bottom of the start zone plus the hitbox size + Some(data.models.model(mode.get_start().into()).transform.vertex.translation) + ).unwrap_or(Planar64Vec3::ZERO); + set_position(&mut state.body,&mut state.touching,spawn_point); + set_velocity(&mut state.body,&state.touching,&data.models,&data.hitbox_mesh,Planar64Vec3::ZERO); + (state.move_state,state.body.acceleration)=state.touching.get_move_state(&state.body,&data.models,&state.style,&data.hitbox_mesh,&state.camera,state.controls,&state.next_mouse,state.time); + b_refresh_walk_target=false; }, - PhysicsInputInstruction::Idle => {refresh_walk_target=false;},//literally idle! + PhysicsInputInstruction::Idle=>{b_refresh_walk_target=false;},//literally idle! } - if refresh_walk_target{ - let a=self.refresh_walk_target(); - if set_acceleration_cull(&mut self.body,&mut self.touching,&self.models,&self.style.mesh(),a){ - (self.move_state,self.body.acceleration)=self.touching.get_move_state(&self.body,&self.models,&self.style,&self.camera,self.controls,&self.next_mouse,self.time); + if b_refresh_walk_target{ + let a=refresh_walk_target(state,data); + if set_acceleration_cull(&mut state.body,&mut state.touching,&data.models,&data.hitbox_mesh,a){ + (state.move_state,state.body.acceleration)=state.touching.get_move_state(&state.body,&data.models,&state.style,&data.hitbox_mesh,&state.camera,state.controls,&state.next_mouse,state.time); } } }, } } -} -#[allow(dead_code)] -fn test_collision_axis_aligned(relative_body:Body,expected_collision_time:Option