Compare commits
296 Commits
bot
...
debug-merg
| Author | SHA1 | Date | |
|---|---|---|---|
| 2de27d5aa9 | |||
| 5e7748ccf8 | |||
| 3d5e76f078 | |||
| 07bab657f1 | |||
| 6b0938eb3b | |||
| a56195c887 | |||
| 8e427e37b3 | |||
| 0bd720beb6 | |||
| 5a4276052a | |||
| e230eeccb7 | |||
| b9ed031919 | |||
| 364939167c | |||
| 8b6f3620f8 | |||
| 71426c257f | |||
| 041cc15f20 | |||
| 6ef6174693 | |||
| ed1250e2f1 | |||
| 2b272e1b7c | |||
| 06e29b3d42 | |||
| aba3e99722 | |||
| 51d7d75e9a | |||
| 70fb970582 | |||
| 31184d21da | |||
| 6b4b17f49d | |||
| d0c17873f6 | |||
| d39705d5e7 | |||
| a3a50580f8 | |||
| d5bf4549a9 | |||
| b5da85fae1 | |||
| 78c53ae429 | |||
| e794b2649c | |||
| 50f6fe5bd8 | |||
| fc3681f41f | |||
| a2369b4211 | |||
| bfea49ffae | |||
| 8c7063d340 | |||
| 651150760c | |||
| 3d203e2da9 | |||
| 3798f438cf | |||
| 4ace8317fe | |||
| ab4a9bb922 | |||
| a9ef07ce78 | |||
| 561e41c760 | |||
| 9896a09576 | |||
| 82840fa6cb | |||
| 2c87cb71df | |||
| 709f622547 | |||
| b20264b382 | |||
| 17cce2d702 | |||
| a2b2ddc9ce | |||
| cb57d893e0 | |||
| 9e8d66cec1 | |||
| 39fe833198 | |||
| e7688a95fd | |||
| d2cc98b04d | |||
| 9e887580af | |||
| 92feac572e | |||
| fd02a40783 | |||
| 7c787a0e0f | |||
| 6a7c076203 | |||
| af3abbcb4d | |||
| 4859c37780 | |||
| 19e65802f6 | |||
| 2cf1547423 | |||
| 63305f91c7 | |||
| e875826250 | |||
| 0a44c1630f | |||
| 5cffc03ef6 | |||
| d638e633ba | |||
| 61e44f2aba | |||
| 347b1176d2 | |||
| 7c0ad5b601 | |||
| 62851bbd60 | |||
| b3a6d08656 | |||
| 5409548348 | |||
| 57552c1a6a | |||
| aace3bb2a3 | |||
| 4899003766 | |||
| 0c991715ab | |||
| 72e0caa84a | |||
| 4805d237e2 | |||
| 8e91fbb08e | |||
| a2a69c48d6 | |||
| e74535d91f | |||
| dbca48f051 | |||
| ad6584d82a | |||
| ceb5fff333 | |||
| c6f31a3eb5 | |||
| 6c81fc43bb | |||
| 5317cd0609 | |||
| 09d151ee1c | |||
| 441b9a8559 | |||
| 707ff98d27 | |||
| 2c1910bc24 | |||
| 614535ce8c | |||
| 78c29657d6 | |||
| 9d9695f363 | |||
| ce3d8fde11 | |||
| 1ddc013fd0 | |||
| d0d2e6eca3 | |||
| 1ba7b0591b | |||
| 205db9a0db | |||
| ca50bf35c2 | |||
| 6522c255cd | |||
| 6b4122c59c | |||
| eb0365217d | |||
| c128046d88 | |||
| ff57825232 | |||
| 8e867449f0 | |||
| f028dc6c85 | |||
| 85480cb13e | |||
| a594f77453 | |||
| c356a9d654 | |||
| 23ee771c22 | |||
| a5079f21d7 | |||
| 349cd9c233 | |||
| d455cf4dc9 | |||
| 3227a6486a | |||
| 1ce51dd4da | |||
| 1ad9723905 | |||
| be9eb30a5c | |||
| 42310c5d2b | |||
| 988750a3c6 | |||
| 2f26662dda | |||
| 6f739dba8d | |||
| 2eaddd493d | |||
| d5a6f8e1bc | |||
| 95fb43b908 | |||
| 3e814cb41a | |||
| ea854a548f | |||
| 5f2cf8f32e | |||
| b6b090de78 | |||
| affbada62e | |||
| 8d2ba28700 | |||
| 3eb4e76ab2 | |||
| 5d31419370 | |||
| 44a58044c7 | |||
| 83ac776b78 | |||
| 871aadb78a | |||
| 65b49d2726 | |||
| ed70b7c484 | |||
| c7575f2e21 | |||
| c2f78eab48 | |||
| 195014400f | |||
| ee9585a4c2 | |||
| 77012d6caa | |||
| f9509353dd | |||
| 5bce4a84cf | |||
| 4bbccd68ed | |||
| 8be3be4002 | |||
| 76bafa4d0a | |||
| a612d1f864 | |||
| ad9ef141bf | |||
| 4c11980989 | |||
| 091da88b5c | |||
| 045e540020 | |||
| c14f8975fc | |||
| 8e228033e0 | |||
| eaecbc5b73 | |||
| 91a1b3d65e | |||
| e1d51ff2da | |||
| c5f2395b3a | |||
| 77aa83d6ac | |||
| 184b12a9cc | |||
| 9ba3484e25 | |||
| adcd7db4f1 | |||
| 710670d78e | |||
| 70e1514ef9 | |||
| 45e9b3d0ce | |||
| 1eac734c35 | |||
| 25e1312fe4 | |||
| cc8f6b059d | |||
| 4cd287dbb8 | |||
| 40ea0e6c7d | |||
| b45b92c627 | |||
| e90f53a111 | |||
| 6beb6c5f9a | |||
| a1507d4f26 | |||
| 77db5a7a6b | |||
| 41b28fa7d2 | |||
| a2ab23097b | |||
| 602061b44c | |||
| 1989369956 | |||
| a18aea828c | |||
| b7000ee9af | |||
| 2b77ea5712 | |||
| cf98f8e7bb | |||
|
a56c114d08
|
|||
|
b6a5324ae7
|
|||
| 6f5a3c5176 | |||
| 6bab31f3b3 | |||
| 9cdeed160f | |||
| d0c59b51a4 | |||
| 451f3ccecb | |||
| ed9701981d | |||
| 60e0197344 | |||
| 4d97a490c1 | |||
| 52ba44c6be | |||
| 95b6272b18 | |||
| 0172675b04 | |||
| 982b4aecac | |||
| c1ddcdb0c5 | |||
| c2d0a4487c | |||
| dc9fd2c442 | |||
| 4199d41d3f | |||
| 7fbcb206ff | |||
| a17901d473 | |||
| b88c6b899a | |||
| 835d4bbecd | |||
| b756dc979c | |||
| 1e888ebb01 | |||
| b9dccb1af5 | |||
| c6d293cc6b | |||
| a386f90f51 | |||
| 43115cbac6 | |||
| 35b5aff9a7 | |||
| 36419af870 | |||
| a7518bef46 | |||
| 6df1f41599 | |||
| 422d0a160d | |||
| 1727f9213c | |||
| afa9e7447d | |||
| ff85efa54f | |||
| fa69c53cfc | |||
| a57c228580 | |||
| 5dc69db885 | |||
| e54400a436 | |||
| e2a5edf8df | |||
| d6dd1b8abd | |||
| a2b793fcd3 | |||
| 9cb34f14c8 | |||
| bd2e3aa2d3 | |||
| 07f6053839 | |||
| 0d5b918ea1 | |||
| 20a568220a | |||
| d670d4129e | |||
| de7b0bd5cc | |||
| 01524146c7 | |||
| 45e8e415d0 | |||
| 4417bafc5c | |||
| 8553625738 | |||
| 3a3749eaeb | |||
| 53539f290b | |||
| 479dd37f53 | |||
| 34b6a869f0 | |||
| 19a455ee5e | |||
| 9904b7a044 | |||
| 6efa811eb6 | |||
| 81e4a201bd | |||
| 8fd5618af2 | |||
| 54c26d6e1e | |||
| 110ec94a08 | |||
| 980da5a6a7 | |||
| 1cd77984d4 | |||
| b0fe231388 | |||
| 5a4a39ab75 | |||
|
|
1b2324deeb | ||
| 4c485e76e4 | |||
| 7bbb9ca24f | |||
| eff55af1b4 | |||
| 0d05cc9996 | |||
| 2a55ef90df | |||
| 1a6202ae66 | |||
|
|
742f7b4ec0 | ||
| 2cb346f49a | |||
| e5cca9ed04 | |||
| 52d911a25a | |||
| 7ab20f36a7 | |||
| a7554da1c5 | |||
| 37f0dad7a1 | |||
| e309f15cb8 | |||
| 29374e4ff5 | |||
| b7d04d1f40 | |||
| 432ec11ea6 | |||
| 01449b1850 | |||
| 327d0a4992 | |||
| 420dbaa022 | |||
| cad29af4bb | |||
| e0e8744bfd | |||
| b434dce0f6 | |||
| 6ef8fd2f69 | |||
| 7234065bd8 | |||
| 41d8e700c5 | |||
| 4ca3d56f0f | |||
| 593b6902fd | |||
| 7523c4313a | |||
| 694440bd29 | |||
| 755e1d4d5b | |||
| 4334a6f330 | |||
| 553ad2cca5 | |||
| 3f15d2f5a8 | |||
| 206bb01c1a | |||
| df24bc9945 | |||
| 4c1632c0b6 | |||
| 9e482891ba | |||
| 38d1ff62ab |
@@ -1,6 +1,2 @@
|
|||||||
[registries.strafesnet]
|
[registries.strafesnet]
|
||||||
index = "sparse+https://git.itzana.me/api/packages/strafesnet/cargo/"
|
index = "sparse+https://git.itzana.me/api/packages/strafesnet/cargo/"
|
||||||
|
|
||||||
[target.x86_64-unknown-linux-gnu]
|
|
||||||
linker = "clang"
|
|
||||||
rustflags = ["-C", "link-arg=-fuse-ld=/usr/bin/mold"]
|
|
||||||
|
|||||||
2407
Cargo.lock
generated
2407
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,10 @@
|
|||||||
[workspace]
|
[workspace]
|
||||||
members = [
|
members = [
|
||||||
|
"engine/graphics",
|
||||||
|
"engine/physics",
|
||||||
|
"engine/session",
|
||||||
|
"engine/settings",
|
||||||
|
"integration-testing",
|
||||||
"lib/bsp_loader",
|
"lib/bsp_loader",
|
||||||
"lib/common",
|
"lib/common",
|
||||||
"lib/deferred_loader",
|
"lib/deferred_loader",
|
||||||
@@ -7,8 +12,10 @@ members = [
|
|||||||
"lib/linear_ops",
|
"lib/linear_ops",
|
||||||
"lib/ratio_ops",
|
"lib/ratio_ops",
|
||||||
"lib/rbx_loader",
|
"lib/rbx_loader",
|
||||||
|
"lib/rbxassetid",
|
||||||
"lib/roblox_emulator",
|
"lib/roblox_emulator",
|
||||||
"lib/snf",
|
"lib/snf",
|
||||||
|
"map-tool",
|
||||||
"strafe-client",
|
"strafe-client",
|
||||||
]
|
]
|
||||||
resolver = "2"
|
resolver = "2"
|
||||||
|
|||||||
@@ -3,6 +3,9 @@
|
|||||||
# Strafe Project
|
# Strafe Project
|
||||||
Monorepo for working on projects related to strafe client.
|
Monorepo for working on projects related to strafe client.
|
||||||
|
|
||||||
|
## Try it out
|
||||||
|
See [releases](https://git.itzana.me/StrafesNET/strafe-project/releases) for downloads.
|
||||||
|
|
||||||
## How to build and run
|
## How to build and run
|
||||||
1. Have rust and git installed
|
1. Have rust and git installed
|
||||||
2. `git clone https://git.itzana.me/StrafesNET/strafe-project`
|
2. `git clone https://git.itzana.me/StrafesNET/strafe-project`
|
||||||
@@ -10,4 +13,4 @@ Monorepo for working on projects related to strafe client.
|
|||||||
4. `cargo run --release --bin strafe-client`
|
4. `cargo run --release --bin strafe-client`
|
||||||
|
|
||||||
## Licenses
|
## Licenses
|
||||||
Each project has its own license. Most crates are MIT/Apache but notably the Strafe Client has a sole proprietor license.
|
Each project has its own license. Most crates are MIT/Apache but notably the Strafe Client and engine crates have a sole proprietor license.
|
||||||
|
|||||||
14
engine/graphics/Cargo.toml
Normal file
14
engine/graphics/Cargo.toml
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
[package]
|
||||||
|
name = "strafesnet_graphics"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2024"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
bytemuck = { version = "1.13.1", features = ["derive"] }
|
||||||
|
ddsfile = "0.5.1"
|
||||||
|
glam = "0.30.0"
|
||||||
|
id = { version = "0.1.0", registry = "strafesnet" }
|
||||||
|
strafesnet_common = { path = "../../lib/common", registry = "strafesnet" }
|
||||||
|
strafesnet_session = { path = "../session", registry = "strafesnet" }
|
||||||
|
strafesnet_settings = { path = "../settings", registry = "strafesnet" }
|
||||||
|
wgpu = "24.0.0"
|
||||||
8
engine/graphics/LICENSE
Normal file
8
engine/graphics/LICENSE
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
/*******************************************************
|
||||||
|
* Copyright (C) 2023-2024 Rhys Lloyd <krakow20@gmail.com>
|
||||||
|
*
|
||||||
|
* This file is part of the StrafesNET bhop/surf client.
|
||||||
|
*
|
||||||
|
* StrafesNET can not be copied and/or distributed
|
||||||
|
* without the express permission of Rhys Lloyd
|
||||||
|
*******************************************************/
|
||||||
@@ -1,9 +1,15 @@
|
|||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
use std::collections::{HashSet,HashMap};
|
use std::collections::{HashSet,HashMap};
|
||||||
use strafesnet_common::map;
|
use strafesnet_common::map;
|
||||||
|
use strafesnet_settings::settings;
|
||||||
|
use strafesnet_session::session;
|
||||||
use strafesnet_common::model::{self, ColorId, NormalId, PolygonIter, PositionId, RenderConfigId, TextureCoordinateId, VertexId};
|
use strafesnet_common::model::{self, ColorId, NormalId, PolygonIter, PositionId, RenderConfigId, TextureCoordinateId, VertexId};
|
||||||
use wgpu::{util::DeviceExt,AstcBlock,AstcChannel};
|
use wgpu::{util::DeviceExt,AstcBlock,AstcChannel};
|
||||||
use crate::model_graphics::{self,IndexedGraphicsMeshOwnedRenderConfig,IndexedGraphicsMeshOwnedRenderConfigId,GraphicsMeshOwnedRenderConfig,GraphicsModelColor4,GraphicsModelOwned,GraphicsVertex};
|
use crate::model::{self as model_graphics,IndexedGraphicsMeshOwnedRenderConfig,IndexedGraphicsMeshOwnedRenderConfigId,GraphicsMeshOwnedRenderConfig,GraphicsModelColor4,GraphicsModelOwned,GraphicsVertex,DebugGraphicsVertex};
|
||||||
|
|
||||||
|
pub fn required_limits()->wgpu::Limits{
|
||||||
|
wgpu::Limits::default()
|
||||||
|
}
|
||||||
|
|
||||||
struct Indices{
|
struct Indices{
|
||||||
count:u32,
|
count:u32,
|
||||||
@@ -30,12 +36,22 @@ struct GraphicsModel{
|
|||||||
instance_count:u32,
|
instance_count:u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct DebugGraphicsMesh{
|
||||||
|
indices:Indices,
|
||||||
|
vertex_buf:wgpu::Buffer,
|
||||||
|
}
|
||||||
|
struct DebugGraphicsModel{
|
||||||
|
debug_mesh_id:u32,
|
||||||
|
bind_group:wgpu::BindGroup,
|
||||||
|
}
|
||||||
|
|
||||||
struct GraphicsSamplers{
|
struct GraphicsSamplers{
|
||||||
repeat:wgpu::Sampler,
|
repeat:wgpu::Sampler,
|
||||||
}
|
}
|
||||||
|
|
||||||
struct GraphicsBindGroupLayouts{
|
struct GraphicsBindGroupLayouts{
|
||||||
model:wgpu::BindGroupLayout,
|
model:wgpu::BindGroupLayout,
|
||||||
|
debug_model:wgpu::BindGroupLayout,
|
||||||
}
|
}
|
||||||
|
|
||||||
struct GraphicsBindGroups{
|
struct GraphicsBindGroups{
|
||||||
@@ -46,6 +62,7 @@ struct GraphicsBindGroups{
|
|||||||
struct GraphicsPipelines{
|
struct GraphicsPipelines{
|
||||||
skybox:wgpu::RenderPipeline,
|
skybox:wgpu::RenderPipeline,
|
||||||
model:wgpu::RenderPipeline,
|
model:wgpu::RenderPipeline,
|
||||||
|
debug:wgpu::RenderPipeline,
|
||||||
}
|
}
|
||||||
|
|
||||||
struct GraphicsCamera{
|
struct GraphicsCamera{
|
||||||
@@ -106,6 +123,8 @@ pub struct GraphicsState{
|
|||||||
camera_buf:wgpu::Buffer,
|
camera_buf:wgpu::Buffer,
|
||||||
temp_squid_texture_view:wgpu::TextureView,
|
temp_squid_texture_view:wgpu::TextureView,
|
||||||
models:Vec<GraphicsModel>,
|
models:Vec<GraphicsModel>,
|
||||||
|
debug_meshes:Vec<DebugGraphicsMesh>,
|
||||||
|
debug_models:Vec<DebugGraphicsModel>,
|
||||||
depth_view:wgpu::TextureView,
|
depth_view:wgpu::TextureView,
|
||||||
staging_belt:wgpu::util::StagingBelt,
|
staging_belt:wgpu::util::StagingBelt,
|
||||||
}
|
}
|
||||||
@@ -136,10 +155,80 @@ impl GraphicsState{
|
|||||||
pub fn clear(&mut self){
|
pub fn clear(&mut self){
|
||||||
self.models.clear();
|
self.models.clear();
|
||||||
}
|
}
|
||||||
pub fn load_user_settings(&mut self,user_settings:&crate::settings::UserSettings){
|
pub fn load_user_settings(&mut self,user_settings:&settings::UserSettings){
|
||||||
self.camera.fov=user_settings.calculate_fov(1.0,&self.camera.screen_size).as_vec2();
|
self.camera.fov=user_settings.calculate_fov(1.0,&self.camera.screen_size).as_vec2();
|
||||||
}
|
}
|
||||||
pub fn generate_models(&mut self,device:&wgpu::Device,queue:&wgpu::Queue,map:&map::CompleteMap){
|
pub fn generate_models(&mut self,device:&wgpu::Device,queue:&wgpu::Queue,map:&map::CompleteMap){
|
||||||
|
//generate debug meshes, each debug model refers to one
|
||||||
|
self.debug_meshes=map.meshes.iter().map(|mesh|{
|
||||||
|
let vertices:Vec<DebugGraphicsVertex>=mesh.unique_vertices.iter().map(|vertex|{
|
||||||
|
DebugGraphicsVertex{
|
||||||
|
pos:mesh.unique_pos[vertex.pos.get() as usize].to_array().map(Into::into),
|
||||||
|
normal:mesh.unique_normal[vertex.normal.get() as usize].to_array().map(Into::into),
|
||||||
|
}
|
||||||
|
}).collect();
|
||||||
|
let vertex_buf=device.create_buffer_init(&wgpu::util::BufferInitDescriptor{
|
||||||
|
label:Some("Vertex"),
|
||||||
|
contents:bytemuck::cast_slice(&vertices),
|
||||||
|
usage:wgpu::BufferUsages::VERTEX,
|
||||||
|
});
|
||||||
|
|
||||||
|
let mut indices=Vec::new();
|
||||||
|
for physics_group in &mesh.physics_groups{
|
||||||
|
for polygon_group_id in &physics_group.groups{
|
||||||
|
for poly in mesh.polygon_groups[polygon_group_id.get() as usize].polys(){
|
||||||
|
// triangulate
|
||||||
|
let mut poly_vertices=poly.into_iter().copied();
|
||||||
|
if let (Some(a),Some(mut b))=(poly_vertices.next(),poly_vertices.next()){
|
||||||
|
for c in poly_vertices{
|
||||||
|
indices.extend([a,b,c]);
|
||||||
|
b=c;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DebugGraphicsMesh{
|
||||||
|
indices:if (u32::MAX as usize)<vertices.len(){
|
||||||
|
panic!("Model has too many vertices!")
|
||||||
|
}else if (u16::MAX as usize)<vertices.len(){
|
||||||
|
Indices::new(device,&indices.into_iter().map(|vertex_idx|vertex_idx.get() as u32).collect(),wgpu::IndexFormat::Uint32)
|
||||||
|
}else{
|
||||||
|
Indices::new(device,&indices.into_iter().map(|vertex_idx|vertex_idx.get() as u16).collect(),wgpu::IndexFormat::Uint16)
|
||||||
|
},
|
||||||
|
vertex_buf,
|
||||||
|
}
|
||||||
|
}).collect();
|
||||||
|
|
||||||
|
//generate debug models, only one will be rendered at a time
|
||||||
|
self.debug_models=map.models.iter().enumerate().map(|(model_id,model)|{
|
||||||
|
let model_uniforms=get_instance_buffer_data(&GraphicsModelOwned{
|
||||||
|
transform:model.transform.into(),
|
||||||
|
normal_transform:glam::Mat3::from_cols_array_2d(&model.transform.matrix3.to_array().map(|row|row.map(Into::into))).inverse().transpose(),
|
||||||
|
color:GraphicsModelColor4::new(glam::vec4(1.0,0.0,0.0,0.2)),
|
||||||
|
});
|
||||||
|
let model_buf=device.create_buffer_init(&wgpu::util::BufferInitDescriptor{
|
||||||
|
label:Some(format!("Debug Model{} Buf",model_id).as_str()),
|
||||||
|
contents:bytemuck::cast_slice(&model_uniforms),
|
||||||
|
usage:wgpu::BufferUsages::UNIFORM|wgpu::BufferUsages::COPY_DST,
|
||||||
|
});
|
||||||
|
let bind_group=device.create_bind_group(&wgpu::BindGroupDescriptor{
|
||||||
|
layout:&self.bind_group_layouts.debug_model,
|
||||||
|
entries:&[
|
||||||
|
wgpu::BindGroupEntry{
|
||||||
|
binding:0,
|
||||||
|
resource:model_buf.as_entire_binding(),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
label:Some(format!("Debug Model{} Bind Group",model_id).as_str()),
|
||||||
|
});
|
||||||
|
DebugGraphicsModel{
|
||||||
|
debug_mesh_id:model.mesh.get(),
|
||||||
|
bind_group,
|
||||||
|
}
|
||||||
|
}).collect();
|
||||||
|
|
||||||
//generate texture view per texture
|
//generate texture view per texture
|
||||||
let texture_views:HashMap<strafesnet_common::model::TextureId,wgpu::TextureView>=map.textures.iter().enumerate().filter_map(|(texture_id,texture_data)|{
|
let texture_views:HashMap<strafesnet_common::model::TextureId,wgpu::TextureView>=map.textures.iter().enumerate().filter_map(|(texture_id,texture_data)|{
|
||||||
let texture_id=model::TextureId::new(texture_id as u32);
|
let texture_id=model::TextureId::new(texture_id as u32);
|
||||||
@@ -448,7 +537,7 @@ impl GraphicsState{
|
|||||||
//.into_iter() the modeldata vec so entities can be /moved/ to models.entities
|
//.into_iter() the modeldata vec so entities can be /moved/ to models.entities
|
||||||
let mut model_count=0;
|
let mut model_count=0;
|
||||||
let mut instance_count=0;
|
let mut instance_count=0;
|
||||||
let uniform_buffer_binding_size=crate::setup::required_limits().max_uniform_buffer_binding_size as usize;
|
let uniform_buffer_binding_size=required_limits().max_uniform_buffer_binding_size as usize;
|
||||||
let chunk_size=uniform_buffer_binding_size/MODEL_BUFFER_SIZE_BYTES;
|
let chunk_size=uniform_buffer_binding_size/MODEL_BUFFER_SIZE_BYTES;
|
||||||
self.models.reserve(models.len());
|
self.models.reserve(models.len());
|
||||||
for model in models.into_iter(){
|
for model in models.into_iter(){
|
||||||
@@ -582,6 +671,21 @@ impl GraphicsState{
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
let debug_model_bind_group_layout=device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor{
|
||||||
|
label:Some("Debug Model Bind Group Layout"),
|
||||||
|
entries:&[
|
||||||
|
wgpu::BindGroupLayoutEntry{
|
||||||
|
binding:0,
|
||||||
|
visibility:wgpu::ShaderStages::VERTEX_FRAGMENT,
|
||||||
|
ty:wgpu::BindingType::Buffer{
|
||||||
|
ty:wgpu::BufferBindingType::Uniform,
|
||||||
|
has_dynamic_offset:false,
|
||||||
|
min_binding_size:None,
|
||||||
|
},
|
||||||
|
count:None,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
let clamp_sampler=device.create_sampler(&wgpu::SamplerDescriptor{
|
let clamp_sampler=device.create_sampler(&wgpu::SamplerDescriptor{
|
||||||
label:Some("Clamp Sampler"),
|
label:Some("Clamp Sampler"),
|
||||||
@@ -608,7 +712,7 @@ impl GraphicsState{
|
|||||||
// Create the render pipeline
|
// Create the render pipeline
|
||||||
let shader=device.create_shader_module(wgpu::ShaderModuleDescriptor{
|
let shader=device.create_shader_module(wgpu::ShaderModuleDescriptor{
|
||||||
label:None,
|
label:None,
|
||||||
source:wgpu::ShaderSource::Wgsl(Cow::Borrowed(include_str!("shader.wgsl"))),
|
source:wgpu::ShaderSource::Wgsl(Cow::Borrowed(include_str!("../../../strafe-client/src/shader.wgsl"))),
|
||||||
});
|
});
|
||||||
|
|
||||||
//load textures
|
//load textures
|
||||||
@@ -636,10 +740,10 @@ impl GraphicsState{
|
|||||||
wgpu::TextureFormat::Astc{
|
wgpu::TextureFormat::Astc{
|
||||||
block:AstcBlock::B4x4,
|
block:AstcBlock::B4x4,
|
||||||
channel:AstcChannel::UnormSrgb,
|
channel:AstcChannel::UnormSrgb,
|
||||||
}=>&include_bytes!("../images/astc.dds")[..],
|
}=>&include_bytes!("../../../strafe-client/images/astc.dds")[..],
|
||||||
wgpu::TextureFormat::Etc2Rgb8UnormSrgb=>&include_bytes!("../images/etc2.dds")[..],
|
wgpu::TextureFormat::Etc2Rgb8UnormSrgb=>&include_bytes!("../../../strafe-client/images/etc2.dds")[..],
|
||||||
wgpu::TextureFormat::Bc1RgbaUnormSrgb=>&include_bytes!("../images/bc1.dds")[..],
|
wgpu::TextureFormat::Bc1RgbaUnormSrgb=>&include_bytes!("../../../strafe-client/images/bc1.dds")[..],
|
||||||
wgpu::TextureFormat::Bgra8UnormSrgb=>&include_bytes!("../images/bgra.dds")[..],
|
wgpu::TextureFormat::Bgra8UnormSrgb=>&include_bytes!("../../../strafe-client/images/bgra.dds")[..],
|
||||||
_=>unreachable!(),
|
_=>unreachable!(),
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -682,7 +786,7 @@ impl GraphicsState{
|
|||||||
|
|
||||||
//squid
|
//squid
|
||||||
let squid_texture_view={
|
let squid_texture_view={
|
||||||
let bytes=include_bytes!("../images/squid.dds");
|
let bytes=include_bytes!("../../../strafe-client/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();
|
||||||
|
|
||||||
@@ -730,6 +834,14 @@ impl GraphicsState{
|
|||||||
],
|
],
|
||||||
push_constant_ranges:&[],
|
push_constant_ranges:&[],
|
||||||
});
|
});
|
||||||
|
let debug_model_pipeline_layout=device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor{
|
||||||
|
label:None,
|
||||||
|
bind_group_layouts:&[
|
||||||
|
&camera_bind_group_layout,
|
||||||
|
&debug_model_bind_group_layout,
|
||||||
|
],
|
||||||
|
push_constant_ranges:&[],
|
||||||
|
});
|
||||||
let sky_pipeline_layout=device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor{
|
let sky_pipeline_layout=device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor{
|
||||||
label:None,
|
label:None,
|
||||||
bind_group_layouts:&[
|
bind_group_layouts:&[
|
||||||
@@ -805,6 +917,45 @@ impl GraphicsState{
|
|||||||
multiview:None,
|
multiview:None,
|
||||||
cache:None,
|
cache:None,
|
||||||
});
|
});
|
||||||
|
let debug_model_pipeline=device.create_render_pipeline(&wgpu::RenderPipelineDescriptor{
|
||||||
|
label:Some("Debug Model Pipeline"),
|
||||||
|
layout:Some(&debug_model_pipeline_layout),
|
||||||
|
vertex:wgpu::VertexState{
|
||||||
|
module:&shader,
|
||||||
|
entry_point:Some("vs_debug"),
|
||||||
|
buffers:&[wgpu::VertexBufferLayout{
|
||||||
|
array_stride:std::mem::size_of::<DebugGraphicsVertex>() as wgpu::BufferAddress,
|
||||||
|
step_mode:wgpu::VertexStepMode::Vertex,
|
||||||
|
attributes:&wgpu::vertex_attr_array![0=>Float32x3,1=>Float32x3],
|
||||||
|
}],
|
||||||
|
compilation_options:wgpu::PipelineCompilationOptions::default(),
|
||||||
|
},
|
||||||
|
fragment:Some(wgpu::FragmentState{
|
||||||
|
module:&shader,
|
||||||
|
entry_point:Some("fs_debug"),
|
||||||
|
targets:&[Some(wgpu::ColorTargetState{
|
||||||
|
format:config.view_formats[0],
|
||||||
|
blend:Some(wgpu::BlendState::ALPHA_BLENDING),
|
||||||
|
write_mask:wgpu::ColorWrites::default(),
|
||||||
|
})],
|
||||||
|
compilation_options:wgpu::PipelineCompilationOptions::default(),
|
||||||
|
}),
|
||||||
|
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::Always,
|
||||||
|
stencil:wgpu::StencilState::default(),
|
||||||
|
bias:wgpu::DepthBiasState::default(),
|
||||||
|
}),
|
||||||
|
multisample:wgpu::MultisampleState::default(),
|
||||||
|
multiview:None,
|
||||||
|
cache:None,
|
||||||
|
});
|
||||||
|
|
||||||
let camera=GraphicsCamera::default();
|
let camera=GraphicsCamera::default();
|
||||||
let camera_uniforms=camera.to_uniform_data(glam::Vec3::ZERO,glam::Vec2::ZERO);
|
let camera_uniforms=camera.to_uniform_data(glam::Vec3::ZERO,glam::Vec2::ZERO);
|
||||||
@@ -844,7 +995,8 @@ impl GraphicsState{
|
|||||||
Self{
|
Self{
|
||||||
pipelines:GraphicsPipelines{
|
pipelines:GraphicsPipelines{
|
||||||
skybox:sky_pipeline,
|
skybox:sky_pipeline,
|
||||||
model:model_pipeline
|
model:model_pipeline,
|
||||||
|
debug:debug_model_pipeline,
|
||||||
},
|
},
|
||||||
bind_groups:GraphicsBindGroups{
|
bind_groups:GraphicsBindGroups{
|
||||||
camera:camera_bind_group,
|
camera:camera_bind_group,
|
||||||
@@ -853,9 +1005,14 @@ impl GraphicsState{
|
|||||||
camera,
|
camera,
|
||||||
camera_buf,
|
camera_buf,
|
||||||
models:Vec::new(),
|
models:Vec::new(),
|
||||||
|
debug_meshes:Vec::new(),
|
||||||
|
debug_models:Vec::new(),
|
||||||
depth_view,
|
depth_view,
|
||||||
staging_belt:wgpu::util::StagingBelt::new(0x100),
|
staging_belt:wgpu::util::StagingBelt::new(0x100),
|
||||||
bind_group_layouts:GraphicsBindGroupLayouts{model:model_bind_group_layout},
|
bind_group_layouts:GraphicsBindGroupLayouts{
|
||||||
|
model:model_bind_group_layout,
|
||||||
|
debug_model:debug_model_bind_group_layout,
|
||||||
|
},
|
||||||
samplers:GraphicsSamplers{repeat:repeat_sampler},
|
samplers:GraphicsSamplers{repeat:repeat_sampler},
|
||||||
temp_squid_texture_view:squid_texture_view,
|
temp_squid_texture_view:squid_texture_view,
|
||||||
}
|
}
|
||||||
@@ -864,7 +1021,7 @@ impl GraphicsState{
|
|||||||
&mut self,
|
&mut self,
|
||||||
device:&wgpu::Device,
|
device:&wgpu::Device,
|
||||||
config:&wgpu::SurfaceConfiguration,
|
config:&wgpu::SurfaceConfiguration,
|
||||||
user_settings:&crate::settings::UserSettings,
|
user_settings:&settings::UserSettings,
|
||||||
){
|
){
|
||||||
self.depth_view=Self::create_depth_texture(config,device);
|
self.depth_view=Self::create_depth_texture(config,device);
|
||||||
self.camera.screen_size=glam::uvec2(config.width,config.height);
|
self.camera.screen_size=glam::uvec2(config.width,config.height);
|
||||||
@@ -875,7 +1032,7 @@ impl GraphicsState{
|
|||||||
view:&wgpu::TextureView,
|
view:&wgpu::TextureView,
|
||||||
device:&wgpu::Device,
|
device:&wgpu::Device,
|
||||||
queue:&wgpu::Queue,
|
queue:&wgpu::Queue,
|
||||||
frame_state:crate::session::FrameState,
|
frame_state:session::FrameState,
|
||||||
){
|
){
|
||||||
//TODO:use scheduled frame times to create beautiful smoothing simulation physics extrapolation assuming no input
|
//TODO:use scheduled frame times to create beautiful smoothing simulation physics extrapolation assuming no input
|
||||||
|
|
||||||
@@ -943,6 +1100,7 @@ impl GraphicsState{
|
|||||||
rpass.set_bind_group(0,&self.bind_groups.camera,&[]);
|
rpass.set_bind_group(0,&self.bind_groups.camera,&[]);
|
||||||
rpass.set_bind_group(1,&self.bind_groups.skybox_texture,&[]);
|
rpass.set_bind_group(1,&self.bind_groups.skybox_texture,&[]);
|
||||||
|
|
||||||
|
// Draw all models.
|
||||||
rpass.set_pipeline(&self.pipelines.model);
|
rpass.set_pipeline(&self.pipelines.model);
|
||||||
for model in &self.models{
|
for model in &self.models{
|
||||||
rpass.set_bind_group(2,&model.bind_group,&[]);
|
rpass.set_bind_group(2,&model.bind_group,&[]);
|
||||||
@@ -954,6 +1112,19 @@ impl GraphicsState{
|
|||||||
|
|
||||||
rpass.set_pipeline(&self.pipelines.skybox);
|
rpass.set_pipeline(&self.pipelines.skybox);
|
||||||
rpass.draw(0..3,0..1);
|
rpass.draw(0..3,0..1);
|
||||||
|
|
||||||
|
// render a single debug_model in red
|
||||||
|
if let Some(model_id)=frame_state.debug_model{
|
||||||
|
if let Some(model)=self.debug_models.get(model_id.get() as usize){
|
||||||
|
let mesh=&self.debug_meshes[model.debug_mesh_id as usize];
|
||||||
|
rpass.set_pipeline(&self.pipelines.debug);
|
||||||
|
rpass.set_bind_group(1,&model.bind_group,&[]);
|
||||||
|
rpass.set_vertex_buffer(0,mesh.vertex_buf.slice(..));
|
||||||
|
rpass.set_index_buffer(mesh.indices.buf.slice(..),mesh.indices.format);
|
||||||
|
//TODO: loop over triangle strips
|
||||||
|
rpass.draw_indexed(0..mesh.indices.count,0,0..1);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
queue.submit(std::iter::once(encoder.finish()));
|
queue.submit(std::iter::once(encoder.finish()));
|
||||||
@@ -962,21 +1133,23 @@ impl GraphicsState{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
const MODEL_BUFFER_SIZE:usize=4*4 + 12 + 4;//let size=std::mem::size_of::<ModelInstance>();
|
const MODEL_BUFFER_SIZE:usize=4*4 + 12 + 4;//let size=std::mem::size_of::<ModelInstance>();
|
||||||
const MODEL_BUFFER_SIZE_BYTES:usize=MODEL_BUFFER_SIZE*4;
|
const MODEL_BUFFER_SIZE_BYTES:usize=MODEL_BUFFER_SIZE*core::mem::size_of::<f32>();
|
||||||
|
fn get_instance_buffer_data(instance:&GraphicsModelOwned)->[f32;MODEL_BUFFER_SIZE]{
|
||||||
|
let mut out=[0.0;MODEL_BUFFER_SIZE];
|
||||||
|
out[0..16].copy_from_slice(instance.transform.as_ref());
|
||||||
|
out[16..19].copy_from_slice(instance.normal_transform.x_axis.as_ref());
|
||||||
|
// out[20]=0.0;
|
||||||
|
out[20..23].copy_from_slice(instance.normal_transform.y_axis.as_ref());
|
||||||
|
// out[24]=0.0;
|
||||||
|
out[24..27].copy_from_slice(instance.normal_transform.z_axis.as_ref());
|
||||||
|
// out[28]=0.0;
|
||||||
|
out[28..32].copy_from_slice(instance.color.get().as_ref());
|
||||||
|
out
|
||||||
|
}
|
||||||
fn get_instances_buffer_data(instances:&[GraphicsModelOwned])->Vec<f32>{
|
fn get_instances_buffer_data(instances:&[GraphicsModelOwned])->Vec<f32>{
|
||||||
let mut raw=Vec::with_capacity(MODEL_BUFFER_SIZE*instances.len());
|
let mut raw=Vec::with_capacity(MODEL_BUFFER_SIZE*instances.len());
|
||||||
for mi in instances{
|
for mi in instances{
|
||||||
//model transform
|
raw.extend_from_slice(&get_instance_buffer_data(mi));
|
||||||
raw.extend_from_slice(&AsRef::<[f32; 4*4]>::as_ref(&mi.transform)[..]);
|
|
||||||
//normal transform
|
|
||||||
raw.extend_from_slice(AsRef::<[f32; 3]>::as_ref(&mi.normal_transform.x_axis));
|
|
||||||
raw.extend_from_slice(&[0.0]);
|
|
||||||
raw.extend_from_slice(AsRef::<[f32; 3]>::as_ref(&mi.normal_transform.y_axis));
|
|
||||||
raw.extend_from_slice(&[0.0]);
|
|
||||||
raw.extend_from_slice(AsRef::<[f32; 3]>::as_ref(&mi.normal_transform.z_axis));
|
|
||||||
raw.extend_from_slice(&[0.0]);
|
|
||||||
//color
|
|
||||||
raw.extend_from_slice(AsRef::<[f32; 4]>::as_ref(&mi.color.get()));
|
|
||||||
}
|
}
|
||||||
raw
|
raw
|
||||||
}
|
}
|
||||||
2
engine/graphics/src/lib.rs
Normal file
2
engine/graphics/src/lib.rs
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
pub mod model;
|
||||||
|
pub mod graphics;
|
||||||
@@ -8,6 +8,12 @@ pub struct GraphicsVertex{
|
|||||||
pub normal:[f32;3],
|
pub normal:[f32;3],
|
||||||
pub color:[f32;4],
|
pub color:[f32;4],
|
||||||
}
|
}
|
||||||
|
#[derive(Clone,Copy,Pod,Zeroable)]
|
||||||
|
#[repr(C)]
|
||||||
|
pub struct DebugGraphicsVertex{
|
||||||
|
pub pos:[f32;3],
|
||||||
|
pub normal:[f32;3],
|
||||||
|
}
|
||||||
#[derive(Clone,Copy,id::Id)]
|
#[derive(Clone,Copy,id::Id)]
|
||||||
pub struct IndexedGraphicsMeshOwnedRenderConfigId(u32);
|
pub struct IndexedGraphicsMeshOwnedRenderConfigId(u32);
|
||||||
pub struct IndexedGraphicsMeshOwnedRenderConfig{
|
pub struct IndexedGraphicsMeshOwnedRenderConfig{
|
||||||
10
engine/physics/Cargo.toml
Normal file
10
engine/physics/Cargo.toml
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
[package]
|
||||||
|
name = "strafesnet_physics"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2024"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
arrayvec = "0.7.6"
|
||||||
|
glam = "0.30.0"
|
||||||
|
id = { version = "0.1.0", registry = "strafesnet" }
|
||||||
|
strafesnet_common = { path = "../../lib/common", registry = "strafesnet" }
|
||||||
8
engine/physics/LICENSE
Normal file
8
engine/physics/LICENSE
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
/*******************************************************
|
||||||
|
* Copyright (C) 2023-2024 Rhys Lloyd <krakow20@gmail.com>
|
||||||
|
*
|
||||||
|
* This file is part of the StrafesNET bhop/surf client.
|
||||||
|
*
|
||||||
|
* StrafesNET can not be copied and/or distributed
|
||||||
|
* without the express permission of Rhys Lloyd
|
||||||
|
*******************************************************/
|
||||||
@@ -31,6 +31,14 @@ impl<T> Body<T>
|
|||||||
time,
|
time,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
pub const fn relative_to<'a>(&'a self,body0:&'a Body<T>)->VirtualBody<'a,T>{
|
||||||
|
//(p0,v0,a0,t0)
|
||||||
|
//(p1,v1,a1,t1)
|
||||||
|
VirtualBody{
|
||||||
|
body0,
|
||||||
|
body1:self,
|
||||||
|
}
|
||||||
|
}
|
||||||
pub fn extrapolated_position(&self,time:Time<T>)->Planar64Vec3{
|
pub fn extrapolated_position(&self,time:Time<T>)->Planar64Vec3{
|
||||||
let dt=time-self.time;
|
let dt=time-self.time;
|
||||||
self.position
|
self.position
|
||||||
@@ -82,7 +90,7 @@ impl<T> Body<T>
|
|||||||
// a*dt + v
|
// a*dt + v
|
||||||
self.acceleration.map(|elem|(dt*elem).divide().fix())+self.velocity
|
self.acceleration.map(|elem|(dt*elem).divide().fix())+self.velocity
|
||||||
}
|
}
|
||||||
pub fn advance_time_ratio_dt(&mut self,dt:crate::model_physics::GigaTime){
|
pub fn advance_time_ratio_dt(&mut self,dt:crate::model::GigaTime){
|
||||||
self.position=self.extrapolated_position_ratio_dt(dt);
|
self.position=self.extrapolated_position_ratio_dt(dt);
|
||||||
self.velocity=self.extrapolated_velocity_ratio_dt(dt);
|
self.velocity=self.extrapolated_velocity_ratio_dt(dt);
|
||||||
self.time+=dt.into();
|
self.time+=dt.into();
|
||||||
@@ -137,14 +145,6 @@ pub struct VirtualBody<'a,T>{
|
|||||||
impl<T> VirtualBody<'_,T>
|
impl<T> VirtualBody<'_,T>
|
||||||
where Time<T>:Copy,
|
where Time<T>:Copy,
|
||||||
{
|
{
|
||||||
pub const fn relative<'a>(body0:&'a Body<T>,body1:&'a Body<T>)->VirtualBody<'a,T>{
|
|
||||||
//(p0,v0,a0,t0)
|
|
||||||
//(p1,v1,a1,t1)
|
|
||||||
VirtualBody{
|
|
||||||
body0,
|
|
||||||
body1,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub fn extrapolated_position(&self,time:Time<T>)->Planar64Vec3{
|
pub fn extrapolated_position(&self,time:Time<T>)->Planar64Vec3{
|
||||||
self.body1.extrapolated_position(time)-self.body0.extrapolated_position(time)
|
self.body1.extrapolated_position(time)-self.body0.extrapolated_position(time)
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
use crate::model_physics::{GigaTime,FEV,MeshQuery,DirectedEdge};
|
use crate::model::{GigaTime,FEV,MeshQuery,DirectedEdge};
|
||||||
use strafesnet_common::integer::{Fixed,Ratio,vec3::Vector3};
|
use strafesnet_common::integer::{Fixed,Ratio,vec3::Vector3};
|
||||||
use crate::physics::{Time,Body};
|
use crate::physics::{Time,Body};
|
||||||
|
|
||||||
@@ -12,6 +12,20 @@ pub enum CrawlResult<M:MeshQuery>{
|
|||||||
Miss(FEV<M>),
|
Miss(FEV<M>),
|
||||||
Hit(M::Face,GigaTime),
|
Hit(M::Face,GigaTime),
|
||||||
}
|
}
|
||||||
|
impl<M:MeshQuery> CrawlResult<M>{
|
||||||
|
pub fn hit(self)->Option<(M::Face,GigaTime)>{
|
||||||
|
match self{
|
||||||
|
CrawlResult::Miss(_)=>None,
|
||||||
|
CrawlResult::Hit(face,time)=>Some((face,time)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn miss(self)->Option<FEV<M>>{
|
||||||
|
match self{
|
||||||
|
CrawlResult::Miss(fev)=>Some(fev),
|
||||||
|
CrawlResult::Hit(_,_)=>None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<F:Copy,M:MeshQuery<Normal=Vector3<F>,Offset=Fixed<4,128>>> FEV<M>
|
impl<F:Copy,M:MeshQuery<Normal=Vector3<F>,Offset=Fixed<4,128>>> FEV<M>
|
||||||
where
|
where
|
||||||
45
engine/physics/src/lib.rs
Normal file
45
engine/physics/src/lib.rs
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
mod body;
|
||||||
|
mod push_solve;
|
||||||
|
mod face_crawler;
|
||||||
|
mod model;
|
||||||
|
|
||||||
|
pub mod physics;
|
||||||
|
|
||||||
|
// Physics bug fixes can easily desync all bots.
|
||||||
|
//
|
||||||
|
// When replaying a bot, use the exact physics version which it was recorded with.
|
||||||
|
//
|
||||||
|
// When validating a new bot, ignore the version and use the latest version,
|
||||||
|
// and overwrite the version in the file.
|
||||||
|
//
|
||||||
|
// Compatible physics versions should be determined
|
||||||
|
// empirically at development time via leaderboard resimulation.
|
||||||
|
//
|
||||||
|
// Compatible physics versions should result in an identical leaderboard state,
|
||||||
|
// or the only bots which fail are ones exploiting a surgically patched bug.
|
||||||
|
#[derive(Clone,Copy,Hash,Debug,id::Id,Eq,PartialEq,Ord,PartialOrd)]
|
||||||
|
pub struct PhysicsVersion(u32);
|
||||||
|
pub const VERSION:PhysicsVersion=PhysicsVersion(0);
|
||||||
|
const LATEST_COMPATIBLE_VERSION:[u32;1+VERSION.0 as usize]=const{
|
||||||
|
let compat=[0];
|
||||||
|
|
||||||
|
let mut input_version=0;
|
||||||
|
while input_version<compat.len(){
|
||||||
|
// compatible version must be greater than or equal to the input version
|
||||||
|
assert!(input_version as u32<=compat[input_version]);
|
||||||
|
// compatible version must be a version that exists
|
||||||
|
assert!(compat[input_version]<=VERSION.0);
|
||||||
|
input_version+=1;
|
||||||
|
}
|
||||||
|
compat
|
||||||
|
};
|
||||||
|
pub enum PhysicsVersionError{
|
||||||
|
UnknownPhysicsVersion,
|
||||||
|
}
|
||||||
|
pub const fn get_latest_compatible_version(PhysicsVersion(version):PhysicsVersion)->Result<PhysicsVersion,PhysicsVersionError>{
|
||||||
|
if (version as usize)<LATEST_COMPATIBLE_VERSION.len(){
|
||||||
|
Ok(PhysicsVersion(LATEST_COMPATIBLE_VERSION[version as usize]))
|
||||||
|
}else{
|
||||||
|
Err(PhysicsVersionError::UnknownPhysicsVersion)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
use std::borrow::{Borrow,Cow};
|
use std::borrow::{Borrow,Cow};
|
||||||
use std::collections::{HashSet,HashMap};
|
use std::collections::{HashSet,HashMap};
|
||||||
|
use core::ops::Range;
|
||||||
use strafesnet_common::integer::vec3::Vector3;
|
use strafesnet_common::integer::vec3::Vector3;
|
||||||
use strafesnet_common::model::{self,MeshId,PolygonIter};
|
use strafesnet_common::model::{self,MeshId,PolygonIter};
|
||||||
use strafesnet_common::integer::{self,vec3,Fixed,Planar64,Planar64Vec3,Ratio};
|
use strafesnet_common::integer::{self,vec3,Fixed,Planar64,Planar64Vec3,Ratio};
|
||||||
@@ -483,12 +484,15 @@ impl TransformedMesh<'_>{
|
|||||||
pub fn verts<'a>(&'a self)->impl Iterator<Item=vec3::Vector3<Fixed<2,64>>>+'a{
|
pub fn verts<'a>(&'a self)->impl Iterator<Item=vec3::Vector3<Fixed<2,64>>>+'a{
|
||||||
self.view.data.verts.iter().map(|&Vert(pos)|self.transform.vertex.transform_point3(pos))
|
self.view.data.verts.iter().map(|&Vert(pos)|self.transform.vertex.transform_point3(pos))
|
||||||
}
|
}
|
||||||
|
pub fn faces(&self)->impl Iterator<Item=SubmeshFaceId>{
|
||||||
|
(0..self.view.topology.faces.len() as u32).map(SubmeshFaceId::new)
|
||||||
|
}
|
||||||
fn farthest_vert(&self,dir:Planar64Vec3)->SubmeshVertId{
|
fn farthest_vert(&self,dir:Planar64Vec3)->SubmeshVertId{
|
||||||
//this happens to be well-defined. there are no virtual virtices
|
//this happens to be well-defined. there are no virtual virtices
|
||||||
SubmeshVertId::new(
|
SubmeshVertId::new(
|
||||||
self.view.topology.verts.iter()
|
self.view.topology.verts.iter()
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.max_by_key(|(_,&vert_id)|
|
.max_by_key(|&(_,&vert_id)|
|
||||||
dir.dot(self.transform.vertex.transform_point3(self.view.data.verts[vert_id.get() as usize].0))
|
dir.dot(self.transform.vertex.transform_point3(self.view.data.verts[vert_id.get() as usize].0))
|
||||||
)
|
)
|
||||||
//assume there is more than zero vertices.
|
//assume there is more than zero vertices.
|
||||||
@@ -718,48 +722,40 @@ impl MinkowskiMesh<'_>{
|
|||||||
//
|
//
|
||||||
// Most of the calculation time is just calculating the starting point
|
// Most of the calculation time is just calculating the starting point
|
||||||
// for the "actual" crawling algorithm below (predict_collision_{in|out}).
|
// for the "actual" crawling algorithm below (predict_collision_{in|out}).
|
||||||
fn closest_fev_not_inside(&self,mut infinity_body:Body)->Option<FEV<MinkowskiMesh>>{
|
fn closest_fev_not_inside(&self,mut infinity_body:Body,start_time:Time)->Option<FEV<MinkowskiMesh>>{
|
||||||
infinity_body.infinity_dir().map_or(None,|dir|{
|
infinity_body.infinity_dir().and_then(|dir|{
|
||||||
let infinity_fev=self.infinity_fev(-dir,infinity_body.position);
|
let infinity_fev=self.infinity_fev(-dir,infinity_body.position);
|
||||||
//a line is simpler to solve than a parabola
|
//a line is simpler to solve than a parabola
|
||||||
infinity_body.velocity=dir;
|
infinity_body.velocity=dir;
|
||||||
infinity_body.acceleration=vec3::ZERO;
|
infinity_body.acceleration=vec3::ZERO;
|
||||||
//crawl in from negative infinity along a tangent line to get the closest fev
|
//crawl in from negative infinity along a tangent line to get the closest fev
|
||||||
// TODO: change crawl_fev args to delta time? Optional values?
|
// TODO: change crawl_fev args to delta time? Optional values?
|
||||||
match infinity_fev.crawl(self,&infinity_body,Time::MIN/4,infinity_body.time){
|
infinity_fev.crawl(self,&infinity_body,Time::MIN/4,start_time).miss()
|
||||||
crate::face_crawler::CrawlResult::Miss(fev)=>Some(fev),
|
|
||||||
crate::face_crawler::CrawlResult::Hit(_,_)=>None,
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
pub fn predict_collision_in(&self,relative_body:&Body,time_limit:Time)->Option<(MinkowskiFace,GigaTime)>{
|
pub fn predict_collision_in(&self,relative_body:&Body,Range{start:start_time,end:time_limit}:Range<Time>)->Option<(MinkowskiFace,GigaTime)>{
|
||||||
self.closest_fev_not_inside(relative_body.clone()).map_or(None,|fev|{
|
self.closest_fev_not_inside(relative_body.clone(),start_time).and_then(|fev|{
|
||||||
//continue forwards along the body parabola
|
//continue forwards along the body parabola
|
||||||
match fev.crawl(self,relative_body,relative_body.time,time_limit){
|
fev.crawl(self,relative_body,start_time,time_limit).hit()
|
||||||
crate::face_crawler::CrawlResult::Miss(_)=>None,
|
|
||||||
crate::face_crawler::CrawlResult::Hit(face,time)=>Some((face,time)),
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
pub fn predict_collision_out(&self,relative_body:&Body,time_limit:Time)->Option<(MinkowskiFace,GigaTime)>{
|
pub fn predict_collision_out(&self,relative_body:&Body,Range{start:start_time,end:time_limit}:Range<Time>)->Option<(MinkowskiFace,GigaTime)>{
|
||||||
//create an extrapolated body at time_limit
|
//create an extrapolated body at time_limit
|
||||||
let infinity_body=Body::new(
|
let infinity_body=-relative_body.clone();
|
||||||
relative_body.extrapolated_position(time_limit),
|
self.closest_fev_not_inside(infinity_body,-time_limit).and_then(|fev|{
|
||||||
-relative_body.extrapolated_velocity(time_limit),
|
|
||||||
relative_body.acceleration,
|
|
||||||
-time_limit,
|
|
||||||
);
|
|
||||||
self.closest_fev_not_inside(infinity_body).map_or(None,|fev|{
|
|
||||||
//continue backwards along the body parabola
|
//continue backwards along the body parabola
|
||||||
match fev.crawl(self,&-relative_body.clone(),-time_limit,-relative_body.time){
|
fev.crawl(self,&infinity_body,-time_limit,-start_time).hit()
|
||||||
crate::face_crawler::CrawlResult::Miss(_)=>None,
|
//no need to test -time<time_limit because of the first step
|
||||||
crate::face_crawler::CrawlResult::Hit(face,time)=>Some((face,-time)),//no need to test -time<time_limit because of the first step
|
.map(|(face,time)|(face,-time))
|
||||||
}
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
pub fn predict_collision_face_out(&self,relative_body:&Body,time_limit:Time,contact_face_id:MinkowskiFace)->Option<(MinkowskiEdge,GigaTime)>{
|
pub fn predict_collision_face_out(&self,relative_body:&Body,Range{start:start_time,end:time_limit}:Range<Time>,contact_face_id:MinkowskiFace)->Option<(MinkowskiEdge,GigaTime)>{
|
||||||
//no algorithm needed, there is only one state and two cases (Edge,None)
|
//no algorithm needed, there is only one state and two cases (Edge,None)
|
||||||
//determine when it passes an edge ("sliding off" case)
|
//determine when it passes an edge ("sliding off" case)
|
||||||
|
let start_time={
|
||||||
|
let r=(start_time-relative_body.time).to_ratio();
|
||||||
|
Ratio::new(r.num,r.den)
|
||||||
|
};
|
||||||
let mut best_time={
|
let mut best_time={
|
||||||
let r=(time_limit-relative_body.time).to_ratio();
|
let r=(time_limit-relative_body.time).to_ratio();
|
||||||
Ratio::new(r.num.fix_4(),r.den.fix_4())
|
Ratio::new(r.num.fix_4(),r.den.fix_4())
|
||||||
@@ -775,7 +771,7 @@ impl MinkowskiMesh<'_>{
|
|||||||
//WARNING! d outside of *2
|
//WARNING! d outside of *2
|
||||||
//WARNING: truncated precision
|
//WARNING: truncated precision
|
||||||
for dt in Fixed::<4,128>::zeroes2(((n.dot(relative_body.position))*2-d).fix_4(),n.dot(relative_body.velocity).fix_4()*2,n.dot(relative_body.acceleration).fix_4()){
|
for dt in Fixed::<4,128>::zeroes2(((n.dot(relative_body.position))*2-d).fix_4(),n.dot(relative_body.velocity).fix_4()*2,n.dot(relative_body.acceleration).fix_4()){
|
||||||
if Ratio::new(Planar64::ZERO,Planar64::EPSILON).le_ratio(dt)&&dt.lt_ratio(best_time)&&n.dot(relative_body.extrapolated_velocity_ratio_dt(dt)).is_negative(){
|
if start_time.le_ratio(dt)&&dt.lt_ratio(best_time)&&n.dot(relative_body.extrapolated_velocity_ratio_dt(dt)).is_negative(){
|
||||||
best_time=dt;
|
best_time=dt;
|
||||||
best_edge=Some(directed_edge_id);
|
best_edge=Some(directed_edge_id);
|
||||||
break;
|
break;
|
||||||
@@ -786,10 +782,7 @@ impl MinkowskiMesh<'_>{
|
|||||||
}
|
}
|
||||||
fn infinity_in(&self,infinity_body:Body)->Option<(MinkowskiFace,GigaTime)>{
|
fn infinity_in(&self,infinity_body:Body)->Option<(MinkowskiFace,GigaTime)>{
|
||||||
let infinity_fev=self.infinity_fev(-infinity_body.velocity,infinity_body.position);
|
let infinity_fev=self.infinity_fev(-infinity_body.velocity,infinity_body.position);
|
||||||
match infinity_fev.crawl(self,&infinity_body,Time::MIN/4,infinity_body.time){
|
infinity_fev.crawl(self,&infinity_body,Time::MIN/4,infinity_body.time).hit()
|
||||||
crate::face_crawler::CrawlResult::Miss(_)=>None,
|
|
||||||
crate::face_crawler::CrawlResult::Hit(face,time)=>Some((face,time)),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
pub fn is_point_in_mesh(&self,point:Planar64Vec3)->bool{
|
pub fn is_point_in_mesh(&self,point:Planar64Vec3)->bool{
|
||||||
let infinity_body=Body::new(point,vec3::Y,vec3::ZERO,Time::ZERO);
|
let infinity_body=Body::new(point,vec3::Y,vec3::ZERO,Time::ZERO);
|
||||||
@@ -995,7 +988,7 @@ fn is_empty_volume(normals:Vec<Vector3<Fixed<3,96>>>)->bool{
|
|||||||
for k in 0..len{
|
for k in 0..len{
|
||||||
if k!=i&&k!=j{
|
if k!=i&&k!=j{
|
||||||
let d=n.dot(normals[k]).is_negative();
|
let d=n.dot(normals[k]).is_negative();
|
||||||
if let Some(comp)=&d_comp{
|
if let &Some(comp)=&d_comp{
|
||||||
// This is testing if d_comp*d < 0
|
// This is testing if d_comp*d < 0
|
||||||
if comp^d{
|
if comp^d{
|
||||||
return true;
|
return true;
|
||||||
@@ -1015,9 +1008,3 @@ fn test_is_empty_volume(){
|
|||||||
assert!(!is_empty_volume([vec3::X.fix_3(),vec3::Y.fix_3(),vec3::Z.fix_3()].to_vec()));
|
assert!(!is_empty_volume([vec3::X.fix_3(),vec3::Y.fix_3(),vec3::Z.fix_3()].to_vec()));
|
||||||
assert!(is_empty_volume([vec3::X.fix_3(),vec3::Y.fix_3(),vec3::Z.fix_3(),vec3::NEG_X.fix_3()].to_vec()));
|
assert!(is_empty_volume([vec3::X.fix_3(),vec3::Y.fix_3(),vec3::Z.fix_3(),vec3::NEG_X.fix_3()].to_vec()));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn build_me_a_cube(){
|
|
||||||
let mesh=PhysicsMesh::unit_cube();
|
|
||||||
//println!("mesh={:?}",mesh);
|
|
||||||
}
|
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
use std::collections::{HashMap,HashSet};
|
use std::collections::{HashMap,HashSet};
|
||||||
use crate::model_physics::{self,PhysicsMesh,PhysicsMeshTransform,TransformedMesh,MeshQuery,PhysicsMeshId,PhysicsSubmeshId};
|
use crate::model::DirectedEdge;
|
||||||
|
use crate::model::{self as model_physics,PhysicsMesh,PhysicsMeshTransform,TransformedMesh,MeshQuery,PhysicsMeshId,PhysicsSubmeshId};
|
||||||
use strafesnet_common::bvh;
|
use strafesnet_common::bvh;
|
||||||
use strafesnet_common::map;
|
use strafesnet_common::map;
|
||||||
use strafesnet_common::run;
|
use strafesnet_common::run;
|
||||||
@@ -32,7 +33,7 @@ pub enum InternalInstruction{
|
|||||||
// Water,
|
// Water,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone,Debug,Default)]
|
#[derive(Clone,Debug)]
|
||||||
pub struct InputState{
|
pub struct InputState{
|
||||||
mouse:MouseState,
|
mouse:MouseState,
|
||||||
next_mouse:MouseState,
|
next_mouse:MouseState,
|
||||||
@@ -40,10 +41,15 @@ pub struct InputState{
|
|||||||
}
|
}
|
||||||
impl InputState{
|
impl InputState{
|
||||||
fn set_next_mouse(&mut self,next_mouse:MouseState){
|
fn set_next_mouse(&mut self,next_mouse:MouseState){
|
||||||
|
// would this be correct?
|
||||||
|
// if self.next_mouse.time==next_mouse.time{
|
||||||
|
// self.next_mouse=next_mouse;
|
||||||
|
// }else{
|
||||||
//I like your functions magic language
|
//I like your functions magic language
|
||||||
self.mouse=std::mem::replace(&mut self.next_mouse,next_mouse);
|
self.mouse=std::mem::replace(&mut self.next_mouse,next_mouse);
|
||||||
//equivalently:
|
//equivalently:
|
||||||
//(self.next_mouse,self.mouse)=(next_mouse,self.next_mouse.clone());
|
//(self.next_mouse,self.mouse)=(next_mouse,self.next_mouse.clone());
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
fn replace_mouse(&mut self,mouse:MouseState,next_mouse:MouseState){
|
fn replace_mouse(&mut self,mouse:MouseState,next_mouse:MouseState){
|
||||||
(self.next_mouse,self.mouse)=(next_mouse,mouse);
|
(self.next_mouse,self.mouse)=(next_mouse,mouse);
|
||||||
@@ -65,6 +71,15 @@ impl InputState{
|
|||||||
((dm*t)/dt).as_ivec2()
|
((dm*t)/dt).as_ivec2()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
impl Default for InputState{
|
||||||
|
fn default()->Self{
|
||||||
|
Self{
|
||||||
|
mouse:MouseState{pos:Default::default(),time:Time::ZERO-Time::EPSILON*2},
|
||||||
|
next_mouse:MouseState{pos:Default::default(),time:Time::ZERO-Time::EPSILON},
|
||||||
|
controls:Default::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
#[derive(Clone,Debug)]
|
#[derive(Clone,Debug)]
|
||||||
enum JumpDirection{
|
enum JumpDirection{
|
||||||
Exactly(Planar64Vec3),
|
Exactly(Planar64Vec3),
|
||||||
@@ -147,17 +162,17 @@ fn ground_things(walk_settings:&gameplay_style::WalkSettings,contact:&ContactCol
|
|||||||
let normal=contact_normal(models,hitbox_mesh,contact);
|
let normal=contact_normal(models,hitbox_mesh,contact);
|
||||||
let gravity=touching.base_acceleration(models,style,camera,input_state);
|
let gravity=touching.base_acceleration(models,style,camera,input_state);
|
||||||
let control_dir=style.get_y_control_dir(camera,input_state.controls);
|
let control_dir=style.get_y_control_dir(camera,input_state.controls);
|
||||||
let mut target_velocity=walk_settings.get_walk_target_velocity(control_dir,normal);
|
let target_velocity=walk_settings.get_walk_target_velocity(control_dir,normal);
|
||||||
touching.constrain_velocity(models,hitbox_mesh,&mut target_velocity);
|
let target_velocity_clipped=touching.constrain_velocity(models,hitbox_mesh,target_velocity);
|
||||||
(gravity,target_velocity)
|
(gravity,target_velocity_clipped)
|
||||||
}
|
}
|
||||||
fn ladder_things(ladder_settings:&gameplay_style::LadderSettings,contact:&ContactCollision,touching:&TouchingState,models:&PhysicsModels,hitbox_mesh:&HitboxMesh,style:&StyleModifiers,camera:&PhysicsCamera,input_state:&InputState)->(Planar64Vec3,Planar64Vec3){
|
fn ladder_things(ladder_settings:&gameplay_style::LadderSettings,contact:&ContactCollision,touching:&TouchingState,models:&PhysicsModels,hitbox_mesh:&HitboxMesh,style:&StyleModifiers,camera:&PhysicsCamera,input_state:&InputState)->(Planar64Vec3,Planar64Vec3){
|
||||||
let normal=contact_normal(models,hitbox_mesh,contact);
|
let normal=contact_normal(models,hitbox_mesh,contact);
|
||||||
let gravity=touching.base_acceleration(models,style,camera,input_state);
|
let gravity=touching.base_acceleration(models,style,camera,input_state);
|
||||||
let control_dir=style.get_y_control_dir(camera,input_state.controls);
|
let control_dir=style.get_y_control_dir(camera,input_state.controls);
|
||||||
let mut target_velocity=ladder_settings.get_ladder_target_velocity(control_dir,normal);
|
let target_velocity=ladder_settings.get_ladder_target_velocity(control_dir,normal);
|
||||||
touching.constrain_velocity(models,hitbox_mesh,&mut target_velocity);
|
let target_velocity_clipped=touching.constrain_velocity(models,hitbox_mesh,target_velocity);
|
||||||
(gravity,target_velocity)
|
(gravity,target_velocity_clipped)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
@@ -215,12 +230,6 @@ impl PhysicsModels{
|
|||||||
.map(|model|&model.transform),
|
.map(|model|&model.transform),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn contact_model(&self,model_id:ContactModelId)->&ContactModel{
|
|
||||||
&self.contact_models[&model_id]
|
|
||||||
}
|
|
||||||
fn intersect_model(&self,model_id:IntersectModelId)->&IntersectModel{
|
|
||||||
&self.intersect_models[&model_id]
|
|
||||||
}
|
|
||||||
fn contact_attr(&self,model_id:ContactModelId)->&gameplay_attributes::ContactAttributes{
|
fn contact_attr(&self,model_id:ContactModelId)->&gameplay_attributes::ContactAttributes{
|
||||||
&self.contact_attributes[&self.contact_models[&model_id].attr_id]
|
&self.contact_attributes[&self.contact_models[&model_id].attr_id]
|
||||||
}
|
}
|
||||||
@@ -255,7 +264,7 @@ impl PhysicsCamera{
|
|||||||
);
|
);
|
||||||
self.clamped_mouse_pos=unclamped_mouse_pos;
|
self.clamped_mouse_pos=unclamped_mouse_pos;
|
||||||
}
|
}
|
||||||
pub fn simulate_move_angles(&self,mouse_delta:glam::IVec2)->glam::Vec2 {
|
pub fn simulate_move_angles(&self,mouse_delta:glam::IVec2)->glam::Vec2{
|
||||||
let a=-self.sensitivity.mul_int((self.clamped_mouse_pos+mouse_delta).as_i64vec2());
|
let a=-self.sensitivity.mul_int((self.clamped_mouse_pos+mouse_delta).as_i64vec2());
|
||||||
let ax=Angle32::wrap_from_i64(a.x);
|
let ax=Angle32::wrap_from_i64(a.x);
|
||||||
let ay=Angle32::clamp_from_i64(a.y)
|
let ay=Angle32::clamp_from_i64(a.y)
|
||||||
@@ -272,7 +281,8 @@ impl PhysicsCamera{
|
|||||||
.clamp(Self::ANGLE_PITCH_LOWER_LIMIT,Self::ANGLE_PITCH_UPPER_LIMIT);
|
.clamp(Self::ANGLE_PITCH_LOWER_LIMIT,Self::ANGLE_PITCH_UPPER_LIMIT);
|
||||||
mat3::from_rotation_yx(ax,ay)
|
mat3::from_rotation_yx(ax,ay)
|
||||||
}
|
}
|
||||||
fn rotation(&self)->Planar64Mat3{
|
#[inline]
|
||||||
|
pub fn rotation(&self)->Planar64Mat3{
|
||||||
self.get_rotation(self.clamped_mouse_pos)
|
self.get_rotation(self.clamped_mouse_pos)
|
||||||
}
|
}
|
||||||
fn simulate_move_rotation(&self,mouse_delta:glam::IVec2)->Planar64Mat3{
|
fn simulate_move_rotation(&self,mouse_delta:glam::IVec2)->Planar64Mat3{
|
||||||
@@ -548,7 +558,7 @@ impl MoveState{
|
|||||||
=>None,
|
=>None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn next_move_instruction(&self,strafe:&Option<gameplay_style::StrafeSettings>,time:Time)->Option<TimedInstruction<InternalInstruction,TimeInner>>{
|
fn next_move_instruction(&self,strafe:&Option<gameplay_style::StrafeSettings>,time:Time)->Option<TimedInstruction<InternalInstruction,Time>>{
|
||||||
//check if you have a valid walk state and create an instruction
|
//check if you have a valid walk state and create an instruction
|
||||||
match self{
|
match self{
|
||||||
MoveState::Walk(walk_state)|MoveState::Ladder(walk_state)=>match &walk_state.target{
|
MoveState::Walk(walk_state)|MoveState::Ladder(walk_state)=>match &walk_state.target{
|
||||||
@@ -754,8 +764,8 @@ impl TouchingState{
|
|||||||
//TODO: add water
|
//TODO: add water
|
||||||
a
|
a
|
||||||
}
|
}
|
||||||
fn constrain_velocity(&self,models:&PhysicsModels,hitbox_mesh:&HitboxMesh,velocity:&mut Planar64Vec3){
|
fn constrain_velocity(&self,models:&PhysicsModels,hitbox_mesh:&HitboxMesh,velocity:Planar64Vec3)->Planar64Vec3{
|
||||||
let contacts=self.contacts.iter().map(|contact|{
|
let contacts:Vec<_>=self.contacts.iter().map(|contact|{
|
||||||
let n=contact_normal(models,hitbox_mesh,contact);
|
let n=contact_normal(models,hitbox_mesh,contact);
|
||||||
crate::push_solve::Contact{
|
crate::push_solve::Contact{
|
||||||
position:vec3::ZERO,
|
position:vec3::ZERO,
|
||||||
@@ -763,10 +773,10 @@ impl TouchingState{
|
|||||||
normal:n,
|
normal:n,
|
||||||
}
|
}
|
||||||
}).collect();
|
}).collect();
|
||||||
*velocity=crate::push_solve::push_solve(&contacts,*velocity);
|
crate::push_solve::push_solve(&contacts,velocity)
|
||||||
}
|
}
|
||||||
fn constrain_acceleration(&self,models:&PhysicsModels,hitbox_mesh:&HitboxMesh,acceleration:&mut Planar64Vec3){
|
fn constrain_acceleration(&self,models:&PhysicsModels,hitbox_mesh:&HitboxMesh,acceleration:Planar64Vec3)->Planar64Vec3{
|
||||||
let contacts=self.contacts.iter().map(|contact|{
|
let contacts:Vec<_>=self.contacts.iter().map(|contact|{
|
||||||
let n=contact_normal(models,hitbox_mesh,contact);
|
let n=contact_normal(models,hitbox_mesh,contact);
|
||||||
crate::push_solve::Contact{
|
crate::push_solve::Contact{
|
||||||
position:vec3::ZERO,
|
position:vec3::ZERO,
|
||||||
@@ -774,15 +784,16 @@ impl TouchingState{
|
|||||||
normal:n,
|
normal:n,
|
||||||
}
|
}
|
||||||
}).collect();
|
}).collect();
|
||||||
*acceleration=crate::push_solve::push_solve(&contacts,*acceleration);
|
crate::push_solve::push_solve(&contacts,acceleration)
|
||||||
}
|
}
|
||||||
fn predict_collision_end(&self,collector:&mut instruction::InstructionCollector<InternalInstruction,TimeInner>,models:&PhysicsModels,hitbox_mesh:&HitboxMesh,body:&Body,time:Time){
|
fn predict_collision_end(&self,collector:&mut instruction::InstructionCollector<InternalInstruction,Time>,models:&PhysicsModels,hitbox_mesh:&HitboxMesh,body:&Body,start_time:Time){
|
||||||
let relative_body=crate::body::VirtualBody::relative(&Body::ZERO,body).body(time);
|
// let relative_body=body.relative_to(&Body::ZERO);
|
||||||
|
let relative_body=body;
|
||||||
for contact in &self.contacts{
|
for contact in &self.contacts{
|
||||||
//detect face slide off
|
//detect face slide off
|
||||||
let model_mesh=models.contact_mesh(contact);
|
let model_mesh=models.contact_mesh(contact);
|
||||||
let minkowski=model_physics::MinkowskiMesh::minkowski_sum(model_mesh,hitbox_mesh.transformed_mesh());
|
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)|{
|
collector.collect(minkowski.predict_collision_face_out(&relative_body,start_time..collector.time(),contact.face_id).map(|(_face,time)|{
|
||||||
TimedInstruction{
|
TimedInstruction{
|
||||||
time:relative_body.time+time.into(),
|
time:relative_body.time+time.into(),
|
||||||
instruction:InternalInstruction::CollisionEnd(
|
instruction:InternalInstruction::CollisionEnd(
|
||||||
@@ -796,7 +807,7 @@ impl TouchingState{
|
|||||||
//detect model collision in reverse
|
//detect model collision in reverse
|
||||||
let model_mesh=models.intersect_mesh(intersect);
|
let model_mesh=models.intersect_mesh(intersect);
|
||||||
let minkowski=model_physics::MinkowskiMesh::minkowski_sum(model_mesh,hitbox_mesh.transformed_mesh());
|
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)|{
|
collector.collect(minkowski.predict_collision_out(&relative_body,start_time..collector.time()).map(|(_face,time)|{
|
||||||
TimedInstruction{
|
TimedInstruction{
|
||||||
time:relative_body.time+time.into(),
|
time:relative_body.time+time.into(),
|
||||||
instruction:InternalInstruction::CollisionEnd(
|
instruction:InternalInstruction::CollisionEnd(
|
||||||
@@ -860,13 +871,16 @@ impl PhysicsState{
|
|||||||
pub const fn mode(&self)->gameplay_modes::ModeId{
|
pub const fn mode(&self)->gameplay_modes::ModeId{
|
||||||
self.mode_state.get_mode_id()
|
self.mode_state.get_mode_id()
|
||||||
}
|
}
|
||||||
|
pub fn get_finish_time(&self)->Option<run::Time>{
|
||||||
|
self.run.get_finish_time()
|
||||||
|
}
|
||||||
pub fn clear(&mut self){
|
pub fn clear(&mut self){
|
||||||
self.touching.clear();
|
self.touching.clear();
|
||||||
}
|
}
|
||||||
fn reset_to_default(&mut self){
|
fn reset_to_default(&mut self){
|
||||||
*self=Self::default();
|
*self=Self::default();
|
||||||
}
|
}
|
||||||
fn next_move_instruction(&self)->Option<TimedInstruction<InternalInstruction,TimeInner>>{
|
fn next_move_instruction(&self)->Option<TimedInstruction<InternalInstruction,Time>>{
|
||||||
self.move_state.next_move_instruction(&self.style.strafe,self.time)
|
self.move_state.next_move_instruction(&self.style.strafe,self.time)
|
||||||
}
|
}
|
||||||
fn cull_velocity(&mut self,data:&PhysicsData,velocity:Planar64Vec3){
|
fn cull_velocity(&mut self,data:&PhysicsData,velocity:Planar64Vec3){
|
||||||
@@ -919,14 +933,16 @@ pub struct PhysicsData{
|
|||||||
modes:gameplay_modes::Modes,
|
modes:gameplay_modes::Modes,
|
||||||
//cached calculations
|
//cached calculations
|
||||||
hitbox_mesh:HitboxMesh,
|
hitbox_mesh:HitboxMesh,
|
||||||
|
pub le_models:Vec<strafesnet_common::model::Model>,
|
||||||
}
|
}
|
||||||
impl Default for PhysicsData{
|
impl Default for PhysicsData{
|
||||||
fn default()->Self{
|
fn default()->Self{
|
||||||
Self{
|
Self{
|
||||||
bvh:bvh::BvhNode::default(),
|
bvh:bvh::BvhNode::empty(),
|
||||||
models:Default::default(),
|
models:Default::default(),
|
||||||
modes:Default::default(),
|
modes:Default::default(),
|
||||||
hitbox_mesh:StyleModifiers::default().calculate_mesh(),
|
hitbox_mesh:StyleModifiers::default().calculate_mesh(),
|
||||||
|
le_models:Default::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -938,21 +954,21 @@ pub struct PhysicsContext<'a>{
|
|||||||
// the physics consumes both Instruction and PhysicsInternalInstruction,
|
// the physics consumes both Instruction and PhysicsInternalInstruction,
|
||||||
// but can only emit PhysicsInternalInstruction
|
// but can only emit PhysicsInternalInstruction
|
||||||
impl InstructionConsumer<InternalInstruction> for PhysicsContext<'_>{
|
impl InstructionConsumer<InternalInstruction> for PhysicsContext<'_>{
|
||||||
type TimeInner=TimeInner;
|
type Time=Time;
|
||||||
fn process_instruction(&mut self,ins:TimedInstruction<InternalInstruction,TimeInner>){
|
fn process_instruction(&mut self,ins:TimedInstruction<InternalInstruction,Time>){
|
||||||
atomic_internal_instruction(&mut self.state,&self.data,ins)
|
atomic_internal_instruction(&mut self.state,&self.data,ins)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl InstructionConsumer<Instruction> for PhysicsContext<'_>{
|
impl InstructionConsumer<Instruction> for PhysicsContext<'_>{
|
||||||
type TimeInner=TimeInner;
|
type Time=Time;
|
||||||
fn process_instruction(&mut self,ins:TimedInstruction<Instruction,TimeInner>){
|
fn process_instruction(&mut self,ins:TimedInstruction<Instruction,Time>){
|
||||||
atomic_input_instruction(&mut self.state,&self.data,ins)
|
atomic_input_instruction(&mut self.state,&self.data,ins)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl InstructionEmitter<InternalInstruction> for PhysicsContext<'_>{
|
impl InstructionEmitter<InternalInstruction> for PhysicsContext<'_>{
|
||||||
type TimeInner=TimeInner;
|
type Time=Time;
|
||||||
//this little next instruction function could cache its return value and invalidate the cached value by watching the State.
|
//this little next instruction function could cache its return value and invalidate the cached value by watching the State.
|
||||||
fn next_instruction(&self,time_limit:Time)->Option<TimedInstruction<InternalInstruction,TimeInner>>{
|
fn next_instruction(&self,time_limit:Time)->Option<TimedInstruction<InternalInstruction,Time>>{
|
||||||
next_instruction_internal(&self.state,&self.data,time_limit)
|
next_instruction_internal(&self.state,&self.data,time_limit)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -960,7 +976,7 @@ impl PhysicsContext<'_>{
|
|||||||
pub fn run_input_instruction(
|
pub fn run_input_instruction(
|
||||||
state:&mut PhysicsState,
|
state:&mut PhysicsState,
|
||||||
data:&PhysicsData,
|
data:&PhysicsData,
|
||||||
instruction:TimedInstruction<Instruction,TimeInner>
|
instruction:TimedInstruction<Instruction,Time>
|
||||||
){
|
){
|
||||||
let mut context=PhysicsContext{state,data};
|
let mut context=PhysicsContext{state,data};
|
||||||
context.process_exhaustive(instruction.time);
|
context.process_exhaustive(instruction.time);
|
||||||
@@ -968,12 +984,37 @@ impl PhysicsContext<'_>{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl PhysicsData{
|
impl PhysicsData{
|
||||||
|
pub fn trace_ray(&self,ray:strafesnet_common::ray::Ray)->Option<ModelId>{
|
||||||
|
let (_time,convex_mesh_id)=self.bvh.sample_ray(&ray,Time::ZERO,Time::MAX/4,|&model,ray|{
|
||||||
|
let mesh=self.models.mesh(model);
|
||||||
|
// brute force trace every face
|
||||||
|
mesh.faces().filter_map(|face_id|{
|
||||||
|
let (n,d)=mesh.face_nd(face_id);
|
||||||
|
// trace ray onto face
|
||||||
|
// n.(o+d*t)==n.p
|
||||||
|
// n.o + n.d * t == n.p
|
||||||
|
// t == (n.p - n.o)/n.d
|
||||||
|
let nd=n.dot(ray.direction);
|
||||||
|
if nd.is_zero(){
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let t=(d-n.dot(ray.origin))/nd;
|
||||||
|
// check if point of intersection is behind face edges
|
||||||
|
// *2 because average of 2 vertices
|
||||||
|
let p=ray.extrapolate(t)*2;
|
||||||
|
mesh.face_edges(face_id).iter().all(|&directed_edge_id|{
|
||||||
|
let edge_n=mesh.directed_edge_n(directed_edge_id);
|
||||||
|
let cross_n=edge_n.cross(n);
|
||||||
|
let &[vert0,vert1]=mesh.edge_verts(directed_edge_id.as_undirected()).as_ref();
|
||||||
|
cross_n.dot(p)<cross_n.dot(mesh.vert(vert0)+mesh.vert(vert1))
|
||||||
|
}).then(||t)
|
||||||
|
}).min().map(Into::into)
|
||||||
|
})?;
|
||||||
|
Some(convex_mesh_id.model_id.into())
|
||||||
|
}
|
||||||
/// use with caution, this is the only non-instruction way to mess with physics
|
/// use with caution, this is the only non-instruction way to mess with physics
|
||||||
pub fn generate_models(&mut self,map:&map::CompleteMap){
|
pub fn generate_models(&mut self,map:&map::CompleteMap){
|
||||||
let mut modes=map.modes.clone();
|
let mut modes=map.modes.clone().denormalize();
|
||||||
for mode in &mut modes.modes{
|
|
||||||
mode.denormalize_data();
|
|
||||||
}
|
|
||||||
let mut used_contact_attributes=Vec::new();
|
let mut used_contact_attributes=Vec::new();
|
||||||
let mut used_intersect_attributes=Vec::new();
|
let mut used_intersect_attributes=Vec::new();
|
||||||
|
|
||||||
@@ -1103,15 +1144,16 @@ impl PhysicsData{
|
|||||||
self.bvh=bvh;
|
self.bvh=bvh;
|
||||||
self.models=models;
|
self.models=models;
|
||||||
self.modes=modes;
|
self.modes=modes;
|
||||||
|
self.le_models=map.models.clone();
|
||||||
//hitbox_mesh is unchanged
|
//hitbox_mesh is unchanged
|
||||||
println!("Physics Objects: {}",model_count);
|
println!("Physics Objects: {}",model_count);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//this is the one who asks
|
//this is the one who asks
|
||||||
fn next_instruction_internal(state:&PhysicsState,data:&PhysicsData,time_limit:Time)->Option<TimedInstruction<InternalInstruction,TimeInner>>{
|
fn next_instruction_internal(state:&PhysicsState,data:&PhysicsData,time_limit:Time)->Option<TimedInstruction<InternalInstruction,Time>>{
|
||||||
//JUST POLLING!!! NO MUTATION
|
//JUST POLLING!!! NO MUTATION
|
||||||
let mut collector = instruction::InstructionCollector::new(time_limit);
|
let mut collector=instruction::InstructionCollector::new(time_limit);
|
||||||
|
|
||||||
collector.collect(state.next_move_instruction());
|
collector.collect(state.next_move_instruction());
|
||||||
|
|
||||||
@@ -1122,15 +1164,15 @@ impl PhysicsData{
|
|||||||
state.body.grow_aabb(&mut aabb,state.time,collector.time());
|
state.body.grow_aabb(&mut aabb,state.time,collector.time());
|
||||||
aabb.inflate(data.hitbox_mesh.halfsize);
|
aabb.inflate(data.hitbox_mesh.halfsize);
|
||||||
//relative to moving platforms
|
//relative to moving platforms
|
||||||
//let relative_body=&VirtualBody::relative(&Body::default(),&state.body).body(state.time);
|
//let relative_body=state.body.relative_to(&Body::ZERO);
|
||||||
let relative_body=&state.body;
|
let relative_body=&state.body;
|
||||||
data.bvh.the_tester(&aabb,&mut |&convex_mesh_id|{
|
data.bvh.sample_aabb(&aabb,&mut |&convex_mesh_id|{
|
||||||
//no checks are needed because of the time limits.
|
//no checks are needed because of the time limits.
|
||||||
let model_mesh=data.models.mesh(convex_mesh_id);
|
let model_mesh=data.models.mesh(convex_mesh_id);
|
||||||
let minkowski=model_physics::MinkowskiMesh::minkowski_sum(model_mesh,data.hitbox_mesh.transformed_mesh());
|
let minkowski=model_physics::MinkowskiMesh::minkowski_sum(model_mesh,data.hitbox_mesh.transformed_mesh());
|
||||||
collector.collect(minkowski.predict_collision_in(relative_body,collector.time())
|
collector.collect(minkowski.predict_collision_in(relative_body,state.time..collector.time())
|
||||||
//temp (?) code to avoid collision loops
|
//temp (?) code to avoid collision loops
|
||||||
.map_or(None,|(face,dt)|{
|
.and_then(|(face,dt)|{
|
||||||
// this must be rounded to avoid the infinite loop when hitting the start zone
|
// this must be rounded to avoid the infinite loop when hitting the start zone
|
||||||
let time=relative_body.time+dt.into();
|
let time=relative_body.time+dt.into();
|
||||||
(state.time<time).then_some((time,face,dt))
|
(state.time<time).then_some((time,face,dt))
|
||||||
@@ -1145,7 +1187,7 @@ impl PhysicsData{
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
collector.instruction()
|
collector.take()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -1185,8 +1227,8 @@ fn recalculate_touching(
|
|||||||
aabb.grow(body.position);
|
aabb.grow(body.position);
|
||||||
aabb.inflate(hitbox_mesh.halfsize);
|
aabb.inflate(hitbox_mesh.halfsize);
|
||||||
//relative to moving platforms
|
//relative to moving platforms
|
||||||
//let relative_body=&VirtualBody::relative(&Body::default(),&state.body).body(state.time);
|
//let relative_body=state.body.relative_to(&Body::ZERO);
|
||||||
bvh.the_tester(&aabb,&mut |&convex_mesh_id|{
|
bvh.sample_aabb(&aabb,&mut |&convex_mesh_id|{
|
||||||
//no checks are needed because of the time limits.
|
//no checks are needed because of the time limits.
|
||||||
let model_mesh=models.mesh(convex_mesh_id);
|
let model_mesh=models.mesh(convex_mesh_id);
|
||||||
let minkowski=model_physics::MinkowskiMesh::minkowski_sum(model_mesh,hitbox_mesh.transformed_mesh());
|
let minkowski=model_physics::MinkowskiMesh::minkowski_sum(model_mesh,hitbox_mesh.transformed_mesh());
|
||||||
@@ -1240,16 +1282,14 @@ fn set_velocity_cull(body:&mut Body,touching:&mut TouchingState,models:&PhysicsM
|
|||||||
let r=n.dot(v).is_positive();
|
let r=n.dot(v).is_positive();
|
||||||
if r{
|
if r{
|
||||||
culled=true;
|
culled=true;
|
||||||
println!("set_velocity_cull contact={:?}",contact);
|
|
||||||
}
|
}
|
||||||
!r
|
!r
|
||||||
});
|
});
|
||||||
set_velocity(body,touching,models,hitbox_mesh,v);
|
set_velocity(body,touching,models,hitbox_mesh,v);
|
||||||
culled
|
culled
|
||||||
}
|
}
|
||||||
fn set_velocity(body:&mut Body,touching:&TouchingState,models:&PhysicsModels,hitbox_mesh:&HitboxMesh,mut v:Planar64Vec3){
|
fn set_velocity(body:&mut Body,touching:&TouchingState,models:&PhysicsModels,hitbox_mesh:&HitboxMesh,v:Planar64Vec3){
|
||||||
touching.constrain_velocity(models,hitbox_mesh,&mut v);
|
body.velocity=touching.constrain_velocity(models,hitbox_mesh,v);
|
||||||
body.velocity=v;
|
|
||||||
}
|
}
|
||||||
fn set_acceleration_cull(body:&mut Body,touching:&mut TouchingState,models:&PhysicsModels,hitbox_mesh:&HitboxMesh,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
|
//This is not correct but is better than what I have
|
||||||
@@ -1259,16 +1299,14 @@ fn set_acceleration_cull(body:&mut Body,touching:&mut TouchingState,models:&Phys
|
|||||||
let r=n.dot(a).is_positive();
|
let r=n.dot(a).is_positive();
|
||||||
if r{
|
if r{
|
||||||
culled=true;
|
culled=true;
|
||||||
println!("set_acceleration_cull contact={:?}",contact);
|
|
||||||
}
|
}
|
||||||
!r
|
!r
|
||||||
});
|
});
|
||||||
set_acceleration(body,touching,models,hitbox_mesh,a);
|
set_acceleration(body,touching,models,hitbox_mesh,a);
|
||||||
culled
|
culled
|
||||||
}
|
}
|
||||||
fn set_acceleration(body:&mut Body,touching:&TouchingState,models:&PhysicsModels,hitbox_mesh:&HitboxMesh,mut a:Planar64Vec3){
|
fn set_acceleration(body:&mut Body,touching:&TouchingState,models:&PhysicsModels,hitbox_mesh:&HitboxMesh,a:Planar64Vec3){
|
||||||
touching.constrain_acceleration(models,hitbox_mesh,&mut a);
|
body.acceleration=touching.constrain_acceleration(models,hitbox_mesh,a);
|
||||||
body.acceleration=a;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn teleport(
|
fn teleport(
|
||||||
@@ -1477,7 +1515,7 @@ fn collision_start_contact(
|
|||||||
let model_id=contact.model_id.into();
|
let model_id=contact.model_id.into();
|
||||||
let mut allow_run_teleport_behaviour=not_spawn_at(mode,model_id);
|
let mut allow_run_teleport_behaviour=not_spawn_at(mode,model_id);
|
||||||
match &attr.contacting.contact_behaviour{
|
match &attr.contacting.contact_behaviour{
|
||||||
Some(gameplay_attributes::ContactingBehaviour::Surf)=>println!("I'm surfing!"),
|
Some(gameplay_attributes::ContactingBehaviour::Surf)=>(),
|
||||||
Some(gameplay_attributes::ContactingBehaviour::Cling)=>println!("Unimplemented!"),
|
Some(gameplay_attributes::ContactingBehaviour::Cling)=>println!("Unimplemented!"),
|
||||||
&Some(gameplay_attributes::ContactingBehaviour::Elastic(elasticity))=>{
|
&Some(gameplay_attributes::ContactingBehaviour::Elastic(elasticity))=>{
|
||||||
let reflected_velocity=body.velocity+((body.velocity-incident_velocity)*Planar64::raw(elasticity as i64+1)).fix_1();
|
let reflected_velocity=body.velocity+((body.velocity-incident_velocity)*Planar64::raw(elasticity as i64+1)).fix_1();
|
||||||
@@ -1507,6 +1545,21 @@ fn collision_start_contact(
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
match &attr.general.trajectory{
|
||||||
|
Some(trajectory)=>{
|
||||||
|
match trajectory{
|
||||||
|
gameplay_attributes::SetTrajectory::AirTime(_)=>todo!(),
|
||||||
|
gameplay_attributes::SetTrajectory::Height(_)=>todo!(),
|
||||||
|
gameplay_attributes::SetTrajectory::TargetPointTime{..}=>todo!(),
|
||||||
|
gameplay_attributes::SetTrajectory::TargetPointSpeed{..}=>todo!(),
|
||||||
|
&gameplay_attributes::SetTrajectory::Velocity(velocity)=>{
|
||||||
|
move_state.cull_velocity(velocity,body,touching,models,hitbox_mesh,style,camera,input_state);
|
||||||
|
},
|
||||||
|
gameplay_attributes::SetTrajectory::DotVelocity{..}=>todo!(),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
None=>(),
|
||||||
|
}
|
||||||
//I love making functions with 10 arguments to dodge the borrow checker
|
//I love making functions with 10 arguments to dodge the borrow checker
|
||||||
if allow_run_teleport_behaviour{
|
if allow_run_teleport_behaviour{
|
||||||
run_teleport_behaviour(model_id,attr.general.wormhole.as_ref(),mode,move_state,body,touching,run,mode_state,models,hitbox_mesh,bvh,style,camera,input_state,time);
|
run_teleport_behaviour(model_id,attr.general.wormhole.as_ref(),mode,move_state,body,touching,run,mode_state,models,hitbox_mesh,bvh,style,camera,input_state,time);
|
||||||
@@ -1534,21 +1587,6 @@ fn collision_start_contact(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
match &attr.general.trajectory{
|
|
||||||
Some(trajectory)=>{
|
|
||||||
match trajectory{
|
|
||||||
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)=>{
|
|
||||||
move_state.cull_velocity(velocity,body,touching,models,hitbox_mesh,style,camera,input_state);
|
|
||||||
},
|
|
||||||
gameplay_attributes::SetTrajectory::DotVelocity { direction: _, dot: _ }=>todo!(),
|
|
||||||
}
|
|
||||||
},
|
|
||||||
None=>(),
|
|
||||||
}
|
|
||||||
//doing enum to set the acceleration when surfing
|
//doing enum to set the acceleration when surfing
|
||||||
//doing input_and_body to refresh the walk state if you hit a wall while accelerating
|
//doing input_and_body to refresh the walk state if you hit a wall while accelerating
|
||||||
move_state.apply_enum_and_input_and_body(body,touching,models,hitbox_mesh,style,camera,input_state);
|
move_state.apply_enum_and_input_and_body(body,touching,models,hitbox_mesh,style,camera,input_state);
|
||||||
@@ -1643,7 +1681,7 @@ fn collision_end_intersect(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn atomic_internal_instruction(state:&mut PhysicsState,data:&PhysicsData,ins:TimedInstruction<InternalInstruction,TimeInner>){
|
fn atomic_internal_instruction(state:&mut PhysicsState,data:&PhysicsData,ins:TimedInstruction<InternalInstruction,Time>){
|
||||||
state.time=ins.time;
|
state.time=ins.time;
|
||||||
let (should_advance_body,goober_time)=match ins.instruction{
|
let (should_advance_body,goober_time)=match ins.instruction{
|
||||||
InternalInstruction::CollisionStart(_,dt)
|
InternalInstruction::CollisionStart(_,dt)
|
||||||
@@ -1719,19 +1757,19 @@ fn atomic_internal_instruction(state:&mut PhysicsState,data:&PhysicsData,ins:Tim
|
|||||||
|MoveState::Fly
|
|MoveState::Fly
|
||||||
=>println!("ReachWalkTargetVelocity fired for non-walking MoveState"),
|
=>println!("ReachWalkTargetVelocity fired for non-walking MoveState"),
|
||||||
MoveState::Walk(walk_state)|MoveState::Ladder(walk_state)=>{
|
MoveState::Walk(walk_state)|MoveState::Ladder(walk_state)=>{
|
||||||
match &walk_state.target{
|
//velocity is already handled by advance_time
|
||||||
|
//we know that the acceleration is precisely zero because the walk target is known to be reachable
|
||||||
|
//which means that gravity can be fully cancelled
|
||||||
|
//ignore moving platforms for now
|
||||||
|
let target=core::mem::replace(&mut walk_state.target,TransientAcceleration::Reached);
|
||||||
|
set_acceleration(&mut state.body,&state.touching,&data.models,&data.hitbox_mesh,vec3::ZERO);
|
||||||
|
// check what the target was to see if it was invalid
|
||||||
|
match target{
|
||||||
//you are not supposed to reach a walk target which is already reached!
|
//you are not supposed to reach a walk target which is already reached!
|
||||||
TransientAcceleration::Reached=>unreachable!(),
|
TransientAcceleration::Reached=>println!("Invalid walk target: Reached"),
|
||||||
TransientAcceleration::Reachable{acceleration:_,time:_}=>{
|
TransientAcceleration::Reachable{..}=>(),
|
||||||
//velocity is already handled by advance_time
|
|
||||||
//we know that the acceleration is precisely zero because the walk target is known to be reachable
|
|
||||||
//which means that gravity can be fully cancelled
|
|
||||||
//ignore moving platforms for now
|
|
||||||
set_acceleration(&mut state.body,&state.touching,&data.models,&data.hitbox_mesh,vec3::ZERO);
|
|
||||||
walk_state.target=TransientAcceleration::Reached;
|
|
||||||
},
|
|
||||||
//you are not supposed to reach an unreachable walk target!
|
//you are not supposed to reach an unreachable walk target!
|
||||||
TransientAcceleration::Unreachable{acceleration:_}=>unreachable!(),
|
TransientAcceleration::Unreachable{..}=>println!("Invalid walk target: Unreachable"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1739,7 +1777,7 @@ fn atomic_internal_instruction(state:&mut PhysicsState,data:&PhysicsData,ins:Tim
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn atomic_input_instruction(state:&mut PhysicsState,data:&PhysicsData,ins:TimedInstruction<Instruction,TimeInner>){
|
fn atomic_input_instruction(state:&mut PhysicsState,data:&PhysicsData,ins:TimedInstruction<Instruction,Time>){
|
||||||
state.time=ins.time;
|
state.time=ins.time;
|
||||||
let should_advance_body=match ins.instruction{
|
let should_advance_body=match ins.instruction{
|
||||||
//the body may as well be a quantum wave function
|
//the body may as well be a quantum wave function
|
||||||
@@ -1790,7 +1828,7 @@ fn atomic_input_instruction(state:&mut PhysicsState,data:&PhysicsData,ins:TimedI
|
|||||||
let jump_dir=walk_state.jump_direction.direction(&data.models,&data.hitbox_mesh,&walk_state.contact);
|
let jump_dir=walk_state.jump_direction.direction(&data.models,&data.hitbox_mesh,&walk_state.contact);
|
||||||
let booster_option=data.models.contact_attr(walk_state.contact.model_id).general.booster.as_ref();
|
let booster_option=data.models.contact_attr(walk_state.contact.model_id).general.booster.as_ref();
|
||||||
let jumped_velocity=jump_settings.jumped_velocity(&state.style,jump_dir,state.body.velocity,booster_option);
|
let jumped_velocity=jump_settings.jumped_velocity(&state.style,jump_dir,state.body.velocity,booster_option);
|
||||||
state.cull_velocity(&data,jumped_velocity);
|
state.cull_velocity(data,jumped_velocity);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
b_refresh_walk_target=false;
|
b_refresh_walk_target=false;
|
||||||
@@ -1806,14 +1844,18 @@ fn atomic_input_instruction(state:&mut PhysicsState,data:&PhysicsData,ins:TimedI
|
|||||||
},
|
},
|
||||||
Instruction::Mode(ModeInstruction::Restart(mode_id))=>{
|
Instruction::Mode(ModeInstruction::Restart(mode_id))=>{
|
||||||
//teleport to mode start zone
|
//teleport to mode start zone
|
||||||
|
let mut spawn_point=vec3::ZERO;
|
||||||
let mode=data.modes.get_mode(mode_id);
|
let mode=data.modes.get_mode(mode_id);
|
||||||
let spawn_point=mode.and_then(|mode|
|
if let Some(mode)=mode{
|
||||||
|
// set style
|
||||||
|
state.style=mode.get_style().clone();
|
||||||
//TODO: spawn at the bottom of the start zone plus the hitbox size
|
//TODO: spawn at the bottom of the start zone plus the hitbox size
|
||||||
//TODO: set camera andles to face the same way as the start zone
|
//TODO: set camera angles to face the same way as the start zone
|
||||||
data.models.get_model_transform(mode.get_start().into()).map(|transform|
|
if let Some(transform)=data.models.get_model_transform(mode.get_start()){
|
||||||
transform.vertex.translation
|
// NOTE: this value may be 0,0,0 on source maps
|
||||||
)
|
spawn_point=transform.vertex.translation;
|
||||||
).unwrap_or(vec3::ZERO);
|
}
|
||||||
|
}
|
||||||
set_position(spawn_point,&mut state.move_state,&mut state.body,&mut state.touching,&mut state.run,&mut state.mode_state,mode,&data.models,&data.hitbox_mesh,&data.bvh,&state.style,&state.camera,&state.input_state,state.time);
|
set_position(spawn_point,&mut state.move_state,&mut state.body,&mut state.touching,&mut state.run,&mut state.mode_state,mode,&data.models,&data.hitbox_mesh,&data.bvh,&state.style,&state.camera,&state.input_state,state.time);
|
||||||
set_velocity(&mut state.body,&state.touching,&data.models,&data.hitbox_mesh,vec3::ZERO);
|
set_velocity(&mut state.body,&state.touching,&data.models,&data.hitbox_mesh,vec3::ZERO);
|
||||||
state.set_move_state(data,MoveState::Air);
|
state.set_move_state(data,MoveState::Air);
|
||||||
@@ -1823,6 +1865,9 @@ fn atomic_input_instruction(state:&mut PhysicsState,data:&PhysicsData,ins:TimedI
|
|||||||
Instruction::Mode(ModeInstruction::Spawn(mode_id,stage_id))=>{
|
Instruction::Mode(ModeInstruction::Spawn(mode_id,stage_id))=>{
|
||||||
//spawn at a particular stage
|
//spawn at a particular stage
|
||||||
if let Some(mode)=data.modes.get_mode(mode_id){
|
if let Some(mode)=data.modes.get_mode(mode_id){
|
||||||
|
// set style
|
||||||
|
state.style=mode.get_style().clone();
|
||||||
|
// teleport to stage in mode
|
||||||
if let Some(stage)=mode.get_stage(stage_id){
|
if let Some(stage)=mode.get_stage(stage_id){
|
||||||
let _=teleport_to_spawn(
|
let _=teleport_to_spawn(
|
||||||
stage.spawn(),
|
stage.spawn(),
|
||||||
@@ -1860,7 +1905,6 @@ fn atomic_input_instruction(state:&mut PhysicsState,data:&PhysicsData,ins:TimedI
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test{
|
mod test{
|
||||||
use strafesnet_common::integer::{vec3::{self,int as int3},mat3};
|
use strafesnet_common::integer::{vec3::{self,int as int3},mat3};
|
||||||
use crate::body::VirtualBody;
|
|
||||||
use super::*;
|
use super::*;
|
||||||
fn test_collision_axis_aligned(relative_body:Body,expected_collision_time:Option<Time>){
|
fn test_collision_axis_aligned(relative_body:Body,expected_collision_time:Option<Time>){
|
||||||
let h0=HitboxMesh::new(PhysicsMesh::unit_cube(),integer::Planar64Affine3::new(mat3::from_diagonal(int3(5,1,5)>>1),vec3::ZERO));
|
let h0=HitboxMesh::new(PhysicsMesh::unit_cube(),integer::Planar64Affine3::new(mat3::from_diagonal(int3(5,1,5)>>1),vec3::ZERO));
|
||||||
@@ -1868,7 +1912,7 @@ mod test{
|
|||||||
let hitbox_mesh=h1.transformed_mesh();
|
let hitbox_mesh=h1.transformed_mesh();
|
||||||
let platform_mesh=h0.transformed_mesh();
|
let platform_mesh=h0.transformed_mesh();
|
||||||
let minkowski=model_physics::MinkowskiMesh::minkowski_sum(platform_mesh,hitbox_mesh);
|
let minkowski=model_physics::MinkowskiMesh::minkowski_sum(platform_mesh,hitbox_mesh);
|
||||||
let collision=minkowski.predict_collision_in(&relative_body,Time::from_secs(10));
|
let collision=minkowski.predict_collision_in(&relative_body,Time::ZERO..Time::from_secs(10));
|
||||||
assert_eq!(collision.map(|tup|relative_body.time+tup.1.into()),expected_collision_time,"Incorrect time of collision");
|
assert_eq!(collision.map(|tup|relative_body.time+tup.1.into()),expected_collision_time,"Incorrect time of collision");
|
||||||
}
|
}
|
||||||
fn test_collision_rotated(relative_body:Body,expected_collision_time:Option<Time>){
|
fn test_collision_rotated(relative_body:Body,expected_collision_time:Option<Time>){
|
||||||
@@ -1886,7 +1930,7 @@ mod test{
|
|||||||
let hitbox_mesh=h1.transformed_mesh();
|
let hitbox_mesh=h1.transformed_mesh();
|
||||||
let platform_mesh=h0.transformed_mesh();
|
let platform_mesh=h0.transformed_mesh();
|
||||||
let minkowski=model_physics::MinkowskiMesh::minkowski_sum(platform_mesh,hitbox_mesh);
|
let minkowski=model_physics::MinkowskiMesh::minkowski_sum(platform_mesh,hitbox_mesh);
|
||||||
let collision=minkowski.predict_collision_in(&relative_body,Time::from_secs(10));
|
let collision=minkowski.predict_collision_in(&relative_body,Time::ZERO..Time::from_secs(10));
|
||||||
assert_eq!(collision.map(|tup|relative_body.time+tup.1.into()),expected_collision_time,"Incorrect time of collision");
|
assert_eq!(collision.map(|tup|relative_body.time+tup.1.into()),expected_collision_time,"Incorrect time of collision");
|
||||||
}
|
}
|
||||||
fn test_collision(relative_body:Body,expected_collision_time:Option<Time>){
|
fn test_collision(relative_body:Body,expected_collision_time:Option<Time>){
|
||||||
@@ -1940,111 +1984,111 @@ mod test{
|
|||||||
}
|
}
|
||||||
#[test]
|
#[test]
|
||||||
fn test_collision_parabola_edge_east_from_west(){
|
fn test_collision_parabola_edge_east_from_west(){
|
||||||
test_collision(VirtualBody::relative(&Body::ZERO,&Body::new(
|
test_collision(Body::new(
|
||||||
int3(3,3,0),
|
int3(3,3,0),
|
||||||
int3(100,-1,0),
|
int3(100,-1,0),
|
||||||
int3(0,-1,0),
|
int3(0,-1,0),
|
||||||
Time::ZERO
|
Time::ZERO
|
||||||
)).body(Time::from_secs(-1)),Some(Time::from_secs(0)));
|
).relative_to(&Body::ZERO).body(Time::from_secs(-1)),Some(Time::from_secs(0)));
|
||||||
}
|
}
|
||||||
#[test]
|
#[test]
|
||||||
fn test_collision_parabola_edge_south_from_north(){
|
fn test_collision_parabola_edge_south_from_north(){
|
||||||
test_collision(VirtualBody::relative(&Body::ZERO,&Body::new(
|
test_collision(Body::new(
|
||||||
int3(0,3,3),
|
int3(0,3,3),
|
||||||
int3(0,-1,100),
|
int3(0,-1,100),
|
||||||
int3(0,-1,0),
|
int3(0,-1,0),
|
||||||
Time::ZERO
|
Time::ZERO
|
||||||
)).body(Time::from_secs(-1)),Some(Time::from_secs(0)));
|
).relative_to(&Body::ZERO).body(Time::from_secs(-1)),Some(Time::from_secs(0)));
|
||||||
}
|
}
|
||||||
#[test]
|
#[test]
|
||||||
fn test_collision_parabola_edge_west_from_east(){
|
fn test_collision_parabola_edge_west_from_east(){
|
||||||
test_collision(VirtualBody::relative(&Body::ZERO,&Body::new(
|
test_collision(Body::new(
|
||||||
int3(-3,3,0),
|
int3(-3,3,0),
|
||||||
int3(-100,-1,0),
|
int3(-100,-1,0),
|
||||||
int3(0,-1,0),
|
int3(0,-1,0),
|
||||||
Time::ZERO
|
Time::ZERO
|
||||||
)).body(Time::from_secs(-1)),Some(Time::from_secs(0)));
|
).relative_to(&Body::ZERO).body(Time::from_secs(-1)),Some(Time::from_secs(0)));
|
||||||
}
|
}
|
||||||
#[test]
|
#[test]
|
||||||
fn test_collision_parabola_edge_north_from_south(){
|
fn test_collision_parabola_edge_north_from_south(){
|
||||||
test_collision(VirtualBody::relative(&Body::ZERO,&Body::new(
|
test_collision(Body::new(
|
||||||
int3(0,3,-3),
|
int3(0,3,-3),
|
||||||
int3(0,-1,-100),
|
int3(0,-1,-100),
|
||||||
int3(0,-1,0),
|
int3(0,-1,0),
|
||||||
Time::ZERO
|
Time::ZERO
|
||||||
)).body(Time::from_secs(-1)),Some(Time::from_secs(0)));
|
).relative_to(&Body::ZERO).body(Time::from_secs(-1)),Some(Time::from_secs(0)));
|
||||||
}
|
}
|
||||||
#[test]
|
#[test]
|
||||||
fn test_collision_parabola_edge_north_from_ne(){
|
fn test_collision_parabola_edge_north_from_ne(){
|
||||||
test_collision(VirtualBody::relative(&Body::ZERO,&Body::new(
|
test_collision(Body::new(
|
||||||
int3(0,6,-7)>>1,
|
int3(0,6,-7)>>1,
|
||||||
int3(-10,-1,1),
|
int3(-10,-1,1),
|
||||||
int3(0,-1,0),
|
int3(0,-1,0),
|
||||||
Time::ZERO
|
Time::ZERO
|
||||||
)).body(Time::from_secs(-1)),Some(Time::from_secs(0)));
|
).relative_to(&Body::ZERO).body(Time::from_secs(-1)),Some(Time::from_secs(0)));
|
||||||
}
|
}
|
||||||
#[test]
|
#[test]
|
||||||
fn test_collision_parabola_edge_north_from_nw(){
|
fn test_collision_parabola_edge_north_from_nw(){
|
||||||
test_collision(VirtualBody::relative(&Body::ZERO,&Body::new(
|
test_collision(Body::new(
|
||||||
int3(0,6,-7)>>1,
|
int3(0,6,-7)>>1,
|
||||||
int3(10,-1,1),
|
int3(10,-1,1),
|
||||||
int3(0,-1,0),
|
int3(0,-1,0),
|
||||||
Time::ZERO
|
Time::ZERO
|
||||||
)).body(Time::from_secs(-1)),Some(Time::from_secs(0)));
|
).relative_to(&Body::ZERO).body(Time::from_secs(-1)),Some(Time::from_secs(0)));
|
||||||
}
|
}
|
||||||
#[test]
|
#[test]
|
||||||
fn test_collision_parabola_edge_east_from_se(){
|
fn test_collision_parabola_edge_east_from_se(){
|
||||||
test_collision(VirtualBody::relative(&Body::ZERO,&Body::new(
|
test_collision(Body::new(
|
||||||
int3(7,6,0)>>1,
|
int3(7,6,0)>>1,
|
||||||
int3(-1,-1,-10),
|
int3(-1,-1,-10),
|
||||||
int3(0,-1,0),
|
int3(0,-1,0),
|
||||||
Time::ZERO
|
Time::ZERO
|
||||||
)).body(Time::from_secs(-1)),Some(Time::from_secs(0)));
|
).relative_to(&Body::ZERO).body(Time::from_secs(-1)),Some(Time::from_secs(0)));
|
||||||
}
|
}
|
||||||
#[test]
|
#[test]
|
||||||
fn test_collision_parabola_edge_east_from_ne(){
|
fn test_collision_parabola_edge_east_from_ne(){
|
||||||
test_collision(VirtualBody::relative(&Body::ZERO,&Body::new(
|
test_collision(Body::new(
|
||||||
int3(7,6,0)>>1,
|
int3(7,6,0)>>1,
|
||||||
int3(-1,-1,10),
|
int3(-1,-1,10),
|
||||||
int3(0,-1,0),
|
int3(0,-1,0),
|
||||||
Time::ZERO
|
Time::ZERO
|
||||||
)).body(Time::from_secs(-1)),Some(Time::from_secs(0)));
|
).relative_to(&Body::ZERO).body(Time::from_secs(-1)),Some(Time::from_secs(0)));
|
||||||
}
|
}
|
||||||
#[test]
|
#[test]
|
||||||
fn test_collision_parabola_edge_south_from_se(){
|
fn test_collision_parabola_edge_south_from_se(){
|
||||||
test_collision(VirtualBody::relative(&Body::ZERO,&Body::new(
|
test_collision(Body::new(
|
||||||
int3(0,6,7)>>1,
|
int3(0,6,7)>>1,
|
||||||
int3(-10,-1,-1),
|
int3(-10,-1,-1),
|
||||||
int3(0,-1,0),
|
int3(0,-1,0),
|
||||||
Time::ZERO
|
Time::ZERO
|
||||||
)).body(Time::from_secs(-1)),Some(Time::from_secs(0)));
|
).relative_to(&Body::ZERO).body(Time::from_secs(-1)),Some(Time::from_secs(0)));
|
||||||
}
|
}
|
||||||
#[test]
|
#[test]
|
||||||
fn test_collision_parabola_edge_south_from_sw(){
|
fn test_collision_parabola_edge_south_from_sw(){
|
||||||
test_collision(VirtualBody::relative(&Body::ZERO,&Body::new(
|
test_collision(Body::new(
|
||||||
int3(0,6,7)>>1,
|
int3(0,6,7)>>1,
|
||||||
int3(10,-1,-1),
|
int3(10,-1,-1),
|
||||||
int3(0,-1,0),
|
int3(0,-1,0),
|
||||||
Time::ZERO
|
Time::ZERO
|
||||||
)).body(Time::from_secs(-1)),Some(Time::from_secs(0)));
|
).relative_to(&Body::ZERO).body(Time::from_secs(-1)),Some(Time::from_secs(0)));
|
||||||
}
|
}
|
||||||
#[test]
|
#[test]
|
||||||
fn test_collision_parabola_edge_west_from_se(){
|
fn test_collision_parabola_edge_west_from_se(){
|
||||||
test_collision(VirtualBody::relative(&Body::ZERO,&Body::new(
|
test_collision(Body::new(
|
||||||
int3(-7,6,0)>>1,
|
int3(-7,6,0)>>1,
|
||||||
int3(1,-1,-10),
|
int3(1,-1,-10),
|
||||||
int3(0,-1,0),
|
int3(0,-1,0),
|
||||||
Time::ZERO
|
Time::ZERO
|
||||||
)).body(Time::from_secs(-1)),Some(Time::from_secs(0)));
|
).relative_to(&Body::ZERO).body(Time::from_secs(-1)),Some(Time::from_secs(0)));
|
||||||
}
|
}
|
||||||
#[test]
|
#[test]
|
||||||
fn test_collision_parabola_edge_west_from_ne(){
|
fn test_collision_parabola_edge_west_from_ne(){
|
||||||
test_collision(VirtualBody::relative(&Body::ZERO,&Body::new(
|
test_collision(Body::new(
|
||||||
int3(-7,6,0)>>1,
|
int3(-7,6,0)>>1,
|
||||||
int3(1,-1,10),
|
int3(1,-1,10),
|
||||||
int3(0,-1,0),
|
int3(0,-1,0),
|
||||||
Time::ZERO
|
Time::ZERO
|
||||||
)).body(Time::from_secs(-1)),Some(Time::from_secs(0)));
|
).relative_to(&Body::ZERO).body(Time::from_secs(-1)),Some(Time::from_secs(0)));
|
||||||
}
|
}
|
||||||
#[test]
|
#[test]
|
||||||
fn test_collision_oblique(){
|
fn test_collision_oblique(){
|
||||||
@@ -1,4 +1,6 @@
|
|||||||
use strafesnet_common::integer::{self,vec3::{self,Vector3},Fixed,Planar64,Planar64Vec3,Ratio};
|
use strafesnet_common::integer::vec3::{self,Vector3};
|
||||||
|
use strafesnet_common::integer::{Fixed,Planar64Vec3,Ratio};
|
||||||
|
use strafesnet_common::ray::Ray;
|
||||||
|
|
||||||
// This algorithm is based on Lua code
|
// This algorithm is based on Lua code
|
||||||
// written by Trey Reynolds in 2021
|
// written by Trey Reynolds in 2021
|
||||||
@@ -12,24 +14,6 @@ type Conts<'a>=arrayvec::ArrayVec<&'a Contact,4>;
|
|||||||
// hack to allow comparing ratios to zero
|
// hack to allow comparing ratios to zero
|
||||||
const RATIO_ZERO:Ratio<Fixed<1,32>,Fixed<1,32>>=Ratio::new(Fixed::ZERO,Fixed::EPSILON);
|
const RATIO_ZERO:Ratio<Fixed<1,32>,Fixed<1,32>>=Ratio::new(Fixed::ZERO,Fixed::EPSILON);
|
||||||
|
|
||||||
struct Ray{
|
|
||||||
origin:Planar64Vec3,
|
|
||||||
direction:Planar64Vec3,
|
|
||||||
}
|
|
||||||
impl Ray{
|
|
||||||
fn extrapolate<Num,Den,N1,T1>(&self,t:Ratio<Num,Den>)->Planar64Vec3
|
|
||||||
where
|
|
||||||
Num:Copy,
|
|
||||||
Den:Copy,
|
|
||||||
Num:core::ops::Mul<Planar64,Output=N1>,
|
|
||||||
Planar64:core::ops::Mul<Den,Output=N1>,
|
|
||||||
N1:integer::Divide<Den,Output=T1>,
|
|
||||||
T1:integer::Fix<Planar64>,
|
|
||||||
{
|
|
||||||
self.origin+self.direction.map(|elem|(t*elem).divide().fix())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Information about a contact restriction
|
/// Information about a contact restriction
|
||||||
pub struct Contact{
|
pub struct Contact{
|
||||||
pub position:Planar64Vec3,
|
pub position:Planar64Vec3,
|
||||||
@@ -289,7 +273,7 @@ fn get_best_push_ray_and_conts<'a>(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_first_touch<'a>(contacts:&'a Vec<Contact>,ray:&Ray,conts:&Conts)->Option<(Ratio<Fixed<2,64>,Fixed<2,64>>,&'a Contact)>{
|
fn get_first_touch<'a>(contacts:&'a [Contact],ray:&Ray,conts:&Conts)->Option<(Ratio<Fixed<2,64>,Fixed<2,64>>,&'a Contact)>{
|
||||||
contacts.iter()
|
contacts.iter()
|
||||||
.filter(|&contact|
|
.filter(|&contact|
|
||||||
!conts.iter().any(|&c|std::ptr::eq(c,contact))
|
!conts.iter().any(|&c|std::ptr::eq(c,contact))
|
||||||
@@ -299,7 +283,7 @@ fn get_first_touch<'a>(contacts:&'a Vec<Contact>,ray:&Ray,conts:&Conts)->Option<
|
|||||||
.min_by_key(|&(t,_)|t)
|
.min_by_key(|&(t,_)|t)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn push_solve(contacts:&Vec<Contact>,point:Planar64Vec3)->Planar64Vec3{
|
pub fn push_solve(contacts:&[Contact],point:Planar64Vec3)->Planar64Vec3{
|
||||||
let (mut ray,mut conts)=get_best_push_ray_and_conts_0(point);
|
let (mut ray,mut conts)=get_best_push_ray_and_conts_0(point);
|
||||||
loop{
|
loop{
|
||||||
let (next_t,next_cont)=match get_first_touch(contacts,&ray,&conts){
|
let (next_t,next_cont)=match get_first_touch(contacts,&ray,&conts){
|
||||||
12
engine/session/Cargo.toml
Normal file
12
engine/session/Cargo.toml
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
[package]
|
||||||
|
name = "strafesnet_session"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2024"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
glam = "0.30.0"
|
||||||
|
replace_with = "0.1.7"
|
||||||
|
strafesnet_common = { path = "../../lib/common", registry = "strafesnet" }
|
||||||
|
strafesnet_physics = { path = "../physics", registry = "strafesnet" }
|
||||||
|
strafesnet_settings = { path = "../settings", registry = "strafesnet" }
|
||||||
|
strafesnet_snf = { path = "../../lib/snf", registry = "strafesnet" }
|
||||||
8
engine/session/LICENSE
Normal file
8
engine/session/LICENSE
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
/*******************************************************
|
||||||
|
* Copyright (C) 2023-2024 Rhys Lloyd <krakow20@gmail.com>
|
||||||
|
*
|
||||||
|
* This file is part of the StrafesNET bhop/surf client.
|
||||||
|
*
|
||||||
|
* StrafesNET can not be copied and/or distributed
|
||||||
|
* without the express permission of Rhys Lloyd
|
||||||
|
*******************************************************/
|
||||||
2
engine/session/src/lib.rs
Normal file
2
engine/session/src/lib.rs
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
mod mouse_interpolator;
|
||||||
|
pub mod session;
|
||||||
@@ -5,13 +5,13 @@ use strafesnet_common::physics::{
|
|||||||
TimeInner as PhysicsTimeInner,
|
TimeInner as PhysicsTimeInner,
|
||||||
Time as PhysicsTime,
|
Time as PhysicsTime,
|
||||||
};
|
};
|
||||||
use strafesnet_common::session::{Time as SessionTime,TimeInner as SessionTimeInner};
|
use strafesnet_common::session::Time as SessionTime;
|
||||||
use strafesnet_common::instruction::{InstructionConsumer,InstructionEmitter,TimedInstruction};
|
use strafesnet_common::instruction::{InstructionConsumer,InstructionEmitter,TimedInstruction};
|
||||||
|
|
||||||
type TimedSelfInstruction=TimedInstruction<Instruction,PhysicsTimeInner>;
|
type TimedSelfInstruction=TimedInstruction<Instruction,PhysicsTime>;
|
||||||
type DoubleTimedSelfInstruction=TimedInstruction<TimedSelfInstruction,SessionTimeInner>;
|
type DoubleTimedSelfInstruction=TimedInstruction<TimedSelfInstruction,SessionTime>;
|
||||||
|
|
||||||
type TimedPhysicsInstruction=TimedInstruction<PhysicsInstruction,PhysicsTimeInner>;
|
type TimedPhysicsInstruction=TimedInstruction<PhysicsInstruction,PhysicsTime>;
|
||||||
|
|
||||||
const MOUSE_TIMEOUT:SessionTime=SessionTime::from_millis(10);
|
const MOUSE_TIMEOUT:SessionTime=SessionTime::from_millis(10);
|
||||||
|
|
||||||
@@ -89,14 +89,14 @@ pub struct MouseInterpolator{
|
|||||||
// Maybe MouseInterpolator manipulation is better expressed using impls
|
// Maybe MouseInterpolator manipulation is better expressed using impls
|
||||||
// and called from Instruction trait impls in session
|
// and called from Instruction trait impls in session
|
||||||
impl InstructionConsumer<TimedSelfInstruction> for MouseInterpolator{
|
impl InstructionConsumer<TimedSelfInstruction> for MouseInterpolator{
|
||||||
type TimeInner=SessionTimeInner;
|
type Time=SessionTime;
|
||||||
fn process_instruction(&mut self,ins:DoubleTimedSelfInstruction){
|
fn process_instruction(&mut self,ins:DoubleTimedSelfInstruction){
|
||||||
self.push_unbuffered_input(ins.time,ins.instruction.time,ins.instruction.instruction.into())
|
self.push_unbuffered_input(ins.time,ins.instruction.time,ins.instruction.instruction.into())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl InstructionEmitter<StepInstruction> for MouseInterpolator{
|
impl InstructionEmitter<StepInstruction> for MouseInterpolator{
|
||||||
type TimeInner=SessionTimeInner;
|
type Time=SessionTime;
|
||||||
fn next_instruction(&self,time_limit:SessionTime)->Option<TimedInstruction<StepInstruction,Self::TimeInner>>{
|
fn next_instruction(&self,time_limit:SessionTime)->Option<TimedInstruction<StepInstruction,Self::Time>>{
|
||||||
self.buffered_instruction_with_timeout(time_limit)
|
self.buffered_instruction_with_timeout(time_limit)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -108,7 +108,7 @@ impl MouseInterpolator{
|
|||||||
output:std::collections::VecDeque::new(),
|
output:std::collections::VecDeque::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn push_mouse_and_flush_buffer(&mut self,ins:TimedInstruction<MouseInstruction,PhysicsTimeInner>){
|
fn push_mouse_and_flush_buffer(&mut self,ins:TimedInstruction<MouseInstruction,PhysicsTime>){
|
||||||
self.buffer.push_front(TimedInstruction{
|
self.buffer.push_front(TimedInstruction{
|
||||||
time:ins.time,
|
time:ins.time,
|
||||||
instruction:BufferedInstruction::Mouse(ins.instruction).into(),
|
instruction:BufferedInstruction::Mouse(ins.instruction).into(),
|
||||||
@@ -219,7 +219,7 @@ impl MouseInterpolator{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn buffered_instruction_with_timeout(&self,time_limit:SessionTime)->Option<TimedInstruction<StepInstruction,SessionTimeInner>>{
|
fn buffered_instruction_with_timeout(&self,time_limit:SessionTime)->Option<TimedInstruction<StepInstruction,SessionTime>>{
|
||||||
match self.get_mouse_timedout_at(time_limit){
|
match self.get_mouse_timedout_at(time_limit){
|
||||||
Some(timeout)=>Some(TimedInstruction{
|
Some(timeout)=>Some(TimedInstruction{
|
||||||
time:timeout,
|
time:timeout,
|
||||||
@@ -232,7 +232,7 @@ impl MouseInterpolator{
|
|||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn pop_buffered_instruction(&mut self,ins:TimedInstruction<StepInstruction,PhysicsTimeInner>)->Option<TimedPhysicsInstruction>{
|
pub fn pop_buffered_instruction(&mut self,ins:TimedInstruction<StepInstruction,PhysicsTime>)->Option<TimedPhysicsInstruction>{
|
||||||
match ins.instruction{
|
match ins.instruction{
|
||||||
StepInstruction::Pop=>(),
|
StepInstruction::Pop=>(),
|
||||||
StepInstruction::Timeout=>self.timeout_mouse(ins.time),
|
StepInstruction::Timeout=>self.timeout_mouse(ins.time),
|
||||||
@@ -244,6 +244,7 @@ impl MouseInterpolator{
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test{
|
mod test{
|
||||||
use super::*;
|
use super::*;
|
||||||
|
use strafesnet_common::session::TimeInner as SessionTimeInner;
|
||||||
#[test]
|
#[test]
|
||||||
fn test(){
|
fn test(){
|
||||||
let mut interpolator=MouseInterpolator::new();
|
let mut interpolator=MouseInterpolator::new();
|
||||||
@@ -2,6 +2,7 @@ use std::collections::HashMap;
|
|||||||
|
|
||||||
use strafesnet_common::gameplay_modes::{ModeId,StageId};
|
use strafesnet_common::gameplay_modes::{ModeId,StageId};
|
||||||
use strafesnet_common::instruction::{InstructionConsumer,InstructionEmitter,InstructionFeedback,TimedInstruction};
|
use strafesnet_common::instruction::{InstructionConsumer,InstructionEmitter,InstructionFeedback,TimedInstruction};
|
||||||
|
use strafesnet_common::model::ModelId;
|
||||||
// session represents the non-hardware state of the client.
|
// session represents the non-hardware state of the client.
|
||||||
// Ideally it is a deterministic state which is atomically updated by instructions, same as the simulation state.
|
// Ideally it is a deterministic state which is atomically updated by instructions, same as the simulation state.
|
||||||
use strafesnet_common::physics::{
|
use strafesnet_common::physics::{
|
||||||
@@ -12,16 +13,18 @@ use strafesnet_common::physics::{
|
|||||||
};
|
};
|
||||||
use strafesnet_common::timer::{Scaled,Timer};
|
use strafesnet_common::timer::{Scaled,Timer};
|
||||||
use strafesnet_common::session::{TimeInner as SessionTimeInner,Time as SessionTime};
|
use strafesnet_common::session::{TimeInner as SessionTimeInner,Time as SessionTime};
|
||||||
|
use strafesnet_settings::directories::Directories;
|
||||||
|
|
||||||
use crate::mouse_interpolator::{MouseInterpolator,StepInstruction,Instruction as MouseInterpolatorInstruction};
|
use crate::mouse_interpolator::{MouseInterpolator,StepInstruction,Instruction as MouseInterpolatorInstruction};
|
||||||
use crate::physics::{PhysicsContext,PhysicsData};
|
use strafesnet_physics::physics::{self,PhysicsContext,PhysicsData};
|
||||||
use crate::settings::UserSettings;
|
use strafesnet_settings::settings::UserSettings;
|
||||||
|
|
||||||
pub enum Instruction<'a>{
|
pub enum Instruction<'a>{
|
||||||
Input(SessionInputInstruction),
|
Input(SessionInputInstruction),
|
||||||
Control(SessionControlInstruction),
|
Control(SessionControlInstruction),
|
||||||
Playback(SessionPlaybackInstruction),
|
Playback(SessionPlaybackInstruction),
|
||||||
ChangeMap(&'a strafesnet_common::map::CompleteMap),
|
ChangeMap(&'a strafesnet_common::map::CompleteMap),
|
||||||
|
LoadReplay(strafesnet_snf::bot::Segment),
|
||||||
Idle,
|
Idle,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -44,6 +47,8 @@ pub enum SessionControlInstruction{
|
|||||||
// copy the current session simulation recording into a replay and view it
|
// copy the current session simulation recording into a replay and view it
|
||||||
CopyRecordingIntoReplayAndSpectate,
|
CopyRecordingIntoReplayAndSpectate,
|
||||||
StopSpectate,
|
StopSpectate,
|
||||||
|
SaveReplay,
|
||||||
|
LoadIntoReplayState,
|
||||||
}
|
}
|
||||||
pub enum SessionPlaybackInstruction{
|
pub enum SessionPlaybackInstruction{
|
||||||
SkipForward,
|
SkipForward,
|
||||||
@@ -54,45 +59,52 @@ pub enum SessionPlaybackInstruction{
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub struct FrameState{
|
pub struct FrameState{
|
||||||
pub body:crate::physics::Body,
|
pub body:physics::Body,
|
||||||
pub camera:crate::physics::PhysicsCamera,
|
pub camera:physics::PhysicsCamera,
|
||||||
pub time:PhysicsTime,
|
pub time:PhysicsTime,
|
||||||
|
pub debug_model:Option<strafesnet_common::model::ModelId>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Simulation{
|
pub struct Simulation{
|
||||||
timer:Timer<Scaled<SessionTimeInner,PhysicsTimeInner>>,
|
timer:Timer<Scaled<SessionTimeInner,PhysicsTimeInner>>,
|
||||||
physics:crate::physics::PhysicsState,
|
physics:physics::PhysicsState,
|
||||||
}
|
}
|
||||||
impl Simulation{
|
impl Simulation{
|
||||||
pub const fn new(
|
pub const fn new(
|
||||||
timer:Timer<Scaled<SessionTimeInner,PhysicsTimeInner>>,
|
timer:Timer<Scaled<SessionTimeInner,PhysicsTimeInner>>,
|
||||||
physics:crate::physics::PhysicsState,
|
physics:physics::PhysicsState,
|
||||||
)->Self{
|
)->Self{
|
||||||
Self{
|
Self{
|
||||||
timer,
|
timer,
|
||||||
physics,
|
physics,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn get_frame_state(&self,time:SessionTime)->FrameState{
|
pub fn get_frame_state(&self,time:SessionTime,debug_model:Option<ModelId>)->FrameState{
|
||||||
FrameState{
|
FrameState{
|
||||||
body:self.physics.camera_body(),
|
body:self.physics.camera_body(),
|
||||||
camera:self.physics.camera(),
|
camera:self.physics.camera(),
|
||||||
time:self.timer.time(time),
|
time:self.timer.time(time),
|
||||||
|
debug_model,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct Recording{
|
pub struct Recording{
|
||||||
instructions:Vec<TimedInstruction<PhysicsInputInstruction,PhysicsTimeInner>>,
|
instructions:Vec<TimedInstruction<PhysicsInputInstruction,PhysicsTime>>,
|
||||||
}
|
}
|
||||||
impl Recording{
|
impl Recording{
|
||||||
|
pub fn new(
|
||||||
|
instructions:Vec<TimedInstruction<PhysicsInputInstruction,PhysicsTime>>,
|
||||||
|
)->Self{
|
||||||
|
Self{instructions}
|
||||||
|
}
|
||||||
fn clear(&mut self){
|
fn clear(&mut self){
|
||||||
self.instructions.clear();
|
self.instructions.clear();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub struct Replay{
|
pub struct Replay{
|
||||||
last_instruction_id:usize,
|
next_instruction_id:usize,
|
||||||
recording:Recording,
|
recording:Recording,
|
||||||
simulation:Simulation,
|
simulation:Simulation,
|
||||||
}
|
}
|
||||||
@@ -102,7 +114,7 @@ impl Replay{
|
|||||||
simulation:Simulation,
|
simulation:Simulation,
|
||||||
)->Self{
|
)->Self{
|
||||||
Self{
|
Self{
|
||||||
last_instruction_id:0,
|
next_instruction_id:0,
|
||||||
recording,
|
recording,
|
||||||
simulation,
|
simulation,
|
||||||
}
|
}
|
||||||
@@ -110,16 +122,16 @@ impl Replay{
|
|||||||
pub fn advance(&mut self,physics_data:&PhysicsData,time_limit:SessionTime){
|
pub fn advance(&mut self,physics_data:&PhysicsData,time_limit:SessionTime){
|
||||||
let mut time=self.simulation.timer.time(time_limit);
|
let mut time=self.simulation.timer.time(time_limit);
|
||||||
loop{
|
loop{
|
||||||
if let Some(ins)=self.recording.instructions.get(self.last_instruction_id){
|
if let Some(ins)=self.recording.instructions.get(self.next_instruction_id){
|
||||||
if ins.time<time{
|
if ins.time<time{
|
||||||
PhysicsContext::run_input_instruction(&mut self.simulation.physics,physics_data,ins.clone());
|
PhysicsContext::run_input_instruction(&mut self.simulation.physics,physics_data,ins.clone());
|
||||||
self.last_instruction_id+=1;
|
self.next_instruction_id+=1;
|
||||||
}else{
|
}else{
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}else{
|
}else{
|
||||||
// loop playback
|
// loop playback
|
||||||
self.last_instruction_id=0;
|
self.next_instruction_id=0;
|
||||||
// No need to reset physics because the very first instruction is 'Reset'
|
// No need to reset physics because the very first instruction is 'Reset'
|
||||||
let new_time=self.recording.instructions.first().map_or(PhysicsTime::ZERO,|ins|ins.time);
|
let new_time=self.recording.instructions.first().map_or(PhysicsTime::ZERO,|ins|ins.time);
|
||||||
self.simulation.timer.set_time(time_limit,new_time);
|
self.simulation.timer.set_time(time_limit,new_time);
|
||||||
@@ -141,30 +153,35 @@ enum ViewState{
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub struct Session{
|
pub struct Session{
|
||||||
|
directories:Directories,
|
||||||
user_settings:UserSettings,
|
user_settings:UserSettings,
|
||||||
mouse_interpolator:crate::mouse_interpolator::MouseInterpolator,
|
mouse_interpolator:crate::mouse_interpolator::MouseInterpolator,
|
||||||
view_state:ViewState,
|
view_state:ViewState,
|
||||||
//gui:GuiState
|
//gui:GuiState
|
||||||
geometry_shared:crate::physics::PhysicsData,
|
geometry_shared:physics::PhysicsData,
|
||||||
simulation:Simulation,
|
simulation:Simulation,
|
||||||
// below fields not included in lite session
|
// below fields not included in lite session
|
||||||
recording:Recording,
|
recording:Recording,
|
||||||
//players:HashMap<PlayerId,Simulation>,
|
//players:HashMap<PlayerId,Simulation>,
|
||||||
replays:HashMap<BotId,Replay>,
|
replays:HashMap<BotId,Replay>,
|
||||||
|
last_ray_hit:Option<strafesnet_common::model::ModelId>,
|
||||||
}
|
}
|
||||||
impl Session{
|
impl Session{
|
||||||
pub fn new(
|
pub fn new(
|
||||||
user_settings:UserSettings,
|
user_settings:UserSettings,
|
||||||
|
directories:Directories,
|
||||||
simulation:Simulation,
|
simulation:Simulation,
|
||||||
)->Self{
|
)->Self{
|
||||||
Self{
|
Self{
|
||||||
user_settings,
|
user_settings,
|
||||||
|
directories,
|
||||||
mouse_interpolator:MouseInterpolator::new(),
|
mouse_interpolator:MouseInterpolator::new(),
|
||||||
geometry_shared:Default::default(),
|
geometry_shared:Default::default(),
|
||||||
simulation,
|
simulation,
|
||||||
view_state:ViewState::Play,
|
view_state:ViewState::Play,
|
||||||
recording:Default::default(),
|
recording:Default::default(),
|
||||||
replays:HashMap::new(),
|
replays:HashMap::new(),
|
||||||
|
last_ray_hit:None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn clear_recording(&mut self){
|
fn clear_recording(&mut self){
|
||||||
@@ -176,12 +193,30 @@ impl Session{
|
|||||||
}
|
}
|
||||||
pub fn get_frame_state(&self,time:SessionTime)->Option<FrameState>{
|
pub fn get_frame_state(&self,time:SessionTime)->Option<FrameState>{
|
||||||
match &self.view_state{
|
match &self.view_state{
|
||||||
ViewState::Play=>Some(self.simulation.get_frame_state(time)),
|
ViewState::Play=>Some(self.simulation.get_frame_state(time,self.last_ray_hit)),
|
||||||
ViewState::Replay(bot_id)=>self.replays.get(bot_id).map(|replay|
|
ViewState::Replay(bot_id)=>self.replays.get(bot_id).map(|replay|
|
||||||
replay.simulation.get_frame_state(time)
|
replay.simulation.get_frame_state(time,None)
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
pub fn debug_raycast_print_model_id_if_changed(&mut self,time:SessionTime){
|
||||||
|
if let Some(frame_state)=self.get_frame_state(time){
|
||||||
|
let ray=strafesnet_common::ray::Ray{
|
||||||
|
origin:frame_state.body.extrapolated_position(self.simulation.timer.time(time)),
|
||||||
|
direction:-frame_state.camera.rotation().z_axis,
|
||||||
|
};
|
||||||
|
let model_id=self.geometry_shared.trace_ray(ray);
|
||||||
|
if model_id!=self.last_ray_hit{
|
||||||
|
println!("hit={model_id:?}");
|
||||||
|
self.last_ray_hit=model_id;
|
||||||
|
if let Some(model_id)=model_id{
|
||||||
|
if let Some(model)=self.geometry_shared.le_models.get(model_id.get() as usize){
|
||||||
|
println!("{}",model.debug_info);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
pub fn user_settings(&self)->&UserSettings{
|
pub fn user_settings(&self)->&UserSettings{
|
||||||
&self.user_settings
|
&self.user_settings
|
||||||
}
|
}
|
||||||
@@ -195,8 +230,8 @@ impl Session{
|
|||||||
// Session emits DoStep
|
// Session emits DoStep
|
||||||
|
|
||||||
impl InstructionConsumer<Instruction<'_>> for Session{
|
impl InstructionConsumer<Instruction<'_>> for Session{
|
||||||
type TimeInner=SessionTimeInner;
|
type Time=SessionTime;
|
||||||
fn process_instruction(&mut self,ins:TimedInstruction<Instruction,Self::TimeInner>){
|
fn process_instruction(&mut self,ins:TimedInstruction<Instruction,Self::Time>){
|
||||||
// repetitive procedure macro
|
// repetitive procedure macro
|
||||||
macro_rules! run_mouse_interpolator_instruction{
|
macro_rules! run_mouse_interpolator_instruction{
|
||||||
($instruction:expr)=>{
|
($instruction:expr)=>{
|
||||||
@@ -243,7 +278,7 @@ impl InstructionConsumer<Instruction<'_>> for Session{
|
|||||||
// what if they pause for 5ms lmao
|
// what if they pause for 5ms lmao
|
||||||
_=self.simulation.timer.set_paused(ins.time,paused);
|
_=self.simulation.timer.set_paused(ins.time,paused);
|
||||||
},
|
},
|
||||||
Instruction::Control(SessionControlInstruction::CopyRecordingIntoReplayAndSpectate)=>{
|
Instruction::Control(SessionControlInstruction::CopyRecordingIntoReplayAndSpectate)=> if let ViewState::Play=self.view_state{
|
||||||
// Bind: B
|
// Bind: B
|
||||||
|
|
||||||
// pause simulation
|
// pause simulation
|
||||||
@@ -281,6 +316,41 @@ impl InstructionConsumer<Instruction<'_>> for Session{
|
|||||||
}
|
}
|
||||||
_=self.simulation.timer.set_paused(ins.time,false);
|
_=self.simulation.timer.set_paused(ins.time,false);
|
||||||
},
|
},
|
||||||
|
Instruction::Control(SessionControlInstruction::SaveReplay)=>{
|
||||||
|
// Bind: N
|
||||||
|
let view_state=core::mem::replace(&mut self.view_state,ViewState::Play);
|
||||||
|
match view_state{
|
||||||
|
ViewState::Play=>(),
|
||||||
|
ViewState::Replay(bot_id)=>if let Some(replay)=self.replays.remove(&bot_id){
|
||||||
|
let mut replays_path=self.directories.replays.clone();
|
||||||
|
let file_name=format!("{}.snfb",ins.time);
|
||||||
|
std::thread::spawn(move ||{
|
||||||
|
std::fs::create_dir_all(replays_path.as_path()).unwrap();
|
||||||
|
replays_path.push(file_name);
|
||||||
|
let file=std::fs::File::create(replays_path).unwrap();
|
||||||
|
strafesnet_snf::bot::write_bot(
|
||||||
|
std::io::BufWriter::new(file),
|
||||||
|
strafesnet_physics::VERSION.get(),
|
||||||
|
replay.recording.instructions
|
||||||
|
).unwrap();
|
||||||
|
println!("Finished writing bot file!");
|
||||||
|
});
|
||||||
|
},
|
||||||
|
}
|
||||||
|
_=self.simulation.timer.set_paused(ins.time,false);
|
||||||
|
},
|
||||||
|
Instruction::Control(SessionControlInstruction::LoadIntoReplayState)=>{
|
||||||
|
// Bind: J
|
||||||
|
let view_state=core::mem::replace(&mut self.view_state,ViewState::Play);
|
||||||
|
match view_state{
|
||||||
|
ViewState::Play=>(),
|
||||||
|
ViewState::Replay(bot_id)=>if let Some(replay)=self.replays.remove(&bot_id){
|
||||||
|
self.recording.instructions=replay.recording.instructions.into_iter().take(replay.next_instruction_id).collect();
|
||||||
|
self.simulation=replay.simulation;
|
||||||
|
},
|
||||||
|
}
|
||||||
|
// don't unpause -- use the replay timer state whether it is pasued or unpaused
|
||||||
|
},
|
||||||
Instruction::Playback(SessionPlaybackInstruction::IncreaseTimescale)=>{
|
Instruction::Playback(SessionPlaybackInstruction::IncreaseTimescale)=>{
|
||||||
match &self.view_state{
|
match &self.view_state{
|
||||||
ViewState::Play=>{
|
ViewState::Play=>{
|
||||||
@@ -323,7 +393,7 @@ impl InstructionConsumer<Instruction<'_>> for Session{
|
|||||||
let time=replay.simulation.timer.time(ins.time+SessionTime::from_secs(5));
|
let time=replay.simulation.timer.time(ins.time+SessionTime::from_secs(5));
|
||||||
replay.simulation.timer.set_time(ins.time,time);
|
replay.simulation.timer.set_time(ins.time,time);
|
||||||
// resimulate the entire playback lol
|
// resimulate the entire playback lol
|
||||||
replay.last_instruction_id=0;
|
replay.next_instruction_id=0;
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -339,6 +409,30 @@ impl InstructionConsumer<Instruction<'_>> for Session{
|
|||||||
self.clear_recording();
|
self.clear_recording();
|
||||||
self.change_map(complete_map);
|
self.change_map(complete_map);
|
||||||
},
|
},
|
||||||
|
Instruction::LoadReplay(bot)=>{
|
||||||
|
// pause simulation
|
||||||
|
_=self.simulation.timer.set_paused(ins.time,true);
|
||||||
|
|
||||||
|
// create recording
|
||||||
|
let recording=Recording::new(bot.instructions);
|
||||||
|
|
||||||
|
// create timer starting at first instruction (or zero if the list is empty)
|
||||||
|
let new_time=recording.instructions.first().map_or(PhysicsTime::ZERO,|ins|ins.time);
|
||||||
|
let timer=Timer::unpaused(ins.time,new_time);
|
||||||
|
|
||||||
|
// create default physics state
|
||||||
|
let simulation=Simulation::new(timer,Default::default());
|
||||||
|
|
||||||
|
// invent a new bot id and insert the replay
|
||||||
|
let bot_id=BotId(self.replays.len() as u32);
|
||||||
|
self.replays.insert(bot_id,Replay::new(
|
||||||
|
recording,
|
||||||
|
simulation,
|
||||||
|
));
|
||||||
|
|
||||||
|
// begin spectate
|
||||||
|
self.view_state=ViewState::Replay(bot_id);
|
||||||
|
},
|
||||||
Instruction::Idle=>{
|
Instruction::Idle=>{
|
||||||
run_mouse_interpolator_instruction!(MouseInterpolatorInstruction::Idle);
|
run_mouse_interpolator_instruction!(MouseInterpolatorInstruction::Idle);
|
||||||
// this just refreshes the replays
|
// this just refreshes the replays
|
||||||
@@ -354,8 +448,8 @@ impl InstructionConsumer<Instruction<'_>> for Session{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl InstructionConsumer<StepInstruction> for Session{
|
impl InstructionConsumer<StepInstruction> for Session{
|
||||||
type TimeInner=SessionTimeInner;
|
type Time=SessionTime;
|
||||||
fn process_instruction(&mut self,ins:TimedInstruction<StepInstruction,Self::TimeInner>){
|
fn process_instruction(&mut self,ins:TimedInstruction<StepInstruction,Self::Time>){
|
||||||
let time=self.simulation.timer.time(ins.time);
|
let time=self.simulation.timer.time(ins.time);
|
||||||
if let Some(instruction)=self.mouse_interpolator.pop_buffered_instruction(ins.set_time(time)){
|
if let Some(instruction)=self.mouse_interpolator.pop_buffered_instruction(ins.set_time(time)){
|
||||||
//record
|
//record
|
||||||
@@ -365,8 +459,8 @@ impl InstructionConsumer<StepInstruction> for Session{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl InstructionEmitter<StepInstruction> for Session{
|
impl InstructionEmitter<StepInstruction> for Session{
|
||||||
type TimeInner=SessionTimeInner;
|
type Time=SessionTime;
|
||||||
fn next_instruction(&self,time_limit:SessionTime)->Option<TimedInstruction<StepInstruction,Self::TimeInner>>{
|
fn next_instruction(&self,time_limit:SessionTime)->Option<TimedInstruction<StepInstruction,Self::Time>>{
|
||||||
self.mouse_interpolator.next_instruction(time_limit)
|
self.mouse_interpolator.next_instruction(time_limit)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
10
engine/settings/Cargo.toml
Normal file
10
engine/settings/Cargo.toml
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
[package]
|
||||||
|
name = "strafesnet_settings"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2024"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
configparser = "3.0.2"
|
||||||
|
directories = "6.0.0"
|
||||||
|
glam = "0.30.0"
|
||||||
|
strafesnet_common = { path = "../../lib/common", registry = "strafesnet" }
|
||||||
8
engine/settings/LICENSE
Normal file
8
engine/settings/LICENSE
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
/*******************************************************
|
||||||
|
* Copyright (C) 2023-2024 Rhys Lloyd <krakow20@gmail.com>
|
||||||
|
*
|
||||||
|
* This file is part of the StrafesNET bhop/surf client.
|
||||||
|
*
|
||||||
|
* StrafesNET can not be copied and/or distributed
|
||||||
|
* without the express permission of Rhys Lloyd
|
||||||
|
*******************************************************/
|
||||||
32
engine/settings/src/directories.rs
Normal file
32
engine/settings/src/directories.rs
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
use crate::settings::{UserSettings,load_user_settings};
|
||||||
|
|
||||||
|
pub struct Directories{
|
||||||
|
pub settings:PathBuf,
|
||||||
|
pub maps:PathBuf,
|
||||||
|
pub replays:PathBuf,
|
||||||
|
}
|
||||||
|
impl Directories{
|
||||||
|
pub fn settings(&self)->UserSettings{
|
||||||
|
load_user_settings(&self.settings)
|
||||||
|
}
|
||||||
|
pub fn user()->Option<Self>{
|
||||||
|
let dirs=directories::ProjectDirs::from("net.strafes","StrafesNET","Strafe Client")?;
|
||||||
|
Some(Self{
|
||||||
|
settings:dirs.config_dir().join("settings.conf"),
|
||||||
|
maps:dirs.cache_dir().join("maps"),
|
||||||
|
// separate directory for remote downloaded replays (cache)
|
||||||
|
// bots:dirs.cache_dir().join("bots"),
|
||||||
|
replays:dirs.data_local_dir().join("replays"),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
pub fn portable()->Result<Self,std::io::Error>{
|
||||||
|
let current_dir=std::env::current_dir()?;
|
||||||
|
Ok(Self{
|
||||||
|
settings:current_dir.join("settings.conf"),
|
||||||
|
maps:current_dir.join("maps"),
|
||||||
|
replays:current_dir.join("replays"),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
2
engine/settings/src/lib.rs
Normal file
2
engine/settings/src/lib.rs
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
pub mod settings;
|
||||||
|
pub mod directories;
|
||||||
@@ -74,9 +74,9 @@ sensitivity_y_from_x_ratio=1
|
|||||||
Sensitivity::DeriveY{x:0.0.001,y:DerivedSensitivity{ratio:1.0}}
|
Sensitivity::DeriveY{x:0.0.001,y:DerivedSensitivity{ratio:1.0}}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
pub fn read_user_settings()->UserSettings{
|
pub fn load_user_settings(path:&std::path::Path)->UserSettings{
|
||||||
let mut cfg=configparser::ini::Ini::new();
|
let mut cfg=configparser::ini::Ini::new();
|
||||||
if let Ok(_)=cfg.load("settings.conf"){
|
if let Ok(_)=cfg.load(path){
|
||||||
let (cfg_fov_x,cfg_fov_y)=(cfg.getfloat("camera","fov_x"),cfg.getfloat("camera","fov_y"));
|
let (cfg_fov_x,cfg_fov_y)=(cfg.getfloat("camera","fov_x"),cfg.getfloat("camera","fov_y"));
|
||||||
let fov=match(cfg_fov_x,cfg_fov_y){
|
let fov=match(cfg_fov_x,cfg_fov_y){
|
||||||
(Ok(Some(fov_x)),Ok(Some(fov_y)))=>Fov::Exactly {
|
(Ok(Some(fov_x)),Ok(Some(fov_y)))=>Fov::Exactly {
|
||||||
@@ -136,4 +136,4 @@ pub fn read_user_settings()->UserSettings{
|
|||||||
}else{
|
}else{
|
||||||
UserSettings::default()
|
UserSettings::default()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
9
integration-testing/Cargo.toml
Normal file
9
integration-testing/Cargo.toml
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
[package]
|
||||||
|
name = "integration-testing"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2024"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
strafesnet_common = { path = "../lib/common", registry = "strafesnet" }
|
||||||
|
strafesnet_physics = { path = "../engine/physics", registry = "strafesnet" }
|
||||||
|
strafesnet_snf = { path = "../lib/snf", registry = "strafesnet" }
|
||||||
249
integration-testing/src/main.rs
Normal file
249
integration-testing/src/main.rs
Normal file
@@ -0,0 +1,249 @@
|
|||||||
|
|
||||||
|
use std::io::Cursor;
|
||||||
|
use std::path::Path;
|
||||||
|
use std::time::Instant;
|
||||||
|
|
||||||
|
use strafesnet_physics::physics::{PhysicsData,PhysicsState,PhysicsContext};
|
||||||
|
|
||||||
|
fn main(){
|
||||||
|
test_determinism().unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(unused)]
|
||||||
|
#[derive(Debug)]
|
||||||
|
enum ReplayError{
|
||||||
|
IO(std::io::Error),
|
||||||
|
SNF(strafesnet_snf::Error),
|
||||||
|
SNFM(strafesnet_snf::map::Error),
|
||||||
|
SNFB(strafesnet_snf::bot::Error),
|
||||||
|
}
|
||||||
|
impl From<std::io::Error> for ReplayError{
|
||||||
|
fn from(value:std::io::Error)->Self{
|
||||||
|
Self::IO(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl From<strafesnet_snf::Error> for ReplayError{
|
||||||
|
fn from(value:strafesnet_snf::Error)->Self{
|
||||||
|
Self::SNF(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl From<strafesnet_snf::map::Error> for ReplayError{
|
||||||
|
fn from(value:strafesnet_snf::map::Error)->Self{
|
||||||
|
Self::SNFM(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl From<strafesnet_snf::bot::Error> for ReplayError{
|
||||||
|
fn from(value:strafesnet_snf::bot::Error)->Self{
|
||||||
|
Self::SNFB(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_entire_file(path:impl AsRef<Path>)->Result<Cursor<Vec<u8>>,std::io::Error>{
|
||||||
|
let data=std::fs::read(path)?;
|
||||||
|
Ok(Cursor::new(data))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run_replay()->Result<(),ReplayError>{
|
||||||
|
println!("loading map file..");
|
||||||
|
let data=read_entire_file("../tools/bhop_maps/5692113331.snfm")?;
|
||||||
|
let map=strafesnet_snf::read_map(data)?.into_complete_map()?;
|
||||||
|
|
||||||
|
println!("loading bot file..");
|
||||||
|
let data=read_entire_file("../tools/replays/535s+159764769ns.snfb")?;
|
||||||
|
let bot=strafesnet_snf::read_bot(data)?.read_all()?;
|
||||||
|
|
||||||
|
// create recording
|
||||||
|
let mut physics_data=PhysicsData::default();
|
||||||
|
println!("generating models..");
|
||||||
|
physics_data.generate_models(&map);
|
||||||
|
println!("simulating...");
|
||||||
|
let mut physics=PhysicsState::default();
|
||||||
|
for ins in bot.instructions{
|
||||||
|
PhysicsContext::run_input_instruction(&mut physics,&physics_data,ins);
|
||||||
|
}
|
||||||
|
match physics.get_finish_time(){
|
||||||
|
Some(time)=>println!("finish time:{}",time),
|
||||||
|
None=>println!("simulation did not end in finished state"),
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
enum DeterminismResult{
|
||||||
|
Deterministic,
|
||||||
|
NonDeterministic,
|
||||||
|
}
|
||||||
|
struct SegmentResult{
|
||||||
|
determinism:DeterminismResult,
|
||||||
|
ticks:u64,
|
||||||
|
nanoseconds:u64,
|
||||||
|
}
|
||||||
|
fn segment_determinism(bot:strafesnet_snf::bot::Segment,physics_data:&PhysicsData)->SegmentResult{
|
||||||
|
// create default physics state
|
||||||
|
let mut physics_deterministic=PhysicsState::default();
|
||||||
|
// create a second physics state
|
||||||
|
let mut physics_filtered=PhysicsState::default();
|
||||||
|
|
||||||
|
// invent a new bot id and insert the replay
|
||||||
|
println!("simulating...");
|
||||||
|
|
||||||
|
let mut non_idle_count=0;
|
||||||
|
let instruction_count=bot.instructions.len();
|
||||||
|
|
||||||
|
let start=Instant::now();
|
||||||
|
|
||||||
|
for (i,ins) in bot.instructions.into_iter().enumerate(){
|
||||||
|
let state_deterministic=physics_deterministic.clone();
|
||||||
|
let state_filtered=physics_filtered.clone();
|
||||||
|
PhysicsContext::run_input_instruction(&mut physics_deterministic,&physics_data,ins.clone());
|
||||||
|
match ins{
|
||||||
|
strafesnet_common::instruction::TimedInstruction{instruction:strafesnet_common::physics::Instruction::Idle,..}=>(),
|
||||||
|
other=>{
|
||||||
|
non_idle_count+=1;
|
||||||
|
// run
|
||||||
|
PhysicsContext::run_input_instruction(&mut physics_filtered,&physics_data,other.clone());
|
||||||
|
// check if position matches
|
||||||
|
let b0=physics_deterministic.camera_body();
|
||||||
|
let b1=physics_filtered.camera_body();
|
||||||
|
if b0.position!=b1.position{
|
||||||
|
let nanoseconds=start.elapsed().as_nanos() as u64;
|
||||||
|
println!("desync at instruction #{}",i);
|
||||||
|
println!("non idle instructions completed={non_idle_count}");
|
||||||
|
println!("instruction #{i}={:?}",other);
|
||||||
|
println!("deterministic state0:\n{state_deterministic:?}");
|
||||||
|
println!("filtered state0:\n{state_filtered:?}");
|
||||||
|
println!("deterministic state1:\n{:?}",physics_deterministic);
|
||||||
|
println!("filtered state1:\n{:?}",physics_filtered);
|
||||||
|
return SegmentResult{
|
||||||
|
determinism:DeterminismResult::NonDeterministic,
|
||||||
|
ticks:1+i as u64+non_idle_count,
|
||||||
|
nanoseconds,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let nanoseconds=start.elapsed().as_nanos() as u64;
|
||||||
|
match physics_deterministic.get_finish_time(){
|
||||||
|
Some(time)=>println!("[with idle] finish time:{}",time),
|
||||||
|
None=>println!("[with idle] simulation did not end in finished state"),
|
||||||
|
}
|
||||||
|
match physics_filtered.get_finish_time(){
|
||||||
|
Some(time)=>println!("[filtered] finish time:{}",time),
|
||||||
|
None=>println!("[filtered] simulation did not end in finished state"),
|
||||||
|
}
|
||||||
|
SegmentResult{
|
||||||
|
determinism:DeterminismResult::Deterministic,
|
||||||
|
ticks:instruction_count as u64+non_idle_count,
|
||||||
|
nanoseconds,
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
type ThreadResult=Result<Option<SegmentResult>,ReplayError>;
|
||||||
|
fn read_and_run(file_path:std::path::PathBuf,physics_data:&PhysicsData)->ThreadResult{
|
||||||
|
let data=read_entire_file(file_path.as_path())?;
|
||||||
|
let bot=strafesnet_snf::read_bot(data)?.read_all()?;
|
||||||
|
println!("Running {:?}",file_path.file_stem());
|
||||||
|
Ok(Some(segment_determinism(bot,physics_data)))
|
||||||
|
}
|
||||||
|
fn do_thread<'a>(s:&'a std::thread::Scope<'a,'_>,file_path:std::path::PathBuf,send:std::sync::mpsc::Sender<ThreadResult>,physics_data:&'a PhysicsData){
|
||||||
|
s.spawn(move ||{
|
||||||
|
let result=read_and_run(file_path,physics_data);
|
||||||
|
// send when thread is complete
|
||||||
|
send.send(result).unwrap();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
fn get_file_path(dir_entry:std::fs::DirEntry)->Result<Option<std::path::PathBuf>,std::io::Error>{
|
||||||
|
Ok(dir_entry.file_type()?.is_file().then_some(
|
||||||
|
dir_entry.path()
|
||||||
|
))
|
||||||
|
}
|
||||||
|
fn test_determinism()->Result<(),ReplayError>{
|
||||||
|
let thread_limit=std::thread::available_parallelism()?.get();
|
||||||
|
println!("loading map file..");
|
||||||
|
let data=read_entire_file("../tools/bhop_maps/5692113331.snfm")?;
|
||||||
|
let map=strafesnet_snf::read_map(data)?.into_complete_map()?;
|
||||||
|
|
||||||
|
let mut physics_data=PhysicsData::default();
|
||||||
|
println!("generating models..");
|
||||||
|
physics_data.generate_models(&map);
|
||||||
|
|
||||||
|
let (send,recv)=std::sync::mpsc::channel();
|
||||||
|
|
||||||
|
let mut read_dir=std::fs::read_dir("../tools/replays")?;
|
||||||
|
|
||||||
|
// promise that &physics_data will outlive the spawned threads
|
||||||
|
let thread_results=std::thread::scope(|s|{
|
||||||
|
let mut thread_results=Vec::new();
|
||||||
|
|
||||||
|
// spawn threads
|
||||||
|
println!("spawning up to {thread_limit} threads...");
|
||||||
|
let mut active_thread_count=0;
|
||||||
|
while active_thread_count<thread_limit{
|
||||||
|
if let Some(dir_entry_result)=read_dir.next(){
|
||||||
|
if let Some(file_path)=get_file_path(dir_entry_result?)?{
|
||||||
|
active_thread_count+=1;
|
||||||
|
do_thread(s,file_path,send.clone(),&physics_data);
|
||||||
|
}
|
||||||
|
}else{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// spawn another thread every time a message is received from the channel
|
||||||
|
println!("riding parallelism wave...");
|
||||||
|
while let Some(dir_entry_result)=read_dir.next(){
|
||||||
|
if let Some(file_path)=get_file_path(dir_entry_result?)?{
|
||||||
|
// wait for a thread to complete
|
||||||
|
thread_results.push(recv.recv().unwrap());
|
||||||
|
do_thread(s,file_path,send.clone(),&physics_data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// wait for remaining threads to complete
|
||||||
|
println!("waiting for all threads to complete...");
|
||||||
|
for _ in 0..active_thread_count{
|
||||||
|
thread_results.push(recv.recv().unwrap());
|
||||||
|
}
|
||||||
|
|
||||||
|
println!("done.");
|
||||||
|
Ok::<_,ReplayError>(thread_results)
|
||||||
|
})?;
|
||||||
|
|
||||||
|
// tally results
|
||||||
|
#[derive(Default)]
|
||||||
|
struct Totals{
|
||||||
|
deterministic:u32,
|
||||||
|
nondeterministic:u32,
|
||||||
|
invalid:u32,
|
||||||
|
error:u32,
|
||||||
|
}
|
||||||
|
let mut nanoseconds=0;
|
||||||
|
let mut ticks=0;
|
||||||
|
let Totals{deterministic,nondeterministic,invalid,error}=thread_results.into_iter().fold(Totals::default(),|mut totals,result|{
|
||||||
|
match result{
|
||||||
|
Ok(Some(segment_result))=>{
|
||||||
|
ticks+=segment_result.ticks;
|
||||||
|
nanoseconds+=segment_result.nanoseconds;
|
||||||
|
match segment_result.determinism{
|
||||||
|
DeterminismResult::Deterministic=>totals.deterministic+=1,
|
||||||
|
DeterminismResult::NonDeterministic=>totals.nondeterministic+=1,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Ok(None)=>totals.invalid+=1,
|
||||||
|
Err(_)=>totals.error+=1,
|
||||||
|
}
|
||||||
|
totals
|
||||||
|
});
|
||||||
|
|
||||||
|
println!("deterministic={deterministic}");
|
||||||
|
println!("nondeterministic={nondeterministic}");
|
||||||
|
println!("invalid={invalid}");
|
||||||
|
println!("error={error}");
|
||||||
|
println!("average ticks/s per core: {}",ticks*1_000_000_000/nanoseconds);
|
||||||
|
|
||||||
|
assert!(nondeterministic==0);
|
||||||
|
assert!(invalid==0);
|
||||||
|
assert!(error==0);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "strafesnet_bsp_loader"
|
name = "strafesnet_bsp_loader"
|
||||||
version = "0.2.2"
|
version = "0.3.0"
|
||||||
edition = "2021"
|
edition = "2024"
|
||||||
repository = "https://git.itzana.me/StrafesNET/strafe-project"
|
repository = "https://git.itzana.me/StrafesNET/strafe-project"
|
||||||
license = "MIT OR Apache-2.0"
|
license = "MIT OR Apache-2.0"
|
||||||
description = "Convert Valve BSP files to StrafesNET data structures."
|
description = "Convert Valve BSP files to StrafesNET data structures."
|
||||||
@@ -10,7 +10,10 @@ authors = ["Rhys Lloyd <krakow20@gmail.com>"]
|
|||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
glam = "0.29.0"
|
glam = "0.30.0"
|
||||||
strafesnet_common = { path = "../common", registry = "strafesnet" }
|
strafesnet_common = { version = "0.6.0", path = "../common", registry = "strafesnet" }
|
||||||
vbsp = "0.6.0"
|
strafesnet_deferred_loader = { version = "0.5.0", path = "../deferred_loader", registry = "strafesnet" }
|
||||||
|
vbsp = "0.8.0"
|
||||||
|
vbsp-entities-css = "0.6.0"
|
||||||
vmdl = "0.2.0"
|
vmdl = "0.2.0"
|
||||||
|
vpk = "0.3.0"
|
||||||
|
|||||||
342
lib/bsp_loader/src/brush.rs
Normal file
342
lib/bsp_loader/src/brush.rs
Normal file
@@ -0,0 +1,342 @@
|
|||||||
|
use strafesnet_common::integer::Planar64;
|
||||||
|
use strafesnet_common::{model,integer};
|
||||||
|
use strafesnet_common::integer::{vec3::Vector3,Fixed,Ratio};
|
||||||
|
|
||||||
|
use crate::{valve_transform_normal,valve_transform_dist};
|
||||||
|
|
||||||
|
#[derive(Hash,Eq,PartialEq)]
|
||||||
|
struct Face{
|
||||||
|
normal:integer::Planar64Vec3,
|
||||||
|
dot:integer::Planar64,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct Faces{
|
||||||
|
faces:Vec<Vec<integer::Planar64Vec3>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn solve3(c0:&Face,c1:&Face,c2:&Face)->Option<Ratio<Vector3<Fixed<3,96>>,Fixed<3,96>>>{
|
||||||
|
let n0_n1=c0.normal.cross(c1.normal);
|
||||||
|
let det=c2.normal.dot(n0_n1);
|
||||||
|
if det.abs().is_zero(){
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
Some((
|
||||||
|
c1.normal.cross(c2.normal)*c0.dot
|
||||||
|
+c2.normal.cross(c0.normal)*c1.dot
|
||||||
|
+c0.normal.cross(c1.normal)*c2.dot
|
||||||
|
)/det)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum PlanesToFacesError{
|
||||||
|
InitFace1,
|
||||||
|
InitFace2,
|
||||||
|
InitIntersection,
|
||||||
|
FindNewIntersection,
|
||||||
|
EmptyFaces,
|
||||||
|
InfiniteLoop1,
|
||||||
|
InfiniteLoop2,
|
||||||
|
}
|
||||||
|
impl std::fmt::Display for PlanesToFacesError{
|
||||||
|
fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{
|
||||||
|
write!(f,"{self:?}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl core::error::Error for PlanesToFacesError{}
|
||||||
|
|
||||||
|
fn planes_to_faces(face_list:std::collections::HashSet<Face>)->Result<Faces,PlanesToFacesError>{
|
||||||
|
let mut faces=Vec::new();
|
||||||
|
// for each face, determine one edge at a time until you complete the face
|
||||||
|
'face: for face0 in &face_list{
|
||||||
|
// 1. find first edge
|
||||||
|
// 2. follow edges around face
|
||||||
|
|
||||||
|
// === finding first edge ===
|
||||||
|
// 1. pick the most perpendicular set of 3 faces
|
||||||
|
// 2. check if any faces occlude the intersection
|
||||||
|
// 3. use this test to replace left and right alternating until they are not occluded
|
||||||
|
|
||||||
|
// find the most perpendicular face to face0
|
||||||
|
let mut face1=face_list.iter().min_by_key(|&p|{
|
||||||
|
face0.normal.dot(p.normal).abs()
|
||||||
|
}).ok_or(PlanesToFacesError::InitFace1)?;
|
||||||
|
|
||||||
|
// direction of edge formed by face0 x face1
|
||||||
|
let edge_dir=face0.normal.cross(face1.normal);
|
||||||
|
|
||||||
|
// find the most perpendicular face to both face0 and face1
|
||||||
|
let mut face2=face_list.iter().max_by_key(|&p|{
|
||||||
|
// find the best *oriented* face (no .abs())
|
||||||
|
edge_dir.dot(p.normal)
|
||||||
|
}).ok_or(PlanesToFacesError::InitFace2)?;
|
||||||
|
|
||||||
|
let mut detect_loop=200u8;
|
||||||
|
|
||||||
|
let mut intersection=solve3(face0,face1,face2).ok_or(PlanesToFacesError::InitIntersection)?;
|
||||||
|
|
||||||
|
// repeatedly update face1, face2 until all faces form part of the convex solid
|
||||||
|
'find: loop{
|
||||||
|
detect_loop=detect_loop.checked_sub(1).ok_or(PlanesToFacesError::InfiniteLoop1)?;
|
||||||
|
// test if any *other* faces occlude the intersection
|
||||||
|
for new_face in &face_list{
|
||||||
|
// new face occludes intersection point
|
||||||
|
if (new_face.dot.fix_2()/Planar64::ONE).lt_ratio(new_face.normal.dot(intersection.num)/intersection.den){
|
||||||
|
// replace one of the faces with the new face
|
||||||
|
// dont' try to replace face0 because we are exploring that face in particular
|
||||||
|
if let Some(new_intersection)=solve3(face0,new_face,face2){
|
||||||
|
// face1 does not occlude (or intersect) the new intersection
|
||||||
|
if (face1.dot.fix_2()/Planar64::ONE).gt_ratio(face1.normal.dot(new_intersection.num)/new_intersection.den){
|
||||||
|
face1=new_face;
|
||||||
|
intersection=new_intersection;
|
||||||
|
continue 'find;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(new_intersection)=solve3(face0,face1,new_face){
|
||||||
|
// face2 does not occlude (or intersect) the new intersection
|
||||||
|
if (face2.dot.fix_2()/Planar64::ONE).gt_ratio(face2.normal.dot(new_intersection.num)/new_intersection.den){
|
||||||
|
face2=new_face;
|
||||||
|
intersection=new_intersection;
|
||||||
|
continue 'find;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// we have found a set of faces for which the intersection is on the convex solid
|
||||||
|
break 'find;
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if face0 must go, meaning it is a degenerate face and does not contribute anything to the convex solid
|
||||||
|
for new_face in &face_list{
|
||||||
|
if core::ptr::eq(face0,new_face){
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if core::ptr::eq(face1,new_face){
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if core::ptr::eq(face2,new_face){
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// new_face occludes intersection meaning intersection is not on convex solid and face0 is degenrate
|
||||||
|
if (new_face.dot.fix_2()/Planar64::ONE).lt_ratio(new_face.normal.dot(intersection.num)/intersection.den){
|
||||||
|
// abort! reject face0 entirely
|
||||||
|
continue 'face;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// === follow edges around face ===
|
||||||
|
// Note that we chose face2 such that the 3 faces create a particular winding order.
|
||||||
|
// If we choose a consistent face to follow (face1, face2) it will always wind with a consistent chirality
|
||||||
|
|
||||||
|
let mut detect_loop=200u8;
|
||||||
|
|
||||||
|
// keep looping until we meet this face again
|
||||||
|
let face1=face1;
|
||||||
|
let mut face=Vec::new();
|
||||||
|
loop{
|
||||||
|
// push point onto vertices
|
||||||
|
// problem: this may push a vertex that does not fit in the fixed point range and is thus meaningless
|
||||||
|
face.push(intersection.divide().fix_1());
|
||||||
|
|
||||||
|
// we looped back around to face1, we're done!
|
||||||
|
if core::ptr::eq(face1,face2){
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// the measure
|
||||||
|
let edge_dir=face0.normal.cross(face2.normal);
|
||||||
|
|
||||||
|
// the dot product to beat
|
||||||
|
let d_intersection=edge_dir.dot(intersection.num)/intersection.den;
|
||||||
|
|
||||||
|
// find the next face moving clockwise around face0
|
||||||
|
let (new_face,new_intersection,_)=face_list.iter().filter_map(|new_face|{
|
||||||
|
// ignore faces that are part of the current edge
|
||||||
|
if core::ptr::eq(face0,new_face)
|
||||||
|
|core::ptr::eq(face2,new_face){
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let new_intersection=solve3(face0,face2,new_face)?;
|
||||||
|
|
||||||
|
// the d value must be larger
|
||||||
|
let d_new_intersection=edge_dir.dot(new_intersection.num)/new_intersection.den;
|
||||||
|
if d_new_intersection.le_ratio(d_intersection){
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
Some((new_face,new_intersection,d_new_intersection))
|
||||||
|
}).min_by_key(|&(_,_,d)|d).ok_or(PlanesToFacesError::FindNewIntersection)?;
|
||||||
|
|
||||||
|
face2=new_face;
|
||||||
|
intersection=new_intersection;
|
||||||
|
|
||||||
|
detect_loop=detect_loop.checked_sub(1).ok_or(PlanesToFacesError::InfiniteLoop2)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
faces.push(face);
|
||||||
|
}
|
||||||
|
|
||||||
|
if faces.is_empty(){
|
||||||
|
Err(PlanesToFacesError::EmptyFaces)
|
||||||
|
}else{
|
||||||
|
Ok(Faces{
|
||||||
|
faces,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum BrushToMeshError{
|
||||||
|
SliceBrushSides,
|
||||||
|
MissingPlane,
|
||||||
|
InvalidFaceCount{
|
||||||
|
count:usize,
|
||||||
|
},
|
||||||
|
InvalidPlanes(PlanesToFacesError),
|
||||||
|
}
|
||||||
|
impl std::fmt::Display for BrushToMeshError{
|
||||||
|
fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{
|
||||||
|
write!(f,"{self:?}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl core::error::Error for BrushToMeshError{}
|
||||||
|
|
||||||
|
pub fn faces_to_mesh(faces:Vec<Vec<integer::Planar64Vec3>>)->model::Mesh{
|
||||||
|
// generate the mesh
|
||||||
|
let mut mb=model::MeshBuilder::new();
|
||||||
|
let color=mb.acquire_color_id(glam::Vec4::ONE);
|
||||||
|
let tex=mb.acquire_tex_id(glam::Vec2::ZERO);
|
||||||
|
// normals are ignored by physics
|
||||||
|
let normal=mb.acquire_normal_id(integer::vec3::ZERO);
|
||||||
|
|
||||||
|
let polygon_list=faces.into_iter().map(|face|{
|
||||||
|
face.into_iter().map(|pos|{
|
||||||
|
let pos=mb.acquire_pos_id(pos);
|
||||||
|
mb.acquire_vertex_id(model::IndexedVertex{
|
||||||
|
pos,
|
||||||
|
tex,
|
||||||
|
normal,
|
||||||
|
color,
|
||||||
|
})
|
||||||
|
}).collect()
|
||||||
|
}).collect();
|
||||||
|
|
||||||
|
let polygon_groups=vec![model::PolygonGroup::PolygonList(model::PolygonList::new(polygon_list))];
|
||||||
|
let physics_groups=vec![model::IndexedPhysicsGroup{
|
||||||
|
groups:vec![model::PolygonGroupId::new(0)],
|
||||||
|
}];
|
||||||
|
let graphics_groups=vec![];
|
||||||
|
|
||||||
|
mb.build(polygon_groups,graphics_groups,physics_groups)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn brush_to_mesh(bsp:&vbsp::Bsp,brush:&vbsp::Brush)->Result<model::Mesh,BrushToMeshError>{
|
||||||
|
let brush_start_idx=brush.brush_side as usize;
|
||||||
|
let sides_range=brush_start_idx..brush_start_idx+brush.num_brush_sides as usize;
|
||||||
|
let sides=bsp.brush_sides.get(sides_range).ok_or(BrushToMeshError::SliceBrushSides)?;
|
||||||
|
let face_list=sides.iter().map(|side|{
|
||||||
|
// The so-called tumor brushes have TRIGGER bit set
|
||||||
|
// but also ignore visleaf hint and skip sides
|
||||||
|
const TUMOR:vbsp::TextureFlags=vbsp::TextureFlags::HINT.union(vbsp::TextureFlags::SKIP).union(vbsp::TextureFlags::TRIGGER);
|
||||||
|
if let Some(texture_info)=bsp.texture_info(side.texture_info as usize){
|
||||||
|
if texture_info.flags.intersects(TUMOR){
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let plane=bsp.plane(side.plane as usize)?;
|
||||||
|
Some(Face{
|
||||||
|
normal:valve_transform_normal(plane.normal.into()),
|
||||||
|
dot:valve_transform_dist(plane.dist.into()),
|
||||||
|
})
|
||||||
|
}).collect::<Option<std::collections::HashSet<_>>>().ok_or(BrushToMeshError::MissingPlane)?;
|
||||||
|
|
||||||
|
if face_list.len()<4{
|
||||||
|
return Err(BrushToMeshError::InvalidFaceCount{count:face_list.len()});
|
||||||
|
}
|
||||||
|
|
||||||
|
let faces=planes_to_faces(face_list).map_err(BrushToMeshError::InvalidPlanes)?;
|
||||||
|
|
||||||
|
let mesh=faces_to_mesh(faces.faces);
|
||||||
|
|
||||||
|
Ok(mesh)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn unit_cube()->model::Mesh{
|
||||||
|
let face_list=[
|
||||||
|
Face{normal:integer::vec3::X,dot:Planar64::ONE},
|
||||||
|
Face{normal:integer::vec3::Y,dot:Planar64::ONE},
|
||||||
|
Face{normal:integer::vec3::Z,dot:Planar64::ONE},
|
||||||
|
Face{normal:integer::vec3::NEG_X,dot:Planar64::ONE},
|
||||||
|
Face{normal:integer::vec3::NEG_Y,dot:Planar64::ONE},
|
||||||
|
Face{normal:integer::vec3::NEG_Z,dot:Planar64::ONE},
|
||||||
|
].into_iter().collect();
|
||||||
|
let faces=planes_to_faces(face_list).unwrap();
|
||||||
|
let mesh=faces_to_mesh(faces.faces);
|
||||||
|
mesh
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test{
|
||||||
|
use super::*;
|
||||||
|
#[test]
|
||||||
|
fn test_cube(){
|
||||||
|
let face_list=[
|
||||||
|
Face{normal:integer::vec3::X,dot:Planar64::ONE},
|
||||||
|
Face{normal:integer::vec3::Y,dot:Planar64::ONE},
|
||||||
|
Face{normal:integer::vec3::Z,dot:Planar64::ONE},
|
||||||
|
Face{normal:integer::vec3::NEG_X,dot:Planar64::ONE},
|
||||||
|
Face{normal:integer::vec3::NEG_Y,dot:Planar64::ONE},
|
||||||
|
Face{normal:integer::vec3::NEG_Z,dot:Planar64::ONE},
|
||||||
|
].into_iter().collect();
|
||||||
|
let faces=planes_to_faces(face_list).unwrap();
|
||||||
|
assert_eq!(faces.faces.len(),6);
|
||||||
|
dbg!(faces);
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn test_cube_with_degernate_face(){
|
||||||
|
let face_list=[
|
||||||
|
Face{normal:integer::vec3::X,dot:Planar64::ONE},
|
||||||
|
Face{normal:integer::vec3::Y,dot:Planar64::ONE},
|
||||||
|
Face{normal:integer::vec3::Z,dot:Planar64::ONE},
|
||||||
|
Face{normal:integer::vec3::NEG_X,dot:Planar64::ONE},
|
||||||
|
Face{normal:integer::vec3::NEG_Y,dot:Planar64::ONE},
|
||||||
|
Face{normal:integer::vec3::NEG_Z,dot:Planar64::ONE},
|
||||||
|
Face{normal:integer::vec3::NEG_Z,dot:Planar64::EPSILON},
|
||||||
|
].into_iter().collect();
|
||||||
|
let faces=planes_to_faces(face_list).unwrap();
|
||||||
|
assert_eq!(faces.faces.len(),6);
|
||||||
|
dbg!(faces);
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn test_cube_with_degernate_face2(){
|
||||||
|
let face_list=[
|
||||||
|
Face{normal:integer::vec3::X,dot:Planar64::ONE},
|
||||||
|
Face{normal:integer::vec3::Y,dot:Planar64::ONE},
|
||||||
|
Face{normal:integer::vec3::Z,dot:Planar64::ONE},
|
||||||
|
Face{normal:integer::vec3::NEG_X,dot:Planar64::ONE},
|
||||||
|
Face{normal:integer::vec3::NEG_Y,dot:Planar64::ONE},
|
||||||
|
Face{normal:integer::vec3::NEG_Z,dot:Planar64::ONE},
|
||||||
|
Face{normal:integer::vec3::NEG_X+integer::vec3::NEG_Z,dot:-Planar64::EPSILON},
|
||||||
|
].into_iter().collect();
|
||||||
|
let faces=planes_to_faces(face_list).unwrap();
|
||||||
|
assert_eq!(faces.faces.len(),5);
|
||||||
|
dbg!(faces);
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn test_cube_with_degernate_face3(){
|
||||||
|
let face_list=[
|
||||||
|
Face{normal:integer::vec3::X,dot:Planar64::ONE},
|
||||||
|
Face{normal:integer::vec3::Y,dot:Planar64::ONE},
|
||||||
|
Face{normal:integer::vec3::Z,dot:Planar64::ONE},
|
||||||
|
Face{normal:integer::vec3::NEG_X,dot:Planar64::ONE},
|
||||||
|
Face{normal:integer::vec3::NEG_Y,dot:Planar64::ONE},
|
||||||
|
Face{normal:integer::vec3::NEG_Z,dot:Planar64::ONE},
|
||||||
|
Face{normal:integer::vec3::NEG_X+integer::vec3::NEG_Z,dot:Planar64::EPSILON},
|
||||||
|
].into_iter().collect();
|
||||||
|
let faces=planes_to_faces(face_list).unwrap();
|
||||||
|
assert_eq!(faces.faces.len(),7);
|
||||||
|
dbg!(faces);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,45 +1,157 @@
|
|||||||
use strafesnet_common::{map,model,integer,gameplay_attributes};
|
use std::borrow::Cow;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
const VALVE_SCALE:f32=1.0/16.0;
|
use vbsp_entities_css::Entity;
|
||||||
fn valve_transform([x,y,z]:[f32;3])->integer::Planar64Vec3{
|
|
||||||
integer::vec3::try_from_f32_array([x*VALVE_SCALE,z*VALVE_SCALE,-y*VALVE_SCALE]).unwrap()
|
use strafesnet_common::{map,model,integer,gameplay_attributes as attr};
|
||||||
|
use strafesnet_deferred_loader::deferred_loader::{MeshDeferredLoader,RenderConfigDeferredLoader};
|
||||||
|
use strafesnet_deferred_loader::mesh::Meshes;
|
||||||
|
use strafesnet_deferred_loader::texture::{RenderConfigs,Texture};
|
||||||
|
use strafesnet_common::gameplay_modes::{self as modes,NormalizedMode,NormalizedModes,Mode,Stage};
|
||||||
|
|
||||||
|
use crate::valve_transform;
|
||||||
|
|
||||||
|
fn ingest_vertex(
|
||||||
|
mb:&mut model::MeshBuilder,
|
||||||
|
world_position:vbsp::Vector,
|
||||||
|
texture_transform_u:glam::Vec4,
|
||||||
|
texture_transform_v:glam::Vec4,
|
||||||
|
normal:model::NormalId,
|
||||||
|
color:model::ColorId,
|
||||||
|
)->model::VertexId{
|
||||||
|
//world_model.origin seems to always be 0,0,0
|
||||||
|
let vertex_xyz=world_position.into();
|
||||||
|
let pos=mb.acquire_pos_id(valve_transform(vertex_xyz));
|
||||||
|
|
||||||
|
//calculate texture coordinates
|
||||||
|
let pos_4d=glam::Vec3::from_array(vertex_xyz).extend(1.0);
|
||||||
|
let tex=glam::vec2(texture_transform_u.dot(pos_4d),texture_transform_v.dot(pos_4d));
|
||||||
|
let tex=mb.acquire_tex_id(tex);
|
||||||
|
|
||||||
|
mb.acquire_vertex_id(model::IndexedVertex{
|
||||||
|
pos,
|
||||||
|
tex,
|
||||||
|
normal,
|
||||||
|
color,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
pub fn convert_bsp<AcquireRenderConfigId,AcquireMeshId>(
|
|
||||||
bsp:&vbsp::Bsp,
|
|
||||||
mut acquire_render_config_id:AcquireRenderConfigId,
|
|
||||||
mut acquire_mesh_id:AcquireMeshId
|
|
||||||
)->PartialMap1
|
|
||||||
where
|
|
||||||
AcquireRenderConfigId:FnMut(Option<&str>)->model::RenderConfigId,
|
|
||||||
AcquireMeshId:FnMut(&str)->model::MeshId,
|
|
||||||
{
|
|
||||||
//figure out real attributes later
|
|
||||||
let mut unique_attributes=Vec::new();
|
|
||||||
unique_attributes.push(gameplay_attributes::CollisionAttributes::Decoration);
|
|
||||||
const TEMP_TOUCH_ME_ATTRIBUTE:gameplay_attributes::CollisionAttributesId=gameplay_attributes::CollisionAttributesId::new(0);
|
|
||||||
|
|
||||||
let mut prop_mesh_count=0;
|
#[derive(Debug,Clone,Copy,Hash,Eq,PartialEq)]
|
||||||
|
enum AddBrush{
|
||||||
|
Available(model::ModelId),
|
||||||
|
Deferred(model::ModelId),
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add_brush<'a>(
|
||||||
|
mesh_deferred_loader:&mut MeshDeferredLoader<&'a str>,
|
||||||
|
world_models:&mut Vec<model::Model>,
|
||||||
|
prop_models:&mut Vec<model::Model>,
|
||||||
|
model:&'a str,
|
||||||
|
origin:vbsp::Vector,
|
||||||
|
rendercolor:vbsp::Color,
|
||||||
|
attributes:attr::CollisionAttributesId,
|
||||||
|
debug_info:model::DebugInfo,
|
||||||
|
)->Option<AddBrush>{
|
||||||
|
let transform=integer::Planar64Affine3::from_translation(
|
||||||
|
valve_transform(origin.into())
|
||||||
|
);
|
||||||
|
let color=(glam::Vec3::from_array([
|
||||||
|
rendercolor.r as f32,
|
||||||
|
rendercolor.g as f32,
|
||||||
|
rendercolor.b as f32
|
||||||
|
])/255.0).extend(1.0);
|
||||||
|
|
||||||
|
match model.chars().next(){
|
||||||
|
// The first character of brush.model is '*'
|
||||||
|
Some('*')=>match model[1..].parse(){
|
||||||
|
Ok(mesh_id)=>{
|
||||||
|
let mesh=model::MeshId::new(mesh_id);
|
||||||
|
let model_id=model::ModelId::new(world_models.len() as u32);
|
||||||
|
world_models.push(
|
||||||
|
model::Model{mesh,attributes,transform,color,debug_info}
|
||||||
|
);
|
||||||
|
Some(AddBrush::Available(model_id))
|
||||||
|
},
|
||||||
|
Err(e)=>{
|
||||||
|
println!("Brush model int parse error: {e} model={model}");
|
||||||
|
None
|
||||||
|
},
|
||||||
|
},
|
||||||
|
_=>{
|
||||||
|
let mesh=mesh_deferred_loader.acquire_mesh_id(model);
|
||||||
|
let model_id=model::ModelId::new(prop_models.len() as u32);
|
||||||
|
prop_models.push(
|
||||||
|
model::Model{mesh,attributes,transform,color,debug_info}
|
||||||
|
);
|
||||||
|
Some(AddBrush::Deferred(model_id))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn convert<'a>(
|
||||||
|
bsp:&'a crate::Bsp,
|
||||||
|
render_config_deferred_loader:&mut RenderConfigDeferredLoader<Cow<'a,str>>,
|
||||||
|
mesh_deferred_loader:&mut MeshDeferredLoader<&'a str>,
|
||||||
|
)->PartialMap1<'a>{
|
||||||
|
let bsp=bsp.as_ref();
|
||||||
|
//figure out real attributes later
|
||||||
|
let unique_attributes=vec![
|
||||||
|
attr::CollisionAttributes::Decoration,
|
||||||
|
attr::CollisionAttributes::contact_default(),
|
||||||
|
attr::CollisionAttributes::intersect_default(),
|
||||||
|
// ladder
|
||||||
|
attr::CollisionAttributes::Contact(
|
||||||
|
attr::ContactAttributes{
|
||||||
|
contacting:attr::ContactingAttributes{
|
||||||
|
contact_behaviour:Some(attr::ContactingBehaviour::Ladder(
|
||||||
|
attr::ContactingLadder{sticky:true}
|
||||||
|
))
|
||||||
|
},
|
||||||
|
general:attr::GeneralAttributes::default(),
|
||||||
|
}
|
||||||
|
),
|
||||||
|
// water
|
||||||
|
attr::CollisionAttributes::Intersect(
|
||||||
|
attr::IntersectAttributes{
|
||||||
|
intersecting:attr::IntersectingAttributes{
|
||||||
|
water:Some(attr::IntersectingWater{
|
||||||
|
viscosity:integer::Planar64::ONE,
|
||||||
|
density:integer::Planar64::ONE,
|
||||||
|
velocity:integer::vec3::ZERO,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
general:attr::GeneralAttributes::default(),
|
||||||
|
}
|
||||||
|
),
|
||||||
|
];
|
||||||
|
const ATTRIBUTE_DECORATION:attr::CollisionAttributesId=attr::CollisionAttributesId::new(0);
|
||||||
|
const ATTRIBUTE_CONTACT_DEFAULT:attr::CollisionAttributesId=attr::CollisionAttributesId::new(1);
|
||||||
|
const ATTRIBUTE_INTERSECT_DEFAULT:attr::CollisionAttributesId=attr::CollisionAttributesId::new(2);
|
||||||
|
const ATTRIBUTE_LADDER_DEFAULT:attr::CollisionAttributesId=attr::CollisionAttributesId::new(3);
|
||||||
|
const ATTRIBUTE_WATER_DEFAULT:attr::CollisionAttributesId=attr::CollisionAttributesId::new(4);
|
||||||
|
|
||||||
//declare all prop models to Loader
|
//declare all prop models to Loader
|
||||||
let prop_models=bsp.static_props().map(|prop|{
|
let mut prop_models=bsp.static_props().map(|prop|{
|
||||||
|
const DEG_TO_RAD:f32=std::f32::consts::TAU/360.0;
|
||||||
//get or create mesh_id
|
//get or create mesh_id
|
||||||
let mesh_id=acquire_mesh_id(prop.model());
|
let mesh_id=mesh_deferred_loader.acquire_mesh_id(prop.model());
|
||||||
//not the most failsafe code but this is just for the map tool lmao
|
|
||||||
if prop_mesh_count==mesh_id.get(){
|
|
||||||
prop_mesh_count+=1;
|
|
||||||
};
|
|
||||||
let placement=prop.as_prop_placement();
|
|
||||||
model::Model{
|
model::Model{
|
||||||
mesh:mesh_id,
|
mesh:mesh_id,
|
||||||
attributes:TEMP_TOUCH_ME_ATTRIBUTE,
|
attributes:ATTRIBUTE_DECORATION,
|
||||||
transform:integer::Planar64Affine3::new(
|
transform:integer::Planar64Affine3::new(
|
||||||
integer::mat3::try_from_f32_array_2d((
|
integer::mat3::try_from_f32_array_2d(
|
||||||
glam::Mat3A::from_diagonal(glam::Vec3::splat(placement.scale))
|
|
||||||
//TODO: figure this out
|
//TODO: figure this out
|
||||||
*glam::Mat3A::from_quat(glam::Quat::from_array(placement.rotation.into()))
|
glam::Mat3A::from_euler(
|
||||||
).to_cols_array_2d()).unwrap(),
|
glam::EulerRot::XYZ,
|
||||||
valve_transform(placement.origin.into()),
|
prop.angles.pitch*DEG_TO_RAD,
|
||||||
|
prop.angles.yaw*DEG_TO_RAD,
|
||||||
|
prop.angles.roll*DEG_TO_RAD
|
||||||
|
).to_cols_array_2d()
|
||||||
|
).unwrap(),
|
||||||
|
valve_transform(prop.origin.into()),
|
||||||
),
|
),
|
||||||
color:glam::Vec4::ONE,
|
color:glam::Vec4::ONE,
|
||||||
|
debug_info:model::DebugInfo::Prop,
|
||||||
}
|
}
|
||||||
}).collect();
|
}).collect();
|
||||||
|
|
||||||
@@ -47,14 +159,12 @@ where
|
|||||||
|
|
||||||
//the generated MeshIds in here will collide with the Loader Mesh Ids
|
//the generated MeshIds in here will collide with the Loader Mesh Ids
|
||||||
//but I can't think of a good workaround other than just remapping one later.
|
//but I can't think of a good workaround other than just remapping one later.
|
||||||
let world_meshes:Vec<model::Mesh>=bsp.models().map(|world_model|{
|
let mut world_meshes:Vec<model::Mesh>=bsp.models().map(|world_model|{
|
||||||
//non-deduplicated
|
let mut mb=model::MeshBuilder::new();
|
||||||
let mut spam_pos=Vec::new();
|
|
||||||
let mut spam_tex=Vec::new();
|
let color=mb.acquire_color_id(glam::Vec4::ONE);
|
||||||
let mut spam_normal=Vec::new();
|
|
||||||
let mut spam_vertices=Vec::new();
|
|
||||||
let mut graphics_groups=Vec::new();
|
let mut graphics_groups=Vec::new();
|
||||||
let mut physics_group=model::IndexedPhysicsGroup::default();
|
let mut render_id_to_graphics_group_id=HashMap::new();
|
||||||
let polygon_groups=world_model.faces().enumerate().map(|(polygon_group_id,face)|{
|
let polygon_groups=world_model.faces().enumerate().map(|(polygon_group_id,face)|{
|
||||||
let polygon_group_id=model::PolygonGroupId::new(polygon_group_id as u32);
|
let polygon_group_id=model::PolygonGroupId::new(polygon_group_id as u32);
|
||||||
let face_texture=face.texture();
|
let face_texture=face.texture();
|
||||||
@@ -63,184 +173,480 @@ where
|
|||||||
let texture_transform_u=glam::Vec4::from_array(face_texture.texture_transforms_u)/(face_texture_data.width as f32);
|
let texture_transform_u=glam::Vec4::from_array(face_texture.texture_transforms_u)/(face_texture_data.width as f32);
|
||||||
let texture_transform_v=glam::Vec4::from_array(face_texture.texture_transforms_v)/(face_texture_data.height as f32);
|
let texture_transform_v=glam::Vec4::from_array(face_texture.texture_transforms_v)/(face_texture_data.height as f32);
|
||||||
|
|
||||||
//this automatically figures out what the texture is trying to do and creates
|
|
||||||
//a render config for it, and then returns the id to that render config
|
|
||||||
let render_id=acquire_render_config_id(Some(face_texture_data.name()));
|
|
||||||
|
|
||||||
//normal
|
//normal
|
||||||
let normal=face.normal();
|
let normal=mb.acquire_normal_id(valve_transform(face.normal().into()));
|
||||||
let normal_idx=spam_normal.len() as u32;
|
let mut polygon_iter=face.vertex_positions().map(|vertex_position|
|
||||||
spam_normal.push(valve_transform(normal.into()));
|
world_model.origin+vertex_position
|
||||||
let mut polygon_iter=face.vertex_positions().map(|vertex_position|{
|
);
|
||||||
//world_model.origin seems to always be 0,0,0
|
|
||||||
let vertex_xyz=(world_model.origin+vertex_position).into();
|
|
||||||
let pos_idx=spam_pos.len();
|
|
||||||
spam_pos.push(valve_transform(vertex_xyz));
|
|
||||||
|
|
||||||
//calculate texture coordinates
|
|
||||||
let pos=glam::Vec3::from_array(vertex_xyz).extend(1.0);
|
|
||||||
let tex=glam::vec2(texture_transform_u.dot(pos),texture_transform_v.dot(pos));
|
|
||||||
let tex_idx=spam_tex.len() as u32;
|
|
||||||
spam_tex.push(tex);
|
|
||||||
|
|
||||||
let vertex_id=model::VertexId::new(spam_vertices.len() as u32);
|
|
||||||
spam_vertices.push(model::IndexedVertex{
|
|
||||||
pos:model::PositionId::new(pos_idx as u32),
|
|
||||||
tex:model::TextureCoordinateId::new(tex_idx as u32),
|
|
||||||
normal:model::NormalId::new(normal_idx),
|
|
||||||
color:model::ColorId::new(0),
|
|
||||||
});
|
|
||||||
vertex_id
|
|
||||||
});
|
|
||||||
let polygon_list=std::iter::from_fn(move||{
|
let polygon_list=std::iter::from_fn(move||{
|
||||||
match (polygon_iter.next(),polygon_iter.next(),polygon_iter.next()){
|
match (polygon_iter.next(),polygon_iter.next(),polygon_iter.next()){
|
||||||
(Some(v1),Some(v2),Some(v3))=>Some(vec![v1,v2,v3]),
|
(Some(v1),Some(v2),Some(v3))=>Some([v1,v2,v3]),
|
||||||
//ignore extra vertices, not sure what to do in this case, failing the whole conversion could be appropriate
|
//ignore extra vertices, not sure what to do in this case, failing the whole conversion could be appropriate
|
||||||
_=>None,
|
_=>None,
|
||||||
}
|
}
|
||||||
|
}).map(|triplet|{
|
||||||
|
triplet.map(|world_position|
|
||||||
|
ingest_vertex(&mut mb,world_position,texture_transform_u,texture_transform_v,normal,color)
|
||||||
|
).to_vec()
|
||||||
}).collect();
|
}).collect();
|
||||||
if face.is_visible(){
|
if face.is_visible(){
|
||||||
//TODO: deduplicate graphics groups by render id
|
//this automatically figures out what the texture is trying to do and creates
|
||||||
graphics_groups.push(model::IndexedGraphicsGroup{
|
//a render config for it, and then returns the id to that render config
|
||||||
render:render_id,
|
let render_id=render_config_deferred_loader.acquire_render_config_id(Some(Cow::Borrowed(face_texture_data.name())));
|
||||||
groups:vec![polygon_group_id],
|
//deduplicate graphics groups by render id
|
||||||
})
|
let graphics_group_id=*render_id_to_graphics_group_id.entry(render_id).or_insert_with(||{
|
||||||
|
let graphics_group_id=graphics_groups.len();
|
||||||
|
graphics_groups.push(model::IndexedGraphicsGroup{
|
||||||
|
render:render_id,
|
||||||
|
groups:vec![],
|
||||||
|
});
|
||||||
|
graphics_group_id
|
||||||
|
});
|
||||||
|
graphics_groups[graphics_group_id].groups.push(polygon_group_id);
|
||||||
}
|
}
|
||||||
physics_group.groups.push(polygon_group_id);
|
|
||||||
model::PolygonGroup::PolygonList(model::PolygonList::new(polygon_list))
|
model::PolygonGroup::PolygonList(model::PolygonList::new(polygon_list))
|
||||||
}).collect();
|
}).collect();
|
||||||
model::Mesh{
|
|
||||||
unique_pos:spam_pos,
|
let physics_groups=vec![model::IndexedPhysicsGroup{
|
||||||
unique_tex:spam_tex,
|
groups:(0..world_model.face_count as u32).map(model::PolygonGroupId::new).collect(),
|
||||||
unique_normal:spam_normal,
|
}];
|
||||||
unique_color:vec![glam::Vec4::ONE],
|
|
||||||
unique_vertices:spam_vertices,
|
mb.build(polygon_groups,graphics_groups,physics_groups)
|
||||||
polygon_groups,
|
|
||||||
graphics_groups,
|
|
||||||
physics_groups:vec![physics_group],
|
|
||||||
}
|
|
||||||
}).collect();
|
}).collect();
|
||||||
|
|
||||||
let world_models:Vec<model::Model>=
|
let mut found_spawn=None;
|
||||||
//one instance of the main world mesh
|
|
||||||
std::iter::once((
|
let mut world_models=Vec::new();
|
||||||
//world_model
|
|
||||||
model::MeshId::new(0),
|
// the one and only world model 0
|
||||||
//model_origin
|
world_models.push(model::Model{
|
||||||
vbsp::Vector::from([0.0,0.0,0.0]),
|
mesh:model::MeshId::new(0),
|
||||||
//model_color
|
attributes:ATTRIBUTE_DECORATION,
|
||||||
vbsp::Color{r:255,g:255,b:255},
|
transform:integer::Planar64Affine3::IDENTITY,
|
||||||
)).chain(
|
color:glam::Vec4::W,
|
||||||
//entities sprinkle instances of the other meshes around
|
debug_info:model::DebugInfo::World,
|
||||||
bsp.entities.iter()
|
});
|
||||||
.flat_map(|ent|ent.parse())//ignore entity parsing errors
|
|
||||||
.filter_map(|ent|match ent{
|
// THE CUBE OF DESTINY
|
||||||
vbsp::Entity::Brush(brush)=>Some(brush),
|
let destination_mesh_id=model::MeshId::new(world_meshes.len() as u32);
|
||||||
vbsp::Entity::BrushIllusionary(brush)=>Some(brush),
|
world_meshes.push(crate::brush::unit_cube());
|
||||||
vbsp::Entity::BrushWall(brush)=>Some(brush),
|
|
||||||
vbsp::Entity::BrushWallToggle(brush)=>Some(brush),
|
let mut teleports=HashMap::new();
|
||||||
_=>None,
|
let mut teleport_destinations=HashMap::new();
|
||||||
}).flat_map(|brush|
|
|
||||||
//The first character of brush.model is '*'
|
const WHITE:vbsp::Color=vbsp::Color{r:255,g:255,b:255};
|
||||||
brush.model[1..].parse().map(|mesh_id|//ignore parse int errors
|
const ENTITY_ATTRIBUTE:attr::CollisionAttributesId=ATTRIBUTE_CONTACT_DEFAULT;
|
||||||
(model::MeshId::new(mesh_id),brush.origin,brush.color)
|
const ENTITY_TRIGGER_ATTRIBUTE:attr::CollisionAttributesId=ATTRIBUTE_INTERSECT_DEFAULT;
|
||||||
)
|
for raw_ent in &bsp.entities{
|
||||||
)
|
let debug_info=match model::EntityInfo::new(raw_ent.properties()){
|
||||||
).map(|(mesh_id,model_origin,vbsp::Color{r,g,b})|{
|
Ok(entity_info)=>model::DebugInfo::Entity(entity_info),
|
||||||
model::Model{
|
Err(_)=>{
|
||||||
mesh:mesh_id,
|
println!("EntityInfoError");
|
||||||
attributes:TEMP_TOUCH_ME_ATTRIBUTE,
|
model::DebugInfo::World
|
||||||
transform:integer::Planar64Affine3::new(
|
},
|
||||||
integer::mat3::identity(),
|
};
|
||||||
valve_transform(model_origin.into())
|
macro_rules! ent_brush_default{
|
||||||
),
|
($entity:ident)=>{
|
||||||
color:(glam::Vec3::from_array([r as f32,g as f32,b as f32])/255.0).extend(1.0),
|
{add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,$entity.model,$entity.origin,$entity.rendercolor,ENTITY_ATTRIBUTE,debug_info);}
|
||||||
|
};
|
||||||
|
} macro_rules! ent_brush_prop{
|
||||||
|
($entity:ident)=>{
|
||||||
|
{add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,$entity.model,$entity.origin,WHITE,ENTITY_ATTRIBUTE,debug_info);}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}).collect();
|
macro_rules! ent_brush_trigger{
|
||||||
|
($entity:ident)=>{
|
||||||
|
{add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,$entity.model,$entity.origin,WHITE,ENTITY_TRIGGER_ATTRIBUTE,debug_info);}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
match raw_ent.parse(){
|
||||||
|
Ok(Entity::AmbientGeneric(_ambient_generic))=>(),
|
||||||
|
Ok(Entity::Cycler(brush))=>ent_brush_default!(brush),
|
||||||
|
Ok(Entity::EnvBeam(_env_beam))=>(),
|
||||||
|
Ok(Entity::EnvBubbles(_env_bubbles))=>(),
|
||||||
|
Ok(Entity::EnvDetailController(_env_detail_controller))=>(),
|
||||||
|
Ok(Entity::EnvEmbers(_env_embers))=>(),
|
||||||
|
Ok(Entity::EnvEntityMaker(_env_entity_maker))=>(),
|
||||||
|
Ok(Entity::EnvExplosion(_env_explosion))=>(),
|
||||||
|
Ok(Entity::EnvFade(_env_fade))=>(),
|
||||||
|
Ok(Entity::EnvFire(_env_fire))=>(),
|
||||||
|
Ok(Entity::EnvFireTrail(_env_fire_trail))=>(),
|
||||||
|
Ok(Entity::EnvFiresource(_env_firesource))=>(),
|
||||||
|
Ok(Entity::EnvFogController(_env_fog_controller))=>(),
|
||||||
|
Ok(Entity::EnvHudhint(_env_hudhint))=>(),
|
||||||
|
Ok(Entity::EnvLaser(_env_laser))=>(),
|
||||||
|
Ok(Entity::EnvLightglow(_env_lightglow))=>(),
|
||||||
|
Ok(Entity::EnvPhysexplosion(_env_physexplosion))=>(),
|
||||||
|
Ok(Entity::EnvProjectedtexture(_env_projectedtexture))=>(),
|
||||||
|
Ok(Entity::EnvScreenoverlay(_env_screenoverlay))=>(),
|
||||||
|
Ok(Entity::EnvShake(_env_shake))=>(),
|
||||||
|
Ok(Entity::EnvShooter(_env_shooter))=>(),
|
||||||
|
Ok(Entity::EnvSmokestack(_env_smokestack))=>(),
|
||||||
|
Ok(Entity::EnvSoundscape(_env_soundscape))=>(),
|
||||||
|
Ok(Entity::EnvSoundscapeProxy(_env_soundscape_proxy))=>(),
|
||||||
|
Ok(Entity::EnvSoundscapeTriggerable(_env_soundscape_triggerable))=>(),
|
||||||
|
Ok(Entity::EnvSpark(_env_spark))=>(),
|
||||||
|
Ok(Entity::EnvSprite(brush))=>ent_brush_default!(brush),
|
||||||
|
Ok(Entity::EnvSpritetrail(_env_spritetrail))=>(),
|
||||||
|
Ok(Entity::EnvSteam(_env_steam))=>(),
|
||||||
|
Ok(Entity::EnvSun(_env_sun))=>(),
|
||||||
|
Ok(Entity::EnvTonemapController(_env_tonemap_controller))=>(),
|
||||||
|
Ok(Entity::EnvWind(_env_wind))=>(),
|
||||||
|
// trigger_teleport.filtername probably has to do with one of these
|
||||||
|
Ok(Entity::FilterActivatorClass(_filter_activator_class))=>(),
|
||||||
|
Ok(Entity::FilterActivatorName(_filter_activator_name))=>(),
|
||||||
|
Ok(Entity::FilterDamageType(_filter_damage_type))=>(),
|
||||||
|
Ok(Entity::FilterMulti(_filter_multi))=>(),
|
||||||
|
Ok(Entity::FuncAreaportal(_func_areaportal))=>(),
|
||||||
|
Ok(Entity::FuncAreaportalwindow(_func_areaportalwindow))=>(),
|
||||||
|
Ok(Entity::FuncBombTarget(_func_bomb_target))=>(),
|
||||||
|
Ok(Entity::FuncBreakable(brush))=>ent_brush_default!(brush),
|
||||||
|
Ok(Entity::FuncBreakableSurf(_func_breakable_surf))=>(),
|
||||||
|
Ok(Entity::FuncBrush(brush))=>ent_brush_default!(brush),
|
||||||
|
Ok(Entity::FuncButton(brush))=>ent_brush_default!(brush),
|
||||||
|
Ok(Entity::FuncBuyzone(_func_buyzone))=>(),
|
||||||
|
Ok(Entity::FuncClipVphysics(_func_clip_vphysics))=>(),
|
||||||
|
Ok(Entity::FuncConveyor(_func_conveyor))=>(),
|
||||||
|
// FuncDoor is Platform
|
||||||
|
Ok(Entity::FuncDoor(brush))=>ent_brush_default!(brush),
|
||||||
|
Ok(Entity::FuncDoorRotating(brush))=>ent_brush_default!(brush),
|
||||||
|
Ok(Entity::FuncDustcloud(_func_dustcloud))=>(),
|
||||||
|
Ok(Entity::FuncDustmotes(_func_dustmotes))=>(),
|
||||||
|
Ok(Entity::FuncFishPool(_func_fish_pool))=>(),
|
||||||
|
Ok(Entity::FuncFootstepControl(_func_footstep_control))=>(),
|
||||||
|
Ok(Entity::FuncHostageRescue(_func_hostage_rescue))=>(),
|
||||||
|
Ok(Entity::FuncIllusionary(brush))=>{add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,brush.rendercolor,ATTRIBUTE_DECORATION,debug_info);},
|
||||||
|
Ok(Entity::FuncLod(_func_lod))=>(),
|
||||||
|
Ok(Entity::FuncMonitor(brush))=>ent_brush_default!(brush),
|
||||||
|
Ok(Entity::FuncMovelinear(brush))=>ent_brush_default!(brush),
|
||||||
|
Ok(Entity::FuncOccluder(_func_occluder))=>(),
|
||||||
|
Ok(Entity::FuncPhysbox(brush))=>ent_brush_default!(brush),
|
||||||
|
Ok(Entity::FuncPhysboxMultiplayer(brush))=>ent_brush_default!(brush),
|
||||||
|
Ok(Entity::FuncPrecipitation(_func_precipitation))=>(),
|
||||||
|
Ok(Entity::FuncRotButton(brush))=>ent_brush_prop!(brush),
|
||||||
|
Ok(Entity::FuncRotating(brush))=>ent_brush_default!(brush),
|
||||||
|
Ok(Entity::FuncSmokevolume(_func_smokevolume))=>(),
|
||||||
|
Ok(Entity::FuncTracktrain(brush))=>ent_brush_default!(brush),
|
||||||
|
Ok(Entity::FuncTrain(brush))=>ent_brush_default!(brush),
|
||||||
|
Ok(Entity::FuncWall(brush))=>{add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin.unwrap_or_default(),brush.rendercolor,ENTITY_ATTRIBUTE,debug_info);},
|
||||||
|
Ok(Entity::FuncWallToggle(brush))=>{add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin.unwrap_or_default(),brush.rendercolor,ENTITY_ATTRIBUTE,debug_info);},
|
||||||
|
Ok(Entity::FuncWaterAnalog(brush))=>{add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,brush.rendercolor.unwrap_or(WHITE),ENTITY_ATTRIBUTE,debug_info);},
|
||||||
|
Ok(Entity::GamePlayerEquip(_game_player_equip))=>(),
|
||||||
|
Ok(Entity::GameText(_game_text))=>(),
|
||||||
|
Ok(Entity::GameUi(_game_ui))=>(),
|
||||||
|
Ok(Entity::GameWeaponManager(_game_weapon_manager))=>(),
|
||||||
|
Ok(Entity::HostageEntity(_hostage_entity))=>(),
|
||||||
|
Ok(Entity::InfoCameraLink(_info_camera_link))=>(),
|
||||||
|
Ok(Entity::InfoLadder(_info_ladder))=>(),
|
||||||
|
Ok(Entity::InfoLightingRelative(_info_lighting_relative))=>(),
|
||||||
|
Ok(Entity::InfoMapParameters(_info_map_parameters))=>(),
|
||||||
|
Ok(Entity::InfoNode(_info_node))=>(),
|
||||||
|
Ok(Entity::InfoNodeHint(_info_node_hint))=>(),
|
||||||
|
Ok(Entity::InfoParticleSystem(_info_particle_system))=>(),
|
||||||
|
Ok(Entity::InfoPlayerCounterterrorist(spawn))=>found_spawn=Some(spawn.origin),
|
||||||
|
Ok(Entity::InfoPlayerLogo(_info_player_logo))=>(),
|
||||||
|
Ok(Entity::InfoPlayerStart(_info_player_start))=>(),
|
||||||
|
Ok(Entity::InfoPlayerTerrorist(spawn))=>found_spawn=Some(spawn.origin),
|
||||||
|
Ok(Entity::InfoTarget(_info_target))=>(),
|
||||||
|
// InfoTeleportDestination is Spawn#
|
||||||
|
Ok(Entity::InfoTeleportDestination(info_teleport_destination))=>if let Some(target)=info_teleport_destination.targetname{
|
||||||
|
// create a new model
|
||||||
|
let model_id=model::ModelId::new(world_models.len() as u32);
|
||||||
|
world_models.push(model::Model{
|
||||||
|
mesh:destination_mesh_id,
|
||||||
|
attributes:ATTRIBUTE_INTERSECT_DEFAULT,
|
||||||
|
transform:integer::Planar64Affine3::from_translation(valve_transform(info_teleport_destination.origin.into())),
|
||||||
|
color:glam::Vec4::W,
|
||||||
|
debug_info,
|
||||||
|
});
|
||||||
|
teleport_destinations.insert(target,model_id);
|
||||||
|
},
|
||||||
|
Ok(Entity::Infodecal(_infodecal))=>(),
|
||||||
|
Ok(Entity::KeyframeRope(_keyframe_rope))=>(),
|
||||||
|
Ok(Entity::Light(_light))=>(),
|
||||||
|
Ok(Entity::LightEnvironment(_light_environment))=>(),
|
||||||
|
Ok(Entity::LightSpot(_light_spot))=>(),
|
||||||
|
Ok(Entity::LogicAuto(_logic_auto))=>(),
|
||||||
|
Ok(Entity::LogicBranch(_logic_branch))=>(),
|
||||||
|
Ok(Entity::LogicCase(_logic_case))=>(),
|
||||||
|
Ok(Entity::LogicCompare(_logic_compare))=>(),
|
||||||
|
Ok(Entity::LogicMeasureMovement(_logic_measure_movement))=>(),
|
||||||
|
Ok(Entity::LogicRelay(_logic_relay))=>(),
|
||||||
|
Ok(Entity::LogicTimer(_logic_timer))=>(),
|
||||||
|
Ok(Entity::MathCounter(_math_counter))=>(),
|
||||||
|
Ok(Entity::MoveRope(_move_rope))=>(),
|
||||||
|
Ok(Entity::PathTrack(_path_track))=>(),
|
||||||
|
Ok(Entity::PhysBallsocket(_phys_ballsocket))=>(),
|
||||||
|
Ok(Entity::PhysConstraint(_phys_constraint))=>(),
|
||||||
|
Ok(Entity::PhysConstraintsystem(_phys_constraintsystem))=>(),
|
||||||
|
Ok(Entity::PhysHinge(_phys_hinge))=>(),
|
||||||
|
Ok(Entity::PhysKeepupright(_phys_keepupright))=>(),
|
||||||
|
Ok(Entity::PhysLengthconstraint(_phys_lengthconstraint))=>(),
|
||||||
|
Ok(Entity::PhysPulleyconstraint(_phys_pulleyconstraint))=>(),
|
||||||
|
Ok(Entity::PhysRagdollconstraint(_phys_ragdollconstraint))=>(),
|
||||||
|
Ok(Entity::PhysRagdollmagnet(_phys_ragdollmagnet))=>(),
|
||||||
|
Ok(Entity::PhysThruster(_phys_thruster))=>(),
|
||||||
|
Ok(Entity::PhysTorque(_phys_torque))=>(),
|
||||||
|
Ok(Entity::PlayerSpeedmod(_player_speedmod))=>(),
|
||||||
|
Ok(Entity::PlayerWeaponstrip(_player_weaponstrip))=>(),
|
||||||
|
Ok(Entity::PointCamera(_point_camera))=>(),
|
||||||
|
Ok(Entity::PointClientcommand(_point_clientcommand))=>(),
|
||||||
|
Ok(Entity::PointDevshotCamera(_point_devshot_camera))=>(),
|
||||||
|
Ok(Entity::PointServercommand(_point_servercommand))=>(),
|
||||||
|
Ok(Entity::PointSpotlight(_point_spotlight))=>(),
|
||||||
|
Ok(Entity::PointSurroundtest(_point_surroundtest))=>(),
|
||||||
|
Ok(Entity::PointTemplate(_point_template))=>(),
|
||||||
|
Ok(Entity::PointTesla(_point_tesla))=>(),
|
||||||
|
Ok(Entity::PointViewcontrol(_point_viewcontrol))=>(),
|
||||||
|
Ok(Entity::PropDoorRotating(brush))=>ent_brush_prop!(brush),
|
||||||
|
Ok(Entity::PropDynamic(brush))=>ent_brush_prop!(brush),
|
||||||
|
Ok(Entity::PropDynamicOverride(brush))=>ent_brush_prop!(brush),
|
||||||
|
Ok(Entity::PropPhysics(brush))=>ent_brush_prop!(brush),
|
||||||
|
Ok(Entity::PropPhysicsMultiplayer(brush))=>ent_brush_prop!(brush),
|
||||||
|
Ok(Entity::PropPhysicsOverride(brush))=>ent_brush_prop!(brush),
|
||||||
|
Ok(Entity::PropRagdoll(brush))=>ent_brush_prop!(brush),
|
||||||
|
Ok(Entity::ShadowControl(_shadow_control))=>(),
|
||||||
|
Ok(Entity::SkyCamera(_sky_camera))=>(),
|
||||||
|
Ok(Entity::TriggerGravity(brush))=>ent_brush_trigger!(brush),
|
||||||
|
Ok(Entity::TriggerHurt(brush))=>ent_brush_trigger!(brush),
|
||||||
|
Ok(Entity::TriggerLook(brush))=>ent_brush_trigger!(brush),
|
||||||
|
Ok(Entity::TriggerMultiple(brush))=>{add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model.unwrap_or_default(),brush.origin,WHITE,ENTITY_TRIGGER_ATTRIBUTE,debug_info);},
|
||||||
|
Ok(Entity::TriggerOnce(brush))=>ent_brush_trigger!(brush),
|
||||||
|
Ok(Entity::TriggerProximity(brush))=>ent_brush_trigger!(brush),
|
||||||
|
// TriggerPush is booster
|
||||||
|
Ok(Entity::TriggerPush(brush))=>ent_brush_trigger!(brush),
|
||||||
|
Ok(Entity::TriggerSoundscape(brush))=>ent_brush_trigger!(brush),
|
||||||
|
// TriggerTeleport is Trigger#
|
||||||
|
Ok(Entity::TriggerTeleport(brush))=>{
|
||||||
|
if let (Some(model_id),Some(target))=(add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model.unwrap_or_default(),brush.origin,WHITE,ENTITY_TRIGGER_ATTRIBUTE,debug_info),brush.target){
|
||||||
|
teleports.insert(model_id,target);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Ok(Entity::TriggerVphysicsMotion(brush))=>ent_brush_trigger!(brush),
|
||||||
|
Ok(Entity::TriggerWind(brush))=>ent_brush_trigger!(brush),
|
||||||
|
Ok(Entity::WaterLodControl(_water_lod_control))=>(),
|
||||||
|
Ok(Entity::WeaponAk47(_weapon_ak47))=>(),
|
||||||
|
Ok(Entity::WeaponAwp(_weapon_awp))=>(),
|
||||||
|
Ok(Entity::WeaponDeagle(_weapon_deagle))=>(),
|
||||||
|
Ok(Entity::WeaponElite(_weapon_elite))=>(),
|
||||||
|
Ok(Entity::WeaponFamas(_weapon_famas))=>(),
|
||||||
|
Ok(Entity::WeaponFiveseven(_weapon_fiveseven))=>(),
|
||||||
|
Ok(Entity::WeaponFlashbang(_weapon_flashbang))=>(),
|
||||||
|
Ok(Entity::WeaponG3sg1(_weapon_g3sg1))=>(),
|
||||||
|
Ok(Entity::WeaponGlock(_weapon_glock))=>(),
|
||||||
|
Ok(Entity::WeaponHegrenade(_weapon_hegrenade))=>(),
|
||||||
|
Ok(Entity::WeaponKnife(_weapon_knife))=>(),
|
||||||
|
Ok(Entity::WeaponM249(_weapon_m249))=>(),
|
||||||
|
Ok(Entity::WeaponM3(_weapon_m3))=>(),
|
||||||
|
Ok(Entity::WeaponM4a1(_weapon_m4a1))=>(),
|
||||||
|
Ok(Entity::WeaponMac10(_weapon_mac10))=>(),
|
||||||
|
Ok(Entity::WeaponP228(_weapon_p228))=>(),
|
||||||
|
Ok(Entity::WeaponP90(_weapon_p90))=>(),
|
||||||
|
Ok(Entity::WeaponScout(_weapon_scout))=>(),
|
||||||
|
Ok(Entity::WeaponSg550(_weapon_sg550))=>(),
|
||||||
|
Ok(Entity::WeaponSmokegrenade(_weapon_smokegrenade))=>(),
|
||||||
|
Ok(Entity::WeaponTmp(_weapon_tmp))=>(),
|
||||||
|
Ok(Entity::WeaponUmp45(_weapon_ump45))=>(),
|
||||||
|
Ok(Entity::WeaponUsp(_weapon_usp))=>(),
|
||||||
|
Ok(Entity::WeaponXm1014(_weapon_xm1014))=>(),
|
||||||
|
Ok(Entity::Worldspawn(_worldspawn))=>(),
|
||||||
|
Err(e)=>{
|
||||||
|
println!("Bsp Entity parse error: {e}");
|
||||||
|
},
|
||||||
|
_=>(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// physics models
|
||||||
|
for brush in &bsp.brushes{
|
||||||
|
const RELEVANT:vbsp::BrushFlags=
|
||||||
|
vbsp::BrushFlags::SOLID
|
||||||
|
.union(vbsp::BrushFlags::PLAYERCLIP)
|
||||||
|
.union(vbsp::BrushFlags::WATER)
|
||||||
|
.union(vbsp::BrushFlags::MOVEABLE)
|
||||||
|
.union(vbsp::BrushFlags::LADDER);
|
||||||
|
if !brush.flags.intersects(RELEVANT){
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let is_ladder=brush.flags.contains(vbsp::BrushFlags::LADDER);
|
||||||
|
let is_water=brush.flags.contains(vbsp::BrushFlags::WATER);
|
||||||
|
let attributes=match (is_ladder,is_water){
|
||||||
|
(true,false)=>ATTRIBUTE_LADDER_DEFAULT,
|
||||||
|
(false,true)=>ATTRIBUTE_WATER_DEFAULT,
|
||||||
|
(false,false)=>ATTRIBUTE_CONTACT_DEFAULT,
|
||||||
|
(true,true)=>{
|
||||||
|
// water ladder? wtf
|
||||||
|
println!("brush is a water ladder o_o defaulting to ladder");
|
||||||
|
ATTRIBUTE_LADDER_DEFAULT
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let mesh_result=crate::brush::brush_to_mesh(bsp,brush);
|
||||||
|
match mesh_result{
|
||||||
|
Ok(mesh)=>{
|
||||||
|
let mesh_id=model::MeshId::new(world_meshes.len() as u32);
|
||||||
|
world_meshes.push(mesh);
|
||||||
|
let sides={
|
||||||
|
let brush_start_idx=brush.brush_side as usize;
|
||||||
|
let sides_range=brush_start_idx..brush_start_idx+brush.num_brush_sides as usize;
|
||||||
|
bsp.brush_sides[sides_range].iter().filter_map(|side|bsp.texture_info(side.texture_info as usize)).map(|texture_info|{
|
||||||
|
texture_info.flags
|
||||||
|
}).collect()
|
||||||
|
};
|
||||||
|
world_models.push(model::Model{
|
||||||
|
mesh:mesh_id,
|
||||||
|
attributes,
|
||||||
|
transform:integer::Planar64Affine3::new(
|
||||||
|
integer::mat3::identity(),
|
||||||
|
integer::vec3::ZERO,
|
||||||
|
),
|
||||||
|
color:glam::Vec4::ONE,
|
||||||
|
debug_info:model::DebugInfo::Brush(model::BrushInfo{flags:brush.flags,sides}),
|
||||||
|
});
|
||||||
|
},
|
||||||
|
Err(e)=>println!("Brush mesh error: {e}"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let first_stage=found_spawn.map(|spawn_point|{
|
||||||
|
// create a new model
|
||||||
|
let model_id=model::ModelId::new(world_models.len() as u32);
|
||||||
|
world_models.push(model::Model{
|
||||||
|
mesh:destination_mesh_id,
|
||||||
|
attributes:ATTRIBUTE_INTERSECT_DEFAULT,
|
||||||
|
transform:integer::Planar64Affine3::from_translation(valve_transform(spawn_point.into())),
|
||||||
|
color:glam::Vec4::W,
|
||||||
|
debug_info:model::DebugInfo::World,
|
||||||
|
});
|
||||||
|
|
||||||
|
Stage::empty(model_id)
|
||||||
|
});
|
||||||
|
|
||||||
PartialMap1{
|
PartialMap1{
|
||||||
attributes:unique_attributes,
|
attributes:unique_attributes,
|
||||||
world_meshes,
|
world_meshes,
|
||||||
prop_models,
|
prop_models,
|
||||||
world_models,
|
world_models,
|
||||||
modes:strafesnet_common::gameplay_modes::Modes::new(Vec::new()),
|
first_stage,
|
||||||
|
teleports,
|
||||||
|
teleport_destinations,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//partially constructed map types
|
//partially constructed map types
|
||||||
pub struct PartialMap1{
|
pub struct PartialMap1<'a>{
|
||||||
attributes:Vec<strafesnet_common::gameplay_attributes::CollisionAttributes>,
|
attributes:Vec<attr::CollisionAttributes>,
|
||||||
prop_models:Vec<model::Model>,
|
prop_models:Vec<model::Model>,
|
||||||
world_meshes:Vec<model::Mesh>,
|
world_meshes:Vec<model::Mesh>,
|
||||||
world_models:Vec<model::Model>,
|
world_models:Vec<model::Model>,
|
||||||
modes:strafesnet_common::gameplay_modes::Modes,
|
first_stage:Option<Stage>,
|
||||||
|
teleports:HashMap<AddBrush,&'a str>,
|
||||||
|
teleport_destinations:HashMap<&'a str,model::ModelId>,
|
||||||
}
|
}
|
||||||
impl PartialMap1{
|
impl<'a> PartialMap1<'a>{
|
||||||
pub fn add_prop_meshes<AcquireRenderConfigId>(
|
pub fn add_prop_meshes(
|
||||||
self,
|
self,
|
||||||
prop_meshes:impl IntoIterator<Item=(model::MeshId,crate::data::ModelData)>,
|
prop_meshes:Meshes,
|
||||||
mut acquire_render_config_id:AcquireRenderConfigId,
|
)->PartialMap2<'a>{
|
||||||
)->PartialMap2
|
|
||||||
where
|
|
||||||
AcquireRenderConfigId:FnMut(Option<&str>)->model::RenderConfigId,
|
|
||||||
{
|
|
||||||
PartialMap2{
|
PartialMap2{
|
||||||
attributes:self.attributes,
|
attributes:self.attributes,
|
||||||
prop_meshes:prop_meshes.into_iter().filter_map(|(mesh_id,model_data)|
|
prop_meshes:prop_meshes.consume().collect(),
|
||||||
//this will generate new render ids and texture ids
|
|
||||||
match convert_mesh(model_data,&mut acquire_render_config_id){
|
|
||||||
Ok(mesh)=>Some((mesh_id,mesh)),
|
|
||||||
Err(e)=>{
|
|
||||||
println!("error converting mesh: {e}");
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
).collect(),
|
|
||||||
prop_models:self.prop_models,
|
prop_models:self.prop_models,
|
||||||
world_meshes:self.world_meshes,
|
world_meshes:self.world_meshes,
|
||||||
world_models:self.world_models,
|
world_models:self.world_models,
|
||||||
modes:self.modes,
|
first_stage:self.first_stage,
|
||||||
|
teleports:self.teleports,
|
||||||
|
teleport_destinations:self.teleport_destinations,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub struct PartialMap2{
|
pub struct PartialMap2<'a>{
|
||||||
attributes:Vec<strafesnet_common::gameplay_attributes::CollisionAttributes>,
|
attributes:Vec<attr::CollisionAttributes>,
|
||||||
prop_meshes:Vec<(model::MeshId,model::Mesh)>,
|
prop_meshes:Vec<(model::MeshId,model::Mesh)>,
|
||||||
prop_models:Vec<model::Model>,
|
prop_models:Vec<model::Model>,
|
||||||
world_meshes:Vec<model::Mesh>,
|
world_meshes:Vec<model::Mesh>,
|
||||||
world_models:Vec<model::Model>,
|
world_models:Vec<model::Model>,
|
||||||
modes:strafesnet_common::gameplay_modes::Modes,
|
first_stage:Option<Stage>,
|
||||||
|
teleports:HashMap<AddBrush,&'a str>,
|
||||||
|
teleport_destinations:HashMap<&'a str,model::ModelId>,
|
||||||
}
|
}
|
||||||
impl PartialMap2{
|
impl PartialMap2<'_>{
|
||||||
pub fn add_render_configs_and_textures(
|
pub fn add_render_configs_and_textures(
|
||||||
mut self,
|
mut self,
|
||||||
render_configs:impl IntoIterator<Item=(model::RenderConfigId,model::RenderConfig)>,
|
render_configs:RenderConfigs,
|
||||||
textures:impl IntoIterator<Item=(model::TextureId,Vec<u8>)>,
|
|
||||||
)->map::CompleteMap{
|
)->map::CompleteMap{
|
||||||
//merge mesh and model lists, flatten and remap all ids
|
//merge mesh and model lists, flatten and remap all ids
|
||||||
let mesh_id_offset=self.world_meshes.len();
|
let mesh_id_offset=self.world_meshes.len();
|
||||||
println!("prop_meshes.len()={}",self.prop_meshes.len());
|
println!("prop_meshes.len()={}",self.prop_meshes.len());
|
||||||
let (mut prop_meshes,prop_mesh_id_map):(Vec<model::Mesh>,std::collections::HashMap<model::MeshId,model::MeshId>)
|
let (mut prop_meshes,prop_mesh_id_map):(Vec<model::Mesh>,HashMap<model::MeshId,model::MeshId>)
|
||||||
=self.prop_meshes.into_iter().enumerate().map(|(new_mesh_id,(old_mesh_id,mesh))|{
|
=self.prop_meshes.into_iter().enumerate().map(|(new_mesh_id,(old_mesh_id,mesh))|{
|
||||||
(mesh,(old_mesh_id,model::MeshId::new((mesh_id_offset+new_mesh_id) as u32)))
|
(mesh,(old_mesh_id,model::MeshId::new((mesh_id_offset+new_mesh_id) as u32)))
|
||||||
}).unzip();
|
}).unzip();
|
||||||
self.world_meshes.append(&mut prop_meshes);
|
self.world_meshes.append(&mut prop_meshes);
|
||||||
//there is no modes or runtime behaviour with references to the model ids currently
|
|
||||||
//so just relentlessly cull them if the mesh is missing
|
// cull models with a missing mesh
|
||||||
self.world_models.extend(self.prop_models.into_iter().filter_map(|mut model|
|
let model_id_offset=self.world_models.len();
|
||||||
|
let mut prop_model_id_to_final_model_id=HashMap::new();
|
||||||
|
self.world_models.extend(self.prop_models.into_iter().enumerate().filter_map(|(prop_model_id,mut model)|
|
||||||
prop_mesh_id_map.get(&model.mesh).map(|&new_mesh_id|{
|
prop_mesh_id_map.get(&model.mesh).map(|&new_mesh_id|{
|
||||||
model.mesh=new_mesh_id;
|
model.mesh=new_mesh_id;
|
||||||
model
|
(prop_model_id,model)
|
||||||
})
|
})
|
||||||
|
).enumerate().map(|(model_id,(prop_model_id,model))|{
|
||||||
|
prop_model_id_to_final_model_id.insert(
|
||||||
|
model::ModelId::new(prop_model_id as u32),
|
||||||
|
model::ModelId::new((model_id_offset+model_id) as u32),
|
||||||
|
);
|
||||||
|
model
|
||||||
|
}));
|
||||||
|
|
||||||
|
//calculate teleports
|
||||||
|
let first_stage_spawn_model_id=self.first_stage.as_ref().unwrap().spawn();
|
||||||
|
let mut teleport_destinations=HashMap::new();
|
||||||
|
let stages={
|
||||||
|
let mut stages=self.teleport_destinations.iter().map(|(&target,&model_id)|(target,model_id)).collect::<Vec<_>>();
|
||||||
|
stages.sort_by_key(|&(target,_)|target);
|
||||||
|
self.first_stage.into_iter().chain(
|
||||||
|
stages.into_iter().enumerate().map(|(stage_id,(target,model_id))|{
|
||||||
|
let stage_id=modes::StageId::new(1+stage_id as u32);
|
||||||
|
teleport_destinations.insert(target,stage_id);
|
||||||
|
Stage::empty(model_id)
|
||||||
|
})
|
||||||
|
).collect()
|
||||||
|
};
|
||||||
|
let mut elements=HashMap::new();
|
||||||
|
for (teleport_model,target) in self.teleports{
|
||||||
|
if let Some(&stage_id)=teleport_destinations.get(target){
|
||||||
|
let model_id=match teleport_model{
|
||||||
|
AddBrush::Available(model_id)=>Some(model_id),
|
||||||
|
AddBrush::Deferred(model_id)=>prop_model_id_to_final_model_id.get(&model_id).copied(),
|
||||||
|
};
|
||||||
|
if let Some(model_id)=model_id{
|
||||||
|
elements.insert(model_id,modes::StageElement::new(
|
||||||
|
stage_id,
|
||||||
|
true,
|
||||||
|
modes::StageElementBehaviour::Teleport,
|
||||||
|
None,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let main_mode=NormalizedMode::new(Mode::new(
|
||||||
|
strafesnet_common::gameplay_style::StyleModifiers::source_bhop(),
|
||||||
|
first_stage_spawn_model_id,
|
||||||
|
HashMap::new(),
|
||||||
|
stages,
|
||||||
|
elements,
|
||||||
));
|
));
|
||||||
//let mut models=Vec::new();
|
let modes=NormalizedModes::new(vec![main_mode]);
|
||||||
let (textures,texture_id_map):(Vec<Vec<u8>>,std::collections::HashMap<model::TextureId,model::TextureId>)
|
|
||||||
|
let (textures,render_configs)=render_configs.consume();
|
||||||
|
let (textures,texture_id_map):(Vec<Vec<u8>>,HashMap<model::TextureId,model::TextureId>)
|
||||||
=textures.into_iter()
|
=textures.into_iter()
|
||||||
//.filter_map(f) cull unused textures
|
//.filter_map(f) cull unused textures
|
||||||
.enumerate().map(|(new_texture_id,(old_texture_id,texture))|{
|
.enumerate().map(|(new_texture_id,(old_texture_id,Texture::ImageDDS(texture)))|{
|
||||||
(texture,(old_texture_id,model::TextureId::new(new_texture_id as u32)))
|
(texture,(old_texture_id,model::TextureId::new(new_texture_id as u32)))
|
||||||
}).unzip();
|
}).unzip();
|
||||||
let render_configs=render_configs.into_iter().map(|(render_config_id,mut render_config)|{
|
let render_configs=render_configs.into_iter().map(|(_render_config_id,mut render_config)|{
|
||||||
//this may generate duplicate no-texture render configs but idc
|
//this may generate duplicate no-texture render configs but idc
|
||||||
render_config.texture=render_config.texture.and_then(|texture_id|
|
render_config.texture=render_config.texture.and_then(|texture_id|
|
||||||
texture_id_map.get(&texture_id).copied()
|
texture_id_map.get(&texture_id).copied()
|
||||||
@@ -248,7 +654,7 @@ impl PartialMap2{
|
|||||||
render_config
|
render_config
|
||||||
}).collect();
|
}).collect();
|
||||||
map::CompleteMap{
|
map::CompleteMap{
|
||||||
modes:self.modes,
|
modes,
|
||||||
attributes:self.attributes,
|
attributes:self.attributes,
|
||||||
meshes:self.world_meshes,
|
meshes:self.world_meshes,
|
||||||
models:self.world_models,
|
models:self.world_models,
|
||||||
@@ -257,77 +663,3 @@ impl PartialMap2{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn convert_mesh<AcquireRenderConfigId>(
|
|
||||||
model_data:crate::data::ModelData,
|
|
||||||
acquire_render_config_id:&mut AcquireRenderConfigId,
|
|
||||||
)->Result<model::Mesh,vmdl::ModelError>
|
|
||||||
where
|
|
||||||
AcquireRenderConfigId:FnMut(Option<&str>)->model::RenderConfigId,
|
|
||||||
{
|
|
||||||
let model=model_data.read_model()?;
|
|
||||||
let texture_paths=model.texture_directories();
|
|
||||||
if texture_paths.len()!=1{
|
|
||||||
println!("WARNING: multiple texture paths");
|
|
||||||
}
|
|
||||||
let skin=model.skin_tables().nth(0).unwrap();
|
|
||||||
|
|
||||||
let mut spam_pos=Vec::with_capacity(model.vertices().len());
|
|
||||||
let mut spam_normal=Vec::with_capacity(model.vertices().len());
|
|
||||||
let mut spam_tex=Vec::with_capacity(model.vertices().len());
|
|
||||||
let mut spam_vertices=Vec::with_capacity(model.vertices().len());
|
|
||||||
for (i,vertex) in model.vertices().iter().enumerate(){
|
|
||||||
spam_pos.push(valve_transform(vertex.position.into()));
|
|
||||||
spam_normal.push(valve_transform(vertex.normal.into()));
|
|
||||||
spam_tex.push(glam::Vec2::from_array(vertex.texture_coordinates));
|
|
||||||
spam_vertices.push(model::IndexedVertex{
|
|
||||||
pos:model::PositionId::new(i as u32),
|
|
||||||
tex:model::TextureCoordinateId::new(i as u32),
|
|
||||||
normal:model::NormalId::new(i as u32),
|
|
||||||
color:model::ColorId::new(0),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
let mut graphics_groups=Vec::new();
|
|
||||||
let mut physics_groups=Vec::new();
|
|
||||||
let polygon_groups=model.meshes().enumerate().map(|(polygon_group_id,mesh)|{
|
|
||||||
let polygon_group_id=model::PolygonGroupId::new(polygon_group_id as u32);
|
|
||||||
|
|
||||||
let render_id=if let (Some(texture_path),Some(texture_name))=(texture_paths.get(0),skin.texture(mesh.material_index())){
|
|
||||||
let mut path=std::path::PathBuf::from(texture_path.as_str());
|
|
||||||
path.push(texture_name);
|
|
||||||
acquire_render_config_id(path.as_os_str().to_str())
|
|
||||||
}else{
|
|
||||||
acquire_render_config_id(None)
|
|
||||||
};
|
|
||||||
|
|
||||||
graphics_groups.push(model::IndexedGraphicsGroup{
|
|
||||||
render:render_id,
|
|
||||||
groups:vec![polygon_group_id],
|
|
||||||
});
|
|
||||||
physics_groups.push(model::IndexedPhysicsGroup{
|
|
||||||
groups:vec![polygon_group_id],
|
|
||||||
});
|
|
||||||
model::PolygonGroup::PolygonList(model::PolygonList::new(
|
|
||||||
//looking at the code, it would seem that the strips are pre-deindexed into triangle lists when calling this function
|
|
||||||
mesh.vertex_strip_indices().flat_map(|mut strip|
|
|
||||||
std::iter::from_fn(move||{
|
|
||||||
match (strip.next(),strip.next(),strip.next()){
|
|
||||||
(Some(v1),Some(v2),Some(v3))=>Some([v1,v2,v3].map(|vertex_id|model::VertexId::new(vertex_id as u32)).to_vec()),
|
|
||||||
//ignore extra vertices, not sure what to do in this case, failing the whole conversion could be appropriate
|
|
||||||
_=>None,
|
|
||||||
}
|
|
||||||
})
|
|
||||||
).collect()
|
|
||||||
))
|
|
||||||
}).collect();
|
|
||||||
Ok(model::Mesh{
|
|
||||||
unique_pos:spam_pos,
|
|
||||||
unique_normal:spam_normal,
|
|
||||||
unique_tex:spam_tex,
|
|
||||||
unique_color:vec![glam::Vec4::ONE],
|
|
||||||
unique_vertices:spam_vertices,
|
|
||||||
polygon_groups,
|
|
||||||
graphics_groups,
|
|
||||||
physics_groups,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,60 +0,0 @@
|
|||||||
pub struct Bsp(vbsp::Bsp);
|
|
||||||
impl Bsp{
|
|
||||||
pub const fn new(value:vbsp::Bsp)->Self{
|
|
||||||
Self(value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl AsRef<vbsp::Bsp> for Bsp{
|
|
||||||
fn as_ref(&self)->&vbsp::Bsp{
|
|
||||||
&self.0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct MdlData(Vec<u8>);
|
|
||||||
impl MdlData{
|
|
||||||
pub const fn new(value:Vec<u8>)->Self{
|
|
||||||
Self(value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl AsRef<[u8]> for MdlData{
|
|
||||||
fn as_ref(&self)->&[u8]{
|
|
||||||
self.0.as_ref()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub struct VtxData(Vec<u8>);
|
|
||||||
impl VtxData{
|
|
||||||
pub const fn new(value:Vec<u8>)->Self{
|
|
||||||
Self(value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl AsRef<[u8]> for VtxData{
|
|
||||||
fn as_ref(&self)->&[u8]{
|
|
||||||
self.0.as_ref()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub struct VvdData(Vec<u8>);
|
|
||||||
impl VvdData{
|
|
||||||
pub const fn new(value:Vec<u8>)->Self{
|
|
||||||
Self(value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl AsRef<[u8]> for VvdData{
|
|
||||||
fn as_ref(&self)->&[u8]{
|
|
||||||
self.0.as_ref()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct ModelData{
|
|
||||||
pub mdl:MdlData,
|
|
||||||
pub vtx:VtxData,
|
|
||||||
pub vvd:VvdData,
|
|
||||||
}
|
|
||||||
impl ModelData{
|
|
||||||
pub fn read_model(&self)->Result<vmdl::Model,vmdl::ModelError>{
|
|
||||||
Ok(vmdl::Model::from_parts(
|
|
||||||
vmdl::mdl::Mdl::read(self.mdl.as_ref())?,
|
|
||||||
vmdl::vtx::Vtx::read(self.vtx.as_ref())?,
|
|
||||||
vmdl::vvd::Vvd::read(self.vvd.as_ref())?,
|
|
||||||
))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,7 +1,20 @@
|
|||||||
mod bsp;
|
use strafesnet_deferred_loader::deferred_loader::{LoadFailureMode,MeshDeferredLoader,RenderConfigDeferredLoader};
|
||||||
pub mod data;
|
|
||||||
|
|
||||||
pub use data::Bsp;
|
mod bsp;
|
||||||
|
mod mesh;
|
||||||
|
mod brush;
|
||||||
|
pub mod loader;
|
||||||
|
|
||||||
|
const VALVE_SCALE:f32=1.0/16.0;
|
||||||
|
pub(crate) fn valve_transform_dist(d:f32)->strafesnet_common::integer::Planar64{
|
||||||
|
(d*VALVE_SCALE).try_into().unwrap()
|
||||||
|
}
|
||||||
|
pub(crate) fn valve_transform_normal([x,y,z]:[f32;3])->strafesnet_common::integer::Planar64Vec3{
|
||||||
|
strafesnet_common::integer::vec3::try_from_f32_array([x,z,-y]).unwrap()
|
||||||
|
}
|
||||||
|
pub(crate) fn valve_transform([x,y,z]:[f32;3])->strafesnet_common::integer::Planar64Vec3{
|
||||||
|
strafesnet_common::integer::vec3::try_from_f32_array([x*VALVE_SCALE,z*VALVE_SCALE,-y*VALVE_SCALE]).unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum ReadError{
|
pub enum ReadError{
|
||||||
@@ -15,6 +28,38 @@ impl std::fmt::Display for ReadError{
|
|||||||
}
|
}
|
||||||
impl std::error::Error for ReadError{}
|
impl std::error::Error for ReadError{}
|
||||||
|
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum LoadError{
|
||||||
|
Texture(loader::TextureError),
|
||||||
|
Mesh(loader::MeshError),
|
||||||
|
}
|
||||||
|
impl std::fmt::Display for LoadError{
|
||||||
|
fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{
|
||||||
|
write!(f,"{self:?}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl std::error::Error for LoadError{}
|
||||||
|
impl From<loader::TextureError> for LoadError{
|
||||||
|
fn from(value:loader::TextureError)->Self{
|
||||||
|
Self::Texture(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl From<loader::MeshError> for LoadError{
|
||||||
|
fn from(value:loader::MeshError)->Self{
|
||||||
|
Self::Mesh(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub struct Bsp{
|
||||||
|
bsp:vbsp::Bsp,
|
||||||
|
case_folded_file_names:std::collections::HashMap<String,String>,
|
||||||
|
}
|
||||||
|
impl AsRef<vbsp::Bsp> for Bsp{
|
||||||
|
fn as_ref(&self)->&vbsp::Bsp{
|
||||||
|
&self.bsp
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn read<R:std::io::Read>(mut input:R)->Result<Bsp,ReadError>{
|
pub fn read<R:std::io::Read>(mut input:R)->Result<Bsp,ReadError>{
|
||||||
let mut s=Vec::new();
|
let mut s=Vec::new();
|
||||||
|
|
||||||
@@ -23,15 +68,66 @@ pub fn read<R:std::io::Read>(mut input:R)->Result<Bsp,ReadError>{
|
|||||||
|
|
||||||
vbsp::Bsp::read(s.as_slice()).map(Bsp::new).map_err(ReadError::Bsp)
|
vbsp::Bsp::read(s.as_slice()).map(Bsp::new).map_err(ReadError::Bsp)
|
||||||
}
|
}
|
||||||
|
impl Bsp{
|
||||||
|
pub fn new(bsp:vbsp::Bsp)->Self{
|
||||||
|
let case_folded_file_names=bsp.pack.clone().into_zip().lock().unwrap().file_names().map(|s|{
|
||||||
|
(s.to_lowercase(),s.to_owned())
|
||||||
|
}).collect();
|
||||||
|
Self{
|
||||||
|
bsp,
|
||||||
|
case_folded_file_names,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn pack_get(&self,name_lowercase:&str)->Result<Option<Vec<u8>>,vbsp::BspError>{
|
||||||
|
match self.case_folded_file_names.get(name_lowercase){
|
||||||
|
Some(name_folded)=>self.bsp.pack.get(name_folded),
|
||||||
|
None=>Ok(None),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn to_snf(&self,failure_mode:LoadFailureMode,vpk_list:&[Vpk])->Result<strafesnet_common::map::CompleteMap,LoadError>{
|
||||||
|
let mut texture_deferred_loader=RenderConfigDeferredLoader::new();
|
||||||
|
let mut mesh_deferred_loader=MeshDeferredLoader::new();
|
||||||
|
|
||||||
pub fn convert<AcquireRenderConfigId,AcquireMeshId>(
|
let map_step1=bsp::convert(
|
||||||
bsp:&Bsp,
|
self,
|
||||||
acquire_render_config_id:AcquireRenderConfigId,
|
&mut texture_deferred_loader,
|
||||||
acquire_mesh_id:AcquireMeshId
|
&mut mesh_deferred_loader,
|
||||||
)->bsp::PartialMap1
|
);
|
||||||
where
|
|
||||||
AcquireRenderConfigId:FnMut(Option<&str>)->strafesnet_common::model::RenderConfigId,
|
let mut mesh_loader=loader::MeshLoader::new(loader::BspFinder{bsp:self,vpks:vpk_list},&mut texture_deferred_loader);
|
||||||
AcquireMeshId:FnMut(&str)->strafesnet_common::model::MeshId,
|
let prop_meshes=mesh_deferred_loader.into_meshes(&mut mesh_loader,failure_mode).map_err(LoadError::Mesh)?;
|
||||||
{
|
|
||||||
bsp::convert_bsp(bsp.as_ref(),acquire_render_config_id,acquire_mesh_id)
|
let map_step2=map_step1.add_prop_meshes(prop_meshes);
|
||||||
}
|
|
||||||
|
let mut texture_loader=loader::TextureLoader::new();
|
||||||
|
let render_configs=texture_deferred_loader.into_render_configs(&mut texture_loader,failure_mode).map_err(LoadError::Texture)?;
|
||||||
|
|
||||||
|
let map=map_step2.add_render_configs_and_textures(render_configs);
|
||||||
|
|
||||||
|
Ok(map)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub struct Vpk{
|
||||||
|
vpk:vpk::VPK,
|
||||||
|
case_folded_file_names:std::collections::HashMap<String,String>,
|
||||||
|
}
|
||||||
|
impl AsRef<vpk::VPK> for Vpk{
|
||||||
|
fn as_ref(&self)->&vpk::VPK{
|
||||||
|
&self.vpk
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Vpk{
|
||||||
|
pub fn new(vpk:vpk::VPK)->Vpk{
|
||||||
|
let case_folded_file_names=vpk.tree.keys().map(|s|{
|
||||||
|
(s.to_lowercase(),s.to_owned())
|
||||||
|
}).collect();
|
||||||
|
Vpk{
|
||||||
|
vpk,
|
||||||
|
case_folded_file_names,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn tree_get(&self,name_lowercase:&str)->Option<&vpk::entry::VPKEntry>{
|
||||||
|
let name_folded=self.case_folded_file_names.get(name_lowercase)?;
|
||||||
|
self.vpk.tree.get(name_folded)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
175
lib/bsp_loader/src/loader.rs
Normal file
175
lib/bsp_loader/src/loader.rs
Normal file
@@ -0,0 +1,175 @@
|
|||||||
|
use std::{borrow::Cow, io::Read};
|
||||||
|
|
||||||
|
use strafesnet_common::model::Mesh;
|
||||||
|
use strafesnet_deferred_loader::{loader::Loader,texture::Texture};
|
||||||
|
|
||||||
|
use crate::{Bsp,Vpk};
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum TextureError{
|
||||||
|
Io(std::io::Error),
|
||||||
|
}
|
||||||
|
impl std::fmt::Display for TextureError{
|
||||||
|
fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{
|
||||||
|
write!(f,"{self:?}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl std::error::Error for TextureError{}
|
||||||
|
impl From<std::io::Error> for TextureError{
|
||||||
|
fn from(value:std::io::Error)->Self{
|
||||||
|
Self::Io(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct TextureLoader<'a>(std::marker::PhantomData<&'a ()>);
|
||||||
|
impl TextureLoader<'_>{
|
||||||
|
pub fn new()->Self{
|
||||||
|
Self(std::marker::PhantomData)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<'a> Loader for TextureLoader<'a>{
|
||||||
|
type Error=TextureError;
|
||||||
|
type Index=Cow<'a,str>;
|
||||||
|
type Resource=Texture;
|
||||||
|
fn load(&mut self,index:Self::Index)->Result<Self::Resource,Self::Error>{
|
||||||
|
let file_name=format!("textures/{}.dds",index);
|
||||||
|
let mut file=std::fs::File::open(file_name)?;
|
||||||
|
let mut data=Vec::new();
|
||||||
|
file.read_to_end(&mut data)?;
|
||||||
|
Ok(Texture::ImageDDS(data))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum MeshError{
|
||||||
|
Io(std::io::Error),
|
||||||
|
VMDL(vmdl::ModelError),
|
||||||
|
VBSP(vbsp::BspError),
|
||||||
|
MissingMdl(String),
|
||||||
|
MissingVtx,
|
||||||
|
MissingVvd,
|
||||||
|
}
|
||||||
|
impl std::fmt::Display for MeshError{
|
||||||
|
fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{
|
||||||
|
write!(f,"{self:?}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl std::error::Error for MeshError{}
|
||||||
|
impl From<std::io::Error> for MeshError{
|
||||||
|
fn from(value:std::io::Error)->Self{
|
||||||
|
Self::Io(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl From<vmdl::ModelError> for MeshError{
|
||||||
|
fn from(value:vmdl::ModelError)->Self{
|
||||||
|
Self::VMDL(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl From<vbsp::BspError> for MeshError{
|
||||||
|
fn from(value:vbsp::BspError)->Self{
|
||||||
|
Self::VBSP(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone,Copy)]
|
||||||
|
pub struct BspFinder<'bsp,'vpk>{
|
||||||
|
pub bsp:&'bsp Bsp,
|
||||||
|
pub vpks:&'vpk [Vpk],
|
||||||
|
}
|
||||||
|
impl<'bsp,'vpk> BspFinder<'bsp,'vpk>{
|
||||||
|
pub fn find<'a>(&self,path:&str)->Result<Option<Cow<'a,[u8]>>,vbsp::BspError>
|
||||||
|
where
|
||||||
|
'bsp:'a,
|
||||||
|
'vpk:'a,
|
||||||
|
{
|
||||||
|
// search bsp
|
||||||
|
if let Some(data)=self.bsp.pack_get(path)?{
|
||||||
|
return Ok(Some(Cow::Owned(data)));
|
||||||
|
}
|
||||||
|
|
||||||
|
//search each vpk
|
||||||
|
for vpk in self.vpks{
|
||||||
|
if let Some(vpk_entry)=vpk.tree_get(path){
|
||||||
|
return Ok(Some(vpk_entry.get()?));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct ModelLoader<'bsp,'vpk,'a>{
|
||||||
|
finder:BspFinder<'bsp,'vpk>,
|
||||||
|
life:core::marker::PhantomData<&'a ()>,
|
||||||
|
}
|
||||||
|
impl ModelLoader<'_,'_,'_>{
|
||||||
|
#[inline]
|
||||||
|
pub const fn new<'bsp,'vpk,'a>(
|
||||||
|
finder:BspFinder<'bsp,'vpk>,
|
||||||
|
)->ModelLoader<'bsp,'vpk,'a>{
|
||||||
|
ModelLoader{
|
||||||
|
finder,
|
||||||
|
life:core::marker::PhantomData,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<'bsp,'vpk,'a> Loader for ModelLoader<'bsp,'vpk,'a>
|
||||||
|
where
|
||||||
|
'bsp:'a,
|
||||||
|
'vpk:'a,
|
||||||
|
{
|
||||||
|
type Error=MeshError;
|
||||||
|
type Index=&'a str;
|
||||||
|
type Resource=vmdl::Model;
|
||||||
|
fn load(&mut self,index:Self::Index)->Result<Self::Resource,Self::Error>{
|
||||||
|
let mdl_path_lower=index.to_lowercase();
|
||||||
|
//.mdl, .vvd, .dx90.vtx
|
||||||
|
let path=std::path::PathBuf::from(mdl_path_lower.as_str());
|
||||||
|
let mut vvd_path=path.clone();
|
||||||
|
let mut vtx_path=path;
|
||||||
|
vvd_path.set_extension("vvd");
|
||||||
|
vtx_path.set_extension("dx90.vtx");
|
||||||
|
// TODO: search more packs, possibly using an index of multiple packs
|
||||||
|
let mdl=self.finder.find(mdl_path_lower.as_str())?.ok_or(MeshError::MissingMdl(mdl_path_lower))?;
|
||||||
|
let vtx=self.finder.find(vtx_path.as_os_str().to_str().unwrap())?.ok_or(MeshError::MissingVtx)?;
|
||||||
|
let vvd=self.finder.find(vvd_path.as_os_str().to_str().unwrap())?.ok_or(MeshError::MissingVvd)?;
|
||||||
|
Ok(vmdl::Model::from_parts(
|
||||||
|
vmdl::mdl::Mdl::read(mdl.as_ref())?,
|
||||||
|
vmdl::vtx::Vtx::read(vtx.as_ref())?,
|
||||||
|
vmdl::vvd::Vvd::read(vvd.as_ref())?,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct MeshLoader<'bsp,'vpk,'load,'a>{
|
||||||
|
finder:BspFinder<'bsp,'vpk>,
|
||||||
|
deferred_loader:&'load mut strafesnet_deferred_loader::deferred_loader::RenderConfigDeferredLoader<Cow<'a,str>>,
|
||||||
|
}
|
||||||
|
impl MeshLoader<'_,'_,'_,'_>{
|
||||||
|
#[inline]
|
||||||
|
pub const fn new<'bsp,'vpk,'load,'a>(
|
||||||
|
finder:BspFinder<'bsp,'vpk>,
|
||||||
|
deferred_loader:&'load mut strafesnet_deferred_loader::deferred_loader::RenderConfigDeferredLoader<Cow<'a,str>>,
|
||||||
|
)->MeshLoader<'bsp,'vpk,'load,'a>{
|
||||||
|
MeshLoader{
|
||||||
|
finder,
|
||||||
|
deferred_loader
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<'bsp,'vpk,'load,'a> Loader for MeshLoader<'bsp,'vpk,'load,'a>
|
||||||
|
where
|
||||||
|
'bsp:'a,
|
||||||
|
'vpk:'a,
|
||||||
|
{
|
||||||
|
type Error=MeshError;
|
||||||
|
type Index=&'a str;
|
||||||
|
type Resource=Mesh;
|
||||||
|
fn load(&mut self,index:Self::Index)->Result<Self::Resource,Self::Error>{
|
||||||
|
let model=ModelLoader::new(self.finder).load(index)?;
|
||||||
|
let mesh=crate::mesh::convert_mesh(model,&mut self.deferred_loader);
|
||||||
|
Ok(mesh)
|
||||||
|
}
|
||||||
|
}
|
||||||
78
lib/bsp_loader/src/mesh.rs
Normal file
78
lib/bsp_loader/src/mesh.rs
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
use std::borrow::Cow;
|
||||||
|
|
||||||
|
use strafesnet_common::model;
|
||||||
|
use strafesnet_deferred_loader::deferred_loader::RenderConfigDeferredLoader;
|
||||||
|
|
||||||
|
use crate::valve_transform;
|
||||||
|
|
||||||
|
fn ingest_vertex(mb:&mut model::MeshBuilder,vertex:&vmdl::vvd::Vertex,color:model::ColorId)->model::VertexId{
|
||||||
|
let pos=mb.acquire_pos_id(valve_transform(vertex.position.into()));
|
||||||
|
let normal=mb.acquire_normal_id(valve_transform(vertex.normal.into()));
|
||||||
|
let tex=mb.acquire_tex_id(glam::Vec2::from_array(vertex.texture_coordinates));
|
||||||
|
mb.acquire_vertex_id(model::IndexedVertex{
|
||||||
|
pos,
|
||||||
|
tex,
|
||||||
|
normal,
|
||||||
|
color,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn convert_mesh(model:vmdl::Model,deferred_loader:&mut RenderConfigDeferredLoader<Cow<str>>)->model::Mesh{
|
||||||
|
let texture_paths=model.texture_directories();
|
||||||
|
if texture_paths.len()!=1{
|
||||||
|
println!("WARNING: multiple texture paths");
|
||||||
|
}
|
||||||
|
let skin=model.skin_tables().nth(0).unwrap();
|
||||||
|
|
||||||
|
let mut mb=model::MeshBuilder::new();
|
||||||
|
|
||||||
|
let color=mb.acquire_color_id(glam::Vec4::ONE);
|
||||||
|
|
||||||
|
let model_vertices=model.vertices();
|
||||||
|
|
||||||
|
let mut graphics_groups=Vec::new();
|
||||||
|
let mut physics_groups=Vec::new();
|
||||||
|
let polygon_groups=model.meshes().enumerate().map(|(polygon_group_id,mesh)|{
|
||||||
|
let polygon_group_id=model::PolygonGroupId::new(polygon_group_id as u32);
|
||||||
|
|
||||||
|
let render_id=if let (Some(texture_path),Some(texture_name))=(texture_paths.get(0),skin.texture(mesh.material_index())){
|
||||||
|
let mut path=std::path::PathBuf::from(texture_path.as_str());
|
||||||
|
path.push(texture_name);
|
||||||
|
let index=path.as_os_str().to_str().map(|s|Cow::Owned(s.to_owned()));
|
||||||
|
deferred_loader.acquire_render_config_id(index)
|
||||||
|
}else{
|
||||||
|
deferred_loader.acquire_render_config_id(None)
|
||||||
|
};
|
||||||
|
|
||||||
|
graphics_groups.push(model::IndexedGraphicsGroup{
|
||||||
|
render:render_id,
|
||||||
|
groups:vec![polygon_group_id],
|
||||||
|
});
|
||||||
|
physics_groups.push(model::IndexedPhysicsGroup{
|
||||||
|
groups:vec![polygon_group_id],
|
||||||
|
});
|
||||||
|
model::PolygonGroup::PolygonList(model::PolygonList::new(
|
||||||
|
//looking at the code, it would seem that the strips are pre-deindexed into triangle lists when calling this function
|
||||||
|
mesh.vertex_strip_indices().flat_map(|mut strip|{
|
||||||
|
std::iter::from_fn(move ||{
|
||||||
|
match (strip.next(),strip.next(),strip.next()){
|
||||||
|
(Some(v1),Some(v2),Some(v3))=>Some([v1,v2,v3]),
|
||||||
|
//ignore extra vertices, not sure what to do in this case, failing the whole conversion could be appropriate
|
||||||
|
_=>None,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}).flat_map(|[v1,v2,v3]|{
|
||||||
|
// this should probably be a fatal error :D
|
||||||
|
let v1=model_vertices.get(v1)?;
|
||||||
|
let v2=model_vertices.get(v2)?;
|
||||||
|
let v3=model_vertices.get(v3)?;
|
||||||
|
Some(vec![
|
||||||
|
ingest_vertex(&mut mb,v1,color),
|
||||||
|
ingest_vertex(&mut mb,v2,color),
|
||||||
|
ingest_vertex(&mut mb,v3,color),
|
||||||
|
])
|
||||||
|
}).collect()
|
||||||
|
))
|
||||||
|
}).collect();
|
||||||
|
mb.build(polygon_groups,graphics_groups,physics_groups)
|
||||||
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "strafesnet_common"
|
name = "strafesnet_common"
|
||||||
version = "0.5.2"
|
version = "0.6.0"
|
||||||
edition = "2021"
|
edition = "2024"
|
||||||
repository = "https://git.itzana.me/StrafesNET/strafe-project"
|
repository = "https://git.itzana.me/StrafesNET/strafe-project"
|
||||||
license = "MIT OR Apache-2.0"
|
license = "MIT OR Apache-2.0"
|
||||||
description = "Common types and helpers for Strafe Client associated projects."
|
description = "Common types and helpers for Strafe Client associated projects."
|
||||||
@@ -12,8 +12,9 @@ authors = ["Rhys Lloyd <krakow20@gmail.com>"]
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
arrayvec = "0.7.4"
|
arrayvec = "0.7.4"
|
||||||
bitflags = "2.6.0"
|
bitflags = "2.6.0"
|
||||||
fixed_wide = { path = "../fixed_wide", registry = "strafesnet", features = ["deferred-division","zeroes","wide-mul"] }
|
fixed_wide = { version = "0.1.2", path = "../fixed_wide", registry = "strafesnet", features = ["deferred-division","zeroes","wide-mul"] }
|
||||||
linear_ops = { path = "../linear_ops", registry = "strafesnet", features = ["deferred-division","named-fields"] }
|
linear_ops = { version = "0.1.0", path = "../linear_ops", registry = "strafesnet", features = ["deferred-division","named-fields"] }
|
||||||
ratio_ops = { path = "../ratio_ops", registry = "strafesnet" }
|
ratio_ops = { version = "0.1.0", path = "../ratio_ops", registry = "strafesnet" }
|
||||||
glam = "0.29.0"
|
glam = "0.30.0"
|
||||||
id = { version = "0.1.0", registry = "strafesnet" }
|
id = { version = "0.1.0", registry = "strafesnet" }
|
||||||
|
vbsp = "0.8.0"
|
||||||
|
|||||||
@@ -7,42 +7,57 @@ pub struct Aabb{
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Aabb{
|
impl Default for Aabb{
|
||||||
|
#[inline]
|
||||||
fn default()->Self{
|
fn default()->Self{
|
||||||
Self{min:vec3::MAX,max:vec3::MIN}
|
Self{min:vec3::MAX,max:vec3::MIN}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Aabb{
|
impl Aabb{
|
||||||
|
#[inline]
|
||||||
pub const fn new(min:Planar64Vec3,max:Planar64Vec3)->Self{
|
pub const fn new(min:Planar64Vec3,max:Planar64Vec3)->Self{
|
||||||
Self{min,max}
|
Self{min,max}
|
||||||
}
|
}
|
||||||
|
#[inline]
|
||||||
pub const fn max(&self)->Planar64Vec3{
|
pub const fn max(&self)->Planar64Vec3{
|
||||||
self.max
|
self.max
|
||||||
}
|
}
|
||||||
|
#[inline]
|
||||||
pub const fn min(&self)->Planar64Vec3{
|
pub const fn min(&self)->Planar64Vec3{
|
||||||
self.min
|
self.min
|
||||||
}
|
}
|
||||||
|
#[inline]
|
||||||
pub fn grow(&mut self,point:Planar64Vec3){
|
pub fn grow(&mut self,point:Planar64Vec3){
|
||||||
self.min=self.min.min(point);
|
self.min=self.min.min(point);
|
||||||
self.max=self.max.max(point);
|
self.max=self.max.max(point);
|
||||||
}
|
}
|
||||||
|
#[inline]
|
||||||
pub fn join(&mut self,aabb:&Aabb){
|
pub fn join(&mut self,aabb:&Aabb){
|
||||||
self.min=self.min.min(aabb.min);
|
self.min=self.min.min(aabb.min);
|
||||||
self.max=self.max.max(aabb.max);
|
self.max=self.max.max(aabb.max);
|
||||||
}
|
}
|
||||||
|
#[inline]
|
||||||
pub fn inflate(&mut self,hs:Planar64Vec3){
|
pub fn inflate(&mut self,hs:Planar64Vec3){
|
||||||
self.min-=hs;
|
self.min-=hs;
|
||||||
self.max+=hs;
|
self.max+=hs;
|
||||||
}
|
}
|
||||||
|
#[inline]
|
||||||
|
pub fn contains(&self,point:Planar64Vec3)->bool{
|
||||||
|
let bvec=self.min.lt(point)&point.lt(self.max);
|
||||||
|
bvec.all()
|
||||||
|
}
|
||||||
|
#[inline]
|
||||||
pub fn intersects(&self,aabb:&Aabb)->bool{
|
pub fn intersects(&self,aabb:&Aabb)->bool{
|
||||||
let bvec=self.min.lt(aabb.max)&aabb.min.lt(self.max);
|
let bvec=self.min.lt(aabb.max)&aabb.min.lt(self.max);
|
||||||
bvec.all()
|
bvec.all()
|
||||||
}
|
}
|
||||||
|
#[inline]
|
||||||
pub fn size(&self)->Planar64Vec3{
|
pub fn size(&self)->Planar64Vec3{
|
||||||
self.max-self.min
|
self.max-self.min
|
||||||
}
|
}
|
||||||
|
#[inline]
|
||||||
pub fn center(&self)->Planar64Vec3{
|
pub fn center(&self)->Planar64Vec3{
|
||||||
self.min+(self.max-self.min)>>1
|
self.min.map_zip(self.max,|(min,max)|min.midpoint(max))
|
||||||
}
|
}
|
||||||
//probably use floats for area & volume because we don't care about precision
|
//probably use floats for area & volume because we don't care about precision
|
||||||
// pub fn area_weight(&self)->f32{
|
// pub fn area_weight(&self)->f32{
|
||||||
|
|||||||
@@ -1,4 +1,10 @@
|
|||||||
|
use std::cmp::Ordering;
|
||||||
|
use std::collections::BTreeMap;
|
||||||
|
|
||||||
use crate::aabb::Aabb;
|
use crate::aabb::Aabb;
|
||||||
|
use crate::ray::Ray;
|
||||||
|
use crate::integer::{Ratio,Planar64};
|
||||||
|
use crate::instruction::{InstructionCollector,TimedInstruction};
|
||||||
|
|
||||||
//da algaritum
|
//da algaritum
|
||||||
//lista boxens
|
//lista boxens
|
||||||
@@ -10,35 +16,117 @@ use crate::aabb::Aabb;
|
|||||||
//sort the centerpoints on each axis (3 lists)
|
//sort the centerpoints on each axis (3 lists)
|
||||||
//bv is put into octant based on whether it is upper or lower in each list
|
//bv is put into octant based on whether it is upper or lower in each list
|
||||||
|
|
||||||
pub enum RecursiveContent<R,T>{
|
|
||||||
Branch(Vec<R>),
|
pub fn intersect_aabb(ray:&Ray,aabb:&Aabb)->Option<Ratio<Planar64,Planar64>>{
|
||||||
Leaf(T),
|
// n.(o+d*t)==n.p
|
||||||
|
// n.o + n.d * t == n.p
|
||||||
|
// t == (n.p - n.o)/n.d
|
||||||
|
let mut hit=None;
|
||||||
|
match ray.direction.x.cmp(&Planar64::ZERO){
|
||||||
|
Ordering::Less=>{
|
||||||
|
let rel_min=aabb.min()-ray.origin;
|
||||||
|
let rel_max=aabb.max()-ray.origin;
|
||||||
|
let dy=rel_max.x*ray.direction.y;
|
||||||
|
let dz=rel_max.x*ray.direction.z;
|
||||||
|
// x is negative, so inequalities are flipped
|
||||||
|
if rel_min.y*ray.direction.x>dy&&dy>rel_max.y*ray.direction.x
|
||||||
|
&&rel_min.z*ray.direction.x>dz&&dz>rel_max.z*ray.direction.x{
|
||||||
|
let t=rel_max.x/ray.direction.x;
|
||||||
|
hit=Some(hit.map_or(t,|best_t|t.min(best_t)));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Ordering::Equal=>(),
|
||||||
|
Ordering::Greater=>{
|
||||||
|
let rel_min=aabb.min()-ray.origin;
|
||||||
|
let rel_max=aabb.max()-ray.origin;
|
||||||
|
let dy=rel_min.x*ray.direction.y;
|
||||||
|
let dz=rel_min.x*ray.direction.z;
|
||||||
|
// x is positive, so inequalities are normal
|
||||||
|
if rel_min.y*ray.direction.x<dy&&dy<rel_max.y*ray.direction.x
|
||||||
|
&&rel_min.z*ray.direction.x<dz&&dz<rel_max.z*ray.direction.x{
|
||||||
|
let t=rel_min.x/ray.direction.x;
|
||||||
|
hit=Some(hit.map_or(t,|best_t|t.min(best_t)));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
match ray.direction.z.cmp(&Planar64::ZERO){
|
||||||
|
Ordering::Less=>{
|
||||||
|
let rel_min=aabb.min()-ray.origin;
|
||||||
|
let rel_max=aabb.max()-ray.origin;
|
||||||
|
let dx=rel_max.z*ray.direction.x;
|
||||||
|
let dy=rel_max.z*ray.direction.y;
|
||||||
|
// z is negative, so inequalities are flipped
|
||||||
|
if rel_min.x*ray.direction.z>dx&&dx>rel_max.x*ray.direction.z
|
||||||
|
&&rel_min.y*ray.direction.z>dy&&dy>rel_max.y*ray.direction.z{
|
||||||
|
let t=rel_max.z/ray.direction.z;
|
||||||
|
hit=Some(hit.map_or(t,|best_t|t.min(best_t)));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Ordering::Equal=>(),
|
||||||
|
Ordering::Greater=>{
|
||||||
|
let rel_min=aabb.min()-ray.origin;
|
||||||
|
let rel_max=aabb.max()-ray.origin;
|
||||||
|
let dx=rel_min.z*ray.direction.x;
|
||||||
|
let dy=rel_min.z*ray.direction.y;
|
||||||
|
// z is positive, so inequalities are normal
|
||||||
|
if rel_min.x*ray.direction.z<dx&&dx<rel_max.x*ray.direction.z
|
||||||
|
&&rel_min.y*ray.direction.z<dy&&dy<rel_max.y*ray.direction.z{
|
||||||
|
let t=rel_min.z/ray.direction.z;
|
||||||
|
hit=Some(hit.map_or(t,|best_t|t.min(best_t)));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
match ray.direction.y.cmp(&Planar64::ZERO){
|
||||||
|
Ordering::Less=>{
|
||||||
|
let rel_min=aabb.min()-ray.origin;
|
||||||
|
let rel_max=aabb.max()-ray.origin;
|
||||||
|
let dz=rel_max.y*ray.direction.z;
|
||||||
|
let dx=rel_max.y*ray.direction.x;
|
||||||
|
// y is negative, so inequalities are flipped
|
||||||
|
if rel_min.z*ray.direction.y>dz&&dz>rel_max.z*ray.direction.y
|
||||||
|
&&rel_min.x*ray.direction.y>dx&&dx>rel_max.x*ray.direction.y{
|
||||||
|
let t=rel_max.y/ray.direction.y;
|
||||||
|
hit=Some(hit.map_or(t,|best_t|t.min(best_t)));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Ordering::Equal=>(),
|
||||||
|
Ordering::Greater=>{
|
||||||
|
let rel_min=aabb.min()-ray.origin;
|
||||||
|
let rel_max=aabb.max()-ray.origin;
|
||||||
|
let dz=rel_min.y*ray.direction.z;
|
||||||
|
let dx=rel_min.y*ray.direction.x;
|
||||||
|
// y is positive, so inequalities are normal
|
||||||
|
if rel_min.z*ray.direction.y<dz&&dz<rel_max.z*ray.direction.y
|
||||||
|
&&rel_min.x*ray.direction.y<dx&&dx<rel_max.x*ray.direction.y{
|
||||||
|
let t=rel_min.y/ray.direction.y;
|
||||||
|
hit=Some(hit.map_or(t,|best_t|t.min(best_t)));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
hit
|
||||||
}
|
}
|
||||||
impl<R,T> Default for RecursiveContent<R,T>{
|
|
||||||
fn default()->Self{
|
pub enum RecursiveContent<N,L>{
|
||||||
|
Branch(Vec<N>),
|
||||||
|
Leaf(L),
|
||||||
|
}
|
||||||
|
impl<N,L> RecursiveContent<N,L>{
|
||||||
|
pub fn empty()->Self{
|
||||||
Self::Branch(Vec::new())
|
Self::Branch(Vec::new())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub struct BvhNode<T>{
|
pub struct BvhNode<L>{
|
||||||
content:RecursiveContent<BvhNode<T>,T>,
|
content:RecursiveContent<BvhNode<L>,L>,
|
||||||
aabb:Aabb,
|
aabb:Aabb,
|
||||||
}
|
}
|
||||||
impl<T> Default for BvhNode<T>{
|
impl<L> BvhNode<L>{
|
||||||
fn default()->Self{
|
pub fn empty()->Self{
|
||||||
Self{
|
Self{
|
||||||
content:Default::default(),
|
content:RecursiveContent::empty(),
|
||||||
aabb:Aabb::default(),
|
aabb:Aabb::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
pub fn sample_aabb<F:FnMut(&L)>(&self,aabb:&Aabb,f:&mut F){
|
||||||
pub struct BvhWeightNode<W,T>{
|
|
||||||
content:RecursiveContent<BvhWeightNode<W,T>,T>,
|
|
||||||
weight:W,
|
|
||||||
aabb:Aabb,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> BvhNode<T>{
|
|
||||||
pub fn the_tester<F:FnMut(&T)>(&self,aabb:&Aabb,f:&mut F){
|
|
||||||
match &self.content{
|
match &self.content{
|
||||||
RecursiveContent::Leaf(model)=>f(model),
|
RecursiveContent::Leaf(model)=>f(model),
|
||||||
RecursiveContent::Branch(children)=>for child in children{
|
RecursiveContent::Branch(children)=>for child in children{
|
||||||
@@ -47,51 +135,101 @@ impl<T> BvhNode<T>{
|
|||||||
//you're probably not going to spend a lot of time outside the map,
|
//you're probably not going to spend a lot of time outside the map,
|
||||||
//so the test is extra work for nothing
|
//so the test is extra work for nothing
|
||||||
if aabb.intersects(&child.aabb){
|
if aabb.intersects(&child.aabb){
|
||||||
child.the_tester(aabb,f);
|
child.sample_aabb(aabb,f);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn into_visitor<F:FnMut(T)>(self,f:&mut F){
|
fn populate_nodes<'a,T,F>(
|
||||||
match self.content{
|
&'a self,
|
||||||
RecursiveContent::Leaf(model)=>f(model),
|
collector:&mut InstructionCollector<&'a L,Ratio<Planar64,Planar64>>,
|
||||||
|
nodes:&mut BTreeMap<Ratio<Planar64,Planar64>,&'a BvhNode<L>>,
|
||||||
|
ray:&Ray,
|
||||||
|
start_time:Ratio<Planar64,Planar64>,
|
||||||
|
f:&F,
|
||||||
|
)
|
||||||
|
where
|
||||||
|
T:Ord+Copy,
|
||||||
|
Ratio<Planar64,Planar64>:From<T>,
|
||||||
|
F:Fn(&L,&Ray)->Option<T>,
|
||||||
|
{
|
||||||
|
match &self.content{
|
||||||
|
RecursiveContent::Leaf(leaf)=>if let Some(time)=f(leaf,ray){
|
||||||
|
let ins=TimedInstruction{time:time.into(),instruction:leaf};
|
||||||
|
if start_time.lt_ratio(ins.time)&&ins.time.lt_ratio(collector.time()){
|
||||||
|
collector.collect(Some(ins));
|
||||||
|
}
|
||||||
|
},
|
||||||
RecursiveContent::Branch(children)=>for child in children{
|
RecursiveContent::Branch(children)=>for child in children{
|
||||||
child.into_visitor(f)
|
if child.aabb.contains(ray.origin){
|
||||||
},
|
child.populate_nodes(collector,nodes,ray,start_time,f);
|
||||||
}
|
}else{
|
||||||
}
|
// Am I an upcoming superstar?
|
||||||
pub fn weigh_contents<W:Copy+std::iter::Sum<W>,F:Fn(&T)->W>(self,f:&F)->BvhWeightNode<W,T>{
|
if let Some(t)=intersect_aabb(ray,&child.aabb){
|
||||||
match self.content{
|
if start_time.lt_ratio(t)&&t.lt_ratio(collector.time()){
|
||||||
RecursiveContent::Leaf(model)=>BvhWeightNode{
|
nodes.insert(t,child);
|
||||||
weight:f(&model),
|
}
|
||||||
content:RecursiveContent::Leaf(model),
|
}
|
||||||
aabb:self.aabb,
|
|
||||||
},
|
|
||||||
RecursiveContent::Branch(children)=>{
|
|
||||||
let branch:Vec<BvhWeightNode<W,T>>=children.into_iter().map(|child|
|
|
||||||
child.weigh_contents(f)
|
|
||||||
).collect();
|
|
||||||
BvhWeightNode{
|
|
||||||
weight:branch.iter().map(|node|node.weight).sum(),
|
|
||||||
content:RecursiveContent::Branch(branch),
|
|
||||||
aabb:self.aabb,
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
pub fn sample_ray<T,F>(
|
||||||
|
&self,
|
||||||
|
ray:&Ray,
|
||||||
|
start_time:T,
|
||||||
|
time_limit:T,
|
||||||
|
f:F,
|
||||||
|
)->Option<(T,&L)>
|
||||||
|
where
|
||||||
|
T:Ord+Copy,
|
||||||
|
T:From<Ratio<Planar64,Planar64>>,
|
||||||
|
Ratio<Planar64,Planar64>:From<T>,
|
||||||
|
F:Fn(&L,&Ray)->Option<T>,
|
||||||
|
{
|
||||||
|
// source of nondeterminism when Aabb boundaries are coplanar
|
||||||
|
let mut nodes=BTreeMap::new();
|
||||||
|
|
||||||
impl <W,T> BvhWeightNode<W,T>{
|
let start_time=start_time.into();
|
||||||
pub const fn weight(&self)->&W{
|
let time_limit=time_limit.into();
|
||||||
&self.weight
|
let mut collector=InstructionCollector::new(time_limit);
|
||||||
|
// break open all nodes that contain ray.origin and populate nodes with future intersection times
|
||||||
|
self.populate_nodes(&mut collector,&mut nodes,ray,start_time,&f);
|
||||||
|
|
||||||
|
// swim through nodes one at a time
|
||||||
|
while let Some((t,node))=nodes.pop_first(){
|
||||||
|
if collector.time()<t{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
match &node.content{
|
||||||
|
RecursiveContent::Leaf(leaf)=>if let Some(time)=f(leaf,ray){
|
||||||
|
let ins=TimedInstruction{time:time.into(),instruction:leaf};
|
||||||
|
// this lower bound can also be omitted
|
||||||
|
// but it causes type inference errors lol
|
||||||
|
if start_time.lt_ratio(ins.time)&&ins.time.lt_ratio(collector.time()){
|
||||||
|
collector.collect(Some(ins));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// break open the node and predict collisions with the child nodes
|
||||||
|
RecursiveContent::Branch(children)=>for child in children{
|
||||||
|
// Am I an upcoming superstar?
|
||||||
|
if let Some(t)=intersect_aabb(ray,&child.aabb){
|
||||||
|
// we don't need to check the lower bound
|
||||||
|
// because child aabbs are guaranteed to be within the parent bounds.
|
||||||
|
if t<collector.time(){
|
||||||
|
nodes.insert(t,child);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
collector.take().map(|TimedInstruction{time,instruction:leaf}|(time.into(),leaf))
|
||||||
}
|
}
|
||||||
pub const fn aabb(&self)->&Aabb{
|
pub fn into_inner(self)->(RecursiveContent<BvhNode<L>,L>,Aabb){
|
||||||
&self.aabb
|
(self.content,self.aabb)
|
||||||
}
|
}
|
||||||
pub fn into_content(self)->RecursiveContent<BvhWeightNode<W,T>,T>{
|
pub fn into_visitor<F:FnMut(L)>(self,f:&mut F){
|
||||||
self.content
|
|
||||||
}
|
|
||||||
pub fn into_visitor<F:FnMut(T)>(self,f:&mut F){
|
|
||||||
match self.content{
|
match self.content{
|
||||||
RecursiveContent::Leaf(model)=>f(model),
|
RecursiveContent::Leaf(model)=>f(model),
|
||||||
RecursiveContent::Branch(children)=>for child in children{
|
RecursiveContent::Branch(children)=>for child in children{
|
||||||
@@ -130,9 +268,9 @@ fn generate_bvh_node<T>(boxen:Vec<(T,Aabb)>,force:bool)->BvhNode<T>{
|
|||||||
sort_y.push((i,center.y));
|
sort_y.push((i,center.y));
|
||||||
sort_z.push((i,center.z));
|
sort_z.push((i,center.z));
|
||||||
}
|
}
|
||||||
sort_x.sort_by(|tup0,tup1|tup0.1.cmp(&tup1.1));
|
sort_x.sort_by_key(|&(_,c)|c);
|
||||||
sort_y.sort_by(|tup0,tup1|tup0.1.cmp(&tup1.1));
|
sort_y.sort_by_key(|&(_,c)|c);
|
||||||
sort_z.sort_by(|tup0,tup1|tup0.1.cmp(&tup1.1));
|
sort_z.sort_by_key(|&(_,c)|c);
|
||||||
let h=n/2;
|
let h=n/2;
|
||||||
let median_x=sort_x[h].1;
|
let median_x=sort_x[h].1;
|
||||||
let median_y=sort_y[h].1;
|
let median_y=sort_y[h].1;
|
||||||
|
|||||||
@@ -171,4 +171,7 @@ impl CollisionAttributes{
|
|||||||
pub fn contact_default()->Self{
|
pub fn contact_default()->Self{
|
||||||
Self::Contact(ContactAttributes::default())
|
Self::Contact(ContactAttributes::default())
|
||||||
}
|
}
|
||||||
|
pub fn intersect_default()->Self{
|
||||||
|
Self::Intersect(IntersectAttributes::default())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
use std::collections::{HashSet,HashMap};
|
use std::collections::{HashSet,HashMap};
|
||||||
use crate::model::ModelId;
|
use crate::model::ModelId;
|
||||||
use crate::gameplay_style;
|
use crate::gameplay_style;
|
||||||
use crate::updatable::Updatable;
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct StageElement{
|
pub struct StageElement{
|
||||||
@@ -128,18 +127,6 @@ impl Stage{
|
|||||||
self.unordered_checkpoints.contains(&model_id)
|
self.unordered_checkpoints.contains(&model_id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#[derive(Default)]
|
|
||||||
pub struct StageUpdate{
|
|
||||||
//other behaviour models of this stage can have
|
|
||||||
ordered_checkpoints:HashMap<CheckpointId,ModelId>,
|
|
||||||
unordered_checkpoints:HashSet<ModelId>,
|
|
||||||
}
|
|
||||||
impl Updatable<StageUpdate> for Stage{
|
|
||||||
fn update(&mut self,update:StageUpdate){
|
|
||||||
self.ordered_checkpoints.extend(update.ordered_checkpoints);
|
|
||||||
self.unordered_checkpoints.extend(update.unordered_checkpoints);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone,Copy,Hash,Eq,PartialEq)]
|
#[derive(Clone,Copy,Hash,Eq,PartialEq)]
|
||||||
pub enum Zone{
|
pub enum Zone{
|
||||||
@@ -211,34 +198,47 @@ impl Mode{
|
|||||||
pub fn push_stage(&mut self,stage:Stage){
|
pub fn push_stage(&mut self,stage:Stage){
|
||||||
self.stages.push(stage)
|
self.stages.push(stage)
|
||||||
}
|
}
|
||||||
pub fn get_stage_mut(&mut self,stage:StageId)->Option<&mut Stage>{
|
pub fn get_stage_mut(&mut self,StageId(stage_id):StageId)->Option<&mut Stage>{
|
||||||
self.stages.get_mut(stage.0 as usize)
|
self.stages.get_mut(stage_id as usize)
|
||||||
}
|
}
|
||||||
pub fn get_spawn_model_id(&self,stage:StageId)->Option<ModelId>{
|
pub fn get_spawn_model_id(&self,StageId(stage_id):StageId)->Option<ModelId>{
|
||||||
self.stages.get(stage.0 as usize).map(|s|s.spawn)
|
self.stages.get(stage_id as usize).map(|s|s.spawn)
|
||||||
}
|
}
|
||||||
pub fn get_zone(&self,model_id:ModelId)->Option<&Zone>{
|
pub fn get_zone(&self,model_id:ModelId)->Option<&Zone>{
|
||||||
self.zones.get(&model_id)
|
self.zones.get(&model_id)
|
||||||
}
|
}
|
||||||
pub fn get_stage(&self,stage_id:StageId)->Option<&Stage>{
|
pub fn get_stage(&self,StageId(stage_id):StageId)->Option<&Stage>{
|
||||||
self.stages.get(stage_id.0 as usize)
|
self.stages.get(stage_id as usize)
|
||||||
}
|
}
|
||||||
pub fn get_element(&self,model_id:ModelId)->Option<&StageElement>{
|
pub fn get_element(&self,model_id:ModelId)->Option<&StageElement>{
|
||||||
self.elements.get(&model_id)
|
self.elements.get(&model_id)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct NormalizedMode(Mode);
|
||||||
|
impl NormalizedMode{
|
||||||
|
pub fn new(mode:Mode)->Self{
|
||||||
|
Self(mode)
|
||||||
|
}
|
||||||
|
pub fn into_inner(self)->Mode{
|
||||||
|
let Self(mode)=self;
|
||||||
|
mode
|
||||||
|
}
|
||||||
//TODO: put this in the SNF
|
//TODO: put this in the SNF
|
||||||
pub fn denormalize_data(&mut self){
|
pub fn denormalize(self)->Mode{
|
||||||
|
let NormalizedMode(mut mode)=self;
|
||||||
//expand and index normalized data
|
//expand and index normalized data
|
||||||
self.zones.insert(self.start,Zone::Start);
|
mode.zones.insert(mode.start,Zone::Start);
|
||||||
for (stage_id,stage) in self.stages.iter().enumerate(){
|
for (stage_id,stage) in mode.stages.iter().enumerate(){
|
||||||
self.elements.insert(stage.spawn,StageElement{
|
mode.elements.insert(stage.spawn,StageElement{
|
||||||
stage_id:StageId(stage_id as u32),
|
stage_id:StageId(stage_id as u32),
|
||||||
force:false,
|
force:false,
|
||||||
behaviour:StageElementBehaviour::SpawnAt,
|
behaviour:StageElementBehaviour::SpawnAt,
|
||||||
jump_limit:None,
|
jump_limit:None,
|
||||||
});
|
});
|
||||||
for (_,&model) in &stage.ordered_checkpoints{
|
for (_,&model) in &stage.ordered_checkpoints{
|
||||||
self.elements.insert(model,StageElement{
|
mode.elements.insert(model,StageElement{
|
||||||
stage_id:StageId(stage_id as u32),
|
stage_id:StageId(stage_id as u32),
|
||||||
force:false,
|
force:false,
|
||||||
behaviour:StageElementBehaviour::Checkpoint,
|
behaviour:StageElementBehaviour::Checkpoint,
|
||||||
@@ -246,7 +246,7 @@ impl Mode{
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
for &model in &stage.unordered_checkpoints{
|
for &model in &stage.unordered_checkpoints{
|
||||||
self.elements.insert(model,StageElement{
|
mode.elements.insert(model,StageElement{
|
||||||
stage_id:StageId(stage_id as u32),
|
stage_id:StageId(stage_id as u32),
|
||||||
force:false,
|
force:false,
|
||||||
behaviour:StageElementBehaviour::Checkpoint,
|
behaviour:StageElementBehaviour::Checkpoint,
|
||||||
@@ -254,53 +254,13 @@ impl Mode{
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
mode
|
||||||
}
|
|
||||||
//this would be nice as a macro
|
|
||||||
#[derive(Default)]
|
|
||||||
pub struct ModeUpdate{
|
|
||||||
zones:HashMap<ModelId,Zone>,
|
|
||||||
stages:HashMap<StageId,StageUpdate>,
|
|
||||||
//mutually exlusive stage element behaviour
|
|
||||||
elements:HashMap<ModelId,StageElement>,
|
|
||||||
}
|
|
||||||
impl Updatable<ModeUpdate> for Mode{
|
|
||||||
fn update(&mut self,update:ModeUpdate){
|
|
||||||
self.zones.extend(update.zones);
|
|
||||||
for (stage,stage_update) in update.stages{
|
|
||||||
if let Some(stage)=self.stages.get_mut(stage.0 as usize){
|
|
||||||
stage.update(stage_update);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
self.elements.extend(update.elements);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl ModeUpdate{
|
|
||||||
pub fn zone(model_id:ModelId,zone:Zone)->Self{
|
|
||||||
let mut mu=Self::default();
|
|
||||||
mu.zones.insert(model_id,zone);
|
|
||||||
mu
|
|
||||||
}
|
|
||||||
pub fn stage(stage_id:StageId,stage_update:StageUpdate)->Self{
|
|
||||||
let mut mu=Self::default();
|
|
||||||
mu.stages.insert(stage_id,stage_update);
|
|
||||||
mu
|
|
||||||
}
|
|
||||||
pub fn element(model_id:ModelId,element:StageElement)->Self{
|
|
||||||
let mut mu=Self::default();
|
|
||||||
mu.elements.insert(model_id,element);
|
|
||||||
mu
|
|
||||||
}
|
|
||||||
pub fn map_stage_element_ids<F:Fn(StageId)->StageId>(&mut self,f:F){
|
|
||||||
for (_,stage_element) in self.elements.iter_mut(){
|
|
||||||
stage_element.stage_id=f(stage_element.stage_id);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default,Clone)]
|
#[derive(Default,Clone)]
|
||||||
pub struct Modes{
|
pub struct Modes{
|
||||||
pub modes:Vec<Mode>,
|
modes:Vec<Mode>,
|
||||||
}
|
}
|
||||||
impl Modes{
|
impl Modes{
|
||||||
pub const fn new(modes:Vec<Mode>)->Self{
|
pub const fn new(modes:Vec<Mode>)->Self{
|
||||||
@@ -314,19 +274,182 @@ impl Modes{
|
|||||||
pub fn push_mode(&mut self,mode:Mode){
|
pub fn push_mode(&mut self,mode:Mode){
|
||||||
self.modes.push(mode)
|
self.modes.push(mode)
|
||||||
}
|
}
|
||||||
pub fn get_mode(&self,mode:ModeId)->Option<&Mode>{
|
pub fn get_mode(&self,ModeId(mode_id):ModeId)->Option<&Mode>{
|
||||||
self.modes.get(mode.0 as usize)
|
self.modes.get(mode_id as usize)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub struct ModesUpdate{
|
|
||||||
modes:HashMap<ModeId,ModeUpdate>,
|
#[derive(Clone)]
|
||||||
|
pub struct NormalizedModes{
|
||||||
|
modes:Vec<NormalizedMode>,
|
||||||
}
|
}
|
||||||
impl Updatable<ModesUpdate> for Modes{
|
impl NormalizedModes{
|
||||||
fn update(&mut self,update:ModesUpdate){
|
pub fn new(modes:Vec<NormalizedMode>)->Self{
|
||||||
for (mode,mode_update) in update.modes{
|
Self{modes}
|
||||||
if let Some(mode)=self.modes.get_mut(mode.0 as usize){
|
}
|
||||||
mode.update(mode_update);
|
pub fn len(&self)->usize{
|
||||||
}
|
self.modes.len()
|
||||||
|
}
|
||||||
|
pub fn denormalize(self)->Modes{
|
||||||
|
Modes{
|
||||||
|
modes:self.modes.into_iter().map(NormalizedMode::denormalize).collect(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
impl IntoIterator for NormalizedModes{
|
||||||
|
type Item=<Vec<NormalizedMode> as IntoIterator>::Item;
|
||||||
|
type IntoIter=<Vec<NormalizedMode> as IntoIterator>::IntoIter;
|
||||||
|
fn into_iter(self)->Self::IntoIter{
|
||||||
|
self.modes.into_iter()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct StageUpdate{
|
||||||
|
//other behaviour models of this stage can have
|
||||||
|
ordered_checkpoints:HashMap<CheckpointId,ModelId>,
|
||||||
|
unordered_checkpoints:HashSet<ModelId>,
|
||||||
|
}
|
||||||
|
impl StageUpdate{
|
||||||
|
fn apply_to(self,stage:&mut Stage){
|
||||||
|
stage.ordered_checkpoints.extend(self.ordered_checkpoints);
|
||||||
|
stage.unordered_checkpoints.extend(self.unordered_checkpoints);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//this would be nice as a macro
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct ModeUpdate{
|
||||||
|
zones:Option<(ModelId,Zone)>,
|
||||||
|
stages:Option<(StageId,StageUpdate)>,
|
||||||
|
//mutually exlusive stage element behaviour
|
||||||
|
elements:Option<(ModelId,StageElement)>,
|
||||||
|
}
|
||||||
|
impl ModeUpdate{
|
||||||
|
fn apply_to(self,mode:&mut Mode){
|
||||||
|
mode.zones.extend(self.zones);
|
||||||
|
if let Some((StageId(stage_id),stage_update))=self.stages{
|
||||||
|
if let Some(stage)=mode.stages.get_mut(stage_id as usize){
|
||||||
|
stage_update.apply_to(stage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mode.elements.extend(self.elements);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl ModeUpdate{
|
||||||
|
pub fn zone(model_id:ModelId,zone:Zone)->Self{
|
||||||
|
Self{
|
||||||
|
zones:Some((model_id,zone)),
|
||||||
|
stages:None,
|
||||||
|
elements:None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn stage(stage_id:StageId,stage_update:StageUpdate)->Self{
|
||||||
|
Self{
|
||||||
|
zones:None,
|
||||||
|
stages:Some((stage_id,stage_update)),
|
||||||
|
elements:None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn element(model_id:ModelId,element:StageElement)->Self{
|
||||||
|
Self{
|
||||||
|
zones:None,
|
||||||
|
stages:None,
|
||||||
|
elements:Some((model_id,element)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn map_stage_element_ids<F:Fn(StageId)->StageId>(&mut self,f:F){
|
||||||
|
for (_,stage_element) in self.elements.iter_mut(){
|
||||||
|
stage_element.stage_id=f(stage_element.stage_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ModeBuilder{
|
||||||
|
mode:Mode,
|
||||||
|
stage_id_map:HashMap<StageId,StageId>,
|
||||||
|
}
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct ModesBuilder{
|
||||||
|
modes:HashMap<ModeId,Mode>,
|
||||||
|
stages:HashMap<ModeId,HashMap<StageId,Stage>>,
|
||||||
|
mode_updates:Vec<(ModeId,ModeUpdate)>,
|
||||||
|
stage_updates:Vec<(ModeId,StageId,StageUpdate)>,
|
||||||
|
}
|
||||||
|
impl ModesBuilder{
|
||||||
|
pub fn build_normalized(mut self)->NormalizedModes{
|
||||||
|
//collect modes and stages into contiguous arrays
|
||||||
|
let mut unique_modes:Vec<(ModeId,Mode)>
|
||||||
|
=self.modes.into_iter().collect();
|
||||||
|
unique_modes.sort_by_key(|&(mode_id,_)|mode_id);
|
||||||
|
let (mut modes,mode_id_map):(Vec<ModeBuilder>,HashMap<ModeId,ModeId>)
|
||||||
|
=unique_modes.into_iter().enumerate()
|
||||||
|
.map(|(final_mode_id,(builder_mode_id,mut mode))|{
|
||||||
|
(
|
||||||
|
ModeBuilder{
|
||||||
|
stage_id_map:self.stages.remove(&builder_mode_id).map_or_else(||HashMap::new(),|stages|{
|
||||||
|
let mut unique_stages:Vec<(StageId,Stage)>
|
||||||
|
=stages.into_iter().collect();
|
||||||
|
unique_stages.sort_by_key(|&(StageId(stage_id),_)|stage_id);
|
||||||
|
unique_stages.into_iter().enumerate()
|
||||||
|
.map(|(final_stage_id,(builder_stage_id,stage))|{
|
||||||
|
mode.push_stage(stage);
|
||||||
|
(builder_stage_id,StageId::new(final_stage_id as u32))
|
||||||
|
}).collect()
|
||||||
|
}),
|
||||||
|
mode,
|
||||||
|
},
|
||||||
|
(
|
||||||
|
builder_mode_id,
|
||||||
|
ModeId::new(final_mode_id as u32)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}).unzip();
|
||||||
|
//TODO: failure messages or errors or something
|
||||||
|
//push stage updates
|
||||||
|
for (builder_mode_id,builder_stage_id,stage_update) in self.stage_updates{
|
||||||
|
if let Some(final_mode_id)=mode_id_map.get(&builder_mode_id){
|
||||||
|
if let Some(mode)=modes.get_mut(final_mode_id.get() as usize){
|
||||||
|
if let Some(&final_stage_id)=mode.stage_id_map.get(&builder_stage_id){
|
||||||
|
if let Some(stage)=mode.mode.get_stage_mut(final_stage_id){
|
||||||
|
stage_update.apply_to(stage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//push mode updates
|
||||||
|
for (builder_mode_id,mut mode_update) in self.mode_updates{
|
||||||
|
if let Some(final_mode_id)=mode_id_map.get(&builder_mode_id){
|
||||||
|
if let Some(mode)=modes.get_mut(final_mode_id.get() as usize){
|
||||||
|
//map stage id on stage elements
|
||||||
|
mode_update.map_stage_element_ids(|stage_id|
|
||||||
|
//walk down one stage id at a time until a stage is found
|
||||||
|
//TODO use better logic like BTreeMap::upper_bound instead of walking
|
||||||
|
// final_stage_id_from_builder_stage_id.upper_bound(Bound::Included(&stage_id))
|
||||||
|
// .value().copied().unwrap_or(StageId::FIRST)
|
||||||
|
(0..=stage_id.get()).rev().find_map(|builder_stage_id|
|
||||||
|
//map the stage element to that stage
|
||||||
|
mode.stage_id_map.get(&StageId::new(builder_stage_id)).copied()
|
||||||
|
).unwrap_or(StageId::FIRST)
|
||||||
|
);
|
||||||
|
mode_update.apply_to(&mut mode.mode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
NormalizedModes::new(modes.into_iter().map(|mode_builder|NormalizedMode(mode_builder.mode)).collect())
|
||||||
|
}
|
||||||
|
pub fn insert_mode(&mut self,mode_id:ModeId,mode:Mode){
|
||||||
|
assert!(self.modes.insert(mode_id,mode).is_none(),"Cannot replace existing mode");
|
||||||
|
}
|
||||||
|
pub fn insert_stage(&mut self,mode_id:ModeId,stage_id:StageId,stage:Stage){
|
||||||
|
assert!(self.stages.entry(mode_id).or_insert(HashMap::new()).insert(stage_id,stage).is_none(),"Cannot replace existing stage");
|
||||||
|
}
|
||||||
|
pub fn push_mode_update(&mut self,mode_id:ModeId,mode_update:ModeUpdate){
|
||||||
|
self.mode_updates.push((mode_id,mode_update));
|
||||||
|
}
|
||||||
|
// fn push_stage_update(&mut self,mode_id:ModeId,stage_id:StageId,stage_update:StageUpdate){
|
||||||
|
// self.stage_updates.push((mode_id,stage_id,stage_update));
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ impl JumpImpulse{
|
|||||||
velocity:Planar64Vec3,
|
velocity:Planar64Vec3,
|
||||||
jump_dir:Planar64Vec3,
|
jump_dir:Planar64Vec3,
|
||||||
gravity:&Planar64Vec3,
|
gravity:&Planar64Vec3,
|
||||||
mass:Planar64,
|
_mass:Planar64,
|
||||||
)->Planar64Vec3{
|
)->Planar64Vec3{
|
||||||
match self{
|
match self{
|
||||||
&JumpImpulse::Time(time)=>velocity-(*gravity*time).map(|t|t.divide().fix_1()),
|
&JumpImpulse::Time(time)=>velocity-(*gravity*time).map(|t|t.divide().fix_1()),
|
||||||
@@ -78,7 +78,7 @@ impl JumpImpulse{
|
|||||||
velocity-(*gravity*(radicand.sqrt().fix_2()+v_g)/gg).divide().fix_1()
|
velocity-(*gravity*(radicand.sqrt().fix_2()+v_g)/gg).divide().fix_1()
|
||||||
},
|
},
|
||||||
&JumpImpulse::Linear(jump_speed)=>velocity+(jump_dir*jump_speed/jump_dir.length()).divide().fix_1(),
|
&JumpImpulse::Linear(jump_speed)=>velocity+(jump_dir*jump_speed/jump_dir.length()).divide().fix_1(),
|
||||||
&JumpImpulse::Energy(energy)=>{
|
&JumpImpulse::Energy(_energy)=>{
|
||||||
//calculate energy
|
//calculate energy
|
||||||
//let e=gravity.dot(velocity);
|
//let e=gravity.dot(velocity);
|
||||||
//add
|
//add
|
||||||
@@ -355,7 +355,7 @@ pub struct LadderSettings{
|
|||||||
pub dot:Planar64,
|
pub dot:Planar64,
|
||||||
}
|
}
|
||||||
impl LadderSettings{
|
impl LadderSettings{
|
||||||
pub const fn accel(&self,target_diff:Planar64Vec3,gravity:Planar64Vec3)->Planar64{
|
pub const fn accel(&self,_target_diff:Planar64Vec3,_gravity:Planar64Vec3)->Planar64{
|
||||||
//TODO: fallible ladder accel
|
//TODO: fallible ladder accel
|
||||||
self.accelerate.accel
|
self.accelerate.accel
|
||||||
}
|
}
|
||||||
@@ -529,12 +529,12 @@ impl StyleModifiers{
|
|||||||
|
|
||||||
pub fn source_bhop()->Self{
|
pub fn source_bhop()->Self{
|
||||||
Self{
|
Self{
|
||||||
controls_mask:Controls::all()-Controls::MoveUp-Controls::MoveDown,
|
controls_mask:Controls::all(),
|
||||||
controls_mask_state:Controls::all(),
|
controls_mask_state:Controls::all(),
|
||||||
strafe:Some(StrafeSettings{
|
strafe:Some(StrafeSettings{
|
||||||
enable:ControlsActivation::full_2d(),
|
enable:ControlsActivation::full_2d(),
|
||||||
air_accel_limit:Some(Planar64::raw(150<<28)*100),
|
air_accel_limit:Some(Planar64::raw((150<<28)*100)),
|
||||||
mv:(Planar64::raw(30)*VALVE_SCALE).fix_1(),
|
mv:Planar64::raw(30<<28),
|
||||||
tick_rate:Ratio64::new(100,AbsoluteTime::ONE_SECOND.get() as u64).unwrap(),
|
tick_rate:Ratio64::new(100,AbsoluteTime::ONE_SECOND.get() as u64).unwrap(),
|
||||||
}),
|
}),
|
||||||
jump:Some(JumpSettings{
|
jump:Some(JumpSettings{
|
||||||
@@ -570,12 +570,12 @@ impl StyleModifiers{
|
|||||||
}
|
}
|
||||||
pub fn source_surf()->Self{
|
pub fn source_surf()->Self{
|
||||||
Self{
|
Self{
|
||||||
controls_mask:Controls::all()-Controls::MoveUp-Controls::MoveDown,
|
controls_mask:Controls::all(),
|
||||||
controls_mask_state:Controls::all(),
|
controls_mask_state:Controls::all(),
|
||||||
strafe:Some(StrafeSettings{
|
strafe:Some(StrafeSettings{
|
||||||
enable:ControlsActivation::full_2d(),
|
enable:ControlsActivation::full_2d(),
|
||||||
air_accel_limit:Some((int(150)*66*VALVE_SCALE).fix_1()),
|
air_accel_limit:Some((int(150)*66*VALVE_SCALE).fix_1()),
|
||||||
mv:(int(30)*VALVE_SCALE).fix_1(),
|
mv:Planar64::raw(30<<28),
|
||||||
tick_rate:Ratio64::new(66,AbsoluteTime::ONE_SECOND.get() as u64).unwrap(),
|
tick_rate:Ratio64::new(66,AbsoluteTime::ONE_SECOND.get() as u64).unwrap(),
|
||||||
}),
|
}),
|
||||||
jump:Some(JumpSettings{
|
jump:Some(JumpSettings{
|
||||||
|
|||||||
@@ -1,13 +1,11 @@
|
|||||||
use crate::integer::Time;
|
|
||||||
|
|
||||||
#[derive(Clone,Debug)]
|
#[derive(Clone,Debug)]
|
||||||
pub struct TimedInstruction<I,T>{
|
pub struct TimedInstruction<I,T>{
|
||||||
pub time:Time<T>,
|
pub time:T,
|
||||||
pub instruction:I,
|
pub instruction:I,
|
||||||
}
|
}
|
||||||
impl<I,T> TimedInstruction<I,T>{
|
impl<I,T> TimedInstruction<I,T>{
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn set_time<TimeInner>(self,new_time:Time<TimeInner>)->TimedInstruction<I,TimeInner>{
|
pub fn set_time<T2>(self,new_time:T2)->TimedInstruction<I,T2>{
|
||||||
TimedInstruction{
|
TimedInstruction{
|
||||||
time:new_time,
|
time:new_time,
|
||||||
instruction:self.instruction,
|
instruction:self.instruction,
|
||||||
@@ -17,21 +15,21 @@ impl<I,T> TimedInstruction<I,T>{
|
|||||||
|
|
||||||
/// Ensure all emitted instructions are processed before consuming external instructions
|
/// Ensure all emitted instructions are processed before consuming external instructions
|
||||||
pub trait InstructionEmitter<I>{
|
pub trait InstructionEmitter<I>{
|
||||||
type TimeInner;
|
type Time;
|
||||||
fn next_instruction(&self,time_limit:Time<Self::TimeInner>)->Option<TimedInstruction<I,Self::TimeInner>>;
|
fn next_instruction(&self,time_limit:Self::Time)->Option<TimedInstruction<I,Self::Time>>;
|
||||||
}
|
}
|
||||||
/// Apply an atomic state update
|
/// Apply an atomic state update
|
||||||
pub trait InstructionConsumer<I>{
|
pub trait InstructionConsumer<I>{
|
||||||
type TimeInner;
|
type Time;
|
||||||
fn process_instruction(&mut self,instruction:TimedInstruction<I,Self::TimeInner>);
|
fn process_instruction(&mut self,instruction:TimedInstruction<I,Self::Time>);
|
||||||
}
|
}
|
||||||
/// If the object produces its own instructions, allow exhaustively feeding them back in
|
/// If the object produces its own instructions, allow exhaustively feeding them back in
|
||||||
pub trait InstructionFeedback<I,T>:InstructionEmitter<I,TimeInner=T>+InstructionConsumer<I,TimeInner=T>
|
pub trait InstructionFeedback<I,T>:InstructionEmitter<I,Time=T>+InstructionConsumer<I,Time=T>
|
||||||
where
|
where
|
||||||
Time<T>:Copy,
|
T:Copy,
|
||||||
{
|
{
|
||||||
#[inline]
|
#[inline]
|
||||||
fn process_exhaustive(&mut self,time_limit:Time<T>){
|
fn process_exhaustive(&mut self,time_limit:T){
|
||||||
while let Some(instruction)=self.next_instruction(time_limit){
|
while let Some(instruction)=self.next_instruction(time_limit){
|
||||||
self.process_instruction(instruction);
|
self.process_instruction(instruction);
|
||||||
}
|
}
|
||||||
@@ -39,29 +37,40 @@ pub trait InstructionFeedback<I,T>:InstructionEmitter<I,TimeInner=T>+Instruction
|
|||||||
}
|
}
|
||||||
impl<I,T,X> InstructionFeedback<I,T> for X
|
impl<I,T,X> InstructionFeedback<I,T> for X
|
||||||
where
|
where
|
||||||
Time<T>:Copy,
|
T:Copy,
|
||||||
X:InstructionEmitter<I,TimeInner=T>+InstructionConsumer<I,TimeInner=T>,
|
X:InstructionEmitter<I,Time=T>+InstructionConsumer<I,Time=T>,
|
||||||
{}
|
{}
|
||||||
|
|
||||||
//PROPER PRIVATE FIELDS!!!
|
//PROPER PRIVATE FIELDS!!!
|
||||||
pub struct InstructionCollector<I,T>{
|
pub struct InstructionCollector<I,T>{
|
||||||
time:Time<T>,
|
time:T,
|
||||||
instruction:Option<I>,
|
instruction:Option<I>,
|
||||||
}
|
}
|
||||||
impl<I,T> InstructionCollector<I,T>
|
impl<I,T> InstructionCollector<I,T>{
|
||||||
where Time<T>:Copy+PartialOrd,
|
|
||||||
{
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub const fn new(time:Time<T>)->Self{
|
pub const fn new(time:T)->Self{
|
||||||
Self{
|
Self{
|
||||||
time,
|
time,
|
||||||
instruction:None
|
instruction:None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#[inline]
|
#[inline]
|
||||||
pub const fn time(&self)->Time<T>{
|
pub fn take(self)->Option<TimedInstruction<I,T>>{
|
||||||
|
//STEAL INSTRUCTION AND DESTROY INSTRUCTIONCOLLECTOR
|
||||||
|
self.instruction.map(|instruction|TimedInstruction{
|
||||||
|
time:self.time,
|
||||||
|
instruction
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<I,T:Copy> InstructionCollector<I,T>{
|
||||||
|
#[inline]
|
||||||
|
pub const fn time(&self)->T{
|
||||||
self.time
|
self.time
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
impl<I,T:PartialOrd> InstructionCollector<I,T>{
|
||||||
|
#[inline]
|
||||||
pub fn collect(&mut self,instruction:Option<TimedInstruction<I,T>>){
|
pub fn collect(&mut self,instruction:Option<TimedInstruction<I,T>>){
|
||||||
if let Some(ins)=instruction{
|
if let Some(ins)=instruction{
|
||||||
if ins.time<self.time{
|
if ins.time<self.time{
|
||||||
@@ -70,11 +79,4 @@ impl<I,T> InstructionCollector<I,T>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn instruction(self)->Option<TimedInstruction<I,T>>{
|
|
||||||
//STEAL INSTRUCTION AND DESTROY INSTRUCTIONCOLLECTOR
|
|
||||||
self.instruction.map(|instruction|TimedInstruction{
|
|
||||||
time:self.time,
|
|
||||||
instruction
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,16 +4,17 @@ pub use ratio_ops::ratio::{Ratio,Divide};
|
|||||||
//integer units
|
//integer units
|
||||||
|
|
||||||
/// specific example of a "default" time type
|
/// specific example of a "default" time type
|
||||||
#[derive(Clone,Copy,Hash,Eq,PartialEq,PartialOrd,Debug)]
|
#[derive(Clone,Copy,Hash,Eq,PartialEq,Ord,PartialOrd,Debug)]
|
||||||
pub enum TimeInner{}
|
pub enum TimeInner{}
|
||||||
pub type AbsoluteTime=Time<TimeInner>;
|
pub type AbsoluteTime=Time<TimeInner>;
|
||||||
|
|
||||||
#[derive(Clone,Copy,Hash,Eq,PartialEq,PartialOrd,Debug)]
|
#[derive(Clone,Copy,Hash,Eq,PartialEq,Ord,PartialOrd,Debug)]
|
||||||
pub struct Time<T>(i64,core::marker::PhantomData<T>);
|
pub struct Time<T>(i64,core::marker::PhantomData<T>);
|
||||||
impl<T> Time<T>{
|
impl<T> Time<T>{
|
||||||
pub const MIN:Self=Self::raw(i64::MIN);
|
pub const MIN:Self=Self::raw(i64::MIN);
|
||||||
pub const MAX:Self=Self::raw(i64::MAX);
|
pub const MAX:Self=Self::raw(i64::MAX);
|
||||||
pub const ZERO:Self=Self::raw(0);
|
pub const ZERO:Self=Self::raw(0);
|
||||||
|
pub const EPSILON:Self=Self::raw(1);
|
||||||
pub const ONE_SECOND:Self=Self::raw(1_000_000_000);
|
pub const ONE_SECOND:Self=Self::raw(1_000_000_000);
|
||||||
pub const ONE_MILLISECOND:Self=Self::raw(1_000_000);
|
pub const ONE_MILLISECOND:Self=Self::raw(1_000_000);
|
||||||
pub const ONE_MICROSECOND:Self=Self::raw(1_000);
|
pub const ONE_MICROSECOND:Self=Self::raw(1_000);
|
||||||
@@ -62,6 +63,12 @@ impl<T> From<Planar64> for Time<T>{
|
|||||||
Self::raw((value*Planar64::raw(1_000_000_000)).fix_1().to_raw())
|
Self::raw((value*Planar64::raw(1_000_000_000)).fix_1().to_raw())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
impl<T> From<Time<T>> for Ratio<Planar64,Planar64>{
|
||||||
|
#[inline]
|
||||||
|
fn from(value:Time<T>)->Self{
|
||||||
|
value.to_ratio()
|
||||||
|
}
|
||||||
|
}
|
||||||
impl<T,Num,Den,N1,T1> From<Ratio<Num,Den>> for Time<T>
|
impl<T,Num,Den,N1,T1> From<Ratio<Num,Den>> for Time<T>
|
||||||
where
|
where
|
||||||
Num:core::ops::Mul<Planar64,Output=N1>,
|
Num:core::ops::Mul<Planar64,Output=N1>,
|
||||||
@@ -647,11 +654,19 @@ pub struct Planar64Affine3{
|
|||||||
pub translation:Planar64Vec3,
|
pub translation:Planar64Vec3,
|
||||||
}
|
}
|
||||||
impl Planar64Affine3{
|
impl Planar64Affine3{
|
||||||
|
pub const IDENTITY:Self=Self::new(mat3::identity(),vec3::ZERO);
|
||||||
#[inline]
|
#[inline]
|
||||||
pub const fn new(matrix3:Planar64Mat3,translation:Planar64Vec3)->Self{
|
pub const fn new(matrix3:Planar64Mat3,translation:Planar64Vec3)->Self{
|
||||||
Self{matrix3,translation}
|
Self{matrix3,translation}
|
||||||
}
|
}
|
||||||
#[inline]
|
#[inline]
|
||||||
|
pub const fn from_translation(translation:Planar64Vec3)->Self{
|
||||||
|
Self{
|
||||||
|
matrix3:mat3::identity(),
|
||||||
|
translation,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[inline]
|
||||||
pub fn transform_point3(&self,point:Planar64Vec3)->vec3::Vector3<Fixed<2,64>>{
|
pub fn transform_point3(&self,point:Planar64Vec3)->vec3::Vector3<Fixed<2,64>>{
|
||||||
self.translation.fix_2()+self.matrix3*point
|
self.translation.fix_2()+self.matrix3*point
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
pub mod bvh;
|
pub mod bvh;
|
||||||
pub mod map;
|
pub mod map;
|
||||||
|
pub mod ray;
|
||||||
pub mod run;
|
pub mod run;
|
||||||
pub mod aabb;
|
pub mod aabb;
|
||||||
pub mod model;
|
pub mod model;
|
||||||
@@ -8,7 +9,6 @@ pub mod timer;
|
|||||||
pub mod integer;
|
pub mod integer;
|
||||||
pub mod physics;
|
pub mod physics;
|
||||||
pub mod session;
|
pub mod session;
|
||||||
pub mod updatable;
|
|
||||||
pub mod instruction;
|
pub mod instruction;
|
||||||
pub mod gameplay_attributes;
|
pub mod gameplay_attributes;
|
||||||
pub mod gameplay_modes;
|
pub mod gameplay_modes;
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ use crate::gameplay_attributes;
|
|||||||
//this is a temporary struct to try to get the code running again
|
//this is a temporary struct to try to get the code running again
|
||||||
//TODO: use snf::map::Region to update the data in physics and graphics instead of this
|
//TODO: use snf::map::Region to update the data in physics and graphics instead of this
|
||||||
pub struct CompleteMap{
|
pub struct CompleteMap{
|
||||||
pub modes:gameplay_modes::Modes,
|
pub modes:gameplay_modes::NormalizedModes,
|
||||||
pub attributes:Vec<gameplay_attributes::CollisionAttributes>,
|
pub attributes:Vec<gameplay_attributes::CollisionAttributes>,
|
||||||
pub meshes:Vec<model::Mesh>,
|
pub meshes:Vec<model::Mesh>,
|
||||||
pub models:Vec<model::Model>,
|
pub models:Vec<model::Model>,
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use crate::integer::{Planar64Vec3,Planar64Affine3};
|
use crate::integer::{Planar64Vec3,Planar64Affine3};
|
||||||
use crate::gameplay_attributes;
|
use crate::gameplay_attributes;
|
||||||
|
|
||||||
@@ -123,11 +125,170 @@ pub struct Mesh{
|
|||||||
pub physics_groups:Vec<IndexedPhysicsGroup>,
|
pub physics_groups:Vec<IndexedPhysicsGroup>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct MeshBuilder{
|
||||||
|
unique_pos:Vec<Planar64Vec3>,//Unit32Vec3
|
||||||
|
unique_normal:Vec<Planar64Vec3>,//Unit32Vec3
|
||||||
|
unique_tex:Vec<TextureCoordinate>,
|
||||||
|
unique_color:Vec<Color4>,
|
||||||
|
unique_vertices:Vec<IndexedVertex>,
|
||||||
|
pos_id_from:HashMap<Planar64Vec3,PositionId>,//Unit32Vec3
|
||||||
|
normal_id_from:HashMap<Planar64Vec3,NormalId>,//Unit32Vec3
|
||||||
|
tex_id_from:HashMap<[u32;2],TextureCoordinateId>,
|
||||||
|
color_id_from:HashMap<[u32;4],ColorId>,
|
||||||
|
vertex_id_from:HashMap<IndexedVertex,VertexId>,
|
||||||
|
}
|
||||||
|
impl MeshBuilder{
|
||||||
|
pub fn new()->Self{
|
||||||
|
Self::default()
|
||||||
|
}
|
||||||
|
pub fn build(
|
||||||
|
self,
|
||||||
|
polygon_groups:Vec<PolygonGroup>,
|
||||||
|
graphics_groups:Vec<IndexedGraphicsGroup>,
|
||||||
|
physics_groups:Vec<IndexedPhysicsGroup>,
|
||||||
|
)->Mesh{
|
||||||
|
let MeshBuilder{
|
||||||
|
unique_pos,
|
||||||
|
unique_normal,
|
||||||
|
unique_tex,
|
||||||
|
unique_color,
|
||||||
|
unique_vertices,
|
||||||
|
..
|
||||||
|
}=self;
|
||||||
|
Mesh{
|
||||||
|
unique_pos,
|
||||||
|
unique_normal,
|
||||||
|
unique_tex,
|
||||||
|
unique_color,
|
||||||
|
unique_vertices,
|
||||||
|
polygon_groups,
|
||||||
|
graphics_groups,
|
||||||
|
physics_groups,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn acquire_pos_id(&mut self,pos:Planar64Vec3)->PositionId{
|
||||||
|
*self.pos_id_from.entry(pos).or_insert_with(||{
|
||||||
|
let pos_id=PositionId::new(self.unique_pos.len() as u32);
|
||||||
|
self.unique_pos.push(pos);
|
||||||
|
pos_id
|
||||||
|
})
|
||||||
|
}
|
||||||
|
pub fn acquire_normal_id(&mut self,normal:Planar64Vec3)->NormalId{
|
||||||
|
*self.normal_id_from.entry(normal).or_insert_with(||{
|
||||||
|
let normal_id=NormalId::new(self.unique_normal.len() as u32);
|
||||||
|
self.unique_normal.push(normal);
|
||||||
|
normal_id
|
||||||
|
})
|
||||||
|
}
|
||||||
|
pub fn acquire_tex_id(&mut self,tex:TextureCoordinate)->TextureCoordinateId{
|
||||||
|
let h=tex.to_array().map(f32::to_bits);
|
||||||
|
*self.tex_id_from.entry(h).or_insert_with(||{
|
||||||
|
let tex_id=TextureCoordinateId::new(self.unique_tex.len() as u32);
|
||||||
|
self.unique_tex.push(tex);
|
||||||
|
tex_id
|
||||||
|
})
|
||||||
|
}
|
||||||
|
pub fn acquire_color_id(&mut self,color:Color4)->ColorId{
|
||||||
|
let h=color.to_array().map(f32::to_bits);
|
||||||
|
*self.color_id_from.entry(h).or_insert_with(||{
|
||||||
|
let color_id=ColorId::new(self.unique_color.len() as u32);
|
||||||
|
self.unique_color.push(color);
|
||||||
|
color_id
|
||||||
|
})
|
||||||
|
}
|
||||||
|
pub fn acquire_vertex_id(&mut self,vertex:IndexedVertex)->VertexId{
|
||||||
|
*self.vertex_id_from.entry(vertex.clone()).or_insert_with(||{
|
||||||
|
let vertex_id=VertexId::new(self.unique_vertices.len() as u32);
|
||||||
|
self.unique_vertices.push(vertex);
|
||||||
|
vertex_id
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug,Clone,Copy,Hash,id::Id,Eq,PartialEq)]
|
#[derive(Debug,Clone,Copy,Hash,id::Id,Eq,PartialEq)]
|
||||||
pub struct ModelId(u32);
|
pub struct ModelId(u32);
|
||||||
|
#[derive(Clone)]
|
||||||
pub struct Model{
|
pub struct Model{
|
||||||
pub mesh:MeshId,
|
pub mesh:MeshId,
|
||||||
pub attributes:gameplay_attributes::CollisionAttributesId,
|
pub attributes:gameplay_attributes::CollisionAttributesId,
|
||||||
pub color:Color4,//transparency is in here
|
pub color:Color4,//transparency is in here
|
||||||
pub transform:Planar64Affine3,
|
pub transform:Planar64Affine3,
|
||||||
|
pub debug_info:DebugInfo,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug,Clone)]
|
||||||
|
pub enum DebugInfo{
|
||||||
|
World,
|
||||||
|
Prop,
|
||||||
|
Brush(BrushInfo),
|
||||||
|
Entity(EntityInfo),
|
||||||
|
}
|
||||||
|
impl std::fmt::Display for DebugInfo{
|
||||||
|
fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{
|
||||||
|
match self{
|
||||||
|
DebugInfo::World=>write!(f,"World"),
|
||||||
|
DebugInfo::Prop=>write!(f,"Prop"),
|
||||||
|
DebugInfo::Brush(brush_info)=>brush_info.fmt(f),
|
||||||
|
DebugInfo::Entity(entity_info)=>entity_info.fmt(f),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug,Clone)]
|
||||||
|
pub struct BrushInfo{
|
||||||
|
pub flags:vbsp::BrushFlags,
|
||||||
|
pub sides:Vec<vbsp::TextureFlags>,
|
||||||
|
}
|
||||||
|
impl std::fmt::Display for BrushInfo{
|
||||||
|
fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{
|
||||||
|
let noice=self.flags.iter_names().filter_map(|(name,flags)|{
|
||||||
|
(flags.bits()!=0).then(||name)
|
||||||
|
}).collect::<Vec<&str>>().join("|");
|
||||||
|
writeln!(f,"brush_info.flags={noice}")?;
|
||||||
|
for (i,side) in self.sides.iter().enumerate(){
|
||||||
|
let noice_string=side.iter_names().filter_map(|(name,flags)|{
|
||||||
|
(flags.bits()!=0).then(||name)
|
||||||
|
}).collect::<Vec<&str>>().join("|");
|
||||||
|
writeln!(f,"brush_info.sides[{i}]={noice_string}")?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug,Clone)]
|
||||||
|
pub struct EntityInfo{
|
||||||
|
pub classname:Box<str>,
|
||||||
|
pub properties:Vec<(Box<str>,Box<str>)>,
|
||||||
|
}
|
||||||
|
pub enum EntityInfoError{
|
||||||
|
MissingClassname,
|
||||||
|
}
|
||||||
|
impl EntityInfo{
|
||||||
|
pub fn new<'a>(iter:impl IntoIterator<Item=(&'a str,&'a str)>)->Result<Self,EntityInfoError>{
|
||||||
|
let mut classname:Option<Box<str>>=None;
|
||||||
|
let mut properties:Vec<(Box<str>,Box<str>)>=Vec::new();
|
||||||
|
for (name,value) in iter{
|
||||||
|
match name{
|
||||||
|
"classname"=>classname=Some(value.into()),
|
||||||
|
"hammerid"=>(),
|
||||||
|
_=>properties.push((name.into(),value.into())),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
properties.sort_by(|(n0,_),(n1,_)|n0.cmp(n1));
|
||||||
|
let Some(classname)=classname else{
|
||||||
|
return Err(EntityInfoError::MissingClassname);
|
||||||
|
};
|
||||||
|
Ok(EntityInfo{classname,properties})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl std::fmt::Display for EntityInfo{
|
||||||
|
fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{
|
||||||
|
writeln!(f,"struct {}{{",self.classname)?;
|
||||||
|
for (name,value) in &self.properties{
|
||||||
|
writeln!(f,"\t{name}:{value},")?;
|
||||||
|
}
|
||||||
|
write!(f,"}}")?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
use crate::mouse::MouseState;
|
use crate::mouse::MouseState;
|
||||||
use crate::gameplay_modes::{ModeId,StageId};
|
use crate::gameplay_modes::{ModeId,StageId};
|
||||||
|
|
||||||
#[derive(Clone,Copy,Hash,Eq,PartialEq,PartialOrd,Debug)]
|
#[derive(Clone,Copy,Hash,Eq,PartialEq,Ord,PartialOrd,Debug)]
|
||||||
pub enum TimeInner{}
|
pub enum TimeInner{}
|
||||||
pub type Time=crate::integer::Time<TimeInner>;
|
pub type Time=crate::integer::Time<TimeInner>;
|
||||||
|
|
||||||
|
|||||||
20
lib/common/src/ray.rs
Normal file
20
lib/common/src/ray.rs
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
use ratio_ops::ratio::Ratio;
|
||||||
|
use crate::integer::{self,Planar64,Planar64Vec3};
|
||||||
|
|
||||||
|
pub struct Ray{
|
||||||
|
pub origin:Planar64Vec3,
|
||||||
|
pub direction:Planar64Vec3,
|
||||||
|
}
|
||||||
|
impl Ray{
|
||||||
|
pub fn extrapolate<Num,Den,N1,T1>(&self,t:Ratio<Num,Den>)->Planar64Vec3
|
||||||
|
where
|
||||||
|
Num:Copy,
|
||||||
|
Den:Copy,
|
||||||
|
Num:core::ops::Mul<Planar64,Output=N1>,
|
||||||
|
Planar64:core::ops::Mul<Den,Output=N1>,
|
||||||
|
N1:integer::Divide<Den,Output=T1>,
|
||||||
|
T1:integer::Fix<Planar64>,
|
||||||
|
{
|
||||||
|
self.origin+self.direction.map(|elem|(t*elem).divide().fix())
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,7 +2,7 @@ use crate::timer::{TimerFixed,Realtime,Paused,Unpaused};
|
|||||||
|
|
||||||
use crate::physics::{TimeInner as PhysicsTimeInner,Time as PhysicsTime};
|
use crate::physics::{TimeInner as PhysicsTimeInner,Time as PhysicsTime};
|
||||||
|
|
||||||
#[derive(Clone,Copy,Hash,Eq,PartialEq,PartialOrd,Debug)]
|
#[derive(Clone,Copy,Hash,Eq,PartialEq,Ord,PartialOrd,Debug)]
|
||||||
pub enum TimeInner{}
|
pub enum TimeInner{}
|
||||||
pub type Time=crate::integer::Time<TimeInner>;
|
pub type Time=crate::integer::Time<TimeInner>;
|
||||||
|
|
||||||
@@ -76,7 +76,7 @@ impl Run{
|
|||||||
match &self.state{
|
match &self.state{
|
||||||
RunState::Created=>Time::ZERO,
|
RunState::Created=>Time::ZERO,
|
||||||
RunState::Started{timer}=>timer.time(time),
|
RunState::Started{timer}=>timer.time(time),
|
||||||
RunState::Finished{timer}=>timer.time(time),
|
RunState::Finished{timer}=>timer.time(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn start(&mut self,time:PhysicsTime)->Result<(),Error>{
|
pub fn start(&mut self,time:PhysicsTime)->Result<(),Error>{
|
||||||
@@ -110,4 +110,10 @@ impl Run{
|
|||||||
self.flagged=Some(flag_reason);
|
self.flagged=Some(flag_reason);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
pub fn get_finish_time(&self)->Option<Time>{
|
||||||
|
match &self.state{
|
||||||
|
RunState::Finished{timer}=>Some(timer.time()),
|
||||||
|
_=>None,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
#[derive(Clone,Copy,Hash,Eq,PartialEq,PartialOrd,Debug)]
|
#[derive(Clone,Copy,Hash,Eq,PartialEq,Ord,PartialOrd,Debug)]
|
||||||
pub enum TimeInner{}
|
pub enum TimeInner{}
|
||||||
pub type Time=crate::integer::Time<TimeInner>;
|
pub type Time=crate::integer::Time<TimeInner>;
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ impl PauseState for Unpaused{
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone,Copy,Hash,Eq,PartialEq,PartialOrd,Debug)]
|
#[derive(Clone,Copy,Hash,Eq,PartialEq,PartialOrd,Debug)]
|
||||||
enum Inner{}
|
pub enum Inner{}
|
||||||
type InnerTime=Time<Inner>;
|
type InnerTime=Time<Inner>;
|
||||||
|
|
||||||
#[derive(Clone,Copy,Debug)]
|
#[derive(Clone,Copy,Debug)]
|
||||||
@@ -157,7 +157,7 @@ impl<T:TimerState> TimerFixed<T,Paused>
|
|||||||
where Time<T::In>:Copy,
|
where Time<T::In>:Copy,
|
||||||
{
|
{
|
||||||
pub fn into_unpaused(self,time:Time<T::In>)->TimerFixed<T,Unpaused>{
|
pub fn into_unpaused(self,time:Time<T::In>)->TimerFixed<T,Unpaused>{
|
||||||
let new_time=self.time(time);
|
let new_time=self.time();
|
||||||
let mut timer=TimerFixed{
|
let mut timer=TimerFixed{
|
||||||
state:self.state,
|
state:self.state,
|
||||||
_paused:Unpaused,
|
_paused:Unpaused,
|
||||||
@@ -165,6 +165,9 @@ impl<T:TimerState> TimerFixed<T,Paused>
|
|||||||
timer.set_time(time,new_time);
|
timer.set_time(time,new_time);
|
||||||
timer
|
timer
|
||||||
}
|
}
|
||||||
|
pub fn time(&self)->Time<T::Out>{
|
||||||
|
self.state.get_offset().coerce()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
impl<T:TimerState> TimerFixed<T,Unpaused>
|
impl<T:TimerState> TimerFixed<T,Unpaused>
|
||||||
where Time<T::In>:Copy,
|
where Time<T::In>:Copy,
|
||||||
@@ -178,6 +181,9 @@ impl<T:TimerState> TimerFixed<T,Unpaused>
|
|||||||
timer.set_time(time,new_time);
|
timer.set_time(time,new_time);
|
||||||
timer
|
timer
|
||||||
}
|
}
|
||||||
|
pub fn time(&self,time:Time<T::In>)->Time<T::Out>{
|
||||||
|
self.state.get_time(time)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//the new constructor and time queries are generic across both
|
//the new constructor and time queries are generic across both
|
||||||
@@ -199,12 +205,6 @@ impl<T:TimerState,P:PauseState> TimerFixed<T,P>{
|
|||||||
pub fn into_state(self)->T{
|
pub fn into_state(self)->T{
|
||||||
self.state
|
self.state
|
||||||
}
|
}
|
||||||
pub fn time(&self,time:Time<T::In>)->Time<T::Out>{
|
|
||||||
match P::IS_PAUSED{
|
|
||||||
true=>self.state.get_offset().coerce(),
|
|
||||||
false=>self.state.get_time(time),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub fn set_time(&mut self,time:Time<T::In>,new_time:Time<T::Out>){
|
pub fn set_time(&mut self,time:Time<T::In>,new_time:Time<T::Out>){
|
||||||
match P::IS_PAUSED{
|
match P::IS_PAUSED{
|
||||||
true=>self.state.set_offset(new_time.coerce()),
|
true=>self.state.set_offset(new_time.coerce()),
|
||||||
@@ -256,7 +256,7 @@ impl<T:TimerState> Timer<T>
|
|||||||
}
|
}
|
||||||
pub fn time(&self,time:Time<T::In>)->Time<T::Out>{
|
pub fn time(&self,time:Time<T::In>)->Time<T::Out>{
|
||||||
match self{
|
match self{
|
||||||
Self::Paused(timer)=>timer.time(time),
|
Self::Paused(timer)=>timer.time(),
|
||||||
Self::Unpaused(timer)=>timer.time(time),
|
Self::Unpaused(timer)=>timer.time(time),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -329,7 +329,7 @@ mod test{
|
|||||||
//create a paused timer that reads 0s
|
//create a paused timer that reads 0s
|
||||||
let timer=TimerFixed::<Scaled<Parent,Calculated>,Paused>::from_state(Scaled::new(0.5f32.try_into().unwrap(),sec!(0)));
|
let timer=TimerFixed::<Scaled<Parent,Calculated>,Paused>::from_state(Scaled::new(0.5f32.try_into().unwrap(),sec!(0)));
|
||||||
//the paused timer at 1 second should read 0s
|
//the paused timer at 1 second should read 0s
|
||||||
assert_eq!(timer.time(sec!(1)),sec!(0));
|
assert_eq!(timer.time(),sec!(0));
|
||||||
|
|
||||||
//unpause it after one second
|
//unpause it after one second
|
||||||
let timer=timer.into_unpaused(sec!(1));
|
let timer=timer.into_unpaused(sec!(1));
|
||||||
@@ -339,7 +339,7 @@ mod test{
|
|||||||
//pause the timer after 11 seconds
|
//pause the timer after 11 seconds
|
||||||
let timer=timer.into_paused(sec!(11));
|
let timer=timer.into_paused(sec!(11));
|
||||||
//the paused timer at 20 seconds should read 5s
|
//the paused timer at 20 seconds should read 5s
|
||||||
assert_eq!(timer.time(sec!(20)),sec!(5));
|
assert_eq!(timer.time(),sec!(5));
|
||||||
}
|
}
|
||||||
#[test]
|
#[test]
|
||||||
fn test_timer()->Result<(),Error>{
|
fn test_timer()->Result<(),Error>{
|
||||||
|
|||||||
@@ -1,56 +0,0 @@
|
|||||||
pub trait Updatable<Updater>{
|
|
||||||
fn update(&mut self,update:Updater);
|
|
||||||
}
|
|
||||||
#[derive(Clone,Copy,Hash,Eq,PartialEq)]
|
|
||||||
struct InnerId(u32);
|
|
||||||
#[derive(Clone)]
|
|
||||||
struct Inner{
|
|
||||||
id:InnerId,
|
|
||||||
enabled:bool,
|
|
||||||
}
|
|
||||||
#[derive(Clone,Copy,Hash,Eq,PartialEq)]
|
|
||||||
struct OuterId(u32);
|
|
||||||
struct Outer{
|
|
||||||
id:OuterId,
|
|
||||||
inners:std::collections::HashMap<InnerId,Inner>,
|
|
||||||
}
|
|
||||||
|
|
||||||
enum Update<I,U>{
|
|
||||||
Insert(I),
|
|
||||||
Update(U),
|
|
||||||
Remove
|
|
||||||
}
|
|
||||||
|
|
||||||
struct InnerUpdate{
|
|
||||||
//#[updatable(Update)]
|
|
||||||
enabled:Option<bool>,
|
|
||||||
}
|
|
||||||
struct OuterUpdate{
|
|
||||||
//#[updatable(Insert,Update,Remove)]
|
|
||||||
inners:std::collections::HashMap<InnerId,Update<Inner,InnerUpdate>>,
|
|
||||||
//#[updatable(Update)]
|
|
||||||
//inners:std::collections::HashMap<InnerId,InnerUpdate>,
|
|
||||||
}
|
|
||||||
impl Updatable<InnerUpdate> for Inner{
|
|
||||||
fn update(&mut self,update:InnerUpdate){
|
|
||||||
if let Some(enabled)=update.enabled{
|
|
||||||
self.enabled=enabled;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl Updatable<OuterUpdate> for Outer{
|
|
||||||
fn update(&mut self,update:OuterUpdate){
|
|
||||||
for (id,up) in update.inners{
|
|
||||||
match up{
|
|
||||||
Update::Insert(new_inner)=>self.inners.insert(id,new_inner),
|
|
||||||
Update::Update(inner_update)=>self.inners.get_mut(&id).map(|inner|{
|
|
||||||
let old=inner.clone();
|
|
||||||
inner.update(inner_update);
|
|
||||||
old
|
|
||||||
}),
|
|
||||||
Update::Remove=>self.inners.remove(&id),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
//*/
|
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "strafesnet_deferred_loader"
|
name = "strafesnet_deferred_loader"
|
||||||
version = "0.4.1"
|
version = "0.5.0"
|
||||||
edition = "2021"
|
edition = "2024"
|
||||||
repository = "https://git.itzana.me/StrafesNET/strafe-project"
|
repository = "https://git.itzana.me/StrafesNET/strafe-project"
|
||||||
license = "MIT OR Apache-2.0"
|
license = "MIT OR Apache-2.0"
|
||||||
description = "Acquire IDs for objects before loading them in bulk."
|
description = "Acquire IDs for objects before loading them in bulk."
|
||||||
@@ -9,13 +9,5 @@ authors = ["Rhys Lloyd <krakow20@gmail.com>"]
|
|||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[features]
|
|
||||||
default = ["legacy"]
|
|
||||||
legacy = ["dep:url","dep:vbsp"]
|
|
||||||
#roblox = ["dep:lazy-regex"]
|
|
||||||
#source = ["dep:vbsp"]
|
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
strafesnet_common = { path = "../common", registry = "strafesnet" }
|
strafesnet_common = { version = "0.6.0", path = "../common", registry = "strafesnet" }
|
||||||
url = { version = "2.5.2", optional = true }
|
|
||||||
vbsp = { version = "0.6.0", optional = true }
|
|
||||||
|
|||||||
116
lib/deferred_loader/src/deferred_loader.rs
Normal file
116
lib/deferred_loader/src/deferred_loader.rs
Normal file
@@ -0,0 +1,116 @@
|
|||||||
|
use std::collections::HashMap;
|
||||||
|
use crate::loader::Loader;
|
||||||
|
use crate::mesh::Meshes;
|
||||||
|
use crate::texture::{RenderConfigs,Texture};
|
||||||
|
use strafesnet_common::model::{Mesh,MeshId,RenderConfig,RenderConfigId,TextureId};
|
||||||
|
|
||||||
|
#[derive(Clone,Copy,Debug)]
|
||||||
|
pub enum LoadFailureMode{
|
||||||
|
DefaultToNone,
|
||||||
|
Fatal,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct RenderConfigDeferredLoader<H>{
|
||||||
|
texture_count:u32,
|
||||||
|
render_configs:Vec<RenderConfig>,
|
||||||
|
render_config_id_from_asset_id:HashMap<Option<H>,RenderConfigId>,
|
||||||
|
}
|
||||||
|
impl<H> RenderConfigDeferredLoader<H>{
|
||||||
|
pub fn new()->Self{
|
||||||
|
Self{
|
||||||
|
texture_count:0,
|
||||||
|
render_configs:Vec::new(),
|
||||||
|
render_config_id_from_asset_id:HashMap::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<H:core::hash::Hash+Eq> RenderConfigDeferredLoader<H>{
|
||||||
|
pub fn acquire_render_config_id(&mut self,index:Option<H>)->RenderConfigId{
|
||||||
|
let some_texture=index.is_some();
|
||||||
|
*self.render_config_id_from_asset_id.entry(index).or_insert_with(||{
|
||||||
|
//create the render config.
|
||||||
|
let render_config=if some_texture{
|
||||||
|
let render_config=RenderConfig::texture(TextureId::new(self.texture_count));
|
||||||
|
self.texture_count+=1;
|
||||||
|
render_config
|
||||||
|
}else{
|
||||||
|
RenderConfig::default()
|
||||||
|
};
|
||||||
|
let render_id=RenderConfigId::new(self.render_configs.len() as u32);
|
||||||
|
self.render_configs.push(render_config);
|
||||||
|
render_id
|
||||||
|
})
|
||||||
|
}
|
||||||
|
pub fn into_indices(self)->impl Iterator<Item=H>{
|
||||||
|
self.render_config_id_from_asset_id.into_keys().flatten()
|
||||||
|
}
|
||||||
|
pub fn into_render_configs<L:Loader<Resource=Texture,Index=H>>(mut self,loader:&mut L,failure_mode:LoadFailureMode)->Result<RenderConfigs,L::Error>{
|
||||||
|
let mut sorted_textures=vec![None;self.texture_count as usize];
|
||||||
|
for (index_option,render_config_id) in self.render_config_id_from_asset_id{
|
||||||
|
let render_config=&mut self.render_configs[render_config_id.get() as usize];
|
||||||
|
if let (Some(index),Some(texture_id))=(index_option,render_config.texture){
|
||||||
|
let resource_result=loader.load(index);
|
||||||
|
let texture=match failure_mode{
|
||||||
|
// if texture fails to load, use no texture
|
||||||
|
LoadFailureMode::DefaultToNone=>match resource_result{
|
||||||
|
Ok(texture)=>Some(texture),
|
||||||
|
Err(e)=>{
|
||||||
|
render_config.texture=None;
|
||||||
|
println!("Error loading texture: {e}");
|
||||||
|
None
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// loading failure is fatal
|
||||||
|
LoadFailureMode::Fatal=>Some(resource_result?)
|
||||||
|
};
|
||||||
|
sorted_textures[texture_id.get() as usize]=texture;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(RenderConfigs::new(
|
||||||
|
sorted_textures,
|
||||||
|
self.render_configs,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct MeshDeferredLoader<H>{
|
||||||
|
mesh_id_from_asset_id:HashMap<H,MeshId>,
|
||||||
|
}
|
||||||
|
impl<H> MeshDeferredLoader<H>{
|
||||||
|
pub fn new()->Self{
|
||||||
|
Self{
|
||||||
|
mesh_id_from_asset_id:HashMap::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<H:core::hash::Hash+Eq> MeshDeferredLoader<H>{
|
||||||
|
pub fn acquire_mesh_id(&mut self,index:H)->MeshId{
|
||||||
|
let mesh_id=MeshId::new(self.mesh_id_from_asset_id.len() as u32);
|
||||||
|
*self.mesh_id_from_asset_id.entry(index).or_insert(mesh_id)
|
||||||
|
}
|
||||||
|
pub fn into_indices(self)->impl Iterator<Item=H>{
|
||||||
|
self.mesh_id_from_asset_id.into_keys()
|
||||||
|
}
|
||||||
|
pub fn into_meshes<L:Loader<Resource=Mesh,Index=H>>(self,loader:&mut L,failure_mode:LoadFailureMode)->Result<Meshes,L::Error>{
|
||||||
|
let mut mesh_list=vec![None;self.mesh_id_from_asset_id.len()];
|
||||||
|
for (index,mesh_id) in self.mesh_id_from_asset_id{
|
||||||
|
let resource_result=loader.load(index);
|
||||||
|
let mesh=match failure_mode{
|
||||||
|
// if mesh fails to load, use no mesh
|
||||||
|
LoadFailureMode::DefaultToNone=>match resource_result{
|
||||||
|
Ok(mesh)=>Some(mesh),
|
||||||
|
Err(e)=>{
|
||||||
|
println!("Error loading mesh: {e}");
|
||||||
|
None
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// loading failure is fatal
|
||||||
|
LoadFailureMode::Fatal=>Some(resource_result?)
|
||||||
|
};
|
||||||
|
mesh_list[mesh_id.get() as usize]=mesh;
|
||||||
|
}
|
||||||
|
Ok(Meshes::new(mesh_list))
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,34 +1,5 @@
|
|||||||
#[cfg(feature="legacy")]
|
|
||||||
mod roblox_legacy;
|
|
||||||
#[cfg(feature="legacy")]
|
|
||||||
mod source_legacy;
|
|
||||||
#[cfg(feature="roblox")]
|
|
||||||
mod roblox;
|
|
||||||
#[cfg(feature="source")]
|
|
||||||
mod source;
|
|
||||||
|
|
||||||
#[cfg(any(feature="roblox",feature="legacy"))]
|
|
||||||
pub mod rbxassetid;
|
|
||||||
|
|
||||||
|
pub mod mesh;
|
||||||
|
pub mod loader;
|
||||||
pub mod texture;
|
pub mod texture;
|
||||||
#[cfg(any(feature="source",feature="legacy"))]
|
pub mod deferred_loader;
|
||||||
pub mod valve_mesh;
|
|
||||||
#[cfg(any(feature="roblox",feature="legacy"))]
|
|
||||||
pub mod roblox_mesh;
|
|
||||||
|
|
||||||
#[cfg(feature="legacy")]
|
|
||||||
pub fn roblox_legacy()->roblox_legacy::Loader{
|
|
||||||
roblox_legacy::Loader::new()
|
|
||||||
}
|
|
||||||
#[cfg(feature="legacy")]
|
|
||||||
pub fn source_legacy()->source_legacy::Loader{
|
|
||||||
source_legacy::Loader::new()
|
|
||||||
}
|
|
||||||
#[cfg(feature="roblox")]
|
|
||||||
pub fn roblox()->roblox::Loader{
|
|
||||||
roblox::Loader::new()
|
|
||||||
}
|
|
||||||
#[cfg(feature="source")]
|
|
||||||
pub fn source()->source::Loader{
|
|
||||||
source::Loader::new()
|
|
||||||
}
|
|
||||||
|
|||||||
8
lib/deferred_loader/src/loader.rs
Normal file
8
lib/deferred_loader/src/loader.rs
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
use std::error::Error;
|
||||||
|
|
||||||
|
pub trait Loader{
|
||||||
|
type Error:Error;
|
||||||
|
type Index;
|
||||||
|
type Resource;
|
||||||
|
fn load(&mut self,index:Self::Index)->Result<Self::Resource,Self::Error>;
|
||||||
|
}
|
||||||
17
lib/deferred_loader/src/mesh.rs
Normal file
17
lib/deferred_loader/src/mesh.rs
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
use strafesnet_common::model::{Mesh,MeshId};
|
||||||
|
|
||||||
|
pub struct Meshes{
|
||||||
|
meshes:Vec<Option<Mesh>>,
|
||||||
|
}
|
||||||
|
impl Meshes{
|
||||||
|
pub(crate) const fn new(meshes:Vec<Option<Mesh>>)->Self{
|
||||||
|
Self{
|
||||||
|
meshes,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn consume(self)->impl Iterator<Item=(MeshId,Mesh)>{
|
||||||
|
self.meshes.into_iter().enumerate().filter_map(|(mesh_id,maybe_mesh)|
|
||||||
|
maybe_mesh.map(|mesh|(MeshId::new(mesh_id as u32),mesh))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,48 +0,0 @@
|
|||||||
#[derive(Hash,Eq,PartialEq)]
|
|
||||||
pub struct RobloxAssetId(pub u64);
|
|
||||||
#[derive(Debug)]
|
|
||||||
#[allow(dead_code)]
|
|
||||||
pub struct StringWithError{
|
|
||||||
string:String,
|
|
||||||
error:RobloxAssetIdParseErr,
|
|
||||||
}
|
|
||||||
impl std::fmt::Display for StringWithError{
|
|
||||||
fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{
|
|
||||||
write!(f,"{self:?}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl std::error::Error for StringWithError{}
|
|
||||||
impl StringWithError{
|
|
||||||
const fn new(
|
|
||||||
string:String,
|
|
||||||
error:RobloxAssetIdParseErr,
|
|
||||||
)->Self{
|
|
||||||
Self{string,error}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub enum RobloxAssetIdParseErr{
|
|
||||||
Url(url::ParseError),
|
|
||||||
UnknownScheme,
|
|
||||||
ParseInt(std::num::ParseIntError),
|
|
||||||
MissingAssetId,
|
|
||||||
}
|
|
||||||
impl std::str::FromStr for RobloxAssetId{
|
|
||||||
type Err=StringWithError;
|
|
||||||
fn from_str(s:&str)->Result<Self,Self::Err>{
|
|
||||||
let url=url::Url::parse(s).map_err(|e|StringWithError::new(s.to_owned(),RobloxAssetIdParseErr::Url(e)))?;
|
|
||||||
let parsed_asset_id=match url.scheme(){
|
|
||||||
"rbxassetid"=>url.domain().ok_or_else(||StringWithError::new(s.to_owned(),RobloxAssetIdParseErr::MissingAssetId))?.parse(),
|
|
||||||
"http"|"https"=>{
|
|
||||||
let (_,asset_id)=url.query_pairs()
|
|
||||||
.find(|(id,_)|match id.as_ref(){
|
|
||||||
"ID"|"id"|"Id"|"iD"=>true,
|
|
||||||
_=>false,
|
|
||||||
}).ok_or_else(||StringWithError::new(s.to_owned(),RobloxAssetIdParseErr::MissingAssetId))?;
|
|
||||||
asset_id.parse()
|
|
||||||
},
|
|
||||||
_=>Err(StringWithError::new(s.to_owned(),RobloxAssetIdParseErr::UnknownScheme))?,
|
|
||||||
};
|
|
||||||
Ok(Self(parsed_asset_id.map_err(|e|StringWithError::new(s.to_owned(),RobloxAssetIdParseErr::ParseInt(e)))?))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,112 +0,0 @@
|
|||||||
use std::io::Read;
|
|
||||||
use std::collections::HashMap;
|
|
||||||
use crate::roblox_mesh;
|
|
||||||
use crate::texture::{RenderConfigs,Texture};
|
|
||||||
use strafesnet_common::model::{MeshId,RenderConfig,RenderConfigId,TextureId};
|
|
||||||
use crate::rbxassetid::RobloxAssetId;
|
|
||||||
|
|
||||||
#[derive(Default)]
|
|
||||||
pub struct RenderConfigLoader{
|
|
||||||
texture_count:u32,
|
|
||||||
render_configs:Vec<RenderConfig>,
|
|
||||||
render_config_id_from_asset_id:HashMap<Option<RobloxAssetId>,RenderConfigId>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl RenderConfigLoader{
|
|
||||||
pub fn acquire_render_config_id(&mut self,name:Option<&str>)->RenderConfigId{
|
|
||||||
let render_id=RenderConfigId::new(self.render_config_id_from_asset_id.len() as u32);
|
|
||||||
let index=name.and_then(|name|{
|
|
||||||
match name.parse::<RobloxAssetId>(){
|
|
||||||
Ok(asset_id)=>Some(asset_id),
|
|
||||||
Err(e)=>{
|
|
||||||
println!("Failed to parse AssetId: {e}");
|
|
||||||
None
|
|
||||||
},
|
|
||||||
}
|
|
||||||
});
|
|
||||||
*self.render_config_id_from_asset_id.entry(index).or_insert_with(||{
|
|
||||||
//create the render config.
|
|
||||||
let render_config=if name.is_some(){
|
|
||||||
let render_config=RenderConfig::texture(TextureId::new(self.texture_count));
|
|
||||||
self.texture_count+=1;
|
|
||||||
render_config
|
|
||||||
}else{
|
|
||||||
RenderConfig::default()
|
|
||||||
};
|
|
||||||
self.render_configs.push(render_config);
|
|
||||||
render_id
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Default)]
|
|
||||||
pub struct MeshLoader{
|
|
||||||
mesh_id_from_asset_id:HashMap<Option<RobloxAssetId>,MeshId>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl MeshLoader{
|
|
||||||
pub fn acquire_mesh_id(&mut self,name:&str)->MeshId{
|
|
||||||
let mesh_id=MeshId::new(self.mesh_id_from_asset_id.len() as u32);
|
|
||||||
let index=match name.parse::<RobloxAssetId>(){
|
|
||||||
Ok(asset_id)=>Some(asset_id),
|
|
||||||
Err(e)=>{
|
|
||||||
println!("Failed to parse AssetId: {e}");
|
|
||||||
None
|
|
||||||
},
|
|
||||||
};
|
|
||||||
*self.mesh_id_from_asset_id.entry(index).or_insert(mesh_id)
|
|
||||||
}
|
|
||||||
pub fn load_meshes(&mut self)->Result<roblox_mesh::Meshes,std::io::Error>{
|
|
||||||
let mut mesh_data=vec![None;self.mesh_id_from_asset_id.len()];
|
|
||||||
for (asset_id_option,mesh_id) in &self.mesh_id_from_asset_id{
|
|
||||||
if let Some(asset_id)=asset_id_option{
|
|
||||||
if let Ok(mut file)=std::fs::File::open(format!("meshes/{}",asset_id.0)){
|
|
||||||
//TODO: parallel
|
|
||||||
let mut data=Vec::<u8>::new();
|
|
||||||
file.read_to_end(&mut data)?;
|
|
||||||
mesh_data[mesh_id.get() as usize]=Some(roblox_mesh::RobloxMeshData::new(data));
|
|
||||||
}else{
|
|
||||||
println!("[roblox_legacy] no mesh name={}",asset_id.0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(roblox_mesh::Meshes::new(mesh_data))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct Loader{
|
|
||||||
render_config_loader:RenderConfigLoader,
|
|
||||||
mesh_loader:MeshLoader,
|
|
||||||
}
|
|
||||||
impl Loader{
|
|
||||||
pub fn new()->Self{
|
|
||||||
Self{
|
|
||||||
render_config_loader:RenderConfigLoader::default(),
|
|
||||||
mesh_loader:MeshLoader::default(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub fn get_inner_mut(&mut self)->(&mut RenderConfigLoader,&mut MeshLoader){
|
|
||||||
(&mut self.render_config_loader,&mut self.mesh_loader)
|
|
||||||
}
|
|
||||||
pub fn into_render_configs(mut self)->Result<RenderConfigs,std::io::Error>{
|
|
||||||
let mut sorted_textures=vec![None;self.render_config_loader.texture_count as usize];
|
|
||||||
for (asset_id_option,render_config_id) in self.render_config_loader.render_config_id_from_asset_id{
|
|
||||||
let render_config=self.render_config_loader.render_configs.get_mut(render_config_id.get() as usize).unwrap();
|
|
||||||
if let (Some(asset_id),Some(texture_id))=(asset_id_option,render_config.texture){
|
|
||||||
if let Ok(mut file)=std::fs::File::open(format!("textures/{}.dds",asset_id.0)){
|
|
||||||
//TODO: parallel
|
|
||||||
let mut data=Vec::<u8>::new();
|
|
||||||
file.read_to_end(&mut data)?;
|
|
||||||
sorted_textures[texture_id.get() as usize]=Some(Texture::ImageDDS(data));
|
|
||||||
}else{
|
|
||||||
//texture failed to load
|
|
||||||
render_config.texture=None;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(RenderConfigs::new(
|
|
||||||
sorted_textures,
|
|
||||||
self.render_config_loader.render_configs,
|
|
||||||
))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
use strafesnet_common::model::MeshId;
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct RobloxMeshData(Vec<u8>);
|
|
||||||
impl RobloxMeshData{
|
|
||||||
pub(crate) fn new(data:Vec<u8>)->Self{
|
|
||||||
Self(data)
|
|
||||||
}
|
|
||||||
pub fn get(self)->Vec<u8>{
|
|
||||||
self.0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub struct Meshes{
|
|
||||||
meshes:Vec<Option<RobloxMeshData>>,
|
|
||||||
}
|
|
||||||
impl Meshes{
|
|
||||||
pub(crate) const fn new(meshes:Vec<Option<RobloxMeshData>>)->Self{
|
|
||||||
Self{
|
|
||||||
meshes,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub fn get_texture(&self,texture_id:MeshId)->Option<&RobloxMeshData>{
|
|
||||||
self.meshes.get(texture_id.get() as usize)?.as_ref()
|
|
||||||
}
|
|
||||||
pub fn into_iter(self)->impl Iterator<Item=(MeshId,RobloxMeshData)>{
|
|
||||||
self.meshes.into_iter().enumerate().filter_map(|(mesh_id,maybe_mesh)|
|
|
||||||
maybe_mesh.map(|mesh|(MeshId::new(mesh_id as u32),mesh))
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,102 +0,0 @@
|
|||||||
use std::io::Read;
|
|
||||||
use std::collections::HashMap;
|
|
||||||
use crate::valve_mesh;
|
|
||||||
use crate::texture::{Texture,RenderConfigs};
|
|
||||||
use strafesnet_common::model::{MeshId,TextureId,RenderConfig,RenderConfigId};
|
|
||||||
|
|
||||||
pub struct RenderConfigLoader{
|
|
||||||
texture_count:u32,
|
|
||||||
render_configs:Vec<RenderConfig>,
|
|
||||||
texture_paths:HashMap<Option<Box<str>>,RenderConfigId>,
|
|
||||||
}
|
|
||||||
impl RenderConfigLoader{
|
|
||||||
pub fn acquire_render_config_id(&mut self,name:Option<&str>)->RenderConfigId{
|
|
||||||
let render_id=RenderConfigId::new(self.texture_paths.len() as u32);
|
|
||||||
*self.texture_paths.entry(name.map(Into::into)).or_insert_with(||{
|
|
||||||
//create the render config.
|
|
||||||
let render_config=if name.is_some(){
|
|
||||||
let render_config=RenderConfig::texture(TextureId::new(self.texture_count));
|
|
||||||
self.texture_count+=1;
|
|
||||||
render_config
|
|
||||||
}else{
|
|
||||||
RenderConfig::default()
|
|
||||||
};
|
|
||||||
self.render_configs.push(render_config);
|
|
||||||
render_id
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub struct MeshLoader{
|
|
||||||
mesh_paths:HashMap<Box<str>,MeshId>,
|
|
||||||
}
|
|
||||||
impl MeshLoader{
|
|
||||||
pub fn acquire_mesh_id(&mut self,name:&str)->MeshId{
|
|
||||||
let mesh_id=MeshId::new(self.mesh_paths.len() as u32);
|
|
||||||
*self.mesh_paths.entry(name.into()).or_insert(mesh_id)
|
|
||||||
}
|
|
||||||
//load_meshes should look like load_textures
|
|
||||||
pub fn load_meshes(&mut self,bsp:&vbsp::Bsp)->valve_mesh::Meshes{
|
|
||||||
let mut mesh_data=vec![None;self.mesh_paths.len()];
|
|
||||||
for (mesh_path,mesh_id) in &self.mesh_paths{
|
|
||||||
let mesh_path_lower=mesh_path.to_lowercase();
|
|
||||||
//.mdl, .vvd, .dx90.vtx
|
|
||||||
let path=std::path::PathBuf::from(mesh_path_lower.as_str());
|
|
||||||
let mut vvd_path=path.clone();
|
|
||||||
let mut vtx_path=path.clone();
|
|
||||||
vvd_path.set_extension("vvd");
|
|
||||||
vtx_path.set_extension("dx90.vtx");
|
|
||||||
match (bsp.pack.get(mesh_path_lower.as_str()),bsp.pack.get(vvd_path.as_os_str().to_str().unwrap()),bsp.pack.get(vtx_path.as_os_str().to_str().unwrap())){
|
|
||||||
(Ok(Some(mdl_file)),Ok(Some(vvd_file)),Ok(Some(vtx_file)))=>{
|
|
||||||
mesh_data[mesh_id.get() as usize]=Some(valve_mesh::ModelData{
|
|
||||||
mdl:valve_mesh::MdlData::new(mdl_file),
|
|
||||||
vtx:valve_mesh::VtxData::new(vtx_file),
|
|
||||||
vvd:valve_mesh::VvdData::new(vvd_file),
|
|
||||||
});
|
|
||||||
},
|
|
||||||
_=>println!("no model name={}",mesh_path),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
valve_mesh::Meshes::new(mesh_data)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct Loader{
|
|
||||||
render_config_loader:RenderConfigLoader,
|
|
||||||
mesh_loader:MeshLoader,
|
|
||||||
}
|
|
||||||
impl Loader{
|
|
||||||
pub fn new()->Self{
|
|
||||||
Self{
|
|
||||||
render_config_loader:RenderConfigLoader{
|
|
||||||
texture_count:0,
|
|
||||||
texture_paths:HashMap::new(),
|
|
||||||
render_configs:Vec::new(),
|
|
||||||
},
|
|
||||||
mesh_loader:MeshLoader{mesh_paths:HashMap::new()},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub fn get_inner_mut(&mut self)->(&mut RenderConfigLoader,&mut MeshLoader){
|
|
||||||
(&mut self.render_config_loader,&mut self.mesh_loader)
|
|
||||||
}
|
|
||||||
pub fn into_render_configs(mut self)->Result<RenderConfigs,std::io::Error>{
|
|
||||||
let mut sorted_textures=vec![None;self.render_config_loader.texture_count as usize];
|
|
||||||
for (texture_path,render_config_id) in self.render_config_loader.texture_paths{
|
|
||||||
let render_config=self.render_config_loader.render_configs.get_mut(render_config_id.get() as usize).unwrap();
|
|
||||||
if let (Some(texture_path),Some(texture_id))=(texture_path,render_config.texture){
|
|
||||||
if let Ok(mut file)=std::fs::File::open(format!("textures/{}.dds",texture_path)){
|
|
||||||
//TODO: parallel
|
|
||||||
let mut data=Vec::<u8>::new();
|
|
||||||
file.read_to_end(&mut data)?;
|
|
||||||
sorted_textures[texture_id.get() as usize]=Some(Texture::ImageDDS(data));
|
|
||||||
}else{
|
|
||||||
//texture failed to load
|
|
||||||
render_config.texture=None;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(RenderConfigs::new(
|
|
||||||
sorted_textures,
|
|
||||||
self.render_config_loader.render_configs,
|
|
||||||
))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,60 +0,0 @@
|
|||||||
use strafesnet_common::model::MeshId;
|
|
||||||
|
|
||||||
//duplicate this code for now
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct MdlData(Vec<u8>);
|
|
||||||
impl MdlData{
|
|
||||||
pub const fn new(value:Vec<u8>)->Self{
|
|
||||||
Self(value)
|
|
||||||
}
|
|
||||||
pub fn get(self)->Vec<u8>{
|
|
||||||
self.0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct VtxData(Vec<u8>);
|
|
||||||
impl VtxData{
|
|
||||||
pub const fn new(value:Vec<u8>)->Self{
|
|
||||||
Self(value)
|
|
||||||
}
|
|
||||||
pub fn get(self)->Vec<u8>{
|
|
||||||
self.0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct VvdData(Vec<u8>);
|
|
||||||
impl VvdData{
|
|
||||||
pub const fn new(value:Vec<u8>)->Self{
|
|
||||||
Self(value)
|
|
||||||
}
|
|
||||||
pub fn get(self)->Vec<u8>{
|
|
||||||
self.0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct ModelData{
|
|
||||||
pub mdl:MdlData,
|
|
||||||
pub vtx:VtxData,
|
|
||||||
pub vvd:VvdData,
|
|
||||||
}
|
|
||||||
|
|
||||||
//meshes is more prone to failure
|
|
||||||
pub struct Meshes{
|
|
||||||
meshes:Vec<Option<ModelData>>,
|
|
||||||
}
|
|
||||||
impl Meshes{
|
|
||||||
pub(crate) const fn new(meshes:Vec<Option<ModelData>>)->Self{
|
|
||||||
Self{
|
|
||||||
meshes,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub fn get_texture(&self,texture_id:MeshId)->Option<&ModelData>{
|
|
||||||
self.meshes.get(texture_id.get() as usize)?.as_ref()
|
|
||||||
}
|
|
||||||
pub fn into_iter(self)->impl Iterator<Item=(MeshId,ModelData)>{
|
|
||||||
self.meshes.into_iter().enumerate().filter_map(|(mesh_id,maybe_mesh)|
|
|
||||||
maybe_mesh.map(|mesh|(MeshId::new(mesh_id as u32),mesh))
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "fixed_wide"
|
name = "fixed_wide"
|
||||||
version = "0.1.1"
|
version = "0.1.2"
|
||||||
edition = "2021"
|
edition = "2024"
|
||||||
repository = "https://git.itzana.me/StrafesNET/strafe-project"
|
repository = "https://git.itzana.me/StrafesNET/strafe-project"
|
||||||
license = "MIT OR Apache-2.0"
|
license = "MIT OR Apache-2.0"
|
||||||
description = "Fixed point numbers with optional widening Mul operator."
|
description = "Fixed point numbers with optional widening Mul operator."
|
||||||
@@ -14,7 +14,7 @@ wide-mul=[]
|
|||||||
zeroes=["dep:arrayvec"]
|
zeroes=["dep:arrayvec"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
bnum = "0.12.0"
|
bnum = "0.13.0"
|
||||||
arrayvec = { version = "0.7.6", optional = true }
|
arrayvec = { version = "0.7.6", optional = true }
|
||||||
paste = "1.0.15"
|
paste = "1.0.15"
|
||||||
ratio_ops = { path = "../ratio_ops", registry = "strafesnet", optional = true }
|
ratio_ops = { version = "0.1.0", path = "../ratio_ops", registry = "strafesnet", optional = true }
|
||||||
|
|||||||
@@ -33,6 +33,14 @@ impl<const N:usize,const F:usize> Fixed<N,F>{
|
|||||||
self.bits
|
self.bits
|
||||||
}
|
}
|
||||||
#[inline]
|
#[inline]
|
||||||
|
pub const fn as_bits(&self)->&BInt<N>{
|
||||||
|
&self.bits
|
||||||
|
}
|
||||||
|
#[inline]
|
||||||
|
pub const fn as_bits_mut(&mut self)->&mut BInt<N>{
|
||||||
|
&mut self.bits
|
||||||
|
}
|
||||||
|
#[inline]
|
||||||
pub const fn raw_digit(value:i64)->Self{
|
pub const fn raw_digit(value:i64)->Self{
|
||||||
let mut digits=[0u64;N];
|
let mut digits=[0u64;N];
|
||||||
digits[0]=value.abs() as u64;
|
digits[0]=value.abs() as u64;
|
||||||
@@ -56,6 +64,10 @@ impl<const N:usize,const F:usize> Fixed<N,F>{
|
|||||||
pub const fn abs(self)->Self{
|
pub const fn abs(self)->Self{
|
||||||
Self::from_bits(self.bits.abs())
|
Self::from_bits(self.bits.abs())
|
||||||
}
|
}
|
||||||
|
#[inline]
|
||||||
|
pub const fn midpoint(self,other:Self)->Self{
|
||||||
|
Self::from_bits(self.bits.midpoint(other.bits))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
impl<const F:usize> Fixed<1,F>{
|
impl<const F:usize> Fixed<1,F>{
|
||||||
/// My old code called this function everywhere so let's provide it
|
/// My old code called this function everywhere so let's provide it
|
||||||
@@ -788,13 +800,10 @@ macro_rules! impl_not_const_generic{
|
|||||||
let wide_self=self.[<fix_ $_2n>]();
|
let wide_self=self.[<fix_ $_2n>]();
|
||||||
//descend down the bits and check if flipping each bit would push the square over the input value
|
//descend down the bits and check if flipping each bit would push the square over the input value
|
||||||
for shift in (0..=max_shift).rev(){
|
for shift in (0..=max_shift).rev(){
|
||||||
let new_result={
|
result.as_bits_mut().as_bits_mut().set_bit(shift,true);
|
||||||
let mut bits=result.to_bits().to_bits();
|
if wide_self<result.[<wide_mul_ $n _ $n>](result){
|
||||||
bits.set_bit(shift,true);
|
// put it back lol
|
||||||
Self::from_bits(BInt::from_bits(bits))
|
result.as_bits_mut().as_bits_mut().set_bit(shift,false);
|
||||||
};
|
|
||||||
if new_result.[<wide_mul_ $n _ $n>](new_result)<=wide_self{
|
|
||||||
result=new_result;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
result
|
result
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ fn from_f32(){
|
|||||||
assert_eq!(b,Ok(a));
|
assert_eq!(b,Ok(a));
|
||||||
//I32F32::MIN hits a special case since it's not representable as a positive signed integer
|
//I32F32::MIN hits a special case since it's not representable as a positive signed integer
|
||||||
//TODO: don't return an overflow because this is technically possible
|
//TODO: don't return an overflow because this is technically possible
|
||||||
let a=I32F32::MIN;
|
let _a=I32F32::MIN;
|
||||||
let b:Result<I32F32,_>=Into::<f32>::into(I32F32::MIN).try_into();
|
let b:Result<I32F32,_>=Into::<f32>::into(I32F32::MIN).try_into();
|
||||||
assert_eq!(b,Err(crate::fixed::FixedFromFloatError::Overflow));
|
assert_eq!(b,Err(crate::fixed::FixedFromFloatError::Overflow));
|
||||||
//16 is within the 24 bits of float precision
|
//16 is within the 24 bits of float precision
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "linear_ops"
|
name = "linear_ops"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2021"
|
edition = "2024"
|
||||||
repository = "https://git.itzana.me/StrafesNET/strafe-project"
|
repository = "https://git.itzana.me/StrafesNET/strafe-project"
|
||||||
license = "MIT OR Apache-2.0"
|
license = "MIT OR Apache-2.0"
|
||||||
description = "Vector/Matrix operations using trait bounds."
|
description = "Vector/Matrix operations using trait bounds."
|
||||||
@@ -14,8 +14,8 @@ fixed-wide=["dep:fixed_wide","dep:paste"]
|
|||||||
deferred-division=["dep:ratio_ops"]
|
deferred-division=["dep:ratio_ops"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
ratio_ops = { path = "../ratio_ops", registry = "strafesnet", optional = true }
|
ratio_ops = { version = "0.1.0", path = "../ratio_ops", registry = "strafesnet", optional = true }
|
||||||
fixed_wide = { path = "../fixed_wide", registry = "strafesnet", optional = true }
|
fixed_wide = { version = "0.1.2", path = "../fixed_wide", registry = "strafesnet", optional = true }
|
||||||
paste = { version = "1.0.15", optional = true }
|
paste = { version = "1.0.15", optional = true }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
use crate::vector::Vector;
|
use crate::vector::Vector;
|
||||||
|
|
||||||
|
#[repr(transparent)]
|
||||||
#[derive(Clone,Copy,Debug,Hash,Eq,PartialEq)]
|
#[derive(Clone,Copy,Debug,Hash,Eq,PartialEq)]
|
||||||
pub struct Matrix<const X:usize,const Y:usize,T>{
|
pub struct Matrix<const X:usize,const Y:usize,T>{
|
||||||
pub(crate) array:[[T;Y];X],
|
pub(crate) array:[[T;Y];X],
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
/// v.x += v.z;
|
/// v.x += v.z;
|
||||||
/// println!("v.x={}",v.x);
|
/// println!("v.x={}",v.x);
|
||||||
|
|
||||||
|
#[repr(transparent)]
|
||||||
#[derive(Clone,Copy,Debug,Hash,Eq,PartialEq)]
|
#[derive(Clone,Copy,Debug,Hash,Eq,PartialEq)]
|
||||||
pub struct Vector<const N:usize,T>{
|
pub struct Vector<const N:usize,T>{
|
||||||
pub(crate) array:[T;N],
|
pub(crate) array:[T;N],
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "ratio_ops"
|
name = "ratio_ops"
|
||||||
version = "0.1.0"
|
version = "0.1.1"
|
||||||
edition = "2021"
|
edition = "2024"
|
||||||
repository = "https://git.itzana.me/StrafesNET/strafe-project"
|
repository = "https://git.itzana.me/StrafesNET/strafe-project"
|
||||||
license = "MIT OR Apache-2.0"
|
license = "MIT OR Apache-2.0"
|
||||||
description = "Ratio operations using trait bounds for avoiding division like the plague."
|
description = "Ratio operations using trait bounds for avoiding division like the plague."
|
||||||
|
|||||||
@@ -268,30 +268,35 @@ impl<LhsNum,LhsDen,RhsNum,RhsDen,T,U> PartialEq<Ratio<RhsNum,RhsDen>> for Ratio<
|
|||||||
}
|
}
|
||||||
impl<Num,Den> Eq for Ratio<Num,Den> where Self:PartialEq{}
|
impl<Num,Den> Eq for Ratio<Num,Den> where Self:PartialEq{}
|
||||||
|
|
||||||
|
// Wow! These were both completely wrong!
|
||||||
|
// Idea: use a 'signed' trait instead of parity and float the sign to the numerator.
|
||||||
impl<LhsNum,LhsDen,RhsNum,RhsDen,T,U> PartialOrd<Ratio<RhsNum,RhsDen>> for Ratio<LhsNum,LhsDen>
|
impl<LhsNum,LhsDen,RhsNum,RhsDen,T,U> PartialOrd<Ratio<RhsNum,RhsDen>> for Ratio<LhsNum,LhsDen>
|
||||||
where
|
where
|
||||||
LhsNum:Copy,
|
LhsNum:Copy,
|
||||||
LhsDen:Copy,
|
LhsDen:Copy+Parity,
|
||||||
RhsNum:Copy,
|
RhsNum:Copy,
|
||||||
RhsDen:Copy,
|
RhsDen:Copy+Parity,
|
||||||
LhsNum:core::ops::Mul<RhsDen,Output=T>,
|
LhsNum:core::ops::Mul<RhsDen,Output=T>,
|
||||||
|
LhsDen:core::ops::Mul<RhsNum,Output=T>,
|
||||||
RhsNum:core::ops::Mul<LhsDen,Output=U>,
|
RhsNum:core::ops::Mul<LhsDen,Output=U>,
|
||||||
T:PartialOrd<U>,
|
RhsDen:core::ops::Mul<LhsNum,Output=U>,
|
||||||
|
T:PartialOrd<U>+Ord,
|
||||||
{
|
{
|
||||||
#[inline]
|
#[inline]
|
||||||
fn partial_cmp(&self,other:&Ratio<RhsNum,RhsDen>)->Option<core::cmp::Ordering>{
|
fn partial_cmp(&self,&other:&Ratio<RhsNum,RhsDen>)->Option<core::cmp::Ordering>{
|
||||||
(self.num*other.den).partial_cmp(&(other.num*self.den))
|
self.partial_cmp_ratio(other)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl<Num,Den,T> Ord for Ratio<Num,Den>
|
impl<Num,Den,T> Ord for Ratio<Num,Den>
|
||||||
where
|
where
|
||||||
Num:Copy,
|
Num:Copy,
|
||||||
Den:Copy,
|
Den:Copy+Parity,
|
||||||
Num:core::ops::Mul<Den,Output=T>,
|
Num:core::ops::Mul<Den,Output=T>,
|
||||||
|
Den:core::ops::Mul<Num,Output=T>,
|
||||||
T:Ord,
|
T:Ord,
|
||||||
{
|
{
|
||||||
#[inline]
|
#[inline]
|
||||||
fn cmp(&self,other:&Self)->std::cmp::Ordering{
|
fn cmp(&self,&other:&Self)->std::cmp::Ordering{
|
||||||
(self.num*other.den).cmp(&(other.num*self.den))
|
self.cmp_ratio(other)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "strafesnet_rbx_loader"
|
name = "strafesnet_rbx_loader"
|
||||||
version = "0.5.2"
|
version = "0.6.0"
|
||||||
edition = "2021"
|
edition = "2024"
|
||||||
repository = "https://git.itzana.me/StrafesNET/strafe-project"
|
repository = "https://git.itzana.me/StrafesNET/strafe-project"
|
||||||
license = "MIT OR Apache-2.0"
|
license = "MIT OR Apache-2.0"
|
||||||
description = "Convert Roblox place and model files to StrafesNET data structures."
|
description = "Convert Roblox place and model files to StrafesNET data structures."
|
||||||
@@ -11,12 +11,14 @@ authors = ["Rhys Lloyd <krakow20@gmail.com>"]
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
bytemuck = "1.14.3"
|
bytemuck = "1.14.3"
|
||||||
glam = "0.29.0"
|
glam = "0.30.0"
|
||||||
lazy-regex = "3.1.0"
|
lazy-regex = "3.1.0"
|
||||||
rbx_binary = { version = "0.7.4", registry = "strafesnet" }
|
rbx_binary = { version = "0.7.4", registry = "strafesnet" }
|
||||||
rbx_dom_weak = { version = "2.7.0", registry = "strafesnet" }
|
rbx_dom_weak = { version = "2.7.0", registry = "strafesnet" }
|
||||||
rbx_mesh = "0.1.2"
|
rbx_mesh = "0.3.1"
|
||||||
rbx_reflection_database = { version = "0.2.10", registry = "strafesnet" }
|
rbx_reflection_database = { version = "0.2.10", registry = "strafesnet" }
|
||||||
rbx_xml = { version = "0.13.3", registry = "strafesnet" }
|
rbx_xml = { version = "0.13.3", registry = "strafesnet" }
|
||||||
roblox_emulator = { path = "../roblox_emulator", registry = "strafesnet" }
|
rbxassetid = { version = "0.1.0", path = "../rbxassetid", registry = "strafesnet" }
|
||||||
strafesnet_common = { path = "../common", registry = "strafesnet" }
|
roblox_emulator = { version = "0.4.7", path = "../roblox_emulator", registry = "strafesnet" }
|
||||||
|
strafesnet_common = { version = "0.6.0", path = "../common", registry = "strafesnet" }
|
||||||
|
strafesnet_deferred_loader = { version = "0.5.0", path = "../deferred_loader", registry = "strafesnet" }
|
||||||
|
|||||||
@@ -1,8 +1,11 @@
|
|||||||
use std::io::Read;
|
use std::io::Read;
|
||||||
use rbx_dom_weak::WeakDom;
|
use rbx_dom_weak::WeakDom;
|
||||||
|
use strafesnet_deferred_loader::deferred_loader::{LoadFailureMode,MeshDeferredLoader,RenderConfigDeferredLoader};
|
||||||
|
|
||||||
mod rbx;
|
mod rbx;
|
||||||
mod mesh;
|
mod mesh;
|
||||||
|
mod union;
|
||||||
|
pub mod loader;
|
||||||
mod primitives;
|
mod primitives;
|
||||||
|
|
||||||
pub mod data{
|
pub mod data{
|
||||||
@@ -30,6 +33,9 @@ impl Model{
|
|||||||
let services=context.convert_into_place();
|
let services=context.convert_into_place();
|
||||||
Place{dom,services}
|
Place{dom,services}
|
||||||
}
|
}
|
||||||
|
pub fn to_snf(&self,failure_mode:LoadFailureMode)->Result<strafesnet_common::map::CompleteMap,LoadError>{
|
||||||
|
to_snf(self,failure_mode)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
impl AsRef<WeakDom> for Model{
|
impl AsRef<WeakDom> for Model{
|
||||||
fn as_ref(&self)->&WeakDom{
|
fn as_ref(&self)->&WeakDom{
|
||||||
@@ -42,7 +48,7 @@ pub struct Place{
|
|||||||
services:roblox_emulator::context::Services,
|
services:roblox_emulator::context::Services,
|
||||||
}
|
}
|
||||||
impl Place{
|
impl Place{
|
||||||
fn new(dom:WeakDom)->Option<Self>{
|
pub fn new(dom:WeakDom)->Option<Self>{
|
||||||
let context=roblox_emulator::context::Context::from_ref(&dom);
|
let context=roblox_emulator::context::Context::from_ref(&dom);
|
||||||
Some(Self{
|
Some(Self{
|
||||||
services:context.find_services()?,
|
services:context.find_services()?,
|
||||||
@@ -61,6 +67,9 @@ impl Place{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
pub fn to_snf(&self,failure_mode:LoadFailureMode)->Result<strafesnet_common::map::CompleteMap,LoadError>{
|
||||||
|
to_snf(self,failure_mode)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
impl AsRef<WeakDom> for Place{
|
impl AsRef<WeakDom> for Place{
|
||||||
fn as_ref(&self)->&WeakDom{
|
fn as_ref(&self)->&WeakDom{
|
||||||
@@ -92,16 +101,49 @@ pub fn read<R:Read>(input:R)->Result<Model,ReadError>{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//ConvertError
|
#[derive(Debug)]
|
||||||
|
pub enum LoadError{
|
||||||
pub fn convert<AcquireRenderConfigId,AcquireMeshId>(
|
Texture(loader::TextureError),
|
||||||
dom:impl AsRef<WeakDom>,
|
Mesh(loader::MeshError),
|
||||||
acquire_render_config_id:AcquireRenderConfigId,
|
}
|
||||||
acquire_mesh_id:AcquireMeshId
|
impl std::fmt::Display for LoadError{
|
||||||
)->rbx::PartialMap1
|
fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{
|
||||||
where
|
write!(f,"{self:?}")
|
||||||
AcquireRenderConfigId:FnMut(Option<&str>)->strafesnet_common::model::RenderConfigId,
|
}
|
||||||
AcquireMeshId:FnMut(&str)->strafesnet_common::model::MeshId,
|
}
|
||||||
{
|
impl std::error::Error for LoadError{}
|
||||||
rbx::convert(&dom.as_ref(),acquire_render_config_id,acquire_mesh_id)
|
impl From<loader::TextureError> for LoadError{
|
||||||
|
fn from(value:loader::TextureError)->Self{
|
||||||
|
Self::Texture(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl From<loader::MeshError> for LoadError{
|
||||||
|
fn from(value:loader::MeshError)->Self{
|
||||||
|
Self::Mesh(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn to_snf(dom:impl AsRef<WeakDom>,failure_mode:LoadFailureMode)->Result<strafesnet_common::map::CompleteMap,LoadError>{
|
||||||
|
let dom=dom.as_ref();
|
||||||
|
|
||||||
|
let mut texture_deferred_loader=RenderConfigDeferredLoader::new();
|
||||||
|
let mut mesh_deferred_loader=MeshDeferredLoader::new();
|
||||||
|
|
||||||
|
let map_step1=rbx::convert(
|
||||||
|
dom,
|
||||||
|
&mut texture_deferred_loader,
|
||||||
|
&mut mesh_deferred_loader,
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut mesh_loader=loader::MeshLoader::new();
|
||||||
|
let meshpart_meshes=mesh_deferred_loader.into_meshes(&mut mesh_loader,failure_mode).map_err(LoadError::Mesh)?;
|
||||||
|
|
||||||
|
let map_step2=map_step1.add_meshpart_meshes_and_calculate_attributes(meshpart_meshes);
|
||||||
|
|
||||||
|
let mut texture_loader=loader::TextureLoader::new();
|
||||||
|
let render_configs=texture_deferred_loader.into_render_configs(&mut texture_loader,failure_mode).map_err(LoadError::Texture)?;
|
||||||
|
|
||||||
|
let map=map_step2.add_render_configs_and_textures(render_configs);
|
||||||
|
|
||||||
|
Ok(map)
|
||||||
}
|
}
|
||||||
|
|||||||
191
lib/rbx_loader/src/loader.rs
Normal file
191
lib/rbx_loader/src/loader.rs
Normal file
@@ -0,0 +1,191 @@
|
|||||||
|
use std::io::Read;
|
||||||
|
use rbxassetid::{RobloxAssetId,RobloxAssetIdParseErr};
|
||||||
|
use strafesnet_common::model::Mesh;
|
||||||
|
use strafesnet_deferred_loader::{loader::Loader,texture::Texture};
|
||||||
|
|
||||||
|
use crate::data::RobloxMeshBytes;
|
||||||
|
use crate::rbx::RobloxFaceTextureDescription;
|
||||||
|
|
||||||
|
fn read_entire_file(path:impl AsRef<std::path::Path>)->Result<Vec<u8>,std::io::Error>{
|
||||||
|
let mut file=std::fs::File::open(path)?;
|
||||||
|
let mut data=Vec::new();
|
||||||
|
file.read_to_end(&mut data)?;
|
||||||
|
Ok(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum TextureError{
|
||||||
|
Io(std::io::Error),
|
||||||
|
RobloxAssetIdParse(RobloxAssetIdParseErr),
|
||||||
|
}
|
||||||
|
impl std::fmt::Display for TextureError{
|
||||||
|
fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{
|
||||||
|
write!(f,"{self:?}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl std::error::Error for TextureError{}
|
||||||
|
impl From<std::io::Error> for TextureError{
|
||||||
|
fn from(value:std::io::Error)->Self{
|
||||||
|
Self::Io(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl From<RobloxAssetIdParseErr> for TextureError{
|
||||||
|
fn from(value:RobloxAssetIdParseErr)->Self{
|
||||||
|
Self::RobloxAssetIdParse(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct TextureLoader<'a>(std::marker::PhantomData<&'a ()>);
|
||||||
|
impl TextureLoader<'_>{
|
||||||
|
pub fn new()->Self{
|
||||||
|
Self(std::marker::PhantomData)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<'a> Loader for TextureLoader<'a>{
|
||||||
|
type Error=TextureError;
|
||||||
|
type Index=&'a str;
|
||||||
|
type Resource=Texture;
|
||||||
|
fn load(&mut self,index:Self::Index)->Result<Self::Resource,Self::Error>{
|
||||||
|
let RobloxAssetId(asset_id)=index.parse()?;
|
||||||
|
let file_name=format!("textures/{}.dds",asset_id);
|
||||||
|
let data=read_entire_file(file_name)?;
|
||||||
|
Ok(Texture::ImageDDS(data))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum MeshError{
|
||||||
|
Io(std::io::Error),
|
||||||
|
RobloxAssetIdParse(RobloxAssetIdParseErr),
|
||||||
|
Mesh(crate::mesh::Error),
|
||||||
|
Union(crate::union::Error),
|
||||||
|
DecodeBinary(rbx_binary::DecodeError),
|
||||||
|
OneChildPolicy,
|
||||||
|
MissingInstance,
|
||||||
|
}
|
||||||
|
impl std::fmt::Display for MeshError{
|
||||||
|
fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{
|
||||||
|
write!(f,"{self:?}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl std::error::Error for MeshError{}
|
||||||
|
impl From<std::io::Error> for MeshError{
|
||||||
|
fn from(value:std::io::Error)->Self{
|
||||||
|
Self::Io(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl From<RobloxAssetIdParseErr> for MeshError{
|
||||||
|
fn from(value:RobloxAssetIdParseErr)->Self{
|
||||||
|
Self::RobloxAssetIdParse(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl From<crate::mesh::Error> for MeshError{
|
||||||
|
fn from(value:crate::mesh::Error)->Self{
|
||||||
|
Self::Mesh(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl From<crate::union::Error> for MeshError{
|
||||||
|
fn from(value:crate::union::Error)->Self{
|
||||||
|
Self::Union(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl From<rbx_binary::DecodeError> for MeshError{
|
||||||
|
fn from(value:rbx_binary::DecodeError)->Self{
|
||||||
|
Self::DecodeBinary(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Hash,Eq,PartialEq)]
|
||||||
|
pub enum MeshType<'a>{
|
||||||
|
FileMesh,
|
||||||
|
Union{
|
||||||
|
mesh_data:&'a [u8],
|
||||||
|
physics_data:&'a [u8],
|
||||||
|
size_float_bits:[u32;3],
|
||||||
|
part_texture_description:[Option<RobloxFaceTextureDescription>;6],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
#[derive(Hash,Eq,PartialEq)]
|
||||||
|
pub struct MeshIndex<'a>{
|
||||||
|
mesh_type:MeshType<'a>,
|
||||||
|
content:&'a str,
|
||||||
|
}
|
||||||
|
impl MeshIndex<'_>{
|
||||||
|
pub fn file_mesh(content:&str)->MeshIndex{
|
||||||
|
MeshIndex{
|
||||||
|
mesh_type:MeshType::FileMesh,
|
||||||
|
content,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn union<'a>(
|
||||||
|
content:&'a str,
|
||||||
|
mesh_data:&'a [u8],
|
||||||
|
physics_data:&'a [u8],
|
||||||
|
size:&rbx_dom_weak::types::Vector3,
|
||||||
|
part_texture_description:crate::rbx::RobloxPartDescription,
|
||||||
|
)->MeshIndex<'a>{
|
||||||
|
MeshIndex{
|
||||||
|
mesh_type:MeshType::Union{
|
||||||
|
mesh_data,
|
||||||
|
physics_data,
|
||||||
|
size_float_bits:[size.x.to_bits(),size.y.to_bits(),size.z.to_bits()],
|
||||||
|
part_texture_description,
|
||||||
|
},
|
||||||
|
content,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct MeshLoader<'a>(std::marker::PhantomData<&'a ()>);
|
||||||
|
impl MeshLoader<'_>{
|
||||||
|
pub fn new()->Self{
|
||||||
|
Self(std::marker::PhantomData)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<'a> Loader for MeshLoader<'a>{
|
||||||
|
type Error=MeshError;
|
||||||
|
type Index=MeshIndex<'a>;
|
||||||
|
type Resource=Mesh;
|
||||||
|
fn load(&mut self,index:Self::Index)->Result<Self::Resource,Self::Error>{
|
||||||
|
let mesh=match index.mesh_type{
|
||||||
|
MeshType::FileMesh=>{
|
||||||
|
let RobloxAssetId(asset_id)=index.content.parse()?;
|
||||||
|
let file_name=format!("meshes/{}",asset_id);
|
||||||
|
let data=read_entire_file(file_name)?;
|
||||||
|
crate::mesh::convert(RobloxMeshBytes::new(data))?
|
||||||
|
},
|
||||||
|
MeshType::Union{mut physics_data,mut mesh_data,size_float_bits,part_texture_description}=>{
|
||||||
|
// decode asset
|
||||||
|
let size=glam::Vec3::from_array(size_float_bits.map(f32::from_bits));
|
||||||
|
if !index.content.is_empty()&&(physics_data.is_empty()||mesh_data.is_empty()){
|
||||||
|
let RobloxAssetId(asset_id)=index.content.parse()?;
|
||||||
|
let file_name=format!("unions/{}",asset_id);
|
||||||
|
let data=read_entire_file(file_name)?;
|
||||||
|
let dom=rbx_binary::from_reader(std::io::Cursor::new(data))?;
|
||||||
|
let &[referent]=dom.root().children()else{
|
||||||
|
return Err(MeshError::OneChildPolicy);
|
||||||
|
};
|
||||||
|
let Some(instance)=dom.get_by_ref(referent)else{
|
||||||
|
return Err(MeshError::MissingInstance);
|
||||||
|
};
|
||||||
|
if physics_data.is_empty(){
|
||||||
|
if let Some(rbx_dom_weak::types::Variant::BinaryString(data))=instance.properties.get("PhysicsData"){
|
||||||
|
physics_data=data.as_ref();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if mesh_data.is_empty(){
|
||||||
|
if let Some(rbx_dom_weak::types::Variant::BinaryString(data))=instance.properties.get("MeshData"){
|
||||||
|
mesh_data=data.as_ref();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
crate::union::convert(physics_data,mesh_data,size,part_texture_description)?
|
||||||
|
}else{
|
||||||
|
crate::union::convert(physics_data,mesh_data,size,part_texture_description)?
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
Ok(mesh)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,8 +1,9 @@
|
|||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use rbx_mesh::mesh::{Vertex2, Vertex2Truncated};
|
use rbx_mesh::mesh::{Vertex2,Vertex2Truncated};
|
||||||
use strafesnet_common::{integer::vec3,model::{self, ColorId, IndexedVertex, NormalId, PolygonGroup, PolygonList, PositionId, TextureCoordinateId, VertexId}};
|
use strafesnet_common::{integer::vec3,model::{self,ColorId,IndexedVertex,NormalId,PolygonGroup,PolygonList,PositionId,RenderConfigId,TextureCoordinateId,VertexId}};
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum Error{
|
pub enum Error{
|
||||||
Planar64Vec3(strafesnet_common::integer::Planar64TryFromFloatError),
|
Planar64Vec3(strafesnet_common::integer::Planar64TryFromFloatError),
|
||||||
@@ -83,13 +84,13 @@ where
|
|||||||
fn ingest_faces2_lods3(
|
fn ingest_faces2_lods3(
|
||||||
polygon_groups:&mut Vec<PolygonGroup>,
|
polygon_groups:&mut Vec<PolygonGroup>,
|
||||||
vertex_id_map:&HashMap<rbx_mesh::mesh::VertexId2,VertexId>,
|
vertex_id_map:&HashMap<rbx_mesh::mesh::VertexId2,VertexId>,
|
||||||
faces:&Vec<rbx_mesh::mesh::Face2>,
|
faces:&[rbx_mesh::mesh::Face2],
|
||||||
lods:&Vec<rbx_mesh::mesh::Lod3>
|
lods:&[rbx_mesh::mesh::Lod3],
|
||||||
){
|
){
|
||||||
//faces have to be split into polygon groups based on lod
|
//faces have to be split into polygon groups based on lod
|
||||||
polygon_groups.extend(lods.windows(2).map(|lod_pair|
|
polygon_groups.extend(lods.windows(2).map(|lod_pair|
|
||||||
PolygonGroup::PolygonList(PolygonList::new(faces[lod_pair[0].0 as usize..lod_pair[1].0 as usize].iter().map(|face|
|
PolygonGroup::PolygonList(PolygonList::new(faces[lod_pair[0].0 as usize..lod_pair[1].0 as usize].iter().map(|rbx_mesh::mesh::Face2(v0,v1,v2)|
|
||||||
vec![vertex_id_map[&face.0],vertex_id_map[&face.1],vertex_id_map[&face.2]]
|
vec![vertex_id_map[&v0],vertex_id_map[&v1],vertex_id_map[&v2]]
|
||||||
).collect()))
|
).collect()))
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
@@ -204,7 +205,13 @@ pub fn convert(roblox_mesh_bytes:crate::data::RobloxMeshBytes)->Result<model::Me
|
|||||||
unique_vertices,
|
unique_vertices,
|
||||||
polygon_groups,
|
polygon_groups,
|
||||||
//these should probably be moved to the model...
|
//these should probably be moved to the model...
|
||||||
graphics_groups:Vec::new(),
|
//but what if models want to use the same texture
|
||||||
|
graphics_groups:vec![model::IndexedGraphicsGroup{
|
||||||
|
render:RenderConfigId::new(0),
|
||||||
|
//the lowest lod is highest quality
|
||||||
|
groups:vec![model::PolygonGroupId::new(0)]
|
||||||
|
}],
|
||||||
|
//disable physics
|
||||||
physics_groups:Vec::new(),
|
physics_groups:Vec::new(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
use strafesnet_common::model::{Color4,TextureCoordinate,Mesh,IndexedGraphicsGroup,IndexedPhysicsGroup,IndexedVertex,PolygonGroupId,PolygonGroup,PolygonList,IndexedVertexList,PositionId,TextureCoordinateId,NormalId,ColorId,VertexId,RenderConfigId};
|
use strafesnet_common::model::{Color4,TextureCoordinate,Mesh,IndexedGraphicsGroup,IndexedPhysicsGroup,IndexedVertex,PolygonGroupId,PolygonGroup,PolygonList,PositionId,TextureCoordinateId,NormalId,ColorId,VertexId,RenderConfigId};
|
||||||
use strafesnet_common::integer::{vec3,Planar64Vec3};
|
use strafesnet_common::integer::{vec3,Planar64Vec3};
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
@@ -42,50 +42,6 @@ const CUBE_DEFAULT_NORMALS:[Planar64Vec3;6]=[
|
|||||||
vec3::int( 0,-1, 0),//CubeFace::Bottom
|
vec3::int( 0,-1, 0),//CubeFace::Bottom
|
||||||
vec3::int( 0, 0,-1),//CubeFace::Front
|
vec3::int( 0, 0,-1),//CubeFace::Front
|
||||||
];
|
];
|
||||||
const CUBE_DEFAULT_POLYS:[[[u32;3];4];6]=[
|
|
||||||
// right (1, 0, 0)
|
|
||||||
[
|
|
||||||
[6,2,0],//[vertex,tex,norm]
|
|
||||||
[5,1,0],
|
|
||||||
[2,0,0],
|
|
||||||
[1,3,0],
|
|
||||||
],
|
|
||||||
// top (0, 1, 0)
|
|
||||||
[
|
|
||||||
[5,3,1],
|
|
||||||
[4,2,1],
|
|
||||||
[3,1,1],
|
|
||||||
[2,0,1],
|
|
||||||
],
|
|
||||||
// back (0, 0, 1)
|
|
||||||
[
|
|
||||||
[0,3,2],
|
|
||||||
[1,2,2],
|
|
||||||
[2,1,2],
|
|
||||||
[3,0,2],
|
|
||||||
],
|
|
||||||
// left (-1, 0, 0)
|
|
||||||
[
|
|
||||||
[0,2,3],
|
|
||||||
[3,1,3],
|
|
||||||
[4,0,3],
|
|
||||||
[7,3,3],
|
|
||||||
],
|
|
||||||
// bottom (0,-1, 0)
|
|
||||||
[
|
|
||||||
[1,1,4],
|
|
||||||
[0,0,4],
|
|
||||||
[7,3,4],
|
|
||||||
[6,2,4],
|
|
||||||
],
|
|
||||||
// front (0, 0,-1)
|
|
||||||
[
|
|
||||||
[4,1,5],
|
|
||||||
[5,0,5],
|
|
||||||
[6,3,5],
|
|
||||||
[7,2,5],
|
|
||||||
],
|
|
||||||
];
|
|
||||||
|
|
||||||
#[derive(Hash,PartialEq,Eq)]
|
#[derive(Hash,PartialEq,Eq)]
|
||||||
pub enum WedgeFace{
|
pub enum WedgeFace{
|
||||||
@@ -126,17 +82,14 @@ const CORNERWEDGE_DEFAULT_NORMALS:[Planar64Vec3;5]=[
|
|||||||
vec3::int( 0,-1, 0),//CornerWedge::Bottom
|
vec3::int( 0,-1, 0),//CornerWedge::Bottom
|
||||||
vec3::int( 0, 0,-1),//CornerWedge::Front
|
vec3::int( 0, 0,-1),//CornerWedge::Front
|
||||||
];
|
];
|
||||||
pub fn unit_sphere(render:RenderConfigId)->Mesh{
|
|
||||||
unit_cube(render)
|
|
||||||
}
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct CubeFaceDescription([Option<FaceDescription>;6]);
|
pub struct CubeFaceDescription([Option<FaceDescription>;6]);
|
||||||
impl CubeFaceDescription{
|
impl CubeFaceDescription{
|
||||||
pub fn insert(&mut self,index:CubeFace,value:FaceDescription){
|
pub fn insert(&mut self,index:CubeFace,value:FaceDescription){
|
||||||
self.0[index as usize]=Some(value);
|
self.0[index as usize]=Some(value);
|
||||||
}
|
}
|
||||||
pub fn pairs(self)->std::iter::FilterMap<std::iter::Enumerate<std::array::IntoIter<Option<FaceDescription>,6>>,impl FnMut((usize,Option<FaceDescription>))->Option<(usize,FaceDescription)>>{
|
pub fn pairs(self)->impl Iterator<Item=(usize,FaceDescription)>{
|
||||||
self.0.into_iter().enumerate().filter_map(|v|v.1.map(|u|(v.0,u)))
|
self.0.into_iter().enumerate().filter_map(|(i,v)|v.map(|u|(i,u)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn unit_cube(render:RenderConfigId)->Mesh{
|
pub fn unit_cube(render:RenderConfigId)->Mesh{
|
||||||
@@ -149,10 +102,6 @@ pub fn unit_cube(render:RenderConfigId)->Mesh{
|
|||||||
t.insert(CubeFace::Front,FaceDescription::new_with_render_id(render));
|
t.insert(CubeFace::Front,FaceDescription::new_with_render_id(render));
|
||||||
generate_partial_unit_cube(t)
|
generate_partial_unit_cube(t)
|
||||||
}
|
}
|
||||||
pub fn unit_cylinder(render:RenderConfigId)->Mesh{
|
|
||||||
//lmao
|
|
||||||
unit_cube(render)
|
|
||||||
}
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct WedgeFaceDescription([Option<FaceDescription>;5]);
|
pub struct WedgeFaceDescription([Option<FaceDescription>;5]);
|
||||||
impl WedgeFaceDescription{
|
impl WedgeFaceDescription{
|
||||||
@@ -163,15 +112,15 @@ impl WedgeFaceDescription{
|
|||||||
self.0.into_iter().enumerate().filter_map(|v|v.1.map(|u|(v.0,u)))
|
self.0.into_iter().enumerate().filter_map(|v|v.1.map(|u|(v.0,u)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn unit_wedge(render:RenderConfigId)->Mesh{
|
// pub fn unit_wedge(render:RenderConfigId)->Mesh{
|
||||||
let mut t=WedgeFaceDescription::default();
|
// let mut t=WedgeFaceDescription::default();
|
||||||
t.insert(WedgeFace::Right,FaceDescription::new_with_render_id(render));
|
// t.insert(WedgeFace::Right,FaceDescription::new_with_render_id(render));
|
||||||
t.insert(WedgeFace::TopFront,FaceDescription::new_with_render_id(render));
|
// t.insert(WedgeFace::TopFront,FaceDescription::new_with_render_id(render));
|
||||||
t.insert(WedgeFace::Back,FaceDescription::new_with_render_id(render));
|
// t.insert(WedgeFace::Back,FaceDescription::new_with_render_id(render));
|
||||||
t.insert(WedgeFace::Left,FaceDescription::new_with_render_id(render));
|
// t.insert(WedgeFace::Left,FaceDescription::new_with_render_id(render));
|
||||||
t.insert(WedgeFace::Bottom,FaceDescription::new_with_render_id(render));
|
// t.insert(WedgeFace::Bottom,FaceDescription::new_with_render_id(render));
|
||||||
generate_partial_unit_wedge(t)
|
// generate_partial_unit_wedge(t)
|
||||||
}
|
// }
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct CornerWedgeFaceDescription([Option<FaceDescription>;5]);
|
pub struct CornerWedgeFaceDescription([Option<FaceDescription>;5]);
|
||||||
impl CornerWedgeFaceDescription{
|
impl CornerWedgeFaceDescription{
|
||||||
@@ -182,15 +131,15 @@ impl CornerWedgeFaceDescription{
|
|||||||
self.0.into_iter().enumerate().filter_map(|v|v.1.map(|u|(v.0,u)))
|
self.0.into_iter().enumerate().filter_map(|v|v.1.map(|u|(v.0,u)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn unit_cornerwedge(render:RenderConfigId)->Mesh{
|
// pub fn unit_cornerwedge(render:RenderConfigId)->Mesh{
|
||||||
let mut t=CornerWedgeFaceDescription::default();
|
// let mut t=CornerWedgeFaceDescription::default();
|
||||||
t.insert(CornerWedgeFace::Right,FaceDescription::new_with_render_id(render));
|
// t.insert(CornerWedgeFace::Right,FaceDescription::new_with_render_id(render));
|
||||||
t.insert(CornerWedgeFace::TopBack,FaceDescription::new_with_render_id(render));
|
// t.insert(CornerWedgeFace::TopBack,FaceDescription::new_with_render_id(render));
|
||||||
t.insert(CornerWedgeFace::TopLeft,FaceDescription::new_with_render_id(render));
|
// t.insert(CornerWedgeFace::TopLeft,FaceDescription::new_with_render_id(render));
|
||||||
t.insert(CornerWedgeFace::Bottom,FaceDescription::new_with_render_id(render));
|
// t.insert(CornerWedgeFace::Bottom,FaceDescription::new_with_render_id(render));
|
||||||
t.insert(CornerWedgeFace::Front,FaceDescription::new_with_render_id(render));
|
// t.insert(CornerWedgeFace::Front,FaceDescription::new_with_render_id(render));
|
||||||
generate_partial_unit_cornerwedge(t)
|
// generate_partial_unit_cornerwedge(t)
|
||||||
}
|
// }
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct FaceDescription{
|
pub struct FaceDescription{
|
||||||
@@ -208,6 +157,50 @@ impl FaceDescription{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn generate_partial_unit_cube(face_descriptions:CubeFaceDescription)->Mesh{
|
pub fn generate_partial_unit_cube(face_descriptions:CubeFaceDescription)->Mesh{
|
||||||
|
const CUBE_DEFAULT_POLYS:[[[u32;3];4];6]=[
|
||||||
|
// right (1, 0, 0)
|
||||||
|
[
|
||||||
|
[6,2,0],//[vertex,tex,norm]
|
||||||
|
[5,1,0],
|
||||||
|
[2,0,0],
|
||||||
|
[1,3,0],
|
||||||
|
],
|
||||||
|
// top (0, 1, 0)
|
||||||
|
[
|
||||||
|
[5,3,1],
|
||||||
|
[4,2,1],
|
||||||
|
[3,1,1],
|
||||||
|
[2,0,1],
|
||||||
|
],
|
||||||
|
// back (0, 0, 1)
|
||||||
|
[
|
||||||
|
[0,3,2],
|
||||||
|
[1,2,2],
|
||||||
|
[2,1,2],
|
||||||
|
[3,0,2],
|
||||||
|
],
|
||||||
|
// left (-1, 0, 0)
|
||||||
|
[
|
||||||
|
[0,2,3],
|
||||||
|
[3,1,3],
|
||||||
|
[4,0,3],
|
||||||
|
[7,3,3],
|
||||||
|
],
|
||||||
|
// bottom (0,-1, 0)
|
||||||
|
[
|
||||||
|
[1,1,4],
|
||||||
|
[0,0,4],
|
||||||
|
[7,3,4],
|
||||||
|
[6,2,4],
|
||||||
|
],
|
||||||
|
// front (0, 0,-1)
|
||||||
|
[
|
||||||
|
[4,1,5],
|
||||||
|
[5,0,5],
|
||||||
|
[6,3,5],
|
||||||
|
[7,2,5],
|
||||||
|
],
|
||||||
|
];
|
||||||
let mut generated_pos=Vec::new();
|
let mut generated_pos=Vec::new();
|
||||||
let mut generated_tex=Vec::new();
|
let mut generated_tex=Vec::new();
|
||||||
let mut generated_normal=Vec::new();
|
let mut generated_normal=Vec::new();
|
||||||
@@ -286,35 +279,35 @@ pub fn generate_partial_unit_cube(face_descriptions:CubeFaceDescription)->Mesh{
|
|||||||
}
|
}
|
||||||
//don't think too hard about the copy paste because this is all going into the map tool eventually...
|
//don't think too hard about the copy paste because this is all going into the map tool eventually...
|
||||||
pub fn generate_partial_unit_wedge(face_descriptions:WedgeFaceDescription)->Mesh{
|
pub fn generate_partial_unit_wedge(face_descriptions:WedgeFaceDescription)->Mesh{
|
||||||
let wedge_default_polys=[
|
const WEDGE_DEFAULT_POLYS:[&[[u32;3]];5]=[
|
||||||
// right (1, 0, 0)
|
// right (1, 0, 0)
|
||||||
vec![
|
&[
|
||||||
[6,2,0],//[vertex,tex,norm]
|
[6,2,0],//[vertex,tex,norm]
|
||||||
[2,0,0],
|
[2,0,0],
|
||||||
[1,3,0],
|
[1,3,0],
|
||||||
],
|
],
|
||||||
// FrontTop (0, 1, -1)
|
// FrontTop (0, 1, -1)
|
||||||
vec![
|
&[
|
||||||
[3,1,1],
|
[3,1,1],
|
||||||
[2,0,1],
|
[2,0,1],
|
||||||
[6,3,1],
|
[6,3,1],
|
||||||
[7,2,1],
|
[7,2,1],
|
||||||
],
|
],
|
||||||
// back (0, 0, 1)
|
// back (0, 0, 1)
|
||||||
vec![
|
&[
|
||||||
[0,3,2],
|
[0,3,2],
|
||||||
[1,2,2],
|
[1,2,2],
|
||||||
[2,1,2],
|
[2,1,2],
|
||||||
[3,0,2],
|
[3,0,2],
|
||||||
],
|
],
|
||||||
// left (-1, 0, 0)
|
// left (-1, 0, 0)
|
||||||
vec![
|
&[
|
||||||
[0,2,3],
|
[0,2,3],
|
||||||
[3,1,3],
|
[3,1,3],
|
||||||
[7,3,3],
|
[7,3,3],
|
||||||
],
|
],
|
||||||
// bottom (0,-1, 0)
|
// bottom (0,-1, 0)
|
||||||
vec![
|
&[
|
||||||
[1,1,4],
|
[1,1,4],
|
||||||
[0,0,4],
|
[0,0,4],
|
||||||
[7,3,4],
|
[7,3,4],
|
||||||
@@ -358,7 +351,7 @@ pub fn generate_partial_unit_wedge(face_descriptions:WedgeFaceDescription)->Mesh
|
|||||||
//push vertices as they are needed
|
//push vertices as they are needed
|
||||||
let group_id=PolygonGroupId::new(polygon_groups.len() as u32);
|
let group_id=PolygonGroupId::new(polygon_groups.len() as u32);
|
||||||
polygon_groups.push(PolygonGroup::PolygonList(PolygonList::new(vec![
|
polygon_groups.push(PolygonGroup::PolygonList(PolygonList::new(vec![
|
||||||
wedge_default_polys[face_id].iter().map(|tup|{
|
WEDGE_DEFAULT_POLYS[face_id].iter().map(|tup|{
|
||||||
let pos=CUBE_DEFAULT_VERTICES[tup[0] as usize];
|
let pos=CUBE_DEFAULT_VERTICES[tup[0] as usize];
|
||||||
let pos_index=if let Some(pos_index)=generated_pos.iter().position(|&p|p==pos){
|
let pos_index=if let Some(pos_index)=generated_pos.iter().position(|&p|p==pos){
|
||||||
pos_index
|
pos_index
|
||||||
@@ -399,34 +392,34 @@ pub fn generate_partial_unit_wedge(face_descriptions:WedgeFaceDescription)->Mesh
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn generate_partial_unit_cornerwedge(face_descriptions:CornerWedgeFaceDescription)->Mesh{
|
pub fn generate_partial_unit_cornerwedge(face_descriptions:CornerWedgeFaceDescription)->Mesh{
|
||||||
let cornerwedge_default_polys=[
|
const CORNERWEDGE_DEFAULT_POLYS:[&[[u32;3]];5]=[
|
||||||
// right (1, 0, 0)
|
// right (1, 0, 0)
|
||||||
vec![
|
&[
|
||||||
[6,2,0],//[vertex,tex,norm]
|
[6,2,0],//[vertex,tex,norm]
|
||||||
[5,1,0],
|
[5,1,0],
|
||||||
[1,3,0],
|
[1,3,0],
|
||||||
],
|
],
|
||||||
// BackTop (0, 1, 1)
|
// BackTop (0, 1, 1)
|
||||||
vec![
|
&[
|
||||||
[5,3,1],
|
[5,3,1],
|
||||||
[0,1,1],
|
[0,1,1],
|
||||||
[1,0,1],
|
[1,0,1],
|
||||||
],
|
],
|
||||||
// LeftTop (-1, 1, 0)
|
// LeftTop (-1, 1, 0)
|
||||||
vec![
|
&[
|
||||||
[5,3,2],
|
[5,3,2],
|
||||||
[7,2,2],
|
[7,2,2],
|
||||||
[0,1,2],
|
[0,1,2],
|
||||||
],
|
],
|
||||||
// bottom (0,-1, 0)
|
// bottom (0,-1, 0)
|
||||||
vec![
|
&[
|
||||||
[1,1,3],
|
[1,1,3],
|
||||||
[0,0,3],
|
[0,0,3],
|
||||||
[7,3,3],
|
[7,3,3],
|
||||||
[6,2,3],
|
[6,2,3],
|
||||||
],
|
],
|
||||||
// front (0, 0,-1)
|
// front (0, 0,-1)
|
||||||
vec![
|
&[
|
||||||
[5,0,4],
|
[5,0,4],
|
||||||
[6,3,4],
|
[6,3,4],
|
||||||
[7,2,4],
|
[7,2,4],
|
||||||
@@ -469,7 +462,7 @@ pub fn generate_partial_unit_cornerwedge(face_descriptions:CornerWedgeFaceDescri
|
|||||||
//push vertices as they are needed
|
//push vertices as they are needed
|
||||||
let group_id=PolygonGroupId::new(polygon_groups.len() as u32);
|
let group_id=PolygonGroupId::new(polygon_groups.len() as u32);
|
||||||
polygon_groups.push(PolygonGroup::PolygonList(PolygonList::new(vec![
|
polygon_groups.push(PolygonGroup::PolygonList(PolygonList::new(vec![
|
||||||
cornerwedge_default_polys[face_id].iter().map(|tup|{
|
CORNERWEDGE_DEFAULT_POLYS[face_id].iter().map(|tup|{
|
||||||
let pos=CUBE_DEFAULT_VERTICES[tup[0] as usize];
|
let pos=CUBE_DEFAULT_VERTICES[tup[0] as usize];
|
||||||
let pos_index=if let Some(pos_index)=generated_pos.iter().position(|&p|p==pos){
|
let pos_index=if let Some(pos_index)=generated_pos.iter().position(|&p|p==pos){
|
||||||
pos_index
|
pos_index
|
||||||
|
|||||||
@@ -1,13 +1,17 @@
|
|||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
use crate::loader::MeshIndex;
|
||||||
use crate::primitives;
|
use crate::primitives;
|
||||||
|
use strafesnet_common::aabb::Aabb;
|
||||||
use strafesnet_common::map;
|
use strafesnet_common::map;
|
||||||
use strafesnet_common::model;
|
use strafesnet_common::model;
|
||||||
use strafesnet_common::gameplay_modes;
|
use strafesnet_common::gameplay_modes::{NormalizedModes,Mode,ModeId,ModeUpdate,ModesBuilder,Stage,StageElement,StageElementBehaviour,StageId,Zone};
|
||||||
use strafesnet_common::gameplay_style;
|
use strafesnet_common::gameplay_style;
|
||||||
use strafesnet_common::gameplay_attributes as attr;
|
use strafesnet_common::gameplay_attributes as attr;
|
||||||
use strafesnet_common::integer::{self,vec3,Planar64,Planar64Vec3,Planar64Mat3,Planar64Affine3};
|
use strafesnet_common::integer::{self,vec3,Planar64,Planar64Vec3,Planar64Mat3,Planar64Affine3};
|
||||||
use strafesnet_common::model::RenderConfigId;
|
use strafesnet_common::model::RenderConfigId;
|
||||||
use strafesnet_common::updatable::Updatable;
|
use strafesnet_deferred_loader::deferred_loader::{RenderConfigDeferredLoader,MeshDeferredLoader};
|
||||||
|
use strafesnet_deferred_loader::mesh::Meshes;
|
||||||
|
use strafesnet_deferred_loader::texture::{RenderConfigs,Texture};
|
||||||
|
|
||||||
fn class_is_a(class: &str, superclass: &str) -> bool {
|
fn class_is_a(class: &str, superclass: &str) -> bool {
|
||||||
if class==superclass {
|
if class==superclass {
|
||||||
@@ -47,93 +51,7 @@ fn planar64_affine3_from_roblox(cf:&rbx_dom_weak::types::CFrame,size:&rbx_dom_we
|
|||||||
vec3::try_from_f32_array([cf.position.x,cf.position.y,cf.position.z]).unwrap()
|
vec3::try_from_f32_array([cf.position.x,cf.position.y,cf.position.z]).unwrap()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
struct ModeBuilder{
|
|
||||||
mode:gameplay_modes::Mode,
|
|
||||||
final_stage_id_from_builder_stage_id:HashMap<gameplay_modes::StageId,gameplay_modes::StageId>,
|
|
||||||
}
|
|
||||||
#[derive(Default)]
|
|
||||||
struct ModesBuilder{
|
|
||||||
modes:HashMap<gameplay_modes::ModeId,gameplay_modes::Mode>,
|
|
||||||
stages:HashMap<gameplay_modes::ModeId,HashMap<gameplay_modes::StageId,gameplay_modes::Stage>>,
|
|
||||||
mode_updates:Vec<(gameplay_modes::ModeId,gameplay_modes::ModeUpdate)>,
|
|
||||||
stage_updates:Vec<(gameplay_modes::ModeId,gameplay_modes::StageId,gameplay_modes::StageUpdate)>,
|
|
||||||
}
|
|
||||||
impl ModesBuilder{
|
|
||||||
fn build(mut self)->gameplay_modes::Modes{
|
|
||||||
//collect modes and stages into contiguous arrays
|
|
||||||
let mut unique_modes:Vec<(gameplay_modes::ModeId,gameplay_modes::Mode)>
|
|
||||||
=self.modes.into_iter().collect();
|
|
||||||
unique_modes.sort_by_key(|&(mode_id,_)|mode_id);
|
|
||||||
let (mut modes,final_mode_id_from_builder_mode_id):(Vec<ModeBuilder>,HashMap<gameplay_modes::ModeId,gameplay_modes::ModeId>)
|
|
||||||
=unique_modes.into_iter().enumerate()
|
|
||||||
.map(|(final_mode_id,(builder_mode_id,mut mode))|{
|
|
||||||
(
|
|
||||||
ModeBuilder{
|
|
||||||
final_stage_id_from_builder_stage_id:self.stages.remove(&builder_mode_id).map_or_else(||HashMap::new(),|stages|{
|
|
||||||
let mut unique_stages:Vec<(gameplay_modes::StageId,gameplay_modes::Stage)>
|
|
||||||
=stages.into_iter().collect();
|
|
||||||
unique_stages.sort_by(|a,b|a.0.cmp(&b.0));
|
|
||||||
unique_stages.into_iter().enumerate()
|
|
||||||
.map(|(final_stage_id,(builder_stage_id,stage))|{
|
|
||||||
mode.push_stage(stage);
|
|
||||||
(builder_stage_id,gameplay_modes::StageId::new(final_stage_id as u32))
|
|
||||||
}).collect()
|
|
||||||
}),
|
|
||||||
mode,
|
|
||||||
},
|
|
||||||
(
|
|
||||||
builder_mode_id,
|
|
||||||
gameplay_modes::ModeId::new(final_mode_id as u32)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}).unzip();
|
|
||||||
//TODO: failure messages or errors or something
|
|
||||||
//push stage updates
|
|
||||||
for (builder_mode_id,builder_stage_id,stage_update) in self.stage_updates{
|
|
||||||
if let Some(final_mode_id)=final_mode_id_from_builder_mode_id.get(&builder_mode_id){
|
|
||||||
if let Some(mode)=modes.get_mut(final_mode_id.get() as usize){
|
|
||||||
if let Some(&final_stage_id)=mode.final_stage_id_from_builder_stage_id.get(&builder_stage_id){
|
|
||||||
if let Some(stage)=mode.mode.get_stage_mut(final_stage_id){
|
|
||||||
stage.update(stage_update);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
//push mode updates
|
|
||||||
for (builder_mode_id,mut mode_update) in self.mode_updates{
|
|
||||||
if let Some(final_mode_id)=final_mode_id_from_builder_mode_id.get(&builder_mode_id){
|
|
||||||
if let Some(mode)=modes.get_mut(final_mode_id.get() as usize){
|
|
||||||
//map stage id on stage elements
|
|
||||||
mode_update.map_stage_element_ids(|stage_id|
|
|
||||||
//walk down one stage id at a time until a stage is found
|
|
||||||
//TODO use better logic like BTreeMap::upper_bound instead of walking
|
|
||||||
// final_stage_id_from_builder_stage_id.upper_bound(Bound::Included(&stage_id))
|
|
||||||
// .value().copied().unwrap_or(gameplay_modes::StageId::FIRST)
|
|
||||||
(0..=stage_id.get()).rev().find_map(|builder_stage_id|
|
|
||||||
//map the stage element to that stage
|
|
||||||
mode.final_stage_id_from_builder_stage_id.get(&gameplay_modes::StageId::new(builder_stage_id)).copied()
|
|
||||||
).unwrap_or(gameplay_modes::StageId::FIRST)
|
|
||||||
);
|
|
||||||
mode.mode.update(mode_update);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
gameplay_modes::Modes::new(modes.into_iter().map(|mode_builder|mode_builder.mode).collect())
|
|
||||||
}
|
|
||||||
fn insert_mode(&mut self,mode_id:gameplay_modes::ModeId,mode:gameplay_modes::Mode){
|
|
||||||
assert!(self.modes.insert(mode_id,mode).is_none(),"Cannot replace existing mode");
|
|
||||||
}
|
|
||||||
fn insert_stage(&mut self,mode_id:gameplay_modes::ModeId,stage_id:gameplay_modes::StageId,stage:gameplay_modes::Stage){
|
|
||||||
assert!(self.stages.entry(mode_id).or_insert(HashMap::new()).insert(stage_id,stage).is_none(),"Cannot replace existing stage");
|
|
||||||
}
|
|
||||||
fn push_mode_update(&mut self,mode_id:gameplay_modes::ModeId,mode_update:gameplay_modes::ModeUpdate){
|
|
||||||
self.mode_updates.push((mode_id,mode_update));
|
|
||||||
}
|
|
||||||
fn push_stage_update(&mut self,mode_id:gameplay_modes::ModeId,stage_id:gameplay_modes::StageId,stage_update:gameplay_modes::StageUpdate){
|
|
||||||
self.stage_updates.push((mode_id,stage_id,stage_update));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fn get_attributes(name:&str,can_collide:bool,velocity:Planar64Vec3,model_id:model::ModelId,modes_builder:&mut ModesBuilder,wormhole_in_model_to_id:&mut HashMap<model::ModelId,u32>,wormhole_id_to_out_model:&mut HashMap<u32,model::ModelId>)->attr::CollisionAttributes{
|
fn get_attributes(name:&str,can_collide:bool,velocity:Planar64Vec3,model_id:model::ModelId,modes_builder:&mut ModesBuilder,wormhole_in_model_to_id:&mut HashMap<model::ModelId,u32>,wormhole_id_to_out_model:&mut HashMap<u32,model::ModelId>)->attr::CollisionAttributes{
|
||||||
let mut general=attr::GeneralAttributes::default();
|
let mut general=attr::GeneralAttributes::default();
|
||||||
let mut intersecting=attr::IntersectingAttributes::default();
|
let mut intersecting=attr::IntersectingAttributes::default();
|
||||||
@@ -162,8 +80,8 @@ fn get_attributes(name:&str,can_collide:bool,velocity:Planar64Vec3,model_id:mode
|
|||||||
force_can_collide=false;
|
force_can_collide=false;
|
||||||
force_intersecting=true;
|
force_intersecting=true;
|
||||||
modes_builder.insert_mode(
|
modes_builder.insert_mode(
|
||||||
gameplay_modes::ModeId::MAIN,
|
ModeId::MAIN,
|
||||||
gameplay_modes::Mode::empty(
|
Mode::empty(
|
||||||
gameplay_style::StyleModifiers::roblox_bhop(),
|
gameplay_style::StyleModifiers::roblox_bhop(),
|
||||||
model_id
|
model_id
|
||||||
)
|
)
|
||||||
@@ -173,10 +91,10 @@ fn get_attributes(name:&str,can_collide:bool,velocity:Planar64Vec3,model_id:mode
|
|||||||
force_can_collide=false;
|
force_can_collide=false;
|
||||||
force_intersecting=true;
|
force_intersecting=true;
|
||||||
modes_builder.push_mode_update(
|
modes_builder.push_mode_update(
|
||||||
gameplay_modes::ModeId::MAIN,
|
ModeId::MAIN,
|
||||||
gameplay_modes::ModeUpdate::zone(
|
ModeUpdate::zone(
|
||||||
model_id,
|
model_id,
|
||||||
gameplay_modes::Zone::Finish,
|
Zone::Finish,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@@ -184,19 +102,19 @@ fn get_attributes(name:&str,can_collide:bool,velocity:Planar64Vec3,model_id:mode
|
|||||||
force_can_collide=false;
|
force_can_collide=false;
|
||||||
force_intersecting=true;
|
force_intersecting=true;
|
||||||
modes_builder.push_mode_update(
|
modes_builder.push_mode_update(
|
||||||
gameplay_modes::ModeId::MAIN,
|
ModeId::MAIN,
|
||||||
gameplay_modes::ModeUpdate::zone(
|
ModeUpdate::zone(
|
||||||
model_id,
|
model_id,
|
||||||
gameplay_modes::Zone::Anticheat,
|
Zone::Anticheat,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
"Platform"=>{
|
"Platform"=>{
|
||||||
modes_builder.push_mode_update(
|
modes_builder.push_mode_update(
|
||||||
gameplay_modes::ModeId::MAIN,
|
ModeId::MAIN,
|
||||||
gameplay_modes::ModeUpdate::element(
|
ModeUpdate::element(
|
||||||
model_id,
|
model_id,
|
||||||
gameplay_modes::StageElement::new(gameplay_modes::StageId::FIRST,false,gameplay_modes::StageElementBehaviour::Platform,None),//roblox does not know which stage the platform belongs to
|
StageElement::new(StageId::FIRST,false,StageElementBehaviour::Platform,None),//roblox does not know which stage the platform belongs to
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@@ -208,8 +126,8 @@ fn get_attributes(name:&str,can_collide:bool,velocity:Planar64Vec3,model_id:mode
|
|||||||
force_can_collide=false;
|
force_can_collide=false;
|
||||||
force_intersecting=true;
|
force_intersecting=true;
|
||||||
modes_builder.insert_mode(
|
modes_builder.insert_mode(
|
||||||
gameplay_modes::ModeId::new(captures[2].parse::<u32>().unwrap()),
|
ModeId::new(captures[2].parse::<u32>().unwrap()),
|
||||||
gameplay_modes::Mode::empty(
|
Mode::empty(
|
||||||
gameplay_style::StyleModifiers::roblox_bhop(),
|
gameplay_style::StyleModifiers::roblox_bhop(),
|
||||||
model_id
|
model_id
|
||||||
)
|
)
|
||||||
@@ -226,8 +144,8 @@ fn get_attributes(name:&str,can_collide:bool,velocity:Planar64Vec3,model_id:mode
|
|||||||
}else if let Some(captures)=lazy_regex::regex!(r"^(Force)?(Spawn|SpawnAt|Trigger|Teleport|Platform)(\d+)$")
|
}else if let Some(captures)=lazy_regex::regex!(r"^(Force)?(Spawn|SpawnAt|Trigger|Teleport|Platform)(\d+)$")
|
||||||
.captures(other){
|
.captures(other){
|
||||||
force_intersecting=true;
|
force_intersecting=true;
|
||||||
let stage_id=gameplay_modes::StageId::new(captures[3].parse::<u32>().unwrap());
|
let stage_id=StageId::new(captures[3].parse::<u32>().unwrap());
|
||||||
let stage_element=gameplay_modes::StageElement::new(
|
let stage_element=StageElement::new(
|
||||||
//stage_id:
|
//stage_id:
|
||||||
stage_id,
|
stage_id,
|
||||||
//force:
|
//force:
|
||||||
@@ -239,26 +157,26 @@ fn get_attributes(name:&str,can_collide:bool,velocity:Planar64Vec3,model_id:mode
|
|||||||
match &captures[2]{
|
match &captures[2]{
|
||||||
"Spawn"=>{
|
"Spawn"=>{
|
||||||
modes_builder.insert_stage(
|
modes_builder.insert_stage(
|
||||||
gameplay_modes::ModeId::MAIN,
|
ModeId::MAIN,
|
||||||
stage_id,
|
stage_id,
|
||||||
gameplay_modes::Stage::empty(model_id),
|
Stage::empty(model_id),
|
||||||
);
|
);
|
||||||
//TODO: let denormalize handle this
|
//TODO: let denormalize handle this
|
||||||
gameplay_modes::StageElementBehaviour::SpawnAt
|
StageElementBehaviour::SpawnAt
|
||||||
},
|
},
|
||||||
"SpawnAt"=>gameplay_modes::StageElementBehaviour::SpawnAt,
|
"SpawnAt"=>StageElementBehaviour::SpawnAt,
|
||||||
//cancollide false so you don't hit the side
|
//cancollide false so you don't hit the side
|
||||||
//NOT a decoration
|
//NOT a decoration
|
||||||
"Trigger"=>{force_can_collide=false;gameplay_modes::StageElementBehaviour::Trigger},
|
"Trigger"=>{force_can_collide=false;StageElementBehaviour::Trigger},
|
||||||
"Teleport"=>{force_can_collide=false;gameplay_modes::StageElementBehaviour::Teleport},
|
"Teleport"=>{force_can_collide=false;StageElementBehaviour::Teleport},
|
||||||
"Platform"=>gameplay_modes::StageElementBehaviour::Platform,
|
"Platform"=>StageElementBehaviour::Platform,
|
||||||
_=>panic!("regex1[2] messed up bad"),
|
_=>panic!("regex1[2] messed up bad"),
|
||||||
},
|
},
|
||||||
None
|
None
|
||||||
);
|
);
|
||||||
modes_builder.push_mode_update(
|
modes_builder.push_mode_update(
|
||||||
gameplay_modes::ModeId::MAIN,
|
ModeId::MAIN,
|
||||||
gameplay_modes::ModeUpdate::element(
|
ModeUpdate::element(
|
||||||
model_id,
|
model_id,
|
||||||
stage_element,
|
stage_element,
|
||||||
),
|
),
|
||||||
@@ -267,14 +185,14 @@ fn get_attributes(name:&str,can_collide:bool,velocity:Planar64Vec3,model_id:mode
|
|||||||
.captures(other){
|
.captures(other){
|
||||||
match &captures[1]{
|
match &captures[1]{
|
||||||
"Jump"=>modes_builder.push_mode_update(
|
"Jump"=>modes_builder.push_mode_update(
|
||||||
gameplay_modes::ModeId::MAIN,
|
ModeId::MAIN,
|
||||||
gameplay_modes::ModeUpdate::element(
|
ModeUpdate::element(
|
||||||
model_id,
|
model_id,
|
||||||
//jump_limit:
|
//jump_limit:
|
||||||
gameplay_modes::StageElement::new(
|
StageElement::new(
|
||||||
gameplay_modes::StageId::FIRST,
|
StageId::FIRST,
|
||||||
false,
|
false,
|
||||||
gameplay_modes::StageElementBehaviour::Check,
|
StageElementBehaviour::Check,
|
||||||
Some(captures[2].parse::<u8>().unwrap())
|
Some(captures[2].parse::<u8>().unwrap())
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
@@ -291,13 +209,13 @@ fn get_attributes(name:&str,can_collide:bool,velocity:Planar64Vec3,model_id:mode
|
|||||||
force_can_collide=false;
|
force_can_collide=false;
|
||||||
force_intersecting=true;
|
force_intersecting=true;
|
||||||
modes_builder.push_mode_update(
|
modes_builder.push_mode_update(
|
||||||
gameplay_modes::ModeId::new(captures[2].parse::<u32>().unwrap()),
|
ModeId::new(captures[2].parse::<u32>().unwrap()),
|
||||||
gameplay_modes::ModeUpdate::zone(
|
ModeUpdate::zone(
|
||||||
model_id,
|
model_id,
|
||||||
//zone:
|
//zone:
|
||||||
match &captures[1]{
|
match &captures[1]{
|
||||||
"Finish"=>gameplay_modes::Zone::Finish,
|
"Finish"=>Zone::Finish,
|
||||||
"Anticheat"=>gameplay_modes::Zone::Anticheat,
|
"Anticheat"=>Zone::Anticheat,
|
||||||
_=>panic!("regex3[1] messed up bad"),
|
_=>panic!("regex3[1] messed up bad"),
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
@@ -307,9 +225,9 @@ fn get_attributes(name:&str,can_collide:bool,velocity:Planar64Vec3,model_id:mode
|
|||||||
// .captures(other){
|
// .captures(other){
|
||||||
// match &captures[1]{
|
// match &captures[1]{
|
||||||
// "OrderedCheckpoint"=>modes_builder.push_stage_update(
|
// "OrderedCheckpoint"=>modes_builder.push_stage_update(
|
||||||
// gameplay_modes::ModeId::MAIN,
|
// ModeId::MAIN,
|
||||||
// gameplay_modes::StageId::new(0),
|
// StageId::new(0),
|
||||||
// gameplay_modes::StageUpdate::ordered_checkpoint(captures[2].parse::<u32>().unwrap()),
|
// StageUpdate::ordered_checkpoint(captures[2].parse::<u32>().unwrap()),
|
||||||
// ),
|
// ),
|
||||||
// _=>panic!("regex3[1] messed up bad"),
|
// _=>panic!("regex3[1] messed up bad"),
|
||||||
// }
|
// }
|
||||||
@@ -341,58 +259,103 @@ fn get_attributes(name:&str,can_collide:bool,velocity:Planar64Vec3,model_id:mode
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone,Copy,PartialEq)]
|
#[derive(Clone,Copy)]
|
||||||
struct RobloxTextureTransform{
|
pub struct RobloxTextureTransform{
|
||||||
offset_u:f32,
|
offset_studs_u:f32,
|
||||||
offset_v:f32,
|
offset_studs_v:f32,
|
||||||
scale_u:f32,
|
studs_per_tile_u:f32,
|
||||||
scale_v:f32,
|
studs_per_tile_v:f32,
|
||||||
|
size_u:f32,
|
||||||
|
size_v:f32,
|
||||||
}
|
}
|
||||||
impl std::cmp::Eq for RobloxTextureTransform{}//????
|
#[derive(Clone,Copy,Hash,Eq,PartialEq)]
|
||||||
impl std::default::Default for RobloxTextureTransform{
|
pub struct RobloxTextureTransformBits{
|
||||||
fn default()->Self{
|
offset_studs_u:u32,
|
||||||
Self{offset_u:0.0,offset_v:0.0,scale_u:1.0,scale_v:1.0}
|
offset_studs_v:u32,
|
||||||
}
|
studs_per_tile_u:u32,
|
||||||
|
studs_per_tile_v:u32,
|
||||||
|
size_u:u32,
|
||||||
|
size_v:u32,
|
||||||
}
|
}
|
||||||
impl std::hash::Hash for RobloxTextureTransform{
|
impl RobloxTextureTransform{
|
||||||
fn hash<H:std::hash::Hasher>(&self,state:&mut H) {
|
fn identity()->Self{
|
||||||
self.offset_u.to_ne_bytes().hash(state);
|
Self{
|
||||||
self.offset_v.to_ne_bytes().hash(state);
|
offset_studs_u:0.0,
|
||||||
self.scale_u.to_ne_bytes().hash(state);
|
offset_studs_v:0.0,
|
||||||
self.scale_v.to_ne_bytes().hash(state);
|
studs_per_tile_u:1.0,
|
||||||
|
studs_per_tile_v:1.0,
|
||||||
|
size_u:1.0,
|
||||||
|
size_v:1.0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn to_bits(self)->RobloxTextureTransformBits{
|
||||||
|
RobloxTextureTransformBits{
|
||||||
|
offset_studs_u:self.offset_studs_u.to_bits(),
|
||||||
|
offset_studs_v:self.offset_studs_v.to_bits(),
|
||||||
|
studs_per_tile_u:self.studs_per_tile_u.to_bits(),
|
||||||
|
studs_per_tile_v:self.studs_per_tile_v.to_bits(),
|
||||||
|
size_u:self.size_u.to_bits(),
|
||||||
|
size_v:self.size_v.to_bits(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn affine(&self)->glam::Affine2{
|
||||||
|
glam::Affine2::from_translation(
|
||||||
|
glam::vec2(self.offset_studs_u/self.studs_per_tile_u,self.offset_studs_v/self.studs_per_tile_v)
|
||||||
|
)
|
||||||
|
*glam::Affine2::from_scale(
|
||||||
|
glam::vec2(self.size_u/self.studs_per_tile_u,self.size_v/self.studs_per_tile_v)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
pub fn set_size(&mut self,size_u:f32,size_v:f32){
|
||||||
|
self.size_u=size_u;
|
||||||
|
self.size_v=size_v;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#[derive(Clone,PartialEq)]
|
impl core::hash::Hash for RobloxTextureTransform{
|
||||||
struct RobloxFaceTextureDescription{
|
fn hash<H:core::hash::Hasher>(&self,state:&mut H){
|
||||||
render:RenderConfigId,
|
self.to_bits().hash(state);
|
||||||
color:glam::Vec4,
|
}
|
||||||
transform:RobloxTextureTransform,
|
|
||||||
}
|
}
|
||||||
impl std::cmp::Eq for RobloxFaceTextureDescription{}//????
|
#[derive(Clone,Copy,Hash,Eq,PartialEq)]
|
||||||
impl std::hash::Hash for RobloxFaceTextureDescription{
|
pub struct RobloxFaceTextureDescriptionBits{
|
||||||
fn hash<H:std::hash::Hasher>(&self,state:&mut H){
|
render:RenderConfigId,
|
||||||
self.render.hash(state);
|
color:[u32;4],
|
||||||
self.transform.hash(state);
|
transform:RobloxTextureTransformBits,
|
||||||
for &el in self.color.as_ref().iter(){
|
}
|
||||||
el.to_ne_bytes().hash(state);
|
#[derive(Clone,Copy)]
|
||||||
}
|
pub struct RobloxFaceTextureDescription{
|
||||||
}
|
pub render:RenderConfigId,
|
||||||
|
pub color:glam::Vec4,
|
||||||
|
pub transform:RobloxTextureTransform,
|
||||||
|
}
|
||||||
|
impl core::cmp::PartialEq for RobloxFaceTextureDescription{
|
||||||
|
fn eq(&self,other:&Self)->bool{
|
||||||
|
self.to_bits().eq(&other.to_bits())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl core::cmp::Eq for RobloxFaceTextureDescription{}
|
||||||
|
impl core::hash::Hash for RobloxFaceTextureDescription{
|
||||||
|
fn hash<H:core::hash::Hasher>(&self,state:&mut H){
|
||||||
|
self.to_bits().hash(state);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
impl RobloxFaceTextureDescription{
|
impl RobloxFaceTextureDescription{
|
||||||
fn to_face_description(&self)->primitives::FaceDescription{
|
pub fn to_bits(self)->RobloxFaceTextureDescriptionBits{
|
||||||
|
RobloxFaceTextureDescriptionBits{
|
||||||
|
render:self.render,
|
||||||
|
color:self.color.to_array().map(f32::to_bits),
|
||||||
|
transform:self.transform.to_bits(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn to_face_description(&self)->primitives::FaceDescription{
|
||||||
primitives::FaceDescription{
|
primitives::FaceDescription{
|
||||||
render:self.render,
|
render:self.render,
|
||||||
transform:glam::Affine2::from_translation(
|
transform:self.transform.affine(),
|
||||||
glam::vec2(self.transform.offset_u,self.transform.offset_v)
|
|
||||||
)
|
|
||||||
*glam::Affine2::from_scale(
|
|
||||||
glam::vec2(self.transform.scale_u,self.transform.scale_v)
|
|
||||||
),
|
|
||||||
color:self.color,
|
color:self.color,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
type RobloxPartDescription=[Option<RobloxFaceTextureDescription>;6];
|
pub type RobloxPartDescription=[Option<RobloxFaceTextureDescription>;6];
|
||||||
type RobloxWedgeDescription=[Option<RobloxFaceTextureDescription>;5];
|
type RobloxWedgeDescription=[Option<RobloxFaceTextureDescription>;5];
|
||||||
type RobloxCornerWedgeDescription=[Option<RobloxFaceTextureDescription>;5];
|
type RobloxCornerWedgeDescription=[Option<RobloxFaceTextureDescription>;5];
|
||||||
#[derive(Clone,Eq,Hash,PartialEq)]
|
#[derive(Clone,Eq,Hash,PartialEq)]
|
||||||
@@ -403,52 +366,134 @@ enum RobloxBasePartDescription{
|
|||||||
Wedge(RobloxWedgeDescription),
|
Wedge(RobloxWedgeDescription),
|
||||||
CornerWedge(RobloxCornerWedgeDescription),
|
CornerWedge(RobloxCornerWedgeDescription),
|
||||||
}
|
}
|
||||||
|
fn get_texture_description<'a>(
|
||||||
|
temp_objects:&mut Vec<rbx_dom_weak::types::Ref>,
|
||||||
|
render_config_deferred_loader:&mut RenderConfigDeferredLoader<&'a str>,
|
||||||
|
dom:&'a rbx_dom_weak::WeakDom,
|
||||||
|
object:&rbx_dom_weak::Instance,
|
||||||
|
size:&rbx_dom_weak::types::Vector3,
|
||||||
|
)->RobloxPartDescription{
|
||||||
|
//use the biggest one and cut it down later...
|
||||||
|
let mut part_texture_description:RobloxPartDescription=[None,None,None,None,None,None];
|
||||||
|
temp_objects.clear();
|
||||||
|
recursive_collect_superclass(temp_objects,&dom,object,"Decal");
|
||||||
|
for &mut decal_ref in temp_objects{
|
||||||
|
if let Some(decal)=dom.get_by_ref(decal_ref){
|
||||||
|
if let (
|
||||||
|
Some(rbx_dom_weak::types::Variant::Content(content)),
|
||||||
|
Some(rbx_dom_weak::types::Variant::Enum(normalid)),
|
||||||
|
Some(rbx_dom_weak::types::Variant::Color3(decal_color3)),
|
||||||
|
Some(rbx_dom_weak::types::Variant::Float32(decal_transparency)),
|
||||||
|
) = (
|
||||||
|
decal.properties.get("Texture"),
|
||||||
|
decal.properties.get("Face"),
|
||||||
|
decal.properties.get("Color3"),
|
||||||
|
decal.properties.get("Transparency"),
|
||||||
|
) {
|
||||||
|
let render_id=render_config_deferred_loader.acquire_render_config_id(Some(content.as_ref()));
|
||||||
|
let normal_id=normalid.to_u32();
|
||||||
|
if normal_id<6{
|
||||||
|
let (roblox_texture_color,roblox_texture_transform)=if decal.class=="Texture"{
|
||||||
|
//generate tranform
|
||||||
|
if let (
|
||||||
|
Some(&rbx_dom_weak::types::Variant::Float32(offset_studs_u)),
|
||||||
|
Some(&rbx_dom_weak::types::Variant::Float32(offset_studs_v)),
|
||||||
|
Some(&rbx_dom_weak::types::Variant::Float32(studs_per_tile_u)),
|
||||||
|
Some(&rbx_dom_weak::types::Variant::Float32(studs_per_tile_v)),
|
||||||
|
) = (
|
||||||
|
decal.properties.get("OffsetStudsU"),
|
||||||
|
decal.properties.get("OffsetStudsV"),
|
||||||
|
decal.properties.get("StudsPerTileU"),
|
||||||
|
decal.properties.get("StudsPerTileV"),
|
||||||
|
)
|
||||||
|
{
|
||||||
|
let (size_u,size_v)=match normal_id{
|
||||||
|
0=>(size.z,size.y),//right
|
||||||
|
1=>(size.x,size.z),//top
|
||||||
|
2=>(size.x,size.y),//back
|
||||||
|
3=>(size.z,size.y),//left
|
||||||
|
4=>(size.x,size.z),//bottom
|
||||||
|
5=>(size.x,size.y),//front
|
||||||
|
_=>unreachable!(),
|
||||||
|
};
|
||||||
|
(
|
||||||
|
glam::vec4(decal_color3.r,decal_color3.g,decal_color3.b,1.0-*decal_transparency),
|
||||||
|
RobloxTextureTransform{
|
||||||
|
offset_studs_u,
|
||||||
|
offset_studs_v,
|
||||||
|
studs_per_tile_u,
|
||||||
|
studs_per_tile_v,
|
||||||
|
size_u,
|
||||||
|
size_v,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}else{
|
||||||
|
(glam::Vec4::ONE,RobloxTextureTransform::identity())
|
||||||
|
}
|
||||||
|
}else{
|
||||||
|
(glam::Vec4::ONE,RobloxTextureTransform::identity())
|
||||||
|
};
|
||||||
|
part_texture_description[normal_id as usize]=Some(RobloxFaceTextureDescription{
|
||||||
|
render:render_id,
|
||||||
|
color:roblox_texture_color,
|
||||||
|
transform:roblox_texture_transform,
|
||||||
|
});
|
||||||
|
}else{
|
||||||
|
println!("NormalId={} is invalid",normal_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
part_texture_description
|
||||||
|
}
|
||||||
enum Shape{
|
enum Shape{
|
||||||
Primitive(primitives::Primitives),
|
Primitive(primitives::Primitives),
|
||||||
MeshPart,
|
MeshPart,
|
||||||
|
PhysicsData,
|
||||||
}
|
}
|
||||||
enum MeshAvailability{
|
enum MeshAvailability{
|
||||||
Immediate,
|
Immediate,
|
||||||
Deferred(RenderConfigId),
|
DeferredMesh(RenderConfigId),
|
||||||
|
DeferredUnion(RobloxPartDescription),
|
||||||
}
|
}
|
||||||
struct DeferredModelDeferredAttributes{
|
struct DeferredModelDeferredAttributes<'a>{
|
||||||
render:RenderConfigId,
|
render:RenderConfigId,
|
||||||
model:ModelDeferredAttributes,
|
model:ModelDeferredAttributes<'a>,
|
||||||
}
|
}
|
||||||
struct ModelDeferredAttributes{
|
struct ModelDeferredAttributes<'a>{
|
||||||
mesh:model::MeshId,
|
mesh:model::MeshId,
|
||||||
deferred_attributes:GetAttributesArgs,
|
deferred_attributes:GetAttributesArgs<'a>,
|
||||||
color:model::Color4,//transparency is in here
|
color:model::Color4,//transparency is in here
|
||||||
transform:Planar64Affine3,
|
transform:Planar64Affine3,
|
||||||
}
|
}
|
||||||
|
struct DeferredUnionDeferredAttributes<'a>{
|
||||||
|
render:RobloxPartDescription,
|
||||||
|
model:ModelDeferredAttributes<'a>,
|
||||||
|
}
|
||||||
struct ModelOwnedAttributes{
|
struct ModelOwnedAttributes{
|
||||||
mesh:model::MeshId,
|
mesh:model::MeshId,
|
||||||
attributes:attr::CollisionAttributes,
|
attributes:attr::CollisionAttributes,
|
||||||
color:model::Color4,//transparency is in here
|
color:model::Color4,//transparency is in here
|
||||||
transform:Planar64Affine3,
|
transform:Planar64Affine3,
|
||||||
}
|
}
|
||||||
struct GetAttributesArgs{
|
struct GetAttributesArgs<'a>{
|
||||||
name:Box<str>,
|
name:&'a str,
|
||||||
can_collide:bool,
|
can_collide:bool,
|
||||||
velocity:Planar64Vec3,
|
velocity:Planar64Vec3,
|
||||||
}
|
}
|
||||||
pub fn convert<AcquireRenderConfigId,AcquireMeshId>(
|
pub fn convert<'a>(
|
||||||
dom:&rbx_dom_weak::WeakDom,
|
dom:&'a rbx_dom_weak::WeakDom,
|
||||||
mut acquire_render_config_id:AcquireRenderConfigId,
|
render_config_deferred_loader:&mut RenderConfigDeferredLoader<&'a str>,
|
||||||
mut acquire_mesh_id:AcquireMeshId,
|
mesh_deferred_loader:&mut MeshDeferredLoader<MeshIndex<'a>>,
|
||||||
)->PartialMap1
|
)->PartialMap1<'a>{
|
||||||
where
|
|
||||||
AcquireRenderConfigId:FnMut(Option<&str>)->model::RenderConfigId,
|
|
||||||
AcquireMeshId:FnMut(&str)->model::MeshId,
|
|
||||||
{
|
|
||||||
|
|
||||||
let mut deferred_models_deferred_attributes=Vec::new();
|
let mut deferred_models_deferred_attributes=Vec::new();
|
||||||
|
let mut deferred_unions_deferred_attributes=Vec::new();
|
||||||
let mut primitive_models_deferred_attributes=Vec::new();
|
let mut primitive_models_deferred_attributes=Vec::new();
|
||||||
let mut primitive_meshes=Vec::new();
|
let mut primitive_meshes=Vec::new();
|
||||||
let mut mesh_id_from_description=HashMap::new();
|
let mut mesh_id_from_description=HashMap::new();
|
||||||
|
|
||||||
//just going to leave it like this for now instead of reworking the data structures for this whole thing
|
//just going to leave it like this for now instead of reworking the data structures for this whole thing
|
||||||
let textureless_render_group=acquire_render_config_id(None);
|
let textureless_render_group=render_config_deferred_loader.acquire_render_config_id(None);
|
||||||
|
|
||||||
let mut object_refs=Vec::new();
|
let mut object_refs=Vec::new();
|
||||||
let mut temp_objects=Vec::new();
|
let mut temp_objects=Vec::new();
|
||||||
@@ -485,9 +530,6 @@ where
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
//at this point a new model is going to be generated for sure.
|
|
||||||
let model_id=model::ModelId::new(primitive_models_deferred_attributes.len() as u32);
|
|
||||||
|
|
||||||
//TODO: also detect "CylinderMesh" etc here
|
//TODO: also detect "CylinderMesh" etc here
|
||||||
let shape=match object.class.as_str(){
|
let shape=match object.class.as_str(){
|
||||||
"Part"=>if let Some(rbx_dom_weak::types::Variant::Enum(shape))=object.properties.get("Shape"){
|
"Part"=>if let Some(rbx_dom_weak::types::Variant::Enum(shape))=object.properties.get("Shape"){
|
||||||
@@ -506,6 +548,7 @@ where
|
|||||||
"WedgePart"=>Shape::Primitive(primitives::Primitives::Wedge),
|
"WedgePart"=>Shape::Primitive(primitives::Primitives::Wedge),
|
||||||
"CornerWedgePart"=>Shape::Primitive(primitives::Primitives::CornerWedge),
|
"CornerWedgePart"=>Shape::Primitive(primitives::Primitives::CornerWedge),
|
||||||
"MeshPart"=>Shape::MeshPart,
|
"MeshPart"=>Shape::MeshPart,
|
||||||
|
"UnionOperation"=>Shape::PhysicsData,
|
||||||
_=>{
|
_=>{
|
||||||
println!("Unsupported BasePart ClassName={}; defaulting to cube",object.class);
|
println!("Unsupported BasePart ClassName={}; defaulting to cube",object.class);
|
||||||
Shape::Primitive(primitives::Primitives::Cube)
|
Shape::Primitive(primitives::Primitives::Cube)
|
||||||
@@ -514,74 +557,8 @@ where
|
|||||||
|
|
||||||
let (availability,mesh_id)=match shape{
|
let (availability,mesh_id)=match shape{
|
||||||
Shape::Primitive(primitive_shape)=>{
|
Shape::Primitive(primitive_shape)=>{
|
||||||
//TODO: TAB TAB
|
//TODO: TAB TAB
|
||||||
//use the biggest one and cut it down later...
|
let part_texture_description=get_texture_description(&mut temp_objects,render_config_deferred_loader,dom,object,size);
|
||||||
let mut part_texture_description:RobloxPartDescription=[None,None,None,None,None,None];
|
|
||||||
temp_objects.clear();
|
|
||||||
recursive_collect_superclass(&mut temp_objects, &dom, object,"Decal");
|
|
||||||
for &decal_ref in &temp_objects{
|
|
||||||
if let Some(decal)=dom.get_by_ref(decal_ref){
|
|
||||||
if let (
|
|
||||||
Some(rbx_dom_weak::types::Variant::Content(content)),
|
|
||||||
Some(rbx_dom_weak::types::Variant::Enum(normalid)),
|
|
||||||
Some(rbx_dom_weak::types::Variant::Color3(decal_color3)),
|
|
||||||
Some(rbx_dom_weak::types::Variant::Float32(decal_transparency)),
|
|
||||||
) = (
|
|
||||||
decal.properties.get("Texture"),
|
|
||||||
decal.properties.get("Face"),
|
|
||||||
decal.properties.get("Color3"),
|
|
||||||
decal.properties.get("Transparency"),
|
|
||||||
) {
|
|
||||||
let render_id=acquire_render_config_id(Some(content.as_ref()));
|
|
||||||
let normal_id=normalid.to_u32();
|
|
||||||
if normal_id<6{
|
|
||||||
let (roblox_texture_color,roblox_texture_transform)=if decal.class=="Texture"{
|
|
||||||
//generate tranform
|
|
||||||
if let (
|
|
||||||
Some(rbx_dom_weak::types::Variant::Float32(ox)),
|
|
||||||
Some(rbx_dom_weak::types::Variant::Float32(oy)),
|
|
||||||
Some(rbx_dom_weak::types::Variant::Float32(sx)),
|
|
||||||
Some(rbx_dom_weak::types::Variant::Float32(sy)),
|
|
||||||
) = (
|
|
||||||
decal.properties.get("OffsetStudsU"),
|
|
||||||
decal.properties.get("OffsetStudsV"),
|
|
||||||
decal.properties.get("StudsPerTileU"),
|
|
||||||
decal.properties.get("StudsPerTileV"),
|
|
||||||
)
|
|
||||||
{
|
|
||||||
let (size_u,size_v)=match normal_id{
|
|
||||||
0=>(size.z,size.y),//right
|
|
||||||
1=>(size.x,size.z),//top
|
|
||||||
2=>(size.x,size.y),//back
|
|
||||||
3=>(size.z,size.y),//left
|
|
||||||
4=>(size.x,size.z),//bottom
|
|
||||||
5=>(size.x,size.y),//front
|
|
||||||
_=>unreachable!(),
|
|
||||||
};
|
|
||||||
(
|
|
||||||
glam::vec4(decal_color3.r,decal_color3.g,decal_color3.b,1.0-*decal_transparency),
|
|
||||||
RobloxTextureTransform{
|
|
||||||
offset_u:*ox/(*sx),offset_v:*oy/(*sy),
|
|
||||||
scale_u:size_u/(*sx),scale_v:size_v/(*sy),
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}else{
|
|
||||||
(glam::Vec4::ONE,RobloxTextureTransform::default())
|
|
||||||
}
|
|
||||||
}else{
|
|
||||||
(glam::Vec4::ONE,RobloxTextureTransform::default())
|
|
||||||
};
|
|
||||||
part_texture_description[normal_id as usize]=Some(RobloxFaceTextureDescription{
|
|
||||||
render:render_id,
|
|
||||||
color:roblox_texture_color,
|
|
||||||
transform:roblox_texture_transform,
|
|
||||||
});
|
|
||||||
}else{
|
|
||||||
println!("NormalId={} unsupported for shape={:?}",normal_id,primitive_shape);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
//obscure rust syntax "slice pattern"
|
//obscure rust syntax "slice pattern"
|
||||||
let [
|
let [
|
||||||
f0,//Cube::Right
|
f0,//Cube::Right
|
||||||
@@ -598,7 +575,7 @@ where
|
|||||||
//use front face texture first and use top face texture as a fallback
|
//use front face texture first and use top face texture as a fallback
|
||||||
primitives::Primitives::Wedge=>RobloxBasePartDescription::Wedge([
|
primitives::Primitives::Wedge=>RobloxBasePartDescription::Wedge([
|
||||||
f0,//Cube::Right->Wedge::Right
|
f0,//Cube::Right->Wedge::Right
|
||||||
if f5.is_some(){f5}else{f1},//Cube::Front|Cube::Top->Wedge::TopFront
|
f5.or(f1),//Cube::Front|Cube::Top->Wedge::TopFront
|
||||||
f2,//Cube::Back->Wedge::Back
|
f2,//Cube::Back->Wedge::Back
|
||||||
f3,//Cube::Left->Wedge::Left
|
f3,//Cube::Left->Wedge::Left
|
||||||
f4,//Cube::Bottom->Wedge::Bottom
|
f4,//Cube::Bottom->Wedge::Bottom
|
||||||
@@ -606,8 +583,8 @@ where
|
|||||||
//TODO: fix Left+Back texture coordinates to match roblox when not overwridden by Top
|
//TODO: fix Left+Back texture coordinates to match roblox when not overwridden by Top
|
||||||
primitives::Primitives::CornerWedge=>RobloxBasePartDescription::CornerWedge([
|
primitives::Primitives::CornerWedge=>RobloxBasePartDescription::CornerWedge([
|
||||||
f0,//Cube::Right->CornerWedge::Right
|
f0,//Cube::Right->CornerWedge::Right
|
||||||
if f2.is_some(){f2}else{f1.clone()},//Cube::Back|Cube::Top->CornerWedge::TopBack
|
f2.or(f1.clone()),//Cube::Back|Cube::Top->CornerWedge::TopBack
|
||||||
if f3.is_some(){f3}else{f1},//Cube::Left|Cube::Top->CornerWedge::TopLeft
|
f3.or(f1),//Cube::Left|Cube::Top->CornerWedge::TopLeft
|
||||||
f4,//Cube::Bottom->CornerWedge::Bottom
|
f4,//Cube::Bottom->CornerWedge::Bottom
|
||||||
f5,//Cube::Front->CornerWedge::Front
|
f5,//Cube::Front->CornerWedge::Front
|
||||||
]),
|
]),
|
||||||
@@ -694,29 +671,51 @@ where
|
|||||||
object.properties.get("TextureID"),
|
object.properties.get("TextureID"),
|
||||||
){
|
){
|
||||||
(
|
(
|
||||||
MeshAvailability::Deferred(acquire_render_config_id(Some(texture_asset_id.as_ref()))),
|
MeshAvailability::DeferredMesh(render_config_deferred_loader.acquire_render_config_id(Some(texture_asset_id.as_ref()))),
|
||||||
acquire_mesh_id(mesh_asset_id.as_ref()),
|
mesh_deferred_loader.acquire_mesh_id(MeshIndex::file_mesh(mesh_asset_id.as_ref())),
|
||||||
)
|
)
|
||||||
}else{
|
}else{
|
||||||
panic!("Mesh has no Mesh or Texture");
|
panic!("Mesh has no Mesh or Texture");
|
||||||
},
|
},
|
||||||
|
Shape::PhysicsData=>{
|
||||||
|
let mut content="";
|
||||||
|
let mut mesh_data:&[u8]=&[];
|
||||||
|
let mut physics_data:&[u8]=&[];
|
||||||
|
if let Some(rbx_dom_weak::types::Variant::Content(asset_id))=object.properties.get("AssetId"){
|
||||||
|
content=asset_id.as_ref();
|
||||||
|
}
|
||||||
|
if let Some(rbx_dom_weak::types::Variant::BinaryString(data))=object.properties.get("MeshData"){
|
||||||
|
mesh_data=data.as_ref();
|
||||||
|
}
|
||||||
|
if let Some(rbx_dom_weak::types::Variant::BinaryString(data))=object.properties.get("PhysicsData"){
|
||||||
|
physics_data=data.as_ref();
|
||||||
|
}
|
||||||
|
let part_texture_description=get_texture_description(&mut temp_objects,render_config_deferred_loader,dom,object,size);
|
||||||
|
let mesh_index=MeshIndex::union(content,mesh_data,physics_data,size,part_texture_description.clone());
|
||||||
|
let mesh_id=mesh_deferred_loader.acquire_mesh_id(mesh_index);
|
||||||
|
(MeshAvailability::DeferredUnion(part_texture_description),mesh_id)
|
||||||
|
},
|
||||||
};
|
};
|
||||||
let model_deferred_attributes=ModelDeferredAttributes{
|
let model_deferred_attributes=ModelDeferredAttributes{
|
||||||
mesh:mesh_id,
|
mesh:mesh_id,
|
||||||
transform:model_transform,
|
transform:model_transform,
|
||||||
color:glam::vec4(color3.r as f32/255f32, color3.g as f32/255f32, color3.b as f32/255f32, 1.0-*transparency),
|
color:glam::vec4(color3.r as f32/255f32, color3.g as f32/255f32, color3.b as f32/255f32, 1.0-*transparency),
|
||||||
deferred_attributes:GetAttributesArgs{
|
deferred_attributes:GetAttributesArgs{
|
||||||
name:object.name.as_str().into(),
|
name:object.name.as_str(),
|
||||||
can_collide:*can_collide,
|
can_collide:*can_collide,
|
||||||
velocity:vec3::try_from_f32_array([velocity.x,velocity.y,velocity.z]).unwrap(),
|
velocity:vec3::try_from_f32_array([velocity.x,velocity.y,velocity.z]).unwrap(),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
match availability{
|
match availability{
|
||||||
MeshAvailability::Immediate=>primitive_models_deferred_attributes.push(model_deferred_attributes),
|
MeshAvailability::Immediate=>primitive_models_deferred_attributes.push(model_deferred_attributes),
|
||||||
MeshAvailability::Deferred(render)=>deferred_models_deferred_attributes.push(DeferredModelDeferredAttributes{
|
MeshAvailability::DeferredMesh(render)=>deferred_models_deferred_attributes.push(DeferredModelDeferredAttributes{
|
||||||
render,
|
render,
|
||||||
model:model_deferred_attributes
|
model:model_deferred_attributes
|
||||||
}),
|
}),
|
||||||
|
MeshAvailability::DeferredUnion(part_texture_description)=>deferred_unions_deferred_attributes.push(DeferredUnionDeferredAttributes{
|
||||||
|
render:part_texture_description,
|
||||||
|
model:model_deferred_attributes,
|
||||||
|
}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -725,21 +724,71 @@ where
|
|||||||
primitive_meshes,
|
primitive_meshes,
|
||||||
primitive_models_deferred_attributes,
|
primitive_models_deferred_attributes,
|
||||||
deferred_models_deferred_attributes,
|
deferred_models_deferred_attributes,
|
||||||
|
deferred_unions_deferred_attributes,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
struct MeshWithAabb{
|
struct MeshWithAabb{
|
||||||
mesh:model::Mesh,
|
mesh:model::Mesh,
|
||||||
aabb:strafesnet_common::aabb::Aabb,
|
aabb:Aabb,
|
||||||
}
|
}
|
||||||
pub struct PartialMap1{
|
fn acquire_mesh_id_from_render_config_id<'a>(
|
||||||
|
primitive_meshes:&mut Vec<model::Mesh>,
|
||||||
|
mesh_id_from_render_config_id:&mut HashMap<model::MeshId,HashMap<RenderConfigId,model::MeshId>>,
|
||||||
|
loaded_meshes:&'a HashMap<model::MeshId,MeshWithAabb>,
|
||||||
|
old_mesh_id:model::MeshId,
|
||||||
|
render:RenderConfigId,
|
||||||
|
)->Option<(model::MeshId,&'a Aabb)>{
|
||||||
|
//ignore meshes that fail to load completely for now
|
||||||
|
loaded_meshes.get(&old_mesh_id).map(|mesh_with_aabb|(
|
||||||
|
*mesh_id_from_render_config_id.entry(old_mesh_id).or_insert_with(||HashMap::new())
|
||||||
|
.entry(render).or_insert_with(||{
|
||||||
|
let mesh_id=model::MeshId::new(primitive_meshes.len() as u32);
|
||||||
|
let mut mesh_clone=mesh_with_aabb.mesh.clone();
|
||||||
|
//set the render group lool
|
||||||
|
if let Some(graphics_group)=mesh_clone.graphics_groups.first_mut(){
|
||||||
|
graphics_group.render=render;
|
||||||
|
}
|
||||||
|
primitive_meshes.push(mesh_clone);
|
||||||
|
mesh_id
|
||||||
|
}),
|
||||||
|
&mesh_with_aabb.aabb,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
fn acquire_union_id_from_render_config_id<'a>(
|
||||||
|
primitive_meshes:&mut Vec<model::Mesh>,
|
||||||
|
union_id_from_render_config_id:&mut HashMap<model::MeshId,HashMap<RobloxPartDescription,model::MeshId>>,
|
||||||
|
loaded_meshes:&'a HashMap<model::MeshId,MeshWithAabb>,
|
||||||
|
old_union_id:model::MeshId,
|
||||||
|
part_texture_description:RobloxPartDescription,
|
||||||
|
)->Option<(model::MeshId,&'a Aabb)>{
|
||||||
|
//ignore uniones that fail to load completely for now
|
||||||
|
loaded_meshes.get(&old_union_id).map(|union_with_aabb|(
|
||||||
|
*union_id_from_render_config_id.entry(old_union_id).or_insert_with(||HashMap::new())
|
||||||
|
.entry(part_texture_description.clone()).or_insert_with(||{
|
||||||
|
let union_id=model::MeshId::new(primitive_meshes.len() as u32);
|
||||||
|
let mut union_clone=union_with_aabb.mesh.clone();
|
||||||
|
//set the render groups
|
||||||
|
for (graphics_group,maybe_face_texture_description) in union_clone.graphics_groups.iter_mut().zip(part_texture_description){
|
||||||
|
if let Some(face_texture_description)=maybe_face_texture_description{
|
||||||
|
graphics_group.render=face_texture_description.render;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
primitive_meshes.push(union_clone);
|
||||||
|
union_id
|
||||||
|
}),
|
||||||
|
&union_with_aabb.aabb,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
pub struct PartialMap1<'a>{
|
||||||
primitive_meshes:Vec<model::Mesh>,
|
primitive_meshes:Vec<model::Mesh>,
|
||||||
primitive_models_deferred_attributes:Vec<ModelDeferredAttributes>,
|
primitive_models_deferred_attributes:Vec<ModelDeferredAttributes<'a>>,
|
||||||
deferred_models_deferred_attributes:Vec<DeferredModelDeferredAttributes>,
|
deferred_models_deferred_attributes:Vec<DeferredModelDeferredAttributes<'a>>,
|
||||||
|
deferred_unions_deferred_attributes:Vec<DeferredUnionDeferredAttributes<'a>>,
|
||||||
}
|
}
|
||||||
impl PartialMap1{
|
impl PartialMap1<'_>{
|
||||||
pub fn add_meshpart_meshes_and_calculate_attributes(
|
pub fn add_meshpart_meshes_and_calculate_attributes(
|
||||||
mut self,
|
mut self,
|
||||||
meshpart_meshes:impl IntoIterator<Item=(model::MeshId,crate::data::RobloxMeshBytes)>,
|
meshpart_meshes:Meshes,
|
||||||
)->PartialMap2{
|
)->PartialMap2{
|
||||||
//calculate attributes
|
//calculate attributes
|
||||||
let mut modes_builder=ModesBuilder::default();
|
let mut modes_builder=ModesBuilder::default();
|
||||||
@@ -752,51 +801,32 @@ impl PartialMap1{
|
|||||||
//decode roblox meshes
|
//decode roblox meshes
|
||||||
//generate mesh_id_map based on meshes that failed to load
|
//generate mesh_id_map based on meshes that failed to load
|
||||||
let loaded_meshes:HashMap<model::MeshId,MeshWithAabb>=
|
let loaded_meshes:HashMap<model::MeshId,MeshWithAabb>=
|
||||||
meshpart_meshes.into_iter().flat_map(|(old_mesh_id,roblox_mesh_bytes)|
|
meshpart_meshes.consume().map(|(old_mesh_id,mesh)|{
|
||||||
match crate::mesh::convert(roblox_mesh_bytes){
|
let mut aabb=strafesnet_common::aabb::Aabb::default();
|
||||||
Ok(mesh)=>{
|
for &pos in &mesh.unique_pos{
|
||||||
let mut aabb=strafesnet_common::aabb::Aabb::default();
|
aabb.grow(pos);
|
||||||
for &pos in &mesh.unique_pos{
|
|
||||||
aabb.grow(pos);
|
|
||||||
}
|
|
||||||
Some((old_mesh_id,MeshWithAabb{
|
|
||||||
mesh,
|
|
||||||
aabb,
|
|
||||||
}))
|
|
||||||
},
|
|
||||||
Err(e)=>{
|
|
||||||
println!("Error converting mesh: {e:?}");
|
|
||||||
None
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
).collect();
|
(old_mesh_id,MeshWithAabb{
|
||||||
|
mesh,
|
||||||
|
aabb,
|
||||||
|
})
|
||||||
|
}).collect();
|
||||||
|
|
||||||
|
// SAFETY: I have no idea what I'm doing and this is definitely unsound in some subtle way
|
||||||
|
// I just want to chain iterators together man
|
||||||
|
let aint_no_way=core::cell::UnsafeCell::new(&mut self.primitive_meshes);
|
||||||
|
|
||||||
let mut mesh_id_from_render_config_id=HashMap::new();
|
let mut mesh_id_from_render_config_id=HashMap::new();
|
||||||
//ignore meshes that fail to load completely for now
|
let mut union_id_from_render_config_id=HashMap::new();
|
||||||
let mut acquire_mesh_id_from_render_config_id=|old_mesh_id,render|{
|
|
||||||
loaded_meshes.get(&old_mesh_id).map(|mesh_with_aabb|(
|
|
||||||
*mesh_id_from_render_config_id.entry(old_mesh_id).or_insert_with(||HashMap::new())
|
|
||||||
.entry(render).or_insert_with(||{
|
|
||||||
let mesh_id=model::MeshId::new(self.primitive_meshes.len() as u32);
|
|
||||||
let mut mesh_clone=mesh_with_aabb.mesh.clone();
|
|
||||||
//add a render group lool
|
|
||||||
mesh_clone.graphics_groups.push(model::IndexedGraphicsGroup{
|
|
||||||
render,
|
|
||||||
//the lowest lod is highest quality
|
|
||||||
groups:vec![model::PolygonGroupId::new(0)]
|
|
||||||
});
|
|
||||||
self.primitive_meshes.push(mesh_clone);
|
|
||||||
mesh_id
|
|
||||||
}),
|
|
||||||
&mesh_with_aabb.aabb,
|
|
||||||
))
|
|
||||||
};
|
|
||||||
//now that the meshes are loaded, these models can be generated
|
//now that the meshes are loaded, these models can be generated
|
||||||
let models_owned_attributes:Vec<ModelOwnedAttributes>=
|
let models_owned_attributes:Vec<ModelOwnedAttributes>=
|
||||||
self.deferred_models_deferred_attributes.into_iter().flat_map(|deferred_model_deferred_attributes|{
|
self.deferred_models_deferred_attributes.into_iter().flat_map(|deferred_model_deferred_attributes|{
|
||||||
//meshes need to be cloned from loaded_meshes with a new id when they are used with a new render_id
|
//meshes need to be cloned from loaded_meshes with a new id when they are used with a new render_id
|
||||||
//insert into primitive_meshes
|
//insert into primitive_meshes
|
||||||
let (mesh,aabb)=acquire_mesh_id_from_render_config_id(
|
let (mesh,aabb)=acquire_mesh_id_from_render_config_id(
|
||||||
|
unsafe{*aint_no_way.get()},
|
||||||
|
&mut mesh_id_from_render_config_id,
|
||||||
|
&loaded_meshes,
|
||||||
deferred_model_deferred_attributes.model.mesh,
|
deferred_model_deferred_attributes.model.mesh,
|
||||||
deferred_model_deferred_attributes.render
|
deferred_model_deferred_attributes.render
|
||||||
)?;
|
)?;
|
||||||
@@ -814,7 +844,32 @@ impl PartialMap1{
|
|||||||
deferred_model_deferred_attributes.model.transform.translation
|
deferred_model_deferred_attributes.model.transform.translation
|
||||||
),
|
),
|
||||||
})
|
})
|
||||||
}).chain(self.primitive_models_deferred_attributes.into_iter())
|
}).chain(self.deferred_unions_deferred_attributes.into_iter().flat_map(|deferred_union_deferred_attributes|{
|
||||||
|
//meshes need to be cloned from loaded_meshes with a new id when they are used with a new render_id
|
||||||
|
//insert into primitive_meshes
|
||||||
|
let (mesh,aabb)=acquire_union_id_from_render_config_id(
|
||||||
|
unsafe{*aint_no_way.get()},
|
||||||
|
&mut union_id_from_render_config_id,
|
||||||
|
&loaded_meshes,
|
||||||
|
deferred_union_deferred_attributes.model.mesh,
|
||||||
|
deferred_union_deferred_attributes.render
|
||||||
|
)?;
|
||||||
|
let size=aabb.size();
|
||||||
|
Some(ModelDeferredAttributes{
|
||||||
|
mesh,
|
||||||
|
deferred_attributes:deferred_union_deferred_attributes.model.deferred_attributes,
|
||||||
|
color:deferred_union_deferred_attributes.model.color,
|
||||||
|
transform:Planar64Affine3::new(
|
||||||
|
Planar64Mat3::from_cols([
|
||||||
|
(deferred_union_deferred_attributes.model.transform.matrix3.x_axis*2/size.x).divide().fix_1(),
|
||||||
|
(deferred_union_deferred_attributes.model.transform.matrix3.y_axis*2/size.y).divide().fix_1(),
|
||||||
|
(deferred_union_deferred_attributes.model.transform.matrix3.z_axis*2/size.z).divide().fix_1()
|
||||||
|
]),
|
||||||
|
deferred_union_deferred_attributes.model.transform.translation
|
||||||
|
),
|
||||||
|
})
|
||||||
|
}))
|
||||||
|
.chain(self.primitive_models_deferred_attributes.into_iter())
|
||||||
.enumerate().map(|(model_id,model_deferred_attributes)|{
|
.enumerate().map(|(model_id,model_deferred_attributes)|{
|
||||||
let model_id=model::ModelId::new(model_id as u32);
|
let model_id=model::ModelId::new(model_id as u32);
|
||||||
ModelOwnedAttributes{
|
ModelOwnedAttributes{
|
||||||
@@ -867,7 +922,7 @@ impl PartialMap1{
|
|||||||
PartialMap2{
|
PartialMap2{
|
||||||
meshes:self.primitive_meshes,
|
meshes:self.primitive_meshes,
|
||||||
models,
|
models,
|
||||||
modes:modes_builder.build(),
|
modes:modes_builder.build_normalized(),
|
||||||
attributes:unique_attributes,
|
attributes:unique_attributes,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -876,21 +931,27 @@ impl PartialMap1{
|
|||||||
pub struct PartialMap2{
|
pub struct PartialMap2{
|
||||||
meshes:Vec<model::Mesh>,
|
meshes:Vec<model::Mesh>,
|
||||||
models:Vec<model::Model>,
|
models:Vec<model::Model>,
|
||||||
modes:gameplay_modes::Modes,
|
modes:NormalizedModes,
|
||||||
attributes:Vec<strafesnet_common::gameplay_attributes::CollisionAttributes>,
|
attributes:Vec<strafesnet_common::gameplay_attributes::CollisionAttributes>,
|
||||||
}
|
}
|
||||||
impl PartialMap2{
|
impl PartialMap2{
|
||||||
pub fn add_render_configs_and_textures(
|
pub fn add_render_configs_and_textures(
|
||||||
self,
|
self,
|
||||||
render_configs:impl IntoIterator<Item=(model::RenderConfigId,model::RenderConfig)>,
|
render_configs:RenderConfigs,
|
||||||
textures:impl IntoIterator<Item=(model::TextureId,Vec<u8>)>,
|
|
||||||
)->map::CompleteMap{
|
)->map::CompleteMap{
|
||||||
|
let (textures,render_configs)=render_configs.consume();
|
||||||
let (textures,texture_id_map):(Vec<Vec<u8>>,HashMap<model::TextureId,model::TextureId>)
|
let (textures,texture_id_map):(Vec<Vec<u8>>,HashMap<model::TextureId,model::TextureId>)
|
||||||
=textures.into_iter().enumerate().map(|(new_texture_id,(old_texture_id,texture))|{
|
=textures.into_iter().enumerate().map(|(new_texture_id,(old_texture_id,Texture::ImageDDS(texture)))|{
|
||||||
(texture,(old_texture_id,model::TextureId::new(new_texture_id as u32)))
|
(texture,(old_texture_id,model::TextureId::new(new_texture_id as u32)))
|
||||||
}).unzip();
|
}).unzip();
|
||||||
let render_configs=render_configs.into_iter().map(|(render_config_id,mut render_config)|{
|
let render_configs=render_configs.into_iter().map(|(_render_config_id,mut render_config)|{
|
||||||
//this may generate duplicate no-texture render configs but idc
|
// This may generate duplicate no-texture render configs but idc
|
||||||
|
//
|
||||||
|
// This is because some textures may not exist, so the render config
|
||||||
|
// that it points to is unique but is texture.
|
||||||
|
//
|
||||||
|
// I don't think this needs to be fixed because missing textures
|
||||||
|
// should be a conversion error anyways.
|
||||||
render_config.texture=render_config.texture.and_then(|texture_id|
|
render_config.texture=render_config.texture.and_then(|texture_id|
|
||||||
texture_id_map.get(&texture_id).copied()
|
texture_id_map.get(&texture_id).copied()
|
||||||
);
|
);
|
||||||
|
|||||||
177
lib/rbx_loader/src/union.rs
Normal file
177
lib/rbx_loader/src/union.rs
Normal file
@@ -0,0 +1,177 @@
|
|||||||
|
use rbx_mesh::mesh_data::NormalId2 as MeshDataNormalId2;
|
||||||
|
use strafesnet_common::model::{self,IndexedVertex,PolygonGroup,PolygonGroupId,PolygonList,RenderConfigId};
|
||||||
|
use strafesnet_common::integer::vec3;
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum Error{
|
||||||
|
Block,
|
||||||
|
MissingVertexId(u32),
|
||||||
|
Planar64Vec3(strafesnet_common::integer::Planar64TryFromFloatError),
|
||||||
|
RobloxPhysicsData(rbx_mesh::physics_data::Error),
|
||||||
|
RobloxMeshData(rbx_mesh::mesh_data::Error),
|
||||||
|
}
|
||||||
|
impl std::fmt::Display for Error{
|
||||||
|
fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{
|
||||||
|
write!(f,"{self:?}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// wacky state machine to make sure all vertices in a face agree upon what NormalId to use.
|
||||||
|
// Roblox duplicates this information per vertex when it should only exist per-face.
|
||||||
|
enum MeshDataNormalStatus{
|
||||||
|
Agree(MeshDataNormalId2),
|
||||||
|
Conflicting,
|
||||||
|
}
|
||||||
|
struct MeshDataNormalChecker{
|
||||||
|
status:Option<MeshDataNormalStatus>,
|
||||||
|
}
|
||||||
|
impl MeshDataNormalChecker{
|
||||||
|
fn new()->Self{
|
||||||
|
Self{status:None}
|
||||||
|
}
|
||||||
|
fn check(&mut self,normal:MeshDataNormalId2){
|
||||||
|
self.status=match self.status.take(){
|
||||||
|
None=>Some(MeshDataNormalStatus::Agree(normal)),
|
||||||
|
Some(MeshDataNormalStatus::Agree(old_normal))=>{
|
||||||
|
if old_normal==normal{
|
||||||
|
Some(MeshDataNormalStatus::Agree(old_normal))
|
||||||
|
}else{
|
||||||
|
Some(MeshDataNormalStatus::Conflicting)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Some(MeshDataNormalStatus::Conflicting)=>Some(MeshDataNormalStatus::Conflicting),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
fn into_agreed_normal(self)->Option<MeshDataNormalId2>{
|
||||||
|
self.status.and_then(|status|match status{
|
||||||
|
MeshDataNormalStatus::Agree(normal)=>Some(normal),
|
||||||
|
MeshDataNormalStatus::Conflicting=>None,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::error::Error for Error{}
|
||||||
|
pub fn convert(
|
||||||
|
roblox_physics_data:&[u8],
|
||||||
|
roblox_mesh_data:&[u8],
|
||||||
|
size:glam::Vec3,
|
||||||
|
part_texture_description:crate::rbx::RobloxPartDescription,
|
||||||
|
)->Result<model::Mesh,Error>{
|
||||||
|
const NORMAL_FACES:usize=6;
|
||||||
|
let mut polygon_groups_normal_id=vec![Vec::new();NORMAL_FACES];
|
||||||
|
|
||||||
|
// build graphics and physics meshes
|
||||||
|
let mut mb=strafesnet_common::model::MeshBuilder::new();
|
||||||
|
// graphics
|
||||||
|
let graphics_groups=if !roblox_mesh_data.is_empty(){
|
||||||
|
// create per-face texture coordinate affine transforms
|
||||||
|
let cube_face_description=part_texture_description.map(|opt|opt.map(|mut t|{
|
||||||
|
t.transform.set_size(1.0,1.0);
|
||||||
|
t.to_face_description()
|
||||||
|
}));
|
||||||
|
|
||||||
|
let mesh_data=rbx_mesh::read_mesh_data_versioned(
|
||||||
|
std::io::Cursor::new(roblox_mesh_data)
|
||||||
|
).map_err(Error::RobloxMeshData)?;
|
||||||
|
let graphics_mesh=match mesh_data{
|
||||||
|
rbx_mesh::mesh_data::MeshData::CSGK(_)=>return Err(Error::Block),
|
||||||
|
rbx_mesh::mesh_data::MeshData::CSGMDL(rbx_mesh::mesh_data::CSGMDL::CSGMDL2(mesh_data2))=>mesh_data2.mesh,
|
||||||
|
rbx_mesh::mesh_data::MeshData::CSGMDL(rbx_mesh::mesh_data::CSGMDL::CSGMDL4(mesh_data4))=>mesh_data4.mesh,
|
||||||
|
};
|
||||||
|
for [vertex_id0,vertex_id1,vertex_id2] in graphics_mesh.faces{
|
||||||
|
let face=[
|
||||||
|
graphics_mesh.vertices.get(vertex_id0.0 as usize).ok_or(Error::MissingVertexId(vertex_id0.0))?,
|
||||||
|
graphics_mesh.vertices.get(vertex_id1.0 as usize).ok_or(Error::MissingVertexId(vertex_id1.0))?,
|
||||||
|
graphics_mesh.vertices.get(vertex_id2.0 as usize).ok_or(Error::MissingVertexId(vertex_id2.0))?,
|
||||||
|
];
|
||||||
|
let mut normal_agreement_checker=MeshDataNormalChecker::new();
|
||||||
|
let face=face.into_iter().map(|vertex|{
|
||||||
|
normal_agreement_checker.check(vertex.normal_id);
|
||||||
|
let pos=mb.acquire_pos_id(vec3::try_from_f32_array(vertex.pos)?);
|
||||||
|
let normal=mb.acquire_normal_id(vec3::try_from_f32_array(vertex.norm)?);
|
||||||
|
let tex_coord=glam::Vec2::from_array(vertex.tex);
|
||||||
|
let maybe_face_description=&cube_face_description[vertex.normal_id as usize-1];
|
||||||
|
let (tex,color)=match maybe_face_description{
|
||||||
|
Some(face_description)=>{
|
||||||
|
// transform texture coordinates and set decal color
|
||||||
|
let tex=mb.acquire_tex_id(face_description.transform.transform_point2(tex_coord));
|
||||||
|
let color=mb.acquire_color_id(face_description.color);
|
||||||
|
(tex,color)
|
||||||
|
},
|
||||||
|
None=>{
|
||||||
|
// texture coordinates don't matter and pass through mesh vertex color
|
||||||
|
let tex=mb.acquire_tex_id(tex_coord);
|
||||||
|
let color=mb.acquire_color_id(glam::Vec4::from_array(vertex.color.map(|f|f as f32/255.0f32)));
|
||||||
|
(tex,color)
|
||||||
|
},
|
||||||
|
};
|
||||||
|
Ok(mb.acquire_vertex_id(IndexedVertex{pos,tex,normal,color}))
|
||||||
|
}).collect::<Result<Vec<_>,_>>().map_err(Error::Planar64Vec3)?;
|
||||||
|
if let Some(normal_id)=normal_agreement_checker.into_agreed_normal(){
|
||||||
|
polygon_groups_normal_id[normal_id as usize-1].push(face);
|
||||||
|
}else{
|
||||||
|
panic!("Empty face!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(0..NORMAL_FACES).map(|polygon_group_id|{
|
||||||
|
model::IndexedGraphicsGroup{
|
||||||
|
render:cube_face_description[polygon_group_id].as_ref().map_or(RenderConfigId::new(0),|face_description|face_description.render),
|
||||||
|
groups:vec![PolygonGroupId::new(polygon_group_id as u32)]
|
||||||
|
}
|
||||||
|
}).collect()
|
||||||
|
}else{
|
||||||
|
Vec::new()
|
||||||
|
};
|
||||||
|
|
||||||
|
//physics
|
||||||
|
let physics_convex_meshes=if !roblox_physics_data.is_empty(){
|
||||||
|
let physics_data=rbx_mesh::read_physics_data_versioned(
|
||||||
|
std::io::Cursor::new(roblox_physics_data)
|
||||||
|
).map_err(Error::RobloxPhysicsData)?;
|
||||||
|
let physics_convex_meshes=match physics_data{
|
||||||
|
rbx_mesh::physics_data::PhysicsData::CSGK(_)
|
||||||
|
// have not seen this format in practice
|
||||||
|
|rbx_mesh::physics_data::PhysicsData::CSGPHS(rbx_mesh::physics_data::CSGPHS::Block)
|
||||||
|
=>return Err(Error::Block),
|
||||||
|
rbx_mesh::physics_data::PhysicsData::CSGPHS(rbx_mesh::physics_data::CSGPHS::Meshes(meshes))
|
||||||
|
=>meshes.meshes,
|
||||||
|
rbx_mesh::physics_data::PhysicsData::CSGPHS(rbx_mesh::physics_data::CSGPHS::PhysicsInfoMesh(pim))
|
||||||
|
=>vec![pim.mesh],
|
||||||
|
};
|
||||||
|
physics_convex_meshes
|
||||||
|
}else{
|
||||||
|
Vec::new()
|
||||||
|
};
|
||||||
|
let polygon_groups:Vec<PolygonGroup>=polygon_groups_normal_id.into_iter().map(|faces|
|
||||||
|
// graphics polygon groups (to be rendered)
|
||||||
|
Ok(PolygonGroup::PolygonList(PolygonList::new(faces)))
|
||||||
|
).chain(physics_convex_meshes.into_iter().map(|mesh|{
|
||||||
|
// this can be factored out of the loop but I am lazy
|
||||||
|
let color=mb.acquire_color_id(glam::Vec4::ONE);
|
||||||
|
let tex=mb.acquire_tex_id(glam::Vec2::ZERO);
|
||||||
|
// physics polygon groups (to do physics)
|
||||||
|
Ok(PolygonGroup::PolygonList(PolygonList::new(mesh.faces.into_iter().map(|[vertex_id0,vertex_id1,vertex_id2]|{
|
||||||
|
let face=[
|
||||||
|
mesh.vertices.get(vertex_id0.0 as usize).ok_or(Error::MissingVertexId(vertex_id0.0))?,
|
||||||
|
mesh.vertices.get(vertex_id1.0 as usize).ok_or(Error::MissingVertexId(vertex_id1.0))?,
|
||||||
|
mesh.vertices.get(vertex_id2.0 as usize).ok_or(Error::MissingVertexId(vertex_id2.0))?,
|
||||||
|
].map(|v|glam::Vec3::from_slice(v)/size);
|
||||||
|
let vertex_norm=(face[1]-face[0])
|
||||||
|
.cross(face[2]-face[0]);
|
||||||
|
let normal=mb.acquire_normal_id(vec3::try_from_f32_array(vertex_norm.to_array()).map_err(Error::Planar64Vec3)?);
|
||||||
|
face.into_iter().map(|vertex_pos|{
|
||||||
|
let pos=mb.acquire_pos_id(vec3::try_from_f32_array(vertex_pos.to_array()).map_err(Error::Planar64Vec3)?);
|
||||||
|
Ok(mb.acquire_vertex_id(IndexedVertex{pos,tex,normal,color}))
|
||||||
|
}).collect()
|
||||||
|
}).collect::<Result<_,_>>()?)))
|
||||||
|
})).collect::<Result<_,_>>()?;
|
||||||
|
let physics_groups=(NORMAL_FACES..polygon_groups.len()).map(|id|model::IndexedPhysicsGroup{
|
||||||
|
groups:vec![PolygonGroupId::new(id as u32)]
|
||||||
|
}).collect();
|
||||||
|
Ok(mb.build(
|
||||||
|
polygon_groups,
|
||||||
|
graphics_groups,
|
||||||
|
physics_groups,
|
||||||
|
))
|
||||||
|
}
|
||||||
11
lib/rbxassetid/Cargo.toml
Normal file
11
lib/rbxassetid/Cargo.toml
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
[package]
|
||||||
|
name = "rbxassetid"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2024"
|
||||||
|
repository = "https://git.itzana.me/StrafesNET/strafe-project"
|
||||||
|
license = "MIT OR Apache-2.0"
|
||||||
|
description = "Parse Roblox asset id from 'Content' urls."
|
||||||
|
authors = ["Rhys Lloyd <krakow20@gmail.com>"]
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
url = "2.5.4"
|
||||||
176
lib/rbxassetid/LICENSE-APACHE
Normal file
176
lib/rbxassetid/LICENSE-APACHE
Normal file
@@ -0,0 +1,176 @@
|
|||||||
|
Apache License
|
||||||
|
Version 2.0, January 2004
|
||||||
|
http://www.apache.org/licenses/
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||||
|
|
||||||
|
1. Definitions.
|
||||||
|
|
||||||
|
"License" shall mean the terms and conditions for use, reproduction,
|
||||||
|
and distribution as defined by Sections 1 through 9 of this document.
|
||||||
|
|
||||||
|
"Licensor" shall mean the copyright owner or entity authorized by
|
||||||
|
the copyright owner that is granting the License.
|
||||||
|
|
||||||
|
"Legal Entity" shall mean the union of the acting entity and all
|
||||||
|
other entities that control, are controlled by, or are under common
|
||||||
|
control with that entity. For the purposes of this definition,
|
||||||
|
"control" means (i) the power, direct or indirect, to cause the
|
||||||
|
direction or management of such entity, whether by contract or
|
||||||
|
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||||
|
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||||
|
|
||||||
|
"You" (or "Your") shall mean an individual or Legal Entity
|
||||||
|
exercising permissions granted by this License.
|
||||||
|
|
||||||
|
"Source" form shall mean the preferred form for making modifications,
|
||||||
|
including but not limited to software source code, documentation
|
||||||
|
source, and configuration files.
|
||||||
|
|
||||||
|
"Object" form shall mean any form resulting from mechanical
|
||||||
|
transformation or translation of a Source form, including but
|
||||||
|
not limited to compiled object code, generated documentation,
|
||||||
|
and conversions to other media types.
|
||||||
|
|
||||||
|
"Work" shall mean the work of authorship, whether in Source or
|
||||||
|
Object form, made available under the License, as indicated by a
|
||||||
|
copyright notice that is included in or attached to the work
|
||||||
|
(an example is provided in the Appendix below).
|
||||||
|
|
||||||
|
"Derivative Works" shall mean any work, whether in Source or Object
|
||||||
|
form, that is based on (or derived from) the Work and for which the
|
||||||
|
editorial revisions, annotations, elaborations, or other modifications
|
||||||
|
represent, as a whole, an original work of authorship. For the purposes
|
||||||
|
of this License, Derivative Works shall not include works that remain
|
||||||
|
separable from, or merely link (or bind by name) to the interfaces of,
|
||||||
|
the Work and Derivative Works thereof.
|
||||||
|
|
||||||
|
"Contribution" shall mean any work of authorship, including
|
||||||
|
the original version of the Work and any modifications or additions
|
||||||
|
to that Work or Derivative Works thereof, that is intentionally
|
||||||
|
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||||
|
or by an individual or Legal Entity authorized to submit on behalf of
|
||||||
|
the copyright owner. For the purposes of this definition, "submitted"
|
||||||
|
means any form of electronic, verbal, or written communication sent
|
||||||
|
to the Licensor or its representatives, including but not limited to
|
||||||
|
communication on electronic mailing lists, source code control systems,
|
||||||
|
and issue tracking systems that are managed by, or on behalf of, the
|
||||||
|
Licensor for the purpose of discussing and improving the Work, but
|
||||||
|
excluding communication that is conspicuously marked or otherwise
|
||||||
|
designated in writing by the copyright owner as "Not a Contribution."
|
||||||
|
|
||||||
|
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||||
|
on behalf of whom a Contribution has been received by Licensor and
|
||||||
|
subsequently incorporated within the Work.
|
||||||
|
|
||||||
|
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
copyright license to reproduce, prepare Derivative Works of,
|
||||||
|
publicly display, publicly perform, sublicense, and distribute the
|
||||||
|
Work and such Derivative Works in Source or Object form.
|
||||||
|
|
||||||
|
3. Grant of Patent License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
(except as stated in this section) patent license to make, have made,
|
||||||
|
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||||
|
where such license applies only to those patent claims licensable
|
||||||
|
by such Contributor that are necessarily infringed by their
|
||||||
|
Contribution(s) alone or by combination of their Contribution(s)
|
||||||
|
with the Work to which such Contribution(s) was submitted. If You
|
||||||
|
institute patent litigation against any entity (including a
|
||||||
|
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||||
|
or a Contribution incorporated within the Work constitutes direct
|
||||||
|
or contributory patent infringement, then any patent licenses
|
||||||
|
granted to You under this License for that Work shall terminate
|
||||||
|
as of the date such litigation is filed.
|
||||||
|
|
||||||
|
4. Redistribution. You may reproduce and distribute copies of the
|
||||||
|
Work or Derivative Works thereof in any medium, with or without
|
||||||
|
modifications, and in Source or Object form, provided that You
|
||||||
|
meet the following conditions:
|
||||||
|
|
||||||
|
(a) You must give any other recipients of the Work or
|
||||||
|
Derivative Works a copy of this License; and
|
||||||
|
|
||||||
|
(b) You must cause any modified files to carry prominent notices
|
||||||
|
stating that You changed the files; and
|
||||||
|
|
||||||
|
(c) You must retain, in the Source form of any Derivative Works
|
||||||
|
that You distribute, all copyright, patent, trademark, and
|
||||||
|
attribution notices from the Source form of the Work,
|
||||||
|
excluding those notices that do not pertain to any part of
|
||||||
|
the Derivative Works; and
|
||||||
|
|
||||||
|
(d) If the Work includes a "NOTICE" text file as part of its
|
||||||
|
distribution, then any Derivative Works that You distribute must
|
||||||
|
include a readable copy of the attribution notices contained
|
||||||
|
within such NOTICE file, excluding those notices that do not
|
||||||
|
pertain to any part of the Derivative Works, in at least one
|
||||||
|
of the following places: within a NOTICE text file distributed
|
||||||
|
as part of the Derivative Works; within the Source form or
|
||||||
|
documentation, if provided along with the Derivative Works; or,
|
||||||
|
within a display generated by the Derivative Works, if and
|
||||||
|
wherever such third-party notices normally appear. The contents
|
||||||
|
of the NOTICE file are for informational purposes only and
|
||||||
|
do not modify the License. You may add Your own attribution
|
||||||
|
notices within Derivative Works that You distribute, alongside
|
||||||
|
or as an addendum to the NOTICE text from the Work, provided
|
||||||
|
that such additional attribution notices cannot be construed
|
||||||
|
as modifying the License.
|
||||||
|
|
||||||
|
You may add Your own copyright statement to Your modifications and
|
||||||
|
may provide additional or different license terms and conditions
|
||||||
|
for use, reproduction, or distribution of Your modifications, or
|
||||||
|
for any such Derivative Works as a whole, provided Your use,
|
||||||
|
reproduction, and distribution of the Work otherwise complies with
|
||||||
|
the conditions stated in this License.
|
||||||
|
|
||||||
|
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||||
|
any Contribution intentionally submitted for inclusion in the Work
|
||||||
|
by You to the Licensor shall be under the terms and conditions of
|
||||||
|
this License, without any additional terms or conditions.
|
||||||
|
Notwithstanding the above, nothing herein shall supersede or modify
|
||||||
|
the terms of any separate license agreement you may have executed
|
||||||
|
with Licensor regarding such Contributions.
|
||||||
|
|
||||||
|
6. Trademarks. This License does not grant permission to use the trade
|
||||||
|
names, trademarks, service marks, or product names of the Licensor,
|
||||||
|
except as required for reasonable and customary use in describing the
|
||||||
|
origin of the Work and reproducing the content of the NOTICE file.
|
||||||
|
|
||||||
|
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||||
|
agreed to in writing, Licensor provides the Work (and each
|
||||||
|
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
|
implied, including, without limitation, any warranties or conditions
|
||||||
|
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||||
|
appropriateness of using or redistributing the Work and assume any
|
||||||
|
risks associated with Your exercise of permissions under this License.
|
||||||
|
|
||||||
|
8. Limitation of Liability. In no event and under no legal theory,
|
||||||
|
whether in tort (including negligence), contract, or otherwise,
|
||||||
|
unless required by applicable law (such as deliberate and grossly
|
||||||
|
negligent acts) or agreed to in writing, shall any Contributor be
|
||||||
|
liable to You for damages, including any direct, indirect, special,
|
||||||
|
incidental, or consequential damages of any character arising as a
|
||||||
|
result of this License or out of the use or inability to use the
|
||||||
|
Work (including but not limited to damages for loss of goodwill,
|
||||||
|
work stoppage, computer failure or malfunction, or any and all
|
||||||
|
other commercial damages or losses), even if such Contributor
|
||||||
|
has been advised of the possibility of such damages.
|
||||||
|
|
||||||
|
9. Accepting Warranty or Additional Liability. While redistributing
|
||||||
|
the Work or Derivative Works thereof, You may choose to offer,
|
||||||
|
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||||
|
or other liability obligations and/or rights consistent with this
|
||||||
|
License. However, in accepting such obligations, You may act only
|
||||||
|
on Your own behalf and on Your sole responsibility, not on behalf
|
||||||
|
of any other Contributor, and only if You agree to indemnify,
|
||||||
|
defend, and hold each Contributor harmless for any liability
|
||||||
|
incurred by, or claims asserted against, such Contributor by reason
|
||||||
|
of your accepting any such warranty or additional liability.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
23
lib/rbxassetid/LICENSE-MIT
Normal file
23
lib/rbxassetid/LICENSE-MIT
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
Permission is hereby granted, free of charge, to any
|
||||||
|
person obtaining a copy of this software and associated
|
||||||
|
documentation files (the "Software"), to deal in the
|
||||||
|
Software without restriction, including without
|
||||||
|
limitation the rights to use, copy, modify, merge,
|
||||||
|
publish, distribute, sublicense, and/or sell copies of
|
||||||
|
the Software, and to permit persons to whom the Software
|
||||||
|
is furnished to do so, subject to the following
|
||||||
|
conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice
|
||||||
|
shall be included in all copies or substantial portions
|
||||||
|
of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
|
||||||
|
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
|
||||||
|
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
|
||||||
|
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||||
|
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||||
|
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
|
||||||
|
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||||
|
DEALINGS IN THE SOFTWARE.
|
||||||
26
lib/rbxassetid/README.md
Normal file
26
lib/rbxassetid/README.md
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
Roblox Asset Id
|
||||||
|
===============
|
||||||
|
|
||||||
|
## Example
|
||||||
|
|
||||||
|
```rust
|
||||||
|
use rbxassetid::RobloxAssetId;
|
||||||
|
|
||||||
|
let content="rbxassetid://255299419";
|
||||||
|
let RobloxAssetId(asset_id)=content.parse()?;
|
||||||
|
```
|
||||||
|
|
||||||
|
#### License
|
||||||
|
|
||||||
|
<sup>
|
||||||
|
Licensed under either of <a href="LICENSE-APACHE">Apache License, Version
|
||||||
|
2.0</a> or <a href="LICENSE-MIT">MIT license</a> at your option.
|
||||||
|
</sup>
|
||||||
|
|
||||||
|
<br>
|
||||||
|
|
||||||
|
<sub>
|
||||||
|
Unless you explicitly state otherwise, any contribution intentionally submitted
|
||||||
|
for inclusion in this crate by you, as defined in the Apache-2.0 license, shall
|
||||||
|
be dual licensed as above, without any additional terms or conditions.
|
||||||
|
</sub>
|
||||||
41
lib/rbxassetid/src/lib.rs
Normal file
41
lib/rbxassetid/src/lib.rs
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
#[derive(Clone,Copy,Debug,Hash,Eq,PartialEq)]
|
||||||
|
pub struct RobloxAssetId(pub u64);
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum RobloxAssetIdParseErr{
|
||||||
|
Url(url::ParseError),
|
||||||
|
UnknownScheme,
|
||||||
|
ParseInt(std::num::ParseIntError),
|
||||||
|
MissingAssetId,
|
||||||
|
MissingIDQueryParam,
|
||||||
|
}
|
||||||
|
impl std::fmt::Display for RobloxAssetIdParseErr{
|
||||||
|
fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{
|
||||||
|
write!(f,"{self:?}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl std::error::Error for RobloxAssetIdParseErr{}
|
||||||
|
impl std::str::FromStr for RobloxAssetId{
|
||||||
|
type Err=RobloxAssetIdParseErr;
|
||||||
|
fn from_str(s:&str)->Result<Self,Self::Err>{
|
||||||
|
let url=url::Url::parse(s).map_err(RobloxAssetIdParseErr::Url)?;
|
||||||
|
let parsed_asset_id=match url.scheme(){
|
||||||
|
"rbxassetid"=>url.domain().ok_or(RobloxAssetIdParseErr::MissingAssetId)?.parse(),
|
||||||
|
"http"|"https"=>{
|
||||||
|
let (_,asset_id)=url.query_pairs()
|
||||||
|
.find(|(id,_)|match id.as_ref(){
|
||||||
|
"ID"|"id"|"Id"|"iD"=>true,
|
||||||
|
_=>false,
|
||||||
|
}).ok_or(RobloxAssetIdParseErr::MissingIDQueryParam)?;
|
||||||
|
asset_id.parse()
|
||||||
|
},
|
||||||
|
_=>Err(RobloxAssetIdParseErr::UnknownScheme)?,
|
||||||
|
};
|
||||||
|
Ok(Self(parsed_asset_id.map_err(RobloxAssetIdParseErr::ParseInt)?))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_rbxassetid(){
|
||||||
|
let content="rbxassetid://255299419";
|
||||||
|
let RobloxAssetId(_asset_id)=content.parse().unwrap();
|
||||||
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "roblox_emulator"
|
name = "roblox_emulator"
|
||||||
version = "0.4.7"
|
version = "0.4.7"
|
||||||
edition = "2021"
|
edition = "2024"
|
||||||
repository = "https://git.itzana.me/StrafesNET/strafe-project"
|
repository = "https://git.itzana.me/StrafesNET/strafe-project"
|
||||||
license = "MIT OR Apache-2.0"
|
license = "MIT OR Apache-2.0"
|
||||||
description = "Run embedded Luau scripts which manipulate the DOM."
|
description = "Run embedded Luau scripts which manipulate the DOM."
|
||||||
@@ -12,7 +12,7 @@ default=["run-service"]
|
|||||||
run-service=[]
|
run-service=[]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
glam = "0.29.0"
|
glam = "0.30.0"
|
||||||
mlua = { version = "0.10.1", features = ["luau"] }
|
mlua = { version = "0.10.1", features = ["luau"] }
|
||||||
phf = { version = "0.11.2", features = ["macros"] }
|
phf = { version = "0.11.2", features = ["macros"] }
|
||||||
rbx_dom_weak = { version = "2.7.0", registry = "strafesnet" }
|
rbx_dom_weak = { version = "2.7.0", registry = "strafesnet" }
|
||||||
|
|||||||
@@ -1,22 +1,22 @@
|
|||||||
macro_rules! type_from_lua_userdata{
|
macro_rules! type_from_lua_userdata{
|
||||||
($asd:ident)=>{
|
($ty:ident)=>{
|
||||||
impl mlua::FromLua for $asd{
|
impl mlua::FromLua for $ty{
|
||||||
fn from_lua(value:mlua::Value,_lua:&mlua::Lua)->Result<Self,mlua::Error>{
|
fn from_lua(value:mlua::Value,_lua:&mlua::Lua)->Result<Self,mlua::Error>{
|
||||||
match value{
|
match value{
|
||||||
mlua::Value::UserData(ud)=>Ok(*ud.borrow::<Self>()?),
|
mlua::Value::UserData(ud)=>Ok(*ud.borrow::<Self>()?),
|
||||||
other=>Err(mlua::Error::runtime(format!("Expected {} got {:?}",stringify!($asd),other))),
|
other=>Err(mlua::Error::runtime(format!("Expected {} got {:?}",stringify!($ty),other))),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
macro_rules! type_from_lua_userdata_lua_lifetime{
|
macro_rules! type_from_lua_userdata_lua_lifetime{
|
||||||
($asd:ident)=>{
|
($ty:ident)=>{
|
||||||
impl mlua::FromLua for $asd<'static>{
|
impl mlua::FromLua for $ty<'static>{
|
||||||
fn from_lua(value:mlua::Value,_lua:&mlua::Lua)->Result<Self,mlua::Error>{
|
fn from_lua(value:mlua::Value,_lua:&mlua::Lua)->Result<Self,mlua::Error>{
|
||||||
match value{
|
match value{
|
||||||
mlua::Value::UserData(ud)=>Ok(*ud.borrow::<Self>()?),
|
mlua::Value::UserData(ud)=>Ok(*ud.borrow::<Self>()?),
|
||||||
other=>Err(mlua::Error::runtime(format!("Expected {} got {:?}",stringify!($asd),other))),
|
other=>Err(mlua::Error::runtime(format!("Expected {} got {:?}",stringify!($ty),other))),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -59,8 +59,8 @@ fn schedule_thread(lua:&mlua::Lua,dt:mlua::Value)->Result<(),mlua::Error>{
|
|||||||
match delay.classify(){
|
match delay.classify(){
|
||||||
std::num::FpCategory::Nan=>Err(mlua::Error::runtime("NaN"))?,
|
std::num::FpCategory::Nan=>Err(mlua::Error::runtime("NaN"))?,
|
||||||
// cases where the number is too large to schedule
|
// cases where the number is too large to schedule
|
||||||
std::num::FpCategory::Infinite=>return Ok(()),
|
std::num::FpCategory::Infinite
|
||||||
std::num::FpCategory::Normal=>if (u64::MAX as f64)<delay{
|
|std::num::FpCategory::Normal if (u64::MAX as f64)<delay=>{
|
||||||
return Ok(());
|
return Ok(());
|
||||||
},
|
},
|
||||||
_=>(),
|
_=>(),
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "strafesnet_snf"
|
name = "strafesnet_snf"
|
||||||
version = "0.2.0"
|
version = "0.3.0"
|
||||||
edition = "2021"
|
edition = "2024"
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
binrw = "0.14.0"
|
binrw = "0.14.0"
|
||||||
id = { version = "0.1.0", registry = "strafesnet" }
|
id = { version = "0.1.0", registry = "strafesnet" }
|
||||||
strafesnet_common = { path = "../common", registry = "strafesnet" }
|
strafesnet_common = { version = "0.6.0", path = "../common", registry = "strafesnet" }
|
||||||
|
|||||||
@@ -1,98 +1,347 @@
|
|||||||
use binrw::{BinReaderExt, binrw};
|
use binrw::{binrw,BinReaderExt,BinWrite,BinWriterExt};
|
||||||
|
|
||||||
|
use crate::newtypes;
|
||||||
|
use crate::file::BlockId;
|
||||||
|
use strafesnet_common::physics::Time;
|
||||||
|
|
||||||
|
const VERSION:u32=0;
|
||||||
|
|
||||||
|
type TimedPhysicsInstruction=strafesnet_common::instruction::TimedInstruction<strafesnet_common::physics::Instruction,strafesnet_common::physics::Time>;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum Error{
|
pub enum Error{
|
||||||
InvalidHeader,
|
InvalidHeader(binrw::Error),
|
||||||
InvalidSegment(binrw::Error),
|
InvalidSegment(binrw::Error),
|
||||||
|
SegmentConvert(newtypes::integer::RatioError),
|
||||||
|
InstructionConvert(newtypes::physics::InstructionConvert),
|
||||||
|
InstructionWrite(binrw::Error),
|
||||||
InvalidSegmentId(SegmentId),
|
InvalidSegmentId(SegmentId),
|
||||||
|
InvalidData(binrw::Error),
|
||||||
|
IO(std::io::Error),
|
||||||
File(crate::file::Error),
|
File(crate::file::Error),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Bot files are simply the sequence of instructions that the physics received during the run.
|
||||||
|
// The instructions are partitioned into timestamped blocks for ease of streaming.
|
||||||
|
//
|
||||||
|
// Keyframe information required for efficient seeking
|
||||||
|
// is part of a different file, and is generated from this file.
|
||||||
|
|
||||||
/* block types
|
/* block types
|
||||||
|
|
||||||
BLOCK_BOT_HEADER:
|
BLOCK_BOT_HEADER:
|
||||||
u128 map_resource_uuid //which map is this bot running
|
// Segments are laid out in chronological order,
|
||||||
//don't include style info in bot header because it's in the simulation state
|
// but block_id is not necessarily in ascending order.
|
||||||
//blocks are laid out in chronological order, but indices may jump around.
|
//
|
||||||
u64 num_segments
|
// This is to place the final segment close to the start of the file,
|
||||||
|
// which allows the duration of the bot to be conveniently calculated
|
||||||
|
// from the first and last instruction timestamps.
|
||||||
|
//
|
||||||
|
// Use exact physics version for replay playback
|
||||||
|
// Use highest compatible physics version for verification
|
||||||
|
u32 physics_version
|
||||||
|
u32 num_segments
|
||||||
for _ in 0..num_segments{
|
for _ in 0..num_segments{
|
||||||
i64 time //simulation_state timestamp
|
i64 time
|
||||||
u64 block_id
|
u32 instruction_count
|
||||||
|
u32 block_id
|
||||||
}
|
}
|
||||||
|
|
||||||
BLOCK_BOT_SEGMENT:
|
BLOCK_BOT_SEGMENT:
|
||||||
//format version indicates what version of these structures to use
|
// segments can potentially be losslessly compressed!
|
||||||
SimulationState simulation_state //SimulationState is just under ClientState which includes Play/Pause events that the simulation doesn't know about.
|
for _ in 0..instruction_count{
|
||||||
//to read, greedily decode instructions until eof
|
// TODO: delta encode as much as possible (time,mousepos)
|
||||||
loop{
|
i64 time
|
||||||
//delta encode as much as possible (time,mousepos)
|
physics::Instruction instruction
|
||||||
//strafe ticks are implied
|
|
||||||
//physics can be implied in an input-only bot file
|
|
||||||
TimedInstruction<SimulationInstruction> instruction
|
|
||||||
}
|
}
|
||||||
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
//error hiding mock code
|
#[binrw]
|
||||||
mod simulation{
|
#[brw(little)]
|
||||||
#[super::binrw]
|
struct SegmentHeader{
|
||||||
#[brw(little)]
|
time:i64,
|
||||||
pub struct State{}
|
instruction_count:u32,
|
||||||
#[super::binrw]
|
block_id:BlockId,
|
||||||
#[brw(little)]
|
}
|
||||||
pub struct Instruction{}
|
#[binrw]
|
||||||
|
#[brw(little)]
|
||||||
|
struct Header{
|
||||||
|
physics_version:u32,
|
||||||
|
num_segments:u32,
|
||||||
|
#[br(count=num_segments)]
|
||||||
|
segments:Vec<SegmentHeader>,
|
||||||
}
|
}
|
||||||
// mod instruction{
|
|
||||||
// #[super::binrw]
|
|
||||||
// #[brw(little)]
|
|
||||||
// pub struct TimedInstruction<Instruction:binrw::BinRead+binrw::BinWrite>{
|
|
||||||
// time:u64,
|
|
||||||
// instruction:Instruction
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// mod timeline{
|
|
||||||
// #[super::binrw]
|
|
||||||
// #[brw(little)]
|
|
||||||
// pub struct Timeline<Instruction:binrw::BinRead+binrw::BinWrite>{
|
|
||||||
// #[bw(try_calc(u32::try_from(instructions.len())))]
|
|
||||||
// instruction_count:u32,
|
|
||||||
// #[br(count=instruction_count)]
|
|
||||||
// instructions:Vec<super::instruction::TimedInstruction<Instruction>>
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
//serious code
|
|
||||||
|
|
||||||
#[binrw]
|
#[binrw]
|
||||||
#[brw(little)]
|
#[brw(little)]
|
||||||
#[derive(Clone,Copy,Debug,id::Id)]
|
#[derive(Clone,Copy,Debug,id::Id)]
|
||||||
pub struct SegmentId(u32);
|
pub struct SegmentId(u32);
|
||||||
|
|
||||||
#[binrw]
|
|
||||||
#[brw(little)]
|
|
||||||
pub struct Segment{
|
pub struct Segment{
|
||||||
state:simulation::State,
|
pub instructions:Vec<TimedPhysicsInstruction>
|
||||||
//#[bw(try_calc(u32::try_from(instructions.len())))]
|
}
|
||||||
//instruction_count:u32,
|
|
||||||
//#[br(count=instruction_count)]
|
|
||||||
//instructions:Vec<instruction::TimedInstruction<simulation::Instruction>>
|
|
||||||
|
|
||||||
//please remember that strafe ticks are implicit! 33% smaller bot files
|
#[derive(Clone,Copy,Debug)]
|
||||||
|
pub struct SegmentInfo{
|
||||||
|
/// time of the first instruction in this segment.
|
||||||
|
time:Time,
|
||||||
|
instruction_count:u32,
|
||||||
|
/// How many total instructions in segments up to and including this segment
|
||||||
|
/// Alternatively, the id of the first instruction be in the _next_ segment
|
||||||
|
instructions_subtotal:u64,
|
||||||
|
block_id:BlockId,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct StreamableBot<R:BinReaderExt>{
|
pub struct StreamableBot<R:BinReaderExt>{
|
||||||
file:crate::file::File<R>,
|
file:crate::file::File<R>,
|
||||||
//timeline:timeline::Timeline<SegmentId>,
|
segment_map:Vec<SegmentInfo>,
|
||||||
segment_id_to_block_id:Vec<crate::file::BlockId>,
|
|
||||||
}
|
}
|
||||||
impl<R:BinReaderExt> StreamableBot<R>{
|
impl<R:BinReaderExt> StreamableBot<R>{
|
||||||
pub(crate) fn new(file:crate::file::File<R>)->Result<Self,Error>{
|
pub(crate) fn new(mut file:crate::file::File<R>)->Result<Self,Error>{
|
||||||
Err(Error::InvalidHeader)
|
//assume the file seek is in the right place to start reading header
|
||||||
|
let header:Header=file.data_mut().read_le().map_err(Error::InvalidHeader)?;
|
||||||
|
let mut instructions_subtotal=0;
|
||||||
|
let segment_map=header.segments.into_iter().map(|SegmentHeader{time,instruction_count,block_id}|{
|
||||||
|
instructions_subtotal+=instruction_count as u64;
|
||||||
|
SegmentInfo{
|
||||||
|
time:Time::raw(time),
|
||||||
|
instruction_count,
|
||||||
|
instructions_subtotal,
|
||||||
|
block_id,
|
||||||
|
}
|
||||||
|
}).collect();
|
||||||
|
Ok(Self{
|
||||||
|
file,
|
||||||
|
segment_map,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
pub fn load_segment(&mut self,segment_id:SegmentId)->Result<Segment,Error>{
|
fn get_segment_info(&self,segment_id:SegmentId)->Result<SegmentInfo,Error>{
|
||||||
let block_id=*self.segment_id_to_block_id.get(segment_id.get() as usize).ok_or(Error::InvalidSegmentId(segment_id))?;
|
Ok(*self.segment_map.get(segment_id.get() as usize).ok_or(Error::InvalidSegmentId(segment_id))?)
|
||||||
let mut block=self.file.block_reader(block_id).map_err(Error::File)?;
|
}
|
||||||
let segment=block.read_le().map_err(Error::InvalidSegment)?;
|
pub fn find_segments_instruction_range(&self,start_instruction:u64,end_instruction:u64)->&[SegmentInfo]{
|
||||||
|
let start=self.segment_map.partition_point(|segment_info|segment_info.instructions_subtotal<start_instruction);
|
||||||
|
let end=self.segment_map.partition_point(|segment_info|segment_info.instructions_subtotal<end_instruction);
|
||||||
|
&self.segment_map[start..=end]
|
||||||
|
}
|
||||||
|
// pub fn find_segments_time_range(&self,start_time:Time,end_time:Time)->&[SegmentInfo]{
|
||||||
|
// // TODO: This is off by one, both should be one less
|
||||||
|
// let start=self.segment_map.partition_point(|segment_info|segment_info.time<start_time);
|
||||||
|
// let end=self.segment_map.partition_point(|segment_info|segment_info.time<end_time);
|
||||||
|
// &self.segment_map[start..=end]
|
||||||
|
// }
|
||||||
|
fn append_to_segment(&mut self,segment_info:SegmentInfo,segment:&mut Segment)->Result<(),Error>{
|
||||||
|
let mut block=self.file.block_reader(segment_info.block_id).map_err(Error::File)?;
|
||||||
|
for _ in 0..segment_info.instruction_count{
|
||||||
|
let instruction:newtypes::physics::TimedInstruction=block.read_le().map_err(Error::InvalidSegment)?;
|
||||||
|
segment.instructions.push(instruction.try_into().map_err(Error::SegmentConvert)?);
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
pub fn load_segment(&mut self,segment_info:SegmentInfo)->Result<Segment,Error>{
|
||||||
|
let mut segment=Segment{
|
||||||
|
instructions:Vec::with_capacity(segment_info.instruction_count as usize),
|
||||||
|
};
|
||||||
|
self.append_to_segment(segment_info,&mut segment)?;
|
||||||
|
Ok(segment)
|
||||||
|
}
|
||||||
|
pub fn read_all(&mut self)->Result<Segment,Error>{
|
||||||
|
let mut segment=Segment{
|
||||||
|
instructions:Vec::new(),
|
||||||
|
};
|
||||||
|
for i in 0..self.segment_map.len(){
|
||||||
|
let segment_info=self.segment_map[i];
|
||||||
|
self.append_to_segment(segment_info,&mut segment)?;
|
||||||
|
}
|
||||||
Ok(segment)
|
Ok(segment)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const MAX_BLOCK_SIZE:usize=64*1024;//64 kB
|
||||||
|
pub fn write_bot<W:BinWriterExt>(mut writer:W,physics_version:u32,instructions:impl IntoIterator<Item=TimedPhysicsInstruction>)->Result<(),Error>{
|
||||||
|
// decide which instructions to put in which segment
|
||||||
|
// write segment 1 to block 1
|
||||||
|
// write segment N to block 2
|
||||||
|
// write rest of segments
|
||||||
|
// 1 2 3 4 5
|
||||||
|
// becomes
|
||||||
|
// [1 5] 2 3 4
|
||||||
|
struct SegmentHeaderInfo{
|
||||||
|
time:Time,
|
||||||
|
instruction_count:u32,
|
||||||
|
range:core::ops::Range<usize>
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut segment_header_infos=Vec::new();
|
||||||
|
let mut raw_segments=std::io::Cursor::new(Vec::new());
|
||||||
|
|
||||||
|
// block info
|
||||||
|
let mut start_time=Time::ZERO;
|
||||||
|
let mut start_position=raw_segments.position() as usize;
|
||||||
|
let mut instruction_count=0;
|
||||||
|
|
||||||
|
let mut last_position=start_position;
|
||||||
|
|
||||||
|
let mut iter=instructions.into_iter();
|
||||||
|
|
||||||
|
macro_rules! collect_instruction{
|
||||||
|
($instruction:expr)=>{
|
||||||
|
let time=$instruction.time;
|
||||||
|
let instruction_writable:newtypes::physics::TimedInstruction=$instruction.try_into().map_err(Error::InstructionConvert)?;
|
||||||
|
instruction_writable.write_le(&mut raw_segments).map_err(Error::InstructionWrite)?;
|
||||||
|
instruction_count+=1;
|
||||||
|
let position=raw_segments.position() as usize;
|
||||||
|
// exceeds max block size
|
||||||
|
if MAX_BLOCK_SIZE<position-last_position{
|
||||||
|
segment_header_infos.push(SegmentHeaderInfo{
|
||||||
|
time:start_time,
|
||||||
|
instruction_count,
|
||||||
|
range:start_position..last_position,
|
||||||
|
});
|
||||||
|
start_position=last_position;
|
||||||
|
instruction_count=0;
|
||||||
|
start_time=time;
|
||||||
|
}
|
||||||
|
last_position=position;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// unroll one loop iteration to grab the starting time
|
||||||
|
if let Some(instruction)=iter.next(){
|
||||||
|
start_time=instruction.time;
|
||||||
|
collect_instruction!(instruction);
|
||||||
|
}
|
||||||
|
|
||||||
|
for instruction in iter{
|
||||||
|
collect_instruction!(instruction);
|
||||||
|
}
|
||||||
|
//last block, whatever size it happens to be
|
||||||
|
{
|
||||||
|
let final_position=raw_segments.position() as usize;
|
||||||
|
segment_header_infos.push(SegmentHeaderInfo{
|
||||||
|
time:start_time,
|
||||||
|
instruction_count,
|
||||||
|
range:start_position..final_position,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// drop cursor
|
||||||
|
let raw_segments=raw_segments.into_inner();
|
||||||
|
|
||||||
|
let num_segments=segment_header_infos.len();
|
||||||
|
|
||||||
|
// segments list is in chronological order
|
||||||
|
let make_segment_header=|block_id,&SegmentHeaderInfo{time,instruction_count,range:ref _range}|SegmentHeader{
|
||||||
|
time:time.get(),
|
||||||
|
instruction_count,
|
||||||
|
block_id,
|
||||||
|
};
|
||||||
|
let segments=if 2<num_segments{
|
||||||
|
let mut segments=Vec::with_capacity(num_segments);
|
||||||
|
// segment 1 is second block
|
||||||
|
if let Some(seg)=segment_header_infos.first(){
|
||||||
|
segments.push(make_segment_header(BlockId::new(1),seg));
|
||||||
|
}
|
||||||
|
// rest of segments start at fourth block
|
||||||
|
for (i,seg) in segment_header_infos[1..num_segments-1].iter().enumerate(){
|
||||||
|
make_segment_header(BlockId::new(3+i as u32),seg);
|
||||||
|
}
|
||||||
|
// last segment is third block
|
||||||
|
if let Some(seg)=segment_header_infos.last(){
|
||||||
|
segments.push(make_segment_header(BlockId::new(2),seg));
|
||||||
|
}
|
||||||
|
segments
|
||||||
|
}else{
|
||||||
|
// all segments in order
|
||||||
|
segment_header_infos.iter().enumerate().map(|(i,seg)|
|
||||||
|
make_segment_header(BlockId::new(1+i as u32),seg)
|
||||||
|
).collect()
|
||||||
|
};
|
||||||
|
|
||||||
|
let header=Header{
|
||||||
|
physics_version,
|
||||||
|
num_segments:num_segments as u32,
|
||||||
|
segments,
|
||||||
|
};
|
||||||
|
|
||||||
|
// map header is +1
|
||||||
|
let block_count=1+num_segments as u32;
|
||||||
|
|
||||||
|
let mut offset=crate::file::Header::calculate_size(block_count) as u64;
|
||||||
|
// block_location is one longer than block_count
|
||||||
|
let mut block_location=Vec::with_capacity(1+block_count as usize);
|
||||||
|
|
||||||
|
//probe header length
|
||||||
|
let mut bot_header_data=Vec::new();
|
||||||
|
binrw::BinWrite::write_le(&header,&mut std::io::Cursor::new(&mut bot_header_data)).map_err(Error::InvalidData)?;
|
||||||
|
|
||||||
|
// the first block location is the map header
|
||||||
|
block_location.push(offset);
|
||||||
|
offset+=bot_header_data.len() as u64;
|
||||||
|
block_location.push(offset);
|
||||||
|
|
||||||
|
// priming includes file header + first 3 blocks [bot header, first segment, last segment]
|
||||||
|
let priming=if 2<num_segments{
|
||||||
|
// segment 1 is block 2
|
||||||
|
if let Some(seg)=segment_header_infos.first(){
|
||||||
|
offset+=seg.range.len() as u64;
|
||||||
|
block_location.push(offset);
|
||||||
|
}
|
||||||
|
// last segment is block 3
|
||||||
|
if let Some(seg)=segment_header_infos.last(){
|
||||||
|
offset+=seg.range.len() as u64;
|
||||||
|
block_location.push(offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
let priming=offset;
|
||||||
|
|
||||||
|
// rest of segments
|
||||||
|
for seg in &segment_header_infos[1..num_segments-1]{
|
||||||
|
offset+=seg.range.len() as u64;
|
||||||
|
block_location.push(offset);
|
||||||
|
}
|
||||||
|
priming
|
||||||
|
}else{
|
||||||
|
// all segments in order
|
||||||
|
for seg in &segment_header_infos{
|
||||||
|
offset+=seg.range.len() as u64;
|
||||||
|
block_location.push(offset);
|
||||||
|
}
|
||||||
|
offset
|
||||||
|
};
|
||||||
|
|
||||||
|
let file_header=crate::file::Header{
|
||||||
|
fourcc:crate::file::FourCC::Bot,
|
||||||
|
version:VERSION,
|
||||||
|
priming,
|
||||||
|
resource:0,
|
||||||
|
block_count,
|
||||||
|
block_location,
|
||||||
|
};
|
||||||
|
|
||||||
|
// write file header
|
||||||
|
writer.write_le(&file_header).map_err(Error::InvalidData)?;
|
||||||
|
// write bot header
|
||||||
|
writer.write(&bot_header_data).map_err(Error::IO)?;
|
||||||
|
|
||||||
|
// write blocks
|
||||||
|
if 2<num_segments{
|
||||||
|
// segment 1 is block 2
|
||||||
|
if let Some(seg)=segment_header_infos.first(){
|
||||||
|
writer.write(&raw_segments[seg.range.clone()]).map_err(Error::IO)?;
|
||||||
|
}
|
||||||
|
// last segment is block 3
|
||||||
|
if let Some(seg)=segment_header_infos.last(){
|
||||||
|
writer.write(&raw_segments[seg.range.clone()]).map_err(Error::IO)?;
|
||||||
|
}
|
||||||
|
// rest of segments
|
||||||
|
for seg in &segment_header_infos[1..num_segments-1]{
|
||||||
|
writer.write(&raw_segments[seg.range.clone()]).map_err(Error::IO)?;
|
||||||
|
}
|
||||||
|
}else{
|
||||||
|
// all segments in order
|
||||||
|
for seg in segment_header_infos{
|
||||||
|
writer.write(&raw_segments[seg.range]).map_err(Error::IO)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ use crate::file::BlockId;
|
|||||||
use binrw::{binrw,BinReaderExt,BinWriterExt};
|
use binrw::{binrw,BinReaderExt,BinWriterExt};
|
||||||
use strafesnet_common::model;
|
use strafesnet_common::model;
|
||||||
use strafesnet_common::aabb::Aabb;
|
use strafesnet_common::aabb::Aabb;
|
||||||
use strafesnet_common::bvh::BvhNode;
|
use strafesnet_common::bvh::{BvhNode,RecursiveContent};
|
||||||
use strafesnet_common::gameplay_modes;
|
use strafesnet_common::gameplay_modes;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
@@ -95,21 +95,6 @@ enum ResourceType{
|
|||||||
//Video,
|
//Video,
|
||||||
//Animation,
|
//Animation,
|
||||||
}
|
}
|
||||||
const RESOURCE_TYPE_VARIANT_COUNT:u8=2;
|
|
||||||
#[binrw]
|
|
||||||
#[brw(little)]
|
|
||||||
struct ResourceId(u128);
|
|
||||||
impl ResourceId{
|
|
||||||
fn resource_type(&self)->Option<ResourceType>{
|
|
||||||
let discriminant=self.0 as u8;
|
|
||||||
//TODO: use this when it is stabilized https://github.com/rust-lang/rust/issues/73662
|
|
||||||
//if (discriminant as usize)<std::mem::variant_count::<ResourceType>(){
|
|
||||||
match discriminant<RESOURCE_TYPE_VARIANT_COUNT{
|
|
||||||
true=>Some(unsafe{std::mem::transmute::<u8,ResourceType>(discriminant)}),
|
|
||||||
false=>None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct ResourceMap<T>{
|
struct ResourceMap<T>{
|
||||||
meshes:HashMap<strafesnet_common::model::MeshId,T>,
|
meshes:HashMap<strafesnet_common::model::MeshId,T>,
|
||||||
@@ -136,11 +121,6 @@ struct ResourceBlockHeader{
|
|||||||
resource:ResourceType,
|
resource:ResourceType,
|
||||||
id:BlockId,
|
id:BlockId,
|
||||||
}
|
}
|
||||||
#[binrw]
|
|
||||||
#[brw(little)]
|
|
||||||
struct ResourceExternalHeader{
|
|
||||||
resource_uuid:ResourceId,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[binrw]
|
#[binrw]
|
||||||
#[brw(little)]
|
#[brw(little)]
|
||||||
@@ -158,7 +138,7 @@ struct MapHeader{
|
|||||||
//#[br(count=num_resources_external)]
|
//#[br(count=num_resources_external)]
|
||||||
//external_resources:Vec<ResourceExternalHeader>,
|
//external_resources:Vec<ResourceExternalHeader>,
|
||||||
#[br(count=num_modes)]
|
#[br(count=num_modes)]
|
||||||
modes:Vec<newtypes::gameplay_modes::Mode>,
|
modes:Vec<newtypes::gameplay_modes::NormalizedMode>,
|
||||||
#[br(count=num_attributes)]
|
#[br(count=num_attributes)]
|
||||||
attributes:Vec<newtypes::gameplay_attributes::CollisionAttributes>,
|
attributes:Vec<newtypes::gameplay_attributes::CollisionAttributes>,
|
||||||
#[br(count=num_render_configs)]
|
#[br(count=num_render_configs)]
|
||||||
@@ -201,7 +181,7 @@ fn read_texture<R:BinReaderExt>(file:&mut crate::file::File<R>,block_id:BlockId)
|
|||||||
pub struct StreamableMap<R:BinReaderExt>{
|
pub struct StreamableMap<R:BinReaderExt>{
|
||||||
file:crate::file::File<R>,
|
file:crate::file::File<R>,
|
||||||
//this includes every platform... move the unconstrained datas to their appropriate data block?
|
//this includes every platform... move the unconstrained datas to their appropriate data block?
|
||||||
modes:gameplay_modes::Modes,
|
modes:gameplay_modes::NormalizedModes,
|
||||||
//this is every possible attribute... need some sort of streaming system
|
//this is every possible attribute... need some sort of streaming system
|
||||||
attributes:Vec<strafesnet_common::gameplay_attributes::CollisionAttributes>,
|
attributes:Vec<strafesnet_common::gameplay_attributes::CollisionAttributes>,
|
||||||
//this is every possible render configuration... shaders and such... need streaming
|
//this is every possible render configuration... shaders and such... need streaming
|
||||||
@@ -243,7 +223,7 @@ impl<R:BinReaderExt> StreamableMap<R>{
|
|||||||
}
|
}
|
||||||
Ok(Self{
|
Ok(Self{
|
||||||
file,
|
file,
|
||||||
modes:strafesnet_common::gameplay_modes::Modes::new(modes),
|
modes:strafesnet_common::gameplay_modes::NormalizedModes::new(modes),
|
||||||
attributes,
|
attributes,
|
||||||
render_configs,
|
render_configs,
|
||||||
bvh:strafesnet_common::bvh::generate_bvh(bvh),
|
bvh:strafesnet_common::bvh::generate_bvh(bvh),
|
||||||
@@ -253,7 +233,7 @@ impl<R:BinReaderExt> StreamableMap<R>{
|
|||||||
}
|
}
|
||||||
pub fn get_intersecting_region_block_ids(&self,aabb:&Aabb)->Vec<BlockId>{
|
pub fn get_intersecting_region_block_ids(&self,aabb:&Aabb)->Vec<BlockId>{
|
||||||
let mut block_ids=Vec::new();
|
let mut block_ids=Vec::new();
|
||||||
self.bvh.the_tester(aabb,&mut |&block_id|block_ids.push(block_id));
|
self.bvh.sample_aabb(aabb,&mut |&block_id|block_ids.push(block_id));
|
||||||
block_ids
|
block_ids
|
||||||
}
|
}
|
||||||
pub fn load_region(&mut self,block_id:BlockId)->Result<Vec<(model::ModelId,model::Model)>,Error>{
|
pub fn load_region(&mut self,block_id:BlockId)->Result<Vec<(model::ModelId,model::Model)>,Error>{
|
||||||
@@ -307,12 +287,60 @@ impl<R:BinReaderExt> StreamableMap<R>{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// silly redefinition of Bvh for determining the size of subnodes
|
||||||
|
// without duplicating work by running weight calculation recursion top down on every node
|
||||||
|
pub struct BvhWeightNode<W,T>{
|
||||||
|
content:RecursiveContent<BvhWeightNode<W,T>,T>,
|
||||||
|
weight:W,
|
||||||
|
aabb:Aabb,
|
||||||
|
}
|
||||||
|
impl <W,T> BvhWeightNode<W,T>{
|
||||||
|
pub const fn weight(&self)->&W{
|
||||||
|
&self.weight
|
||||||
|
}
|
||||||
|
pub const fn aabb(&self)->&Aabb{
|
||||||
|
&self.aabb
|
||||||
|
}
|
||||||
|
pub fn into_content(self)->RecursiveContent<BvhWeightNode<W,T>,T>{
|
||||||
|
self.content
|
||||||
|
}
|
||||||
|
pub fn into_visitor<F:FnMut(T)>(self,f:&mut F){
|
||||||
|
match self.content{
|
||||||
|
RecursiveContent::Leaf(model)=>f(model),
|
||||||
|
RecursiveContent::Branch(children)=>for child in children{
|
||||||
|
child.into_visitor(f)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn weigh_contents<T,W:Copy+std::iter::Sum<W>,F:Fn(&T)->W>(node:BvhNode<T>,f:&F)->BvhWeightNode<W,T>{
|
||||||
|
let (content,aabb)=node.into_inner();
|
||||||
|
match content{
|
||||||
|
RecursiveContent::Leaf(model)=>BvhWeightNode{
|
||||||
|
weight:f(&model),
|
||||||
|
content:RecursiveContent::Leaf(model),
|
||||||
|
aabb,
|
||||||
|
},
|
||||||
|
RecursiveContent::Branch(children)=>{
|
||||||
|
let branch:Vec<BvhWeightNode<W,T>>=children.into_iter().map(|child|
|
||||||
|
weigh_contents(child,f)
|
||||||
|
).collect();
|
||||||
|
BvhWeightNode{
|
||||||
|
weight:branch.iter().map(|node|node.weight).sum(),
|
||||||
|
content:RecursiveContent::Branch(branch),
|
||||||
|
aabb,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const BVH_NODE_MAX_WEIGHT:usize=64*1024;//64 kB
|
const BVH_NODE_MAX_WEIGHT:usize=64*1024;//64 kB
|
||||||
fn collect_spacial_blocks(
|
fn collect_spacial_blocks(
|
||||||
block_location:&mut Vec<u64>,
|
block_location:&mut Vec<u64>,
|
||||||
block_headers:&mut Vec<SpacialBlockHeader>,
|
block_headers:&mut Vec<SpacialBlockHeader>,
|
||||||
sequential_block_data:&mut std::io::Cursor<&mut Vec<u8>>,
|
sequential_block_data:&mut std::io::Cursor<&mut Vec<u8>>,
|
||||||
bvh_node:strafesnet_common::bvh::BvhWeightNode<usize,(model::ModelId,newtypes::model::Model)>
|
bvh_node:BvhWeightNode<usize,(model::ModelId,newtypes::model::Model)>
|
||||||
)->Result<(),Error>{
|
)->Result<(),Error>{
|
||||||
//inspect the node weights top-down.
|
//inspect the node weights top-down.
|
||||||
//When a node weighs less than the limit,
|
//When a node weighs less than the limit,
|
||||||
@@ -362,7 +390,7 @@ pub fn write_map<W:BinWriterExt>(mut writer:W,map:strafesnet_common::map::Comple
|
|||||||
}
|
}
|
||||||
Ok(((model::ModelId::new(model_id as u32),model.into()),aabb))
|
Ok(((model::ModelId::new(model_id as u32),model.into()),aabb))
|
||||||
}).collect::<Result<Vec<_>,_>>()?;
|
}).collect::<Result<Vec<_>,_>>()?;
|
||||||
let bvh=strafesnet_common::bvh::generate_bvh(boxen).weigh_contents(&|_|std::mem::size_of::<newtypes::model::Model>());
|
let bvh=weigh_contents(strafesnet_common::bvh::generate_bvh(boxen),&|_|std::mem::size_of::<newtypes::model::Model>());
|
||||||
//build blocks
|
//build blocks
|
||||||
//block location is initialized with two values
|
//block location is initialized with two values
|
||||||
//the first value represents the location of the first byte after the file header
|
//the first value represents the location of the first byte after the file header
|
||||||
@@ -402,13 +430,13 @@ pub fn write_map<W:BinWriterExt>(mut writer:W,map:strafesnet_common::map::Comple
|
|||||||
num_spacial_blocks:spacial_blocks.len() as u32,
|
num_spacial_blocks:spacial_blocks.len() as u32,
|
||||||
num_resource_blocks:resource_blocks.len() as u32,
|
num_resource_blocks:resource_blocks.len() as u32,
|
||||||
//num_resources_external:0,
|
//num_resources_external:0,
|
||||||
num_modes:map.modes.modes.len() as u32,
|
num_modes:map.modes.len() as u32,
|
||||||
num_attributes:map.attributes.len() as u32,
|
num_attributes:map.attributes.len() as u32,
|
||||||
num_render_configs:map.render_configs.len() as u32,
|
num_render_configs:map.render_configs.len() as u32,
|
||||||
spacial_blocks,
|
spacial_blocks,
|
||||||
resource_blocks,
|
resource_blocks,
|
||||||
//external_resources:Vec::new(),
|
//external_resources:Vec::new(),
|
||||||
modes:map.modes.modes.into_iter().map(Into::into).collect(),
|
modes:map.modes.into_iter().map(Into::into).collect(),
|
||||||
attributes:map.attributes.into_iter().map(Into::into).collect(),
|
attributes:map.attributes.into_iter().map(Into::into).collect(),
|
||||||
render_configs:map.render_configs.into_iter().map(Into::into).collect(),
|
render_configs:map.render_configs.into_iter().map(Into::into).collect(),
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,3 +1,9 @@
|
|||||||
pub const fn flag(b:bool,mask:u8)->u8{
|
pub const fn flag(b:bool,mask:u8)->u8{
|
||||||
(-(b as i8) as u8)&mask
|
(-(b as i8) as u8)&mask
|
||||||
}
|
}
|
||||||
|
pub fn bool_from_u8(value:u8)->bool{
|
||||||
|
value!=0
|
||||||
|
}
|
||||||
|
pub fn bool_into_u8(value:&bool)->u8{
|
||||||
|
*value as u8
|
||||||
|
}
|
||||||
|
|||||||
@@ -157,7 +157,7 @@ pub struct ModeHeader{
|
|||||||
}
|
}
|
||||||
#[binrw::binrw]
|
#[binrw::binrw]
|
||||||
#[brw(little)]
|
#[brw(little)]
|
||||||
pub struct Mode{
|
pub struct NormalizedMode{
|
||||||
pub header:ModeHeader,
|
pub header:ModeHeader,
|
||||||
pub style:super::gameplay_style::StyleModifiers,
|
pub style:super::gameplay_style::StyleModifiers,
|
||||||
pub start:u32,
|
pub start:u32,
|
||||||
@@ -179,10 +179,10 @@ impl std::fmt::Display for ModeError{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl std::error::Error for ModeError{}
|
impl std::error::Error for ModeError{}
|
||||||
impl TryInto<strafesnet_common::gameplay_modes::Mode> for Mode{
|
impl TryInto<strafesnet_common::gameplay_modes::NormalizedMode> for NormalizedMode{
|
||||||
type Error=ModeError;
|
type Error=ModeError;
|
||||||
fn try_into(self)->Result<strafesnet_common::gameplay_modes::Mode,Self::Error>{
|
fn try_into(self)->Result<strafesnet_common::gameplay_modes::NormalizedMode,Self::Error>{
|
||||||
Ok(strafesnet_common::gameplay_modes::Mode::new(
|
Ok(strafesnet_common::gameplay_modes::NormalizedMode::new(strafesnet_common::gameplay_modes::Mode::new(
|
||||||
self.style.try_into().map_err(ModeError::StyleModifier)?,
|
self.style.try_into().map_err(ModeError::StyleModifier)?,
|
||||||
strafesnet_common::model::ModelId::new(self.start),
|
strafesnet_common::model::ModelId::new(self.start),
|
||||||
self.zones.into_iter().map(|(model_id,zone)|
|
self.zones.into_iter().map(|(model_id,zone)|
|
||||||
@@ -192,12 +192,12 @@ impl TryInto<strafesnet_common::gameplay_modes::Mode> for Mode{
|
|||||||
self.elements.into_iter().map(|(model_id,stage_element)|
|
self.elements.into_iter().map(|(model_id,stage_element)|
|
||||||
Ok((strafesnet_common::model::ModelId::new(model_id),stage_element.try_into()?))
|
Ok((strafesnet_common::model::ModelId::new(model_id),stage_element.try_into()?))
|
||||||
).collect::<Result<_,_>>().map_err(ModeError::StageElement)?,
|
).collect::<Result<_,_>>().map_err(ModeError::StageElement)?,
|
||||||
))
|
)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl From<strafesnet_common::gameplay_modes::Mode> for Mode{
|
impl From<strafesnet_common::gameplay_modes::NormalizedMode> for NormalizedMode{
|
||||||
fn from(value:strafesnet_common::gameplay_modes::Mode)->Self{
|
fn from(value:strafesnet_common::gameplay_modes::NormalizedMode)->Self{
|
||||||
let (style,start,zones,stages,elements)=value.into_inner();
|
let (style,start,zones,stages,elements)=value.into_inner().into_inner();
|
||||||
Self{
|
Self{
|
||||||
header:ModeHeader{
|
header:ModeHeader{
|
||||||
zones:zones.len() as u32,
|
zones:zones.len() as u32,
|
||||||
|
|||||||
@@ -38,6 +38,23 @@ pub struct Ratio64Vec2{
|
|||||||
pub x:Ratio64,
|
pub x:Ratio64,
|
||||||
pub y:Ratio64,
|
pub y:Ratio64,
|
||||||
}
|
}
|
||||||
|
impl TryInto<strafesnet_common::integer::Ratio64Vec2> for Ratio64Vec2{
|
||||||
|
type Error=RatioError;
|
||||||
|
fn try_into(self)->Result<strafesnet_common::integer::Ratio64Vec2,Self::Error>{
|
||||||
|
Ok(strafesnet_common::integer::Ratio64Vec2{
|
||||||
|
x:self.x.try_into()?,
|
||||||
|
y:self.y.try_into()?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl From<strafesnet_common::integer::Ratio64Vec2> for Ratio64Vec2{
|
||||||
|
fn from(value:strafesnet_common::integer::Ratio64Vec2)->Self{
|
||||||
|
Self{
|
||||||
|
x:value.x.into(),
|
||||||
|
y:value.y.into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub type Angle32=i32;
|
pub type Angle32=i32;
|
||||||
pub type Planar64=i64;
|
pub type Planar64=i64;
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
mod common;
|
mod common;
|
||||||
pub mod aabb;
|
pub mod aabb;
|
||||||
pub mod model;
|
pub mod model;
|
||||||
|
pub mod mouse;
|
||||||
pub mod integer;
|
pub mod integer;
|
||||||
|
pub mod physics;
|
||||||
pub mod gameplay_modes;
|
pub mod gameplay_modes;
|
||||||
pub mod gameplay_style;
|
pub mod gameplay_style;
|
||||||
pub mod gameplay_attributes;
|
pub mod gameplay_attributes;
|
||||||
|
|||||||
@@ -250,6 +250,7 @@ impl Into<strafesnet_common::model::Model> for Model{
|
|||||||
]),
|
]),
|
||||||
strafesnet_common::integer::vec3::raw_xyz(_9,_a,_b)
|
strafesnet_common::integer::vec3::raw_xyz(_9,_a,_b)
|
||||||
),
|
),
|
||||||
|
debug_info:strafesnet_common::model::DebugInfo::World,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
25
lib/snf/src/newtypes/mouse.rs
Normal file
25
lib/snf/src/newtypes/mouse.rs
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
use super::integer::Time;
|
||||||
|
|
||||||
|
#[binrw::binrw]
|
||||||
|
#[brw(little)]
|
||||||
|
pub struct MouseState{
|
||||||
|
pub pos:[i32;2],
|
||||||
|
pub time:Time,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Into<strafesnet_common::mouse::MouseState<T>> for MouseState{
|
||||||
|
fn into(self)->strafesnet_common::mouse::MouseState<T>{
|
||||||
|
strafesnet_common::mouse::MouseState{
|
||||||
|
pos:self.pos.into(),
|
||||||
|
time:strafesnet_common::integer::Time::raw(self.time),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<T> From<strafesnet_common::mouse::MouseState<T>> for MouseState{
|
||||||
|
fn from(value:strafesnet_common::mouse::MouseState<T>)->Self{
|
||||||
|
Self{
|
||||||
|
pos:value.pos.to_array(),
|
||||||
|
time:value.time.get(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
156
lib/snf/src/newtypes/physics.rs
Normal file
156
lib/snf/src/newtypes/physics.rs
Normal file
@@ -0,0 +1,156 @@
|
|||||||
|
use super::integer::Time;
|
||||||
|
use super::common::{bool_from_u8,bool_into_u8};
|
||||||
|
|
||||||
|
type TimedPhysicsInstruction=strafesnet_common::instruction::TimedInstruction<strafesnet_common::physics::Instruction,strafesnet_common::physics::Time>;
|
||||||
|
|
||||||
|
#[binrw::binrw]
|
||||||
|
#[brw(little)]
|
||||||
|
pub struct TimedInstruction{
|
||||||
|
pub time:Time,
|
||||||
|
pub instruction:Instruction,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryInto<TimedPhysicsInstruction> for TimedInstruction{
|
||||||
|
type Error=super::integer::RatioError;
|
||||||
|
fn try_into(self)->Result<TimedPhysicsInstruction,Self::Error>{
|
||||||
|
Ok(strafesnet_common::instruction::TimedInstruction{
|
||||||
|
time:strafesnet_common::integer::Time::raw(self.time),
|
||||||
|
instruction:self.instruction.try_into()?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl TryFrom<TimedPhysicsInstruction> for TimedInstruction{
|
||||||
|
type Error=super::physics::InstructionConvert;
|
||||||
|
fn try_from(value:TimedPhysicsInstruction)->Result<Self,Self::Error>{
|
||||||
|
Ok(Self{
|
||||||
|
time:value.time.get(),
|
||||||
|
instruction:value.instruction.try_into()?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[binrw::binrw]
|
||||||
|
#[brw(little)]
|
||||||
|
pub enum Instruction{
|
||||||
|
#[brw(magic=0u8)]
|
||||||
|
ReplaceMouse{
|
||||||
|
m0:super::mouse::MouseState,
|
||||||
|
m1:super::mouse::MouseState
|
||||||
|
},
|
||||||
|
#[brw(magic=1u8)]
|
||||||
|
SetNextMouse(super::mouse::MouseState),
|
||||||
|
#[brw(magic=2u8)]
|
||||||
|
SetMoveRight(
|
||||||
|
#[br(map=bool_from_u8)]
|
||||||
|
#[bw(map=bool_into_u8)]
|
||||||
|
bool),
|
||||||
|
#[brw(magic=3u8)]
|
||||||
|
SetMoveUp(
|
||||||
|
#[br(map=bool_from_u8)]
|
||||||
|
#[bw(map=bool_into_u8)]
|
||||||
|
bool),
|
||||||
|
#[brw(magic=4u8)]
|
||||||
|
SetMoveBack(
|
||||||
|
#[br(map=bool_from_u8)]
|
||||||
|
#[bw(map=bool_into_u8)]
|
||||||
|
bool),
|
||||||
|
#[brw(magic=5u8)]
|
||||||
|
SetMoveLeft(
|
||||||
|
#[br(map=bool_from_u8)]
|
||||||
|
#[bw(map=bool_into_u8)]
|
||||||
|
bool),
|
||||||
|
#[brw(magic=6u8)]
|
||||||
|
SetMoveDown(
|
||||||
|
#[br(map=bool_from_u8)]
|
||||||
|
#[bw(map=bool_into_u8)]
|
||||||
|
bool),
|
||||||
|
#[brw(magic=7u8)]
|
||||||
|
SetMoveForward(
|
||||||
|
#[br(map=bool_from_u8)]
|
||||||
|
#[bw(map=bool_into_u8)]
|
||||||
|
bool),
|
||||||
|
#[brw(magic=8u8)]
|
||||||
|
SetJump(
|
||||||
|
#[br(map=bool_from_u8)]
|
||||||
|
#[bw(map=bool_into_u8)]
|
||||||
|
bool),
|
||||||
|
#[brw(magic=9u8)]
|
||||||
|
SetZoom(
|
||||||
|
#[br(map=bool_from_u8)]
|
||||||
|
#[bw(map=bool_into_u8)]
|
||||||
|
bool),
|
||||||
|
#[brw(magic=10u8)]
|
||||||
|
Reset,
|
||||||
|
#[brw(magic=11u8)]
|
||||||
|
Restart(super::gameplay_modes::ModeId),
|
||||||
|
#[brw(magic=12u8)]
|
||||||
|
Spawn(super::gameplay_modes::ModeId,super::gameplay_modes::StageId),
|
||||||
|
#[brw(magic=13u8)]
|
||||||
|
PracticeFly,
|
||||||
|
#[brw(magic=14u8)]
|
||||||
|
SetSensitivity(super::integer::Ratio64Vec2),
|
||||||
|
#[brw(magic=255u8)]
|
||||||
|
Idle,
|
||||||
|
}
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum InstructionConvert{
|
||||||
|
/// This is an instruction that can be dropped when serializing
|
||||||
|
DropInstruction,
|
||||||
|
}
|
||||||
|
impl std::fmt::Display for InstructionConvert{
|
||||||
|
fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{
|
||||||
|
write!(f,"{self:?}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl std::error::Error for InstructionConvert{}
|
||||||
|
impl TryInto<strafesnet_common::physics::Instruction> for Instruction{
|
||||||
|
type Error=super::integer::RatioError;
|
||||||
|
fn try_into(self)->Result<strafesnet_common::physics::Instruction,Self::Error>{
|
||||||
|
Ok(match self{
|
||||||
|
Instruction::ReplaceMouse{m0,m1}=>strafesnet_common::physics::Instruction::Mouse(strafesnet_common::physics::MouseInstruction::ReplaceMouse{m0:m0.into(),m1:m1.into()}),
|
||||||
|
Instruction::SetNextMouse(m)=>strafesnet_common::physics::Instruction::Mouse(strafesnet_common::physics::MouseInstruction::SetNextMouse(m.into())),
|
||||||
|
Instruction::SetMoveRight(state)=>strafesnet_common::physics::Instruction::SetControl(strafesnet_common::physics::SetControlInstruction::SetMoveRight(state.into())),
|
||||||
|
Instruction::SetMoveUp(state)=>strafesnet_common::physics::Instruction::SetControl(strafesnet_common::physics::SetControlInstruction::SetMoveUp(state.into())),
|
||||||
|
Instruction::SetMoveBack(state)=>strafesnet_common::physics::Instruction::SetControl(strafesnet_common::physics::SetControlInstruction::SetMoveBack(state.into())),
|
||||||
|
Instruction::SetMoveLeft(state)=>strafesnet_common::physics::Instruction::SetControl(strafesnet_common::physics::SetControlInstruction::SetMoveLeft(state.into())),
|
||||||
|
Instruction::SetMoveDown(state)=>strafesnet_common::physics::Instruction::SetControl(strafesnet_common::physics::SetControlInstruction::SetMoveDown(state.into())),
|
||||||
|
Instruction::SetMoveForward(state)=>strafesnet_common::physics::Instruction::SetControl(strafesnet_common::physics::SetControlInstruction::SetMoveForward(state.into())),
|
||||||
|
Instruction::SetJump(state)=>strafesnet_common::physics::Instruction::SetControl(strafesnet_common::physics::SetControlInstruction::SetJump(state.into())),
|
||||||
|
Instruction::SetZoom(state)=>strafesnet_common::physics::Instruction::SetControl(strafesnet_common::physics::SetControlInstruction::SetZoom(state.into())),
|
||||||
|
Instruction::Reset=>strafesnet_common::physics::Instruction::Mode(strafesnet_common::physics::ModeInstruction::Reset),
|
||||||
|
Instruction::Restart(mode_id)=>strafesnet_common::physics::Instruction::Mode(strafesnet_common::physics::ModeInstruction::Restart(strafesnet_common::gameplay_modes::ModeId::new(mode_id))),
|
||||||
|
Instruction::Spawn(mode_id,stage_id)=>strafesnet_common::physics::Instruction::Mode(strafesnet_common::physics::ModeInstruction::Spawn(
|
||||||
|
strafesnet_common::gameplay_modes::ModeId::new(mode_id),
|
||||||
|
strafesnet_common::gameplay_modes::StageId::new(stage_id),
|
||||||
|
)),
|
||||||
|
Instruction::PracticeFly=>strafesnet_common::physics::Instruction::Misc(strafesnet_common::physics::MiscInstruction::PracticeFly),
|
||||||
|
Instruction::SetSensitivity(sensitivity)=>strafesnet_common::physics::Instruction::Misc(strafesnet_common::physics::MiscInstruction::SetSensitivity(sensitivity.try_into()?)),
|
||||||
|
Instruction::Idle=>strafesnet_common::physics::Instruction::Idle,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl TryFrom<strafesnet_common::physics::Instruction> for Instruction{
|
||||||
|
type Error=InstructionConvert;
|
||||||
|
fn try_from(value:strafesnet_common::physics::Instruction)->Result<Self,Self::Error>{
|
||||||
|
match value{
|
||||||
|
strafesnet_common::physics::Instruction::Mouse(strafesnet_common::physics::MouseInstruction::ReplaceMouse{m0,m1})=>Ok(Instruction::ReplaceMouse{m0:m0.into(),m1:m1.into()}),
|
||||||
|
strafesnet_common::physics::Instruction::Mouse(strafesnet_common::physics::MouseInstruction::SetNextMouse(m))=>Ok(Instruction::SetNextMouse(m.into())),
|
||||||
|
strafesnet_common::physics::Instruction::SetControl(strafesnet_common::physics::SetControlInstruction::SetMoveRight(state))=>Ok(Instruction::SetMoveRight(state.into())),
|
||||||
|
strafesnet_common::physics::Instruction::SetControl(strafesnet_common::physics::SetControlInstruction::SetMoveUp(state))=>Ok(Instruction::SetMoveUp(state.into())),
|
||||||
|
strafesnet_common::physics::Instruction::SetControl(strafesnet_common::physics::SetControlInstruction::SetMoveBack(state))=>Ok(Instruction::SetMoveBack(state.into())),
|
||||||
|
strafesnet_common::physics::Instruction::SetControl(strafesnet_common::physics::SetControlInstruction::SetMoveLeft(state))=>Ok(Instruction::SetMoveLeft(state.into())),
|
||||||
|
strafesnet_common::physics::Instruction::SetControl(strafesnet_common::physics::SetControlInstruction::SetMoveDown(state))=>Ok(Instruction::SetMoveDown(state.into())),
|
||||||
|
strafesnet_common::physics::Instruction::SetControl(strafesnet_common::physics::SetControlInstruction::SetMoveForward(state))=>Ok(Instruction::SetMoveForward(state.into())),
|
||||||
|
strafesnet_common::physics::Instruction::SetControl(strafesnet_common::physics::SetControlInstruction::SetJump(state))=>Ok(Instruction::SetJump(state.into())),
|
||||||
|
strafesnet_common::physics::Instruction::SetControl(strafesnet_common::physics::SetControlInstruction::SetZoom(state))=>Ok(Instruction::SetZoom(state.into())),
|
||||||
|
strafesnet_common::physics::Instruction::Mode(strafesnet_common::physics::ModeInstruction::Reset)=>Ok(Instruction::Reset),
|
||||||
|
strafesnet_common::physics::Instruction::Mode(strafesnet_common::physics::ModeInstruction::Restart(mode_id))=>Ok(Instruction::Restart(mode_id.get())),
|
||||||
|
strafesnet_common::physics::Instruction::Mode(strafesnet_common::physics::ModeInstruction::Spawn(mode_id,stage_id))=>Ok(Instruction::Spawn(
|
||||||
|
mode_id.get(),
|
||||||
|
stage_id.get(),
|
||||||
|
)),
|
||||||
|
strafesnet_common::physics::Instruction::Misc(strafesnet_common::physics::MiscInstruction::PracticeFly)=>Ok(Instruction::PracticeFly),
|
||||||
|
strafesnet_common::physics::Instruction::Misc(strafesnet_common::physics::MiscInstruction::SetSensitivity(sensitivity))=>Ok(Instruction::SetSensitivity(sensitivity.into())),
|
||||||
|
strafesnet_common::physics::Instruction::Idle=>Ok(Instruction::Idle),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
38
map-tool/Cargo.toml
Normal file
38
map-tool/Cargo.toml
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
[package]
|
||||||
|
name = "map-tool"
|
||||||
|
version = "1.7.0"
|
||||||
|
edition = "2024"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
anyhow = "1.0.75"
|
||||||
|
clap = { version = "4.4.2", features = ["derive"] }
|
||||||
|
flate2 = "1.0.27"
|
||||||
|
futures = "0.3.31"
|
||||||
|
image = "0.25.2"
|
||||||
|
image_dds = "0.7.1"
|
||||||
|
lazy-regex = "3.1.0"
|
||||||
|
rbx_asset = { version = "0.2.5", registry = "strafesnet" }
|
||||||
|
rbx_binary = { version = "0.7.4", registry = "strafesnet" }
|
||||||
|
rbx_dom_weak = { version = "2.7.0", registry = "strafesnet" }
|
||||||
|
rbx_reflection_database = { version = "0.2.10", registry = "strafesnet" }
|
||||||
|
rbx_xml = { version = "0.13.3", registry = "strafesnet" }
|
||||||
|
rbxassetid = { version = "0.1.0", registry = "strafesnet" }
|
||||||
|
strafesnet_bsp_loader = { version = "0.3.0", path = "../lib/bsp_loader", registry = "strafesnet" }
|
||||||
|
strafesnet_deferred_loader = { version = "0.5.0", path = "../lib/deferred_loader", registry = "strafesnet" }
|
||||||
|
strafesnet_rbx_loader = { version = "0.6.0", path = "../lib/rbx_loader", registry = "strafesnet" }
|
||||||
|
strafesnet_snf = { version = "0.3.0", path = "../lib/snf", registry = "strafesnet" }
|
||||||
|
thiserror = "2.0.11"
|
||||||
|
tokio = { version = "1.43.0", features = ["macros", "rt-multi-thread", "fs"] }
|
||||||
|
vbsp = "0.8.0"
|
||||||
|
vbsp-entities-css = "0.6.0"
|
||||||
|
vmdl = "0.2.0"
|
||||||
|
vmt-parser = "0.2.0"
|
||||||
|
vpk = "0.3.0"
|
||||||
|
vtf = "0.3.0"
|
||||||
|
|
||||||
|
#[profile.release]
|
||||||
|
#lto = true
|
||||||
|
#strip = true
|
||||||
|
#codegen-units = 1
|
||||||
23
map-tool/LICENSE
Normal file
23
map-tool/LICENSE
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
Permission is hereby granted, free of charge, to any
|
||||||
|
person obtaining a copy of this software and associated
|
||||||
|
documentation files (the "Software"), to deal in the
|
||||||
|
Software without restriction, including without
|
||||||
|
limitation the rights to use, copy, modify, merge,
|
||||||
|
publish, distribute, sublicense, and/or sell copies of
|
||||||
|
the Software, and to permit persons to whom the Software
|
||||||
|
is furnished to do so, subject to the following
|
||||||
|
conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice
|
||||||
|
shall be included in all copies or substantial portions
|
||||||
|
of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
|
||||||
|
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
|
||||||
|
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
|
||||||
|
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||||
|
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||||
|
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
|
||||||
|
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||||
|
DEALINGS IN THE SOFTWARE.
|
||||||
2
map-tool/README.md
Normal file
2
map-tool/README.md
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
# map-tool
|
||||||
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user