Compare commits
51 Commits
Author | SHA1 | Date | |
---|---|---|---|
f98ffe6e0b | |||
f9d4ca8370 | |||
fea5bf4398 | |||
d393f9f187 | |||
ada34237c9 | |||
292df72709 | |||
7476d7cdc7 | |||
6138d70a6f | |||
cac4d698c3 | |||
e0ea0d6119 | |||
80a4431ee8 | |||
80424cf24c | |||
c338826513 | |||
a6a242175b | |||
08bd57ffe1 | |||
0d9c6648e2 | |||
405cba3549 | |||
38d8dc1302 | |||
33ccefc411 | |||
93277c042b | |||
90f6437817 | |||
29f9d5298f | |||
b0489a3746 | |||
a8847d3632 | |||
fb8c2a619a | |||
6898302fa5 | |||
52bbaaddc7 | |||
a8581a2a4f | |||
c6ff11dd3e | |||
844c7a08e1 | |||
bd61d03c91 | |||
b58ebb2775 | |||
9095215cad | |||
92c30c3b87 | |||
1b35c96f6e | |||
47bf9f1af3 | |||
719c702b95 | |||
ceb2499ad2 | |||
fe43ce9df6 | |||
1fcd18bc45 | |||
e371f95a4b | |||
b02c1bc7b4 | |||
89446a933a | |||
0a3d965bb6 | |||
b6206d52c8 | |||
498c628280 | |||
273e915f67 | |||
5072e5d7a8 | |||
3f0e3e0d3c | |||
2e88ae0612 | |||
4c216a5b28 |
.cargo
Cargo.lockCargo.tomlREADME.mdengine
graphics
physics
session
settings
integration-testing
lib
bsp_loader
common
Cargo.toml
src
deferred_loader
Cargo.toml
src
fixed_wide
linear_ops
ratio_ops
rbx_loader
rbxassetid
roblox_emulator
@ -1,2 +1,6 @@
|
|||||||
[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"]
|
||||||
|
2912
Cargo.lock
generated
2912
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
11
Cargo.toml
11
Cargo.toml
@ -1,10 +1,5 @@
|
|||||||
[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",
|
||||||
@ -12,10 +7,8 @@ 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"
|
||||||
@ -24,7 +17,3 @@ resolver = "2"
|
|||||||
#lto = true
|
#lto = true
|
||||||
strip = true
|
strip = true
|
||||||
codegen-units = 1
|
codegen-units = 1
|
||||||
|
|
||||||
[profile.dev]
|
|
||||||
strip = false
|
|
||||||
opt-level = 3
|
|
||||||
|
@ -3,9 +3,6 @@
|
|||||||
# 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`
|
||||||
@ -13,4 +10,4 @@ See [releases](https://git.itzana.me/StrafesNET/strafe-project/releases) for dow
|
|||||||
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 and engine crates have a sole proprietor license.
|
Each project has its own license. Most crates are MIT/Apache but notably the Strafe Client has a sole proprietor license.
|
||||||
|
@ -1,14 +0,0 @@
|
|||||||
[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 = "25.0.0"
|
|
@ -1,8 +0,0 @@
|
|||||||
/*******************************************************
|
|
||||||
* Copyright (C) 2023-2024 Rhys Lloyd <krakow20@gmail.com>
|
|
||||||
*
|
|
||||||
* This file is part of the StrafesNET bhop/surf client.
|
|
||||||
*
|
|
||||||
* StrafesNET can not be copied and/or distributed
|
|
||||||
* without the express permission of Rhys Lloyd
|
|
||||||
*******************************************************/
|
|
@ -1,2 +0,0 @@
|
|||||||
pub mod model;
|
|
||||||
pub mod graphics;
|
|
@ -1,10 +0,0 @@
|
|||||||
[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" }
|
|
@ -1,8 +0,0 @@
|
|||||||
/*******************************************************
|
|
||||||
* Copyright (C) 2023-2024 Rhys Lloyd <krakow20@gmail.com>
|
|
||||||
*
|
|
||||||
* This file is part of the StrafesNET bhop/surf client.
|
|
||||||
*
|
|
||||||
* StrafesNET can not be copied and/or distributed
|
|
||||||
* without the express permission of Rhys Lloyd
|
|
||||||
*******************************************************/
|
|
@ -1,45 +0,0 @@
|
|||||||
mod body;
|
|
||||||
mod push_solve;
|
|
||||||
mod face_crawler;
|
|
||||||
mod model;
|
|
||||||
|
|
||||||
pub mod physics;
|
|
||||||
|
|
||||||
// Physics bug fixes can easily desync all bots.
|
|
||||||
//
|
|
||||||
// When replaying a bot, use the exact physics version which it was recorded with.
|
|
||||||
//
|
|
||||||
// When validating a new bot, ignore the version and use the latest version,
|
|
||||||
// and overwrite the version in the file.
|
|
||||||
//
|
|
||||||
// Compatible physics versions should be determined
|
|
||||||
// empirically at development time via leaderboard resimulation.
|
|
||||||
//
|
|
||||||
// Compatible physics versions should result in an identical leaderboard state,
|
|
||||||
// or the only bots which fail are ones exploiting a surgically patched bug.
|
|
||||||
#[derive(Clone,Copy,Hash,Debug,id::Id,Eq,PartialEq,Ord,PartialOrd)]
|
|
||||||
pub struct PhysicsVersion(u32);
|
|
||||||
pub const VERSION:PhysicsVersion=PhysicsVersion(0);
|
|
||||||
const LATEST_COMPATIBLE_VERSION:[u32;1+VERSION.0 as usize]=const{
|
|
||||||
let compat=[0];
|
|
||||||
|
|
||||||
let mut input_version=0;
|
|
||||||
while input_version<compat.len(){
|
|
||||||
// compatible version must be greater 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,12 +0,0 @@
|
|||||||
[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" }
|
|
@ -1,8 +0,0 @@
|
|||||||
/*******************************************************
|
|
||||||
* Copyright (C) 2023-2024 Rhys Lloyd <krakow20@gmail.com>
|
|
||||||
*
|
|
||||||
* This file is part of the StrafesNET bhop/surf client.
|
|
||||||
*
|
|
||||||
* StrafesNET can not be copied and/or distributed
|
|
||||||
* without the express permission of Rhys Lloyd
|
|
||||||
*******************************************************/
|
|
@ -1,2 +0,0 @@
|
|||||||
mod mouse_interpolator;
|
|
||||||
pub mod session;
|
|
@ -1,443 +0,0 @@
|
|||||||
use std::collections::HashMap;
|
|
||||||
|
|
||||||
use strafesnet_common::gameplay_modes::{ModeId,StageId};
|
|
||||||
use strafesnet_common::instruction::{InstructionConsumer,InstructionEmitter,InstructionFeedback,TimedInstruction};
|
|
||||||
// session represents the non-hardware state of the client.
|
|
||||||
// Ideally it is a deterministic state which is atomically updated by instructions, same as the simulation state.
|
|
||||||
use strafesnet_common::physics::{
|
|
||||||
ModeInstruction,MiscInstruction,
|
|
||||||
Instruction as PhysicsInputInstruction,
|
|
||||||
TimeInner as PhysicsTimeInner,
|
|
||||||
Time as PhysicsTime
|
|
||||||
};
|
|
||||||
use strafesnet_common::timer::{Scaled,Timer};
|
|
||||||
use strafesnet_common::session::{TimeInner as SessionTimeInner,Time as SessionTime};
|
|
||||||
use strafesnet_settings::directories::Directories;
|
|
||||||
|
|
||||||
use crate::mouse_interpolator::{MouseInterpolator,StepInstruction,Instruction as MouseInterpolatorInstruction};
|
|
||||||
use strafesnet_physics::physics::{self,PhysicsContext,PhysicsData};
|
|
||||||
use strafesnet_settings::settings::UserSettings;
|
|
||||||
|
|
||||||
pub enum Instruction<'a>{
|
|
||||||
Input(SessionInputInstruction),
|
|
||||||
Control(SessionControlInstruction),
|
|
||||||
Playback(SessionPlaybackInstruction),
|
|
||||||
ChangeMap(&'a strafesnet_common::map::CompleteMap),
|
|
||||||
LoadReplay(strafesnet_snf::bot::Segment),
|
|
||||||
Idle,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub enum SessionInputInstruction{
|
|
||||||
Mouse(glam::IVec2),
|
|
||||||
SetControl(strafesnet_common::physics::SetControlInstruction),
|
|
||||||
Mode(ImplicitModeInstruction),
|
|
||||||
Misc(strafesnet_common::physics::MiscInstruction),
|
|
||||||
}
|
|
||||||
/// Implicit mode instruction are fed separately to session.
|
|
||||||
/// Session generates the explicit mode instructions interlaced with a SetSensitivity instruction
|
|
||||||
#[derive(Clone,Debug)]
|
|
||||||
pub enum ImplicitModeInstruction{
|
|
||||||
ResetAndRestart,
|
|
||||||
ResetAndSpawn(ModeId,StageId),
|
|
||||||
}
|
|
||||||
|
|
||||||
pub enum SessionControlInstruction{
|
|
||||||
SetPaused(bool),
|
|
||||||
// copy the current session simulation recording into a replay and view it
|
|
||||||
CopyRecordingIntoReplayAndSpectate,
|
|
||||||
StopSpectate,
|
|
||||||
SaveReplay,
|
|
||||||
LoadIntoReplayState,
|
|
||||||
}
|
|
||||||
pub enum SessionPlaybackInstruction{
|
|
||||||
SkipForward,
|
|
||||||
SkipBack,
|
|
||||||
TogglePaused,
|
|
||||||
DecreaseTimescale,
|
|
||||||
IncreaseTimescale,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct FrameState{
|
|
||||||
pub body:physics::Body,
|
|
||||||
pub camera:physics::PhysicsCamera,
|
|
||||||
pub time:PhysicsTime,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct Simulation{
|
|
||||||
timer:Timer<Scaled<SessionTimeInner,PhysicsTimeInner>>,
|
|
||||||
physics:physics::PhysicsState,
|
|
||||||
}
|
|
||||||
impl Simulation{
|
|
||||||
pub const fn new(
|
|
||||||
timer:Timer<Scaled<SessionTimeInner,PhysicsTimeInner>>,
|
|
||||||
physics:physics::PhysicsState,
|
|
||||||
)->Self{
|
|
||||||
Self{
|
|
||||||
timer,
|
|
||||||
physics,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub fn get_frame_state(&self,time:SessionTime)->FrameState{
|
|
||||||
FrameState{
|
|
||||||
body:self.physics.camera_body(),
|
|
||||||
camera:self.physics.camera(),
|
|
||||||
time:self.timer.time(time),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Default)]
|
|
||||||
pub struct Recording{
|
|
||||||
instructions:Vec<TimedInstruction<PhysicsInputInstruction,PhysicsTime>>,
|
|
||||||
}
|
|
||||||
impl Recording{
|
|
||||||
pub fn new(
|
|
||||||
instructions:Vec<TimedInstruction<PhysicsInputInstruction,PhysicsTime>>,
|
|
||||||
)->Self{
|
|
||||||
Self{instructions}
|
|
||||||
}
|
|
||||||
fn clear(&mut self){
|
|
||||||
self.instructions.clear();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub struct Replay{
|
|
||||||
next_instruction_id:usize,
|
|
||||||
recording:Recording,
|
|
||||||
simulation:Simulation,
|
|
||||||
}
|
|
||||||
impl Replay{
|
|
||||||
pub const fn new(
|
|
||||||
recording:Recording,
|
|
||||||
simulation:Simulation,
|
|
||||||
)->Self{
|
|
||||||
Self{
|
|
||||||
next_instruction_id:0,
|
|
||||||
recording,
|
|
||||||
simulation,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub fn advance(&mut self,physics_data:&PhysicsData,time_limit:SessionTime){
|
|
||||||
let mut time=self.simulation.timer.time(time_limit);
|
|
||||||
loop{
|
|
||||||
if let Some(ins)=self.recording.instructions.get(self.next_instruction_id){
|
|
||||||
if ins.time<time{
|
|
||||||
PhysicsContext::run_input_instruction(&mut self.simulation.physics,physics_data,ins.clone());
|
|
||||||
self.next_instruction_id+=1;
|
|
||||||
}else{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}else{
|
|
||||||
// loop playback
|
|
||||||
self.next_instruction_id=0;
|
|
||||||
// No need to reset physics because the very first instruction is 'Reset'
|
|
||||||
let new_time=self.recording.instructions.first().map_or(PhysicsTime::ZERO,|ins|ins.time);
|
|
||||||
self.simulation.timer.set_time(time_limit,new_time);
|
|
||||||
time=new_time;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone,Copy,Hash,PartialEq,Eq)]
|
|
||||||
struct BotId(u32);
|
|
||||||
//#[derive(Clone,Copy,Hash,PartialEq,Eq)]
|
|
||||||
//struct PlayerId(u32);
|
|
||||||
|
|
||||||
enum ViewState{
|
|
||||||
Play,
|
|
||||||
//Spectate(PlayerId),
|
|
||||||
Replay(BotId),
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct Session{
|
|
||||||
directories:Directories,
|
|
||||||
user_settings:UserSettings,
|
|
||||||
mouse_interpolator:crate::mouse_interpolator::MouseInterpolator,
|
|
||||||
view_state:ViewState,
|
|
||||||
//gui:GuiState
|
|
||||||
geometry_shared:physics::PhysicsData,
|
|
||||||
simulation:Simulation,
|
|
||||||
// below fields not included in lite session
|
|
||||||
recording:Recording,
|
|
||||||
//players:HashMap<PlayerId,Simulation>,
|
|
||||||
replays:HashMap<BotId,Replay>,
|
|
||||||
}
|
|
||||||
impl Session{
|
|
||||||
pub fn new(
|
|
||||||
user_settings:UserSettings,
|
|
||||||
directories:Directories,
|
|
||||||
simulation:Simulation,
|
|
||||||
)->Self{
|
|
||||||
Self{
|
|
||||||
user_settings,
|
|
||||||
directories,
|
|
||||||
mouse_interpolator:MouseInterpolator::new(),
|
|
||||||
geometry_shared:Default::default(),
|
|
||||||
simulation,
|
|
||||||
view_state:ViewState::Play,
|
|
||||||
recording:Default::default(),
|
|
||||||
replays:HashMap::new(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fn clear_recording(&mut self){
|
|
||||||
self.recording.clear();
|
|
||||||
}
|
|
||||||
fn change_map(&mut self,map:&strafesnet_common::map::CompleteMap){
|
|
||||||
self.simulation.physics.clear();
|
|
||||||
self.geometry_shared.generate_models(map);
|
|
||||||
}
|
|
||||||
pub fn get_frame_state(&self,time:SessionTime)->Option<FrameState>{
|
|
||||||
match &self.view_state{
|
|
||||||
ViewState::Play=>Some(self.simulation.get_frame_state(time)),
|
|
||||||
ViewState::Replay(bot_id)=>self.replays.get(bot_id).map(|replay|
|
|
||||||
replay.simulation.get_frame_state(time)
|
|
||||||
),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub fn user_settings(&self)->&UserSettings{
|
|
||||||
&self.user_settings
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// mouseinterpolator consumes RawInputInstruction
|
|
||||||
// mouseinterpolator emits PhysicsInputInstruction
|
|
||||||
// mouseinterpolator consumes DoStep to move on to the next emitted instruction
|
|
||||||
// Session comsumes SessionInstruction -> forwards RawInputInstruction to mouseinterpolator
|
|
||||||
// Session consumes DoStep -> forwards DoStep to mouseinterpolator
|
|
||||||
// Session emits DoStep
|
|
||||||
|
|
||||||
impl InstructionConsumer<Instruction<'_>> for Session{
|
|
||||||
type Time=SessionTime;
|
|
||||||
fn process_instruction(&mut self,ins:TimedInstruction<Instruction,Self::Time>){
|
|
||||||
// repetitive procedure macro
|
|
||||||
macro_rules! run_mouse_interpolator_instruction{
|
|
||||||
($instruction:expr)=>{
|
|
||||||
self.mouse_interpolator.process_instruction(TimedInstruction{
|
|
||||||
time:ins.time,
|
|
||||||
instruction:TimedInstruction{
|
|
||||||
time:self.simulation.timer.time(ins.time),
|
|
||||||
instruction:$instruction,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// process any timeouts that occured since the last instruction
|
|
||||||
self.process_exhaustive(ins.time);
|
|
||||||
|
|
||||||
match ins.instruction{
|
|
||||||
// send it down to MouseInterpolator with two timestamps, SessionTime and PhysicsTime
|
|
||||||
Instruction::Input(SessionInputInstruction::Mouse(pos))=>{
|
|
||||||
run_mouse_interpolator_instruction!(MouseInterpolatorInstruction::MoveMouse(pos));
|
|
||||||
},
|
|
||||||
Instruction::Input(SessionInputInstruction::SetControl(set_control_instruction))=>{
|
|
||||||
run_mouse_interpolator_instruction!(MouseInterpolatorInstruction::SetControl(set_control_instruction));
|
|
||||||
},
|
|
||||||
Instruction::Input(SessionInputInstruction::Mode(ImplicitModeInstruction::ResetAndRestart))=>{
|
|
||||||
self.clear_recording();
|
|
||||||
let mode_id=self.simulation.physics.mode();
|
|
||||||
run_mouse_interpolator_instruction!(MouseInterpolatorInstruction::Mode(ModeInstruction::Reset));
|
|
||||||
run_mouse_interpolator_instruction!(MouseInterpolatorInstruction::Misc(MiscInstruction::SetSensitivity(self.user_settings().calculate_sensitivity())));
|
|
||||||
run_mouse_interpolator_instruction!(MouseInterpolatorInstruction::Mode(ModeInstruction::Restart(mode_id)));
|
|
||||||
},
|
|
||||||
Instruction::Input(SessionInputInstruction::Mode(ImplicitModeInstruction::ResetAndSpawn(mode_id,spawn_id)))=>{
|
|
||||||
self.clear_recording();
|
|
||||||
run_mouse_interpolator_instruction!(MouseInterpolatorInstruction::Mode(ModeInstruction::Reset));
|
|
||||||
run_mouse_interpolator_instruction!(MouseInterpolatorInstruction::Misc(MiscInstruction::SetSensitivity(self.user_settings().calculate_sensitivity())));
|
|
||||||
run_mouse_interpolator_instruction!(MouseInterpolatorInstruction::Mode(ModeInstruction::Spawn(mode_id,spawn_id)));
|
|
||||||
},
|
|
||||||
Instruction::Input(SessionInputInstruction::Misc(misc_instruction))=>{
|
|
||||||
run_mouse_interpolator_instruction!(MouseInterpolatorInstruction::Misc(misc_instruction));
|
|
||||||
},
|
|
||||||
Instruction::Control(SessionControlInstruction::SetPaused(paused))=>{
|
|
||||||
// don't flush the buffered instructions in the mouse interpolator
|
|
||||||
// until the mouse is confirmed to be not moving at a later time
|
|
||||||
// what if they pause for 5ms lmao
|
|
||||||
_=self.simulation.timer.set_paused(ins.time,paused);
|
|
||||||
},
|
|
||||||
Instruction::Control(SessionControlInstruction::CopyRecordingIntoReplayAndSpectate)=> if let ViewState::Play=self.view_state{
|
|
||||||
// Bind: B
|
|
||||||
|
|
||||||
// pause simulation
|
|
||||||
_=self.simulation.timer.set_paused(ins.time,true);
|
|
||||||
|
|
||||||
// create recording
|
|
||||||
let mut recording=Recording::default();
|
|
||||||
recording.instructions.extend(self.recording.instructions.iter().cloned());
|
|
||||||
|
|
||||||
// create timer starting at first instruction (or zero if the list is empty)
|
|
||||||
let new_time=recording.instructions.first().map_or(PhysicsTime::ZERO,|ins|ins.time);
|
|
||||||
let timer=Timer::unpaused(ins.time,new_time);
|
|
||||||
|
|
||||||
// create default physics state
|
|
||||||
let simulation=Simulation::new(timer,Default::default());
|
|
||||||
|
|
||||||
// invent a new bot id and insert the replay
|
|
||||||
let bot_id=BotId(self.replays.len() as u32);
|
|
||||||
self.replays.insert(bot_id,Replay::new(
|
|
||||||
recording,
|
|
||||||
simulation,
|
|
||||||
));
|
|
||||||
|
|
||||||
// begin spectate
|
|
||||||
self.view_state=ViewState::Replay(bot_id);
|
|
||||||
},
|
|
||||||
Instruction::Control(SessionControlInstruction::StopSpectate)=>{
|
|
||||||
let view_state=core::mem::replace(&mut self.view_state,ViewState::Play);
|
|
||||||
// delete the bot, otherwise it's inaccessible and wastes CPU
|
|
||||||
match view_state{
|
|
||||||
ViewState::Play=>(),
|
|
||||||
ViewState::Replay(bot_id)=>{
|
|
||||||
self.replays.remove(&bot_id);
|
|
||||||
},
|
|
||||||
}
|
|
||||||
_=self.simulation.timer.set_paused(ins.time,false);
|
|
||||||
},
|
|
||||||
Instruction::Control(SessionControlInstruction::SaveReplay)=>{
|
|
||||||
// Bind: N
|
|
||||||
let view_state=core::mem::replace(&mut self.view_state,ViewState::Play);
|
|
||||||
match view_state{
|
|
||||||
ViewState::Play=>(),
|
|
||||||
ViewState::Replay(bot_id)=>if let Some(replay)=self.replays.remove(&bot_id){
|
|
||||||
let mut replays_path=self.directories.replays.clone();
|
|
||||||
let file_name=format!("{}.snfb",ins.time);
|
|
||||||
std::thread::spawn(move ||{
|
|
||||||
std::fs::create_dir_all(replays_path.as_path()).unwrap();
|
|
||||||
replays_path.push(file_name);
|
|
||||||
let file=std::fs::File::create(replays_path).unwrap();
|
|
||||||
strafesnet_snf::bot::write_bot(
|
|
||||||
std::io::BufWriter::new(file),
|
|
||||||
strafesnet_physics::VERSION.get(),
|
|
||||||
replay.recording.instructions
|
|
||||||
).unwrap();
|
|
||||||
println!("Finished writing bot file!");
|
|
||||||
});
|
|
||||||
},
|
|
||||||
}
|
|
||||||
_=self.simulation.timer.set_paused(ins.time,false);
|
|
||||||
},
|
|
||||||
Instruction::Control(SessionControlInstruction::LoadIntoReplayState)=>{
|
|
||||||
// Bind: J
|
|
||||||
let view_state=core::mem::replace(&mut self.view_state,ViewState::Play);
|
|
||||||
match view_state{
|
|
||||||
ViewState::Play=>(),
|
|
||||||
ViewState::Replay(bot_id)=>if let Some(replay)=self.replays.remove(&bot_id){
|
|
||||||
self.recording.instructions=replay.recording.instructions.into_iter().take(replay.next_instruction_id).collect();
|
|
||||||
self.simulation=replay.simulation;
|
|
||||||
},
|
|
||||||
}
|
|
||||||
// don't unpause -- use the replay timer state whether it is pasued or unpaused
|
|
||||||
},
|
|
||||||
Instruction::Playback(SessionPlaybackInstruction::IncreaseTimescale)=>{
|
|
||||||
match &self.view_state{
|
|
||||||
ViewState::Play=>{
|
|
||||||
// allow simulation timescale for fun
|
|
||||||
let scale=self.simulation.timer.get_scale();
|
|
||||||
self.simulation.timer.set_scale(ins.time,strafesnet_common::integer::Ratio64::new(scale.num()*5,scale.den()*4).unwrap());
|
|
||||||
},
|
|
||||||
ViewState::Replay(bot_id)=>if let Some(replay)=self.replays.get_mut(bot_id){
|
|
||||||
let scale=replay.simulation.timer.get_scale();
|
|
||||||
replay.simulation.timer.set_scale(ins.time,strafesnet_common::integer::Ratio64::new(scale.num()*5,scale.den()*4).unwrap());
|
|
||||||
},
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Instruction::Playback(SessionPlaybackInstruction::DecreaseTimescale)=>{
|
|
||||||
match &self.view_state{
|
|
||||||
ViewState::Play=>{
|
|
||||||
// allow simulation timescale for fun
|
|
||||||
let scale=self.simulation.timer.get_scale();
|
|
||||||
self.simulation.timer.set_scale(ins.time,strafesnet_common::integer::Ratio64::new(scale.num()*4,scale.den()*5).unwrap());
|
|
||||||
},
|
|
||||||
ViewState::Replay(bot_id)=>if let Some(replay)=self.replays.get_mut(bot_id){
|
|
||||||
let scale=replay.simulation.timer.get_scale();
|
|
||||||
replay.simulation.timer.set_scale(ins.time,strafesnet_common::integer::Ratio64::new(scale.num()*4,scale.den()*5).unwrap());
|
|
||||||
},
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Instruction::Playback(SessionPlaybackInstruction::SkipForward)=>{
|
|
||||||
match &self.view_state{
|
|
||||||
ViewState::Play=>(),
|
|
||||||
ViewState::Replay(bot_id)=>if let Some(replay)=self.replays.get_mut(bot_id){
|
|
||||||
let time=replay.simulation.timer.time(ins.time+SessionTime::from_secs(5));
|
|
||||||
replay.simulation.timer.set_time(ins.time,time);
|
|
||||||
},
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Instruction::Playback(SessionPlaybackInstruction::SkipBack)=>{
|
|
||||||
match &self.view_state{
|
|
||||||
ViewState::Play=>(),
|
|
||||||
ViewState::Replay(bot_id)=>if let Some(replay)=self.replays.get_mut(bot_id){
|
|
||||||
let time=replay.simulation.timer.time(ins.time+SessionTime::from_secs(5));
|
|
||||||
replay.simulation.timer.set_time(ins.time,time);
|
|
||||||
// resimulate the entire playback lol
|
|
||||||
replay.next_instruction_id=0;
|
|
||||||
},
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Instruction::Playback(SessionPlaybackInstruction::TogglePaused)=>{
|
|
||||||
match &self.view_state{
|
|
||||||
ViewState::Play=>(),
|
|
||||||
ViewState::Replay(bot_id)=>if let Some(replay)=self.replays.get_mut(bot_id){
|
|
||||||
_=replay.simulation.timer.set_paused(ins.time,!replay.simulation.timer.is_paused());
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Instruction::ChangeMap(complete_map)=>{
|
|
||||||
self.clear_recording();
|
|
||||||
self.change_map(complete_map);
|
|
||||||
},
|
|
||||||
Instruction::LoadReplay(bot)=>{
|
|
||||||
// pause simulation
|
|
||||||
_=self.simulation.timer.set_paused(ins.time,true);
|
|
||||||
|
|
||||||
// create recording
|
|
||||||
let recording=Recording::new(bot.instructions);
|
|
||||||
|
|
||||||
// create timer starting at first instruction (or zero if the list is empty)
|
|
||||||
let new_time=recording.instructions.first().map_or(PhysicsTime::ZERO,|ins|ins.time);
|
|
||||||
let timer=Timer::unpaused(ins.time,new_time);
|
|
||||||
|
|
||||||
// create default physics state
|
|
||||||
let simulation=Simulation::new(timer,Default::default());
|
|
||||||
|
|
||||||
// invent a new bot id and insert the replay
|
|
||||||
let bot_id=BotId(self.replays.len() as u32);
|
|
||||||
self.replays.insert(bot_id,Replay::new(
|
|
||||||
recording,
|
|
||||||
simulation,
|
|
||||||
));
|
|
||||||
|
|
||||||
// begin spectate
|
|
||||||
self.view_state=ViewState::Replay(bot_id);
|
|
||||||
},
|
|
||||||
Instruction::Idle=>{
|
|
||||||
run_mouse_interpolator_instruction!(MouseInterpolatorInstruction::Idle);
|
|
||||||
// this just refreshes the replays
|
|
||||||
for replay in self.replays.values_mut(){
|
|
||||||
// TODO: filter idles from recording, inject new idles in real time
|
|
||||||
replay.advance(&self.geometry_shared,ins.time);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// process all emitted output instructions
|
|
||||||
self.process_exhaustive(ins.time);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl InstructionConsumer<StepInstruction> for Session{
|
|
||||||
type Time=SessionTime;
|
|
||||||
fn process_instruction(&mut self,ins:TimedInstruction<StepInstruction,Self::Time>){
|
|
||||||
let time=self.simulation.timer.time(ins.time);
|
|
||||||
if let Some(instruction)=self.mouse_interpolator.pop_buffered_instruction(ins.set_time(time)){
|
|
||||||
//record
|
|
||||||
self.recording.instructions.push(instruction.clone());
|
|
||||||
PhysicsContext::run_input_instruction(&mut self.simulation.physics,&self.geometry_shared,instruction);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl InstructionEmitter<StepInstruction> for Session{
|
|
||||||
type Time=SessionTime;
|
|
||||||
fn next_instruction(&self,time_limit:SessionTime)->Option<TimedInstruction<StepInstruction,Self::Time>>{
|
|
||||||
self.mouse_interpolator.next_instruction(time_limit)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,10 +0,0 @@
|
|||||||
[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" }
|
|
@ -1,8 +0,0 @@
|
|||||||
/*******************************************************
|
|
||||||
* Copyright (C) 2023-2024 Rhys Lloyd <krakow20@gmail.com>
|
|
||||||
*
|
|
||||||
* This file is part of the StrafesNET bhop/surf client.
|
|
||||||
*
|
|
||||||
* StrafesNET can not be copied and/or distributed
|
|
||||||
* without the express permission of Rhys Lloyd
|
|
||||||
*******************************************************/
|
|
@ -1,32 +0,0 @@
|
|||||||
use std::path::PathBuf;
|
|
||||||
|
|
||||||
use crate::settings::{UserSettings,load_user_settings};
|
|
||||||
|
|
||||||
pub struct Directories{
|
|
||||||
pub settings:PathBuf,
|
|
||||||
pub maps:PathBuf,
|
|
||||||
pub replays:PathBuf,
|
|
||||||
}
|
|
||||||
impl Directories{
|
|
||||||
pub fn settings(&self)->UserSettings{
|
|
||||||
load_user_settings(&self.settings)
|
|
||||||
}
|
|
||||||
pub fn user()->Option<Self>{
|
|
||||||
let dirs=directories::ProjectDirs::from("net.strafes","StrafesNET","Strafe Client")?;
|
|
||||||
Some(Self{
|
|
||||||
settings:dirs.config_dir().join("settings.conf"),
|
|
||||||
maps:dirs.cache_dir().join("maps"),
|
|
||||||
// separate directory for remote downloaded replays (cache)
|
|
||||||
// bots:dirs.cache_dir().join("bots"),
|
|
||||||
replays:dirs.data_local_dir().join("replays"),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
pub fn portable()->Result<Self,std::io::Error>{
|
|
||||||
let current_dir=std::env::current_dir()?;
|
|
||||||
Ok(Self{
|
|
||||||
settings:current_dir.join("settings.conf"),
|
|
||||||
maps:current_dir.join("maps"),
|
|
||||||
replays:current_dir.join("replays"),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,2 +0,0 @@
|
|||||||
pub mod settings;
|
|
||||||
pub mod directories;
|
|
@ -1,9 +0,0 @@
|
|||||||
[package]
|
|
||||||
name = "integration-testing"
|
|
||||||
version = "0.1.0"
|
|
||||||
edition = "2024"
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
strafesnet_common = { path = "../lib/common", registry = "strafesnet" }
|
|
||||||
strafesnet_physics = { path = "../engine/physics", registry = "strafesnet" }
|
|
||||||
strafesnet_snf = { path = "../lib/snf", registry = "strafesnet" }
|
|
@ -1,253 +0,0 @@
|
|||||||
use std::io::Cursor;
|
|
||||||
use std::path::Path;
|
|
||||||
use std::time::Instant;
|
|
||||||
|
|
||||||
use strafesnet_physics::physics::{PhysicsData,PhysicsState,PhysicsContext};
|
|
||||||
|
|
||||||
fn main(){
|
|
||||||
let arg=std::env::args().skip(1).next();
|
|
||||||
match arg.as_deref(){
|
|
||||||
Some("determinism")|None=>test_determinism().unwrap(),
|
|
||||||
Some("replay")=>run_replay().unwrap(),
|
|
||||||
_=>println!("invalid argument"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[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.3.0"
|
version = "0.2.2"
|
||||||
edition = "2024"
|
edition = "2021"
|
||||||
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,10 +10,7 @@ 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.30.0"
|
glam = "0.29.0"
|
||||||
strafesnet_common = { version = "0.6.0", path = "../common", registry = "strafesnet" }
|
strafesnet_common = { path = "../common", registry = "strafesnet" }
|
||||||
strafesnet_deferred_loader = { version = "0.5.0", path = "../deferred_loader", registry = "strafesnet" }
|
vbsp = "0.6.0"
|
||||||
vbsp = "0.9.1"
|
|
||||||
vbsp-entities-css = "0.6.0"
|
|
||||||
vmdl = "0.2.0"
|
vmdl = "0.2.0"
|
||||||
vpk = "0.3.0"
|
|
||||||
|
@ -1,343 +0,0 @@
|
|||||||
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,
|
|
||||||
// Narrow(strafesnet_common::integer::NarrowError),
|
|
||||||
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.widen_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.widen_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.widen_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.widen_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().narrow_1().unwrap());
|
|
||||||
|
|
||||||
// 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,142 +1,43 @@
|
|||||||
use std::borrow::Cow;
|
use strafesnet_common::{map,model,integer,gameplay_attributes};
|
||||||
|
|
||||||
use vbsp_entities_css::Entity;
|
const VALVE_SCALE:f32=1.0/16.0;
|
||||||
|
fn valve_transform([x,y,z]:[f32;3])->integer::Planar64Vec3{
|
||||||
use strafesnet_common::{map,model,integer,gameplay_attributes as attr};
|
integer::vec3::try_from_f32_array([x*VALVE_SCALE,z*VALVE_SCALE,-y*VALVE_SCALE]).unwrap()
|
||||||
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::{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>(
|
||||||
fn add_brush<'a>(
|
bsp:&vbsp::Bsp,
|
||||||
mesh_deferred_loader:&mut MeshDeferredLoader<&'a str>,
|
mut acquire_render_config_id:AcquireRenderConfigId,
|
||||||
world_models:&mut Vec<model::Model>,
|
mut acquire_mesh_id:AcquireMeshId
|
||||||
prop_models:&mut Vec<model::Model>,
|
)->PartialMap1
|
||||||
model:&'a str,
|
where
|
||||||
origin:vbsp::Vector,
|
AcquireRenderConfigId:FnMut(Option<&str>)->model::RenderConfigId,
|
||||||
rendercolor:vbsp::Color,
|
AcquireMeshId:FnMut(&str)->model::MeshId,
|
||||||
attributes:attr::CollisionAttributesId,
|
{
|
||||||
){
|
|
||||||
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);
|
|
||||||
world_models.push(
|
|
||||||
model::Model{mesh,attributes,transform,color}
|
|
||||||
);
|
|
||||||
},
|
|
||||||
Err(e)=>{
|
|
||||||
println!("Brush model int parse error: {e} model={model}");
|
|
||||||
return;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
_=>{
|
|
||||||
let mesh=mesh_deferred_loader.acquire_mesh_id(model);
|
|
||||||
prop_models.push(
|
|
||||||
model::Model{mesh,attributes,transform,color}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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{
|
|
||||||
let bsp=bsp.as_ref();
|
|
||||||
//figure out real attributes later
|
//figure out real attributes later
|
||||||
let unique_attributes=vec![
|
let mut unique_attributes=Vec::new();
|
||||||
attr::CollisionAttributes::Decoration,
|
unique_attributes.push(gameplay_attributes::CollisionAttributes::Decoration);
|
||||||
attr::CollisionAttributes::contact_default(),
|
const TEMP_TOUCH_ME_ATTRIBUTE:gameplay_attributes::CollisionAttributesId=gameplay_attributes::CollisionAttributesId::new(0);
|
||||||
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);
|
|
||||||
|
|
||||||
|
let mut prop_mesh_count=0;
|
||||||
//declare all prop models to Loader
|
//declare all prop models to Loader
|
||||||
let mut prop_models=bsp.static_props().map(|prop|{
|
let 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=mesh_deferred_loader.acquire_mesh_id(prop.model());
|
let mesh_id=acquire_mesh_id(prop.model());
|
||||||
|
//not the most failsafe code but this is just for the map tool lmao
|
||||||
|
if prop_mesh_count==mesh_id.get(){
|
||||||
|
prop_mesh_count+=1;
|
||||||
|
};
|
||||||
|
let placement=prop.as_prop_placement();
|
||||||
model::Model{
|
model::Model{
|
||||||
mesh:mesh_id,
|
mesh:mesh_id,
|
||||||
attributes:ATTRIBUTE_DECORATION,
|
attributes:TEMP_TOUCH_ME_ATTRIBUTE,
|
||||||
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_euler(
|
*glam::Mat3A::from_quat(glam::Quat::from_array(placement.rotation.into()))
|
||||||
glam::EulerRot::XYZ,
|
).to_cols_array_2d()).unwrap(),
|
||||||
prop.angles.pitch*DEG_TO_RAD,
|
valve_transform(placement.origin.into()),
|
||||||
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,
|
||||||
}
|
}
|
||||||
@ -146,12 +47,14 @@ pub fn convert<'a>(
|
|||||||
|
|
||||||
//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 mut world_meshes:Vec<model::Mesh>=bsp.models().map(|world_model|{
|
let world_meshes:Vec<model::Mesh>=bsp.models().map(|world_model|{
|
||||||
let mut mb=model::MeshBuilder::new();
|
//non-deduplicated
|
||||||
|
let mut spam_pos=Vec::new();
|
||||||
let color=mb.acquire_color_id(glam::Vec4::ONE);
|
let mut spam_tex=Vec::new();
|
||||||
|
let mut spam_normal=Vec::new();
|
||||||
|
let mut spam_vertices=Vec::new();
|
||||||
let mut graphics_groups=Vec::new();
|
let mut graphics_groups=Vec::new();
|
||||||
let mut render_id_to_graphics_group_id=std::collections::HashMap::new();
|
let mut physics_group=model::IndexedPhysicsGroup::default();
|
||||||
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();
|
||||||
@ -160,200 +63,139 @@ pub fn convert<'a>(
|
|||||||
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=mb.acquire_normal_id(valve_transform(face.normal().into()));
|
let normal=face.normal();
|
||||||
let mut polygon_iter=face.vertex_positions().map(|vertex_position|
|
let normal_idx=spam_normal.len() as u32;
|
||||||
world_model.origin+vertex_position
|
spam_normal.push(valve_transform(normal.into()));
|
||||||
);
|
let mut polygon_iter=face.vertex_positions().map(|vertex_position|{
|
||||||
|
//world_model.origin seems to always be 0,0,0
|
||||||
|
let vertex_xyz=(world_model.origin+vertex_position).into();
|
||||||
|
let pos_idx=spam_pos.len();
|
||||||
|
spam_pos.push(valve_transform(vertex_xyz));
|
||||||
|
|
||||||
|
//calculate texture coordinates
|
||||||
|
let pos=glam::Vec3::from_array(vertex_xyz).extend(1.0);
|
||||||
|
let tex=glam::vec2(texture_transform_u.dot(pos),texture_transform_v.dot(pos));
|
||||||
|
let tex_idx=spam_tex.len() as u32;
|
||||||
|
spam_tex.push(tex);
|
||||||
|
|
||||||
|
let vertex_id=model::VertexId::new(spam_vertices.len() as u32);
|
||||||
|
spam_vertices.push(model::IndexedVertex{
|
||||||
|
pos:model::PositionId::new(pos_idx as u32),
|
||||||
|
tex:model::TextureCoordinateId::new(tex_idx as u32),
|
||||||
|
normal:model::NormalId::new(normal_idx),
|
||||||
|
color:model::ColorId::new(0),
|
||||||
|
});
|
||||||
|
vertex_id
|
||||||
|
});
|
||||||
let polygon_list=std::iter::from_fn(move||{
|
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([v1,v2,v3]),
|
(Some(v1),Some(v2),Some(v3))=>Some(vec![v1,v2,v3]),
|
||||||
//ignore extra vertices, not sure what to do in this case, failing the whole conversion could be appropriate
|
//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(){
|
||||||
//this automatically figures out what the texture is trying to do and creates
|
//TODO: deduplicate graphics groups by render id
|
||||||
//a render config for it, and then returns the id to that render config
|
|
||||||
let render_id=render_config_deferred_loader.acquire_render_config_id(Some(Cow::Borrowed(face_texture_data.name())));
|
|
||||||
//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{
|
graphics_groups.push(model::IndexedGraphicsGroup{
|
||||||
render:render_id,
|
render:render_id,
|
||||||
groups:vec![],
|
groups:vec![polygon_group_id],
|
||||||
});
|
})
|
||||||
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{
|
||||||
mb.build(polygon_groups,graphics_groups,vec![])
|
unique_pos:spam_pos,
|
||||||
|
unique_tex:spam_tex,
|
||||||
|
unique_normal:spam_normal,
|
||||||
|
unique_color:vec![glam::Vec4::ONE],
|
||||||
|
unique_vertices:spam_vertices,
|
||||||
|
polygon_groups,
|
||||||
|
graphics_groups,
|
||||||
|
physics_groups:vec![physics_group],
|
||||||
|
}
|
||||||
}).collect();
|
}).collect();
|
||||||
|
|
||||||
let mut found_spawn=None;
|
let world_models:Vec<model::Model>=
|
||||||
|
//one instance of the main world mesh
|
||||||
let mut world_models=Vec::new();
|
std::iter::once((
|
||||||
|
//world_model
|
||||||
// the one and only world model 0
|
model::MeshId::new(0),
|
||||||
world_models.push(model::Model{
|
//model_origin
|
||||||
mesh:model::MeshId::new(0),
|
vbsp::Vector::from([0.0,0.0,0.0]),
|
||||||
attributes:ATTRIBUTE_DECORATION,
|
//model_color
|
||||||
transform:integer::Planar64Affine3::IDENTITY,
|
vbsp::Color{r:255,g:255,b:255},
|
||||||
color:glam::Vec4::W,
|
)).chain(
|
||||||
});
|
//entities sprinkle instances of the other meshes around
|
||||||
|
bsp.entities.iter()
|
||||||
const WHITE:vbsp::Color=vbsp::Color{r:255,g:255,b:255};
|
.flat_map(|ent|ent.parse())//ignore entity parsing errors
|
||||||
for raw_ent in &bsp.entities{
|
.filter_map(|ent|match ent{
|
||||||
match raw_ent.parse(){
|
vbsp::Entity::Brush(brush)=>Some(brush),
|
||||||
Ok(Entity::Cycler(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,brush.rendercolor,ATTRIBUTE_DECORATION),
|
vbsp::Entity::BrushIllusionary(brush)=>Some(brush),
|
||||||
Ok(Entity::EnvSprite(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,brush.rendercolor,ATTRIBUTE_DECORATION),
|
vbsp::Entity::BrushWall(brush)=>Some(brush),
|
||||||
Ok(Entity::FuncBreakable(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,brush.rendercolor,ATTRIBUTE_DECORATION),
|
vbsp::Entity::BrushWallToggle(brush)=>Some(brush),
|
||||||
Ok(Entity::FuncBrush(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,brush.rendercolor,ATTRIBUTE_DECORATION),
|
_=>None,
|
||||||
Ok(Entity::FuncButton(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,brush.rendercolor,ATTRIBUTE_DECORATION),
|
}).flat_map(|brush|
|
||||||
Ok(Entity::FuncDoor(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,brush.rendercolor,ATTRIBUTE_DECORATION),
|
//The first character of brush.model is '*'
|
||||||
Ok(Entity::FuncDoorRotating(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,brush.rendercolor,ATTRIBUTE_DECORATION),
|
brush.model[1..].parse().map(|mesh_id|//ignore parse int errors
|
||||||
Ok(Entity::FuncIllusionary(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,brush.rendercolor,ATTRIBUTE_DECORATION),
|
(model::MeshId::new(mesh_id),brush.origin,brush.color)
|
||||||
Ok(Entity::FuncMonitor(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,brush.rendercolor,ATTRIBUTE_DECORATION),
|
)
|
||||||
Ok(Entity::FuncMovelinear(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,brush.rendercolor,ATTRIBUTE_DECORATION),
|
)
|
||||||
Ok(Entity::FuncPhysbox(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,brush.rendercolor,ATTRIBUTE_DECORATION),
|
).map(|(mesh_id,model_origin,vbsp::Color{r,g,b})|{
|
||||||
Ok(Entity::FuncPhysboxMultiplayer(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,brush.rendercolor,ATTRIBUTE_DECORATION),
|
model::Model{
|
||||||
Ok(Entity::FuncRotButton(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,WHITE,ATTRIBUTE_DECORATION),
|
|
||||||
Ok(Entity::FuncRotating(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,brush.rendercolor,ATTRIBUTE_DECORATION),
|
|
||||||
Ok(Entity::FuncTracktrain(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,brush.rendercolor,ATTRIBUTE_DECORATION),
|
|
||||||
Ok(Entity::FuncTrain(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,brush.rendercolor,ATTRIBUTE_DECORATION),
|
|
||||||
Ok(Entity::FuncWall(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin.unwrap_or_default(),brush.rendercolor,ATTRIBUTE_DECORATION),
|
|
||||||
Ok(Entity::FuncWallToggle(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin.unwrap_or_default(),brush.rendercolor,ATTRIBUTE_DECORATION),
|
|
||||||
Ok(Entity::FuncWaterAnalog(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,brush.rendercolor.unwrap_or(WHITE),ATTRIBUTE_DECORATION),
|
|
||||||
Ok(Entity::PropDoorRotating(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,WHITE,ATTRIBUTE_DECORATION),
|
|
||||||
Ok(Entity::PropDynamic(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,WHITE,ATTRIBUTE_DECORATION),
|
|
||||||
Ok(Entity::PropDynamicOverride(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,WHITE,ATTRIBUTE_DECORATION),
|
|
||||||
Ok(Entity::PropPhysics(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,WHITE,ATTRIBUTE_DECORATION),
|
|
||||||
Ok(Entity::PropPhysicsMultiplayer(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,WHITE,ATTRIBUTE_DECORATION),
|
|
||||||
Ok(Entity::PropPhysicsOverride(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,WHITE,ATTRIBUTE_DECORATION),
|
|
||||||
Ok(Entity::PropRagdoll(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,WHITE,ATTRIBUTE_DECORATION),
|
|
||||||
Ok(Entity::TriggerGravity(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,WHITE,ATTRIBUTE_DECORATION),
|
|
||||||
Ok(Entity::TriggerHurt(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,WHITE,ATTRIBUTE_DECORATION),
|
|
||||||
Ok(Entity::TriggerLook(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,WHITE,ATTRIBUTE_DECORATION),
|
|
||||||
Ok(Entity::TriggerMultiple(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model.unwrap_or_default(),brush.origin,WHITE,ATTRIBUTE_DECORATION),
|
|
||||||
Ok(Entity::TriggerOnce(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,WHITE,ATTRIBUTE_DECORATION),
|
|
||||||
Ok(Entity::TriggerProximity(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,WHITE,ATTRIBUTE_DECORATION),
|
|
||||||
Ok(Entity::TriggerPush(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,WHITE,ATTRIBUTE_DECORATION),
|
|
||||||
Ok(Entity::TriggerSoundscape(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,WHITE,ATTRIBUTE_DECORATION),
|
|
||||||
Ok(Entity::TriggerTeleport(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model.unwrap_or_default(),brush.origin,WHITE,ATTRIBUTE_DECORATION),
|
|
||||||
Ok(Entity::TriggerVphysicsMotion(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,WHITE,ATTRIBUTE_DECORATION),
|
|
||||||
Ok(Entity::TriggerWind(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,WHITE,ATTRIBUTE_DECORATION),
|
|
||||||
Ok(Entity::InfoPlayerCounterterrorist(spawn))=>{
|
|
||||||
found_spawn=Some(valve_transform(spawn.origin.into()));
|
|
||||||
},
|
|
||||||
Ok(Entity::InfoPlayerTerrorist(spawn))=>{
|
|
||||||
found_spawn=Some(valve_transform(spawn.origin.into()));
|
|
||||||
},
|
|
||||||
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);
|
|
||||||
world_models.push(model::Model{
|
|
||||||
mesh:mesh_id,
|
mesh:mesh_id,
|
||||||
attributes,
|
attributes:TEMP_TOUCH_ME_ATTRIBUTE,
|
||||||
transform:integer::Planar64Affine3::new(
|
transform:integer::Planar64Affine3::new(
|
||||||
integer::mat3::identity(),
|
integer::mat3::identity(),
|
||||||
integer::vec3::ZERO,
|
valve_transform(model_origin.into())
|
||||||
),
|
),
|
||||||
color:glam::Vec4::ONE,
|
color:(glam::Vec3::from_array([r as f32,g as f32,b as f32])/255.0).extend(1.0),
|
||||||
});
|
|
||||||
},
|
|
||||||
Err(e)=>println!("Brush mesh error: {e}"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut modes_list=Vec::new();
|
|
||||||
if let Some(spawn_point)=found_spawn{
|
|
||||||
// create a new mesh
|
|
||||||
let mesh_id=model::MeshId::new(world_meshes.len() as u32);
|
|
||||||
world_meshes.push(crate::brush::unit_cube());
|
|
||||||
// create a new model
|
|
||||||
let model_id=model::ModelId::new(world_models.len() as u32);
|
|
||||||
world_models.push(model::Model{
|
|
||||||
mesh:mesh_id,
|
|
||||||
attributes:ATTRIBUTE_INTERSECT_DEFAULT,
|
|
||||||
transform:integer::Planar64Affine3::from_translation(spawn_point),
|
|
||||||
color:glam::Vec4::W,
|
|
||||||
});
|
|
||||||
|
|
||||||
let first_stage=Stage::empty(model_id);
|
|
||||||
let main_mode=Mode::new(
|
|
||||||
strafesnet_common::gameplay_style::StyleModifiers::source_bhop(),
|
|
||||||
model_id,
|
|
||||||
std::collections::HashMap::new(),
|
|
||||||
vec![first_stage],
|
|
||||||
std::collections::HashMap::new(),
|
|
||||||
);
|
|
||||||
modes_list.push(NormalizedMode::new(main_mode));
|
|
||||||
}
|
}
|
||||||
|
}).collect();
|
||||||
|
|
||||||
PartialMap1{
|
PartialMap1{
|
||||||
attributes:unique_attributes,
|
attributes:unique_attributes,
|
||||||
world_meshes,
|
world_meshes,
|
||||||
prop_models,
|
prop_models,
|
||||||
world_models,
|
world_models,
|
||||||
modes:NormalizedModes::new(modes_list),
|
modes:strafesnet_common::gameplay_modes::Modes::new(Vec::new()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//partially constructed map types
|
//partially constructed map types
|
||||||
pub struct PartialMap1{
|
pub struct PartialMap1{
|
||||||
attributes:Vec<attr::CollisionAttributes>,
|
attributes:Vec<strafesnet_common::gameplay_attributes::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:NormalizedModes,
|
modes:strafesnet_common::gameplay_modes::Modes,
|
||||||
}
|
}
|
||||||
impl PartialMap1{
|
impl PartialMap1{
|
||||||
pub fn add_prop_meshes<'a>(
|
pub fn add_prop_meshes<AcquireRenderConfigId>(
|
||||||
self,
|
self,
|
||||||
prop_meshes:Meshes,
|
prop_meshes:impl IntoIterator<Item=(model::MeshId,crate::data::ModelData)>,
|
||||||
)->PartialMap2{
|
mut acquire_render_config_id:AcquireRenderConfigId,
|
||||||
|
)->PartialMap2
|
||||||
|
where
|
||||||
|
AcquireRenderConfigId:FnMut(Option<&str>)->model::RenderConfigId,
|
||||||
|
{
|
||||||
PartialMap2{
|
PartialMap2{
|
||||||
attributes:self.attributes,
|
attributes:self.attributes,
|
||||||
prop_meshes:prop_meshes.consume().collect(),
|
prop_meshes:prop_meshes.into_iter().filter_map(|(mesh_id,model_data)|
|
||||||
|
//this will generate new render ids and texture ids
|
||||||
|
match convert_mesh(model_data,&mut acquire_render_config_id){
|
||||||
|
Ok(mesh)=>Some((mesh_id,mesh)),
|
||||||
|
Err(e)=>{
|
||||||
|
println!("error converting mesh: {e}");
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
).collect(),
|
||||||
prop_models:self.prop_models,
|
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,
|
||||||
@ -362,17 +204,18 @@ impl PartialMap1{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub struct PartialMap2{
|
pub struct PartialMap2{
|
||||||
attributes:Vec<attr::CollisionAttributes>,
|
attributes:Vec<strafesnet_common::gameplay_attributes::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:NormalizedModes,
|
modes:strafesnet_common::gameplay_modes::Modes,
|
||||||
}
|
}
|
||||||
impl PartialMap2{
|
impl PartialMap2{
|
||||||
pub fn add_render_configs_and_textures(
|
pub fn add_render_configs_and_textures(
|
||||||
mut self,
|
mut self,
|
||||||
render_configs:RenderConfigs,
|
render_configs:impl IntoIterator<Item=(model::RenderConfigId,model::RenderConfig)>,
|
||||||
|
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();
|
||||||
@ -391,14 +234,13 @@ impl PartialMap2{
|
|||||||
})
|
})
|
||||||
));
|
));
|
||||||
//let mut models=Vec::new();
|
//let mut models=Vec::new();
|
||||||
let (textures,render_configs)=render_configs.consume();
|
|
||||||
let (textures,texture_id_map):(Vec<Vec<u8>>,std::collections::HashMap<model::TextureId,model::TextureId>)
|
let (textures,texture_id_map):(Vec<Vec<u8>>,std::collections::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::ImageDDS(texture)))|{
|
.enumerate().map(|(new_texture_id,(old_texture_id,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()
|
||||||
@ -415,3 +257,77 @@ 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,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
60
lib/bsp_loader/src/data.rs
Normal file
60
lib/bsp_loader/src/data.rs
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
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,20 +1,7 @@
|
|||||||
use strafesnet_deferred_loader::deferred_loader::{LoadFailureMode,MeshDeferredLoader,RenderConfigDeferredLoader};
|
|
||||||
|
|
||||||
mod bsp;
|
mod bsp;
|
||||||
mod mesh;
|
pub mod data;
|
||||||
mod brush;
|
|
||||||
pub mod loader;
|
|
||||||
|
|
||||||
const VALVE_SCALE:f32=1.0/16.0;
|
pub use data::Bsp;
|
||||||
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{
|
||||||
@ -28,38 +15,6 @@ 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();
|
||||||
|
|
||||||
@ -68,66 +23,15 @@ 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();
|
|
||||||
|
|
||||||
let map_step1=bsp::convert(
|
pub fn convert<AcquireRenderConfigId,AcquireMeshId>(
|
||||||
self,
|
bsp:&Bsp,
|
||||||
&mut texture_deferred_loader,
|
acquire_render_config_id:AcquireRenderConfigId,
|
||||||
&mut mesh_deferred_loader,
|
acquire_mesh_id:AcquireMeshId
|
||||||
);
|
)->bsp::PartialMap1
|
||||||
|
where
|
||||||
let mut mesh_loader=loader::MeshLoader::new(loader::BspFinder{bsp:self,vpks:vpk_list},&mut texture_deferred_loader);
|
AcquireRenderConfigId:FnMut(Option<&str>)->strafesnet_common::model::RenderConfigId,
|
||||||
let prop_meshes=mesh_deferred_loader.into_meshes(&mut mesh_loader,failure_mode).map_err(LoadError::Mesh)?;
|
AcquireMeshId:FnMut(&str)->strafesnet_common::model::MeshId,
|
||||||
|
{
|
||||||
let map_step2=map_step1.add_prop_meshes(prop_meshes);
|
bsp::convert_bsp(bsp.as_ref(),acquire_render_config_id,acquire_mesh_id)
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
}
|
@ -1,165 +0,0 @@
|
|||||||
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;
|
|
||||||
impl TextureLoader{
|
|
||||||
pub fn new()->Self{
|
|
||||||
Self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl Loader for TextureLoader{
|
|
||||||
type Error=TextureError;
|
|
||||||
type Index<'a>=Cow<'a,str>;
|
|
||||||
type Resource=Texture;
|
|
||||||
fn load<'a>(&mut self,index:Self::Index<'a>)->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>{
|
|
||||||
finder:BspFinder<'bsp,'vpk>,
|
|
||||||
}
|
|
||||||
impl ModelLoader<'_,'_>{
|
|
||||||
#[inline]
|
|
||||||
pub const fn new<'bsp,'vpk>(
|
|
||||||
finder:BspFinder<'bsp,'vpk>,
|
|
||||||
)->ModelLoader<'bsp,'vpk>{
|
|
||||||
ModelLoader{
|
|
||||||
finder,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl<'bsp,'vpk> Loader for ModelLoader<'bsp,'vpk>{
|
|
||||||
type Error=MeshError;
|
|
||||||
type Index<'a>=&'a str where Self:'a;
|
|
||||||
type Resource=vmdl::Model;
|
|
||||||
fn load<'a>(&'a mut self,index:Self::Index<'a>)->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,'str>{
|
|
||||||
finder:BspFinder<'bsp,'vpk>,
|
|
||||||
deferred_loader:&'load mut strafesnet_deferred_loader::deferred_loader::RenderConfigDeferredLoader<Cow<'str,str>>,
|
|
||||||
}
|
|
||||||
impl MeshLoader<'_,'_,'_,'_>{
|
|
||||||
#[inline]
|
|
||||||
pub const fn new<'bsp,'vpk,'load,'str>(
|
|
||||||
finder:BspFinder<'bsp,'vpk>,
|
|
||||||
deferred_loader:&'load mut strafesnet_deferred_loader::deferred_loader::RenderConfigDeferredLoader<Cow<'str,str>>,
|
|
||||||
)->MeshLoader<'bsp,'vpk,'load,'str>{
|
|
||||||
MeshLoader{
|
|
||||||
finder,
|
|
||||||
deferred_loader
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl<'str,'bsp,'vpk,'load> Loader for MeshLoader<'bsp,'vpk,'load,'str>{
|
|
||||||
type Error=MeshError;
|
|
||||||
type Index<'a>=&'a str where Self:'a;
|
|
||||||
type Resource=Mesh;
|
|
||||||
fn load<'a>(&'a mut self,index:Self::Index<'a>)->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)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,78 +0,0 @@
|
|||||||
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.6.0"
|
version = "0.5.2"
|
||||||
edition = "2024"
|
edition = "2021"
|
||||||
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,8 @@ 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 = { version = "0.2.0", path = "../fixed_wide", registry = "strafesnet", features = ["deferred-division","zeroes","wide-mul"] }
|
fixed_wide = { path = "../fixed_wide", registry = "strafesnet", features = ["deferred-division","zeroes","wide-mul"] }
|
||||||
linear_ops = { version = "0.1.1", path = "../linear_ops", registry = "strafesnet", features = ["deferred-division","named-fields"] }
|
linear_ops = { path = "../linear_ops", registry = "strafesnet", features = ["deferred-division","named-fields"] }
|
||||||
ratio_ops = { version = "0.1.0", path = "../ratio_ops", registry = "strafesnet" }
|
ratio_ops = { path = "../ratio_ops", registry = "strafesnet" }
|
||||||
glam = "0.30.0"
|
glam = "0.29.0"
|
||||||
id = { version = "0.1.0", registry = "strafesnet" }
|
id = { version = "0.1.0", registry = "strafesnet" }
|
||||||
|
@ -7,57 +7,42 @@ 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.map_zip(self.max,|(min,max)|min.midpoint(max))
|
self.min+(self.max-self.min)>>1
|
||||||
}
|
}
|
||||||
//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,10 +1,4 @@
|
|||||||
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
|
||||||
@ -16,117 +10,35 @@ use crate::instruction::{InstructionCollector,TimedInstruction};
|
|||||||
//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>{
|
||||||
pub fn intersect_aabb(ray:&Ray,aabb:&Aabb)->Option<Ratio<Planar64,Planar64>>{
|
Branch(Vec<R>),
|
||||||
// n.(o+d*t)==n.p
|
Leaf(T),
|
||||||
// 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>{
|
||||||
pub enum RecursiveContent<N,L>{
|
fn default()->Self{
|
||||||
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<L>{
|
pub struct BvhNode<T>{
|
||||||
content:RecursiveContent<BvhNode<L>,L>,
|
content:RecursiveContent<BvhNode<T>,T>,
|
||||||
aabb:Aabb,
|
aabb:Aabb,
|
||||||
}
|
}
|
||||||
impl<L> BvhNode<L>{
|
impl<T> Default for BvhNode<T>{
|
||||||
pub fn empty()->Self{
|
fn default()->Self{
|
||||||
Self{
|
Self{
|
||||||
content:RecursiveContent::empty(),
|
content:Default::default(),
|
||||||
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{
|
||||||
@ -135,101 +47,51 @@ impl<L> BvhNode<L>{
|
|||||||
//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.sample_aabb(aabb,f);
|
child.the_tester(aabb,f);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn populate_nodes<'a,T,F>(
|
pub fn into_visitor<F:FnMut(T)>(self,f:&mut F){
|
||||||
&'a self,
|
match self.content{
|
||||||
collector:&mut InstructionCollector<&'a L,Ratio<Planar64,Planar64>>,
|
RecursiveContent::Leaf(model)=>f(model),
|
||||||
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{
|
||||||
if child.aabb.contains(ray.origin){
|
child.into_visitor(f)
|
||||||
child.populate_nodes(collector,nodes,ray,start_time,f);
|
},
|
||||||
}else{
|
|
||||||
// Am I an upcoming superstar?
|
|
||||||
if let Some(t)=intersect_aabb(ray,&child.aabb){
|
|
||||||
if start_time.lt_ratio(t)&&t.lt_ratio(collector.time()){
|
|
||||||
nodes.insert(t,child);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
pub fn weigh_contents<W:Copy+std::iter::Sum<W>,F:Fn(&T)->W>(self,f:&F)->BvhWeightNode<W,T>{
|
||||||
|
match self.content{
|
||||||
|
RecursiveContent::Leaf(model)=>BvhWeightNode{
|
||||||
|
weight:f(&model),
|
||||||
|
content:RecursiveContent::Leaf(model),
|
||||||
|
aabb:self.aabb,
|
||||||
|
},
|
||||||
|
RecursiveContent::Branch(children)=>{
|
||||||
|
let branch:Vec<BvhWeightNode<W,T>>=children.into_iter().map(|child|
|
||||||
|
child.weigh_contents(f)
|
||||||
|
).collect();
|
||||||
|
BvhWeightNode{
|
||||||
|
weight:branch.iter().map(|node|node.weight).sum(),
|
||||||
|
content:RecursiveContent::Branch(branch),
|
||||||
|
aabb:self.aabb,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
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();
|
|
||||||
|
|
||||||
let start_time=start_time.into();
|
impl <W,T> BvhWeightNode<W,T>{
|
||||||
let time_limit=time_limit.into();
|
pub const fn weight(&self)->&W{
|
||||||
let mut collector=InstructionCollector::new(time_limit);
|
&self.weight
|
||||||
// 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{
|
pub const fn aabb(&self)->&Aabb{
|
||||||
RecursiveContent::Leaf(leaf)=>if let Some(time)=f(leaf,ray){
|
&self.aabb
|
||||||
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));
|
|
||||||
}
|
}
|
||||||
},
|
pub fn into_content(self)->RecursiveContent<BvhWeightNode<W,T>,T>{
|
||||||
// break open the node and predict collisions with the child nodes
|
self.content
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
}
|
pub fn into_visitor<F:FnMut(T)>(self,f:&mut F){
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
collector.take().map(|TimedInstruction{time,instruction:leaf}|(time.into(),leaf))
|
|
||||||
}
|
|
||||||
pub fn into_inner(self)->(RecursiveContent<BvhNode<L>,L>,Aabb){
|
|
||||||
(self.content,self.aabb)
|
|
||||||
}
|
|
||||||
pub fn into_visitor<F:FnMut(L)>(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{
|
||||||
@ -268,9 +130,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_key(|&(_,c)|c);
|
sort_x.sort_by(|tup0,tup1|tup0.1.cmp(&tup1.1));
|
||||||
sort_y.sort_by_key(|&(_,c)|c);
|
sort_y.sort_by(|tup0,tup1|tup0.1.cmp(&tup1.1));
|
||||||
sort_z.sort_by_key(|&(_,c)|c);
|
sort_z.sort_by(|tup0,tup1|tup0.1.cmp(&tup1.1));
|
||||||
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,7 +171,4 @@ 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,6 +1,7 @@
|
|||||||
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{
|
||||||
@ -127,6 +128,18 @@ 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{
|
||||||
@ -198,47 +211,34 @@ 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,StageId(stage_id):StageId)->Option<&mut Stage>{
|
pub fn get_stage_mut(&mut self,stage:StageId)->Option<&mut Stage>{
|
||||||
self.stages.get_mut(stage_id as usize)
|
self.stages.get_mut(stage.0 as usize)
|
||||||
}
|
}
|
||||||
pub fn get_spawn_model_id(&self,StageId(stage_id):StageId)->Option<ModelId>{
|
pub fn get_spawn_model_id(&self,stage:StageId)->Option<ModelId>{
|
||||||
self.stages.get(stage_id as usize).map(|s|s.spawn)
|
self.stages.get(stage.0 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,StageId(stage_id):StageId)->Option<&Stage>{
|
pub fn get_stage(&self,stage_id:StageId)->Option<&Stage>{
|
||||||
self.stages.get(stage_id as usize)
|
self.stages.get(stage_id.0 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(self)->Mode{
|
pub fn denormalize_data(&mut self){
|
||||||
let NormalizedMode(mut mode)=self;
|
|
||||||
//expand and index normalized data
|
//expand and index normalized data
|
||||||
mode.zones.insert(mode.start,Zone::Start);
|
self.zones.insert(self.start,Zone::Start);
|
||||||
for (stage_id,stage) in mode.stages.iter().enumerate(){
|
for (stage_id,stage) in self.stages.iter().enumerate(){
|
||||||
mode.elements.insert(stage.spawn,StageElement{
|
self.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{
|
||||||
mode.elements.insert(model,StageElement{
|
self.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 NormalizedMode{
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
for &model in &stage.unordered_checkpoints{
|
for &model in &stage.unordered_checkpoints{
|
||||||
mode.elements.insert(model,StageElement{
|
self.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,13 +254,53 @@ impl NormalizedMode{
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
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{
|
||||||
modes:Vec<Mode>,
|
pub modes:Vec<Mode>,
|
||||||
}
|
}
|
||||||
impl Modes{
|
impl Modes{
|
||||||
pub const fn new(modes:Vec<Mode>)->Self{
|
pub const fn new(modes:Vec<Mode>)->Self{
|
||||||
@ -274,191 +314,19 @@ 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,ModeId(mode_id):ModeId)->Option<&Mode>{
|
pub fn get_mode(&self,mode:ModeId)->Option<&Mode>{
|
||||||
self.modes.get(mode_id as usize)
|
self.modes.get(mode.0 as usize)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
pub struct ModesUpdate{
|
||||||
#[derive(Clone)]
|
modes:HashMap<ModeId,ModeUpdate>,
|
||||||
pub struct NormalizedModes{
|
|
||||||
modes:Vec<NormalizedMode>,
|
|
||||||
}
|
}
|
||||||
impl NormalizedModes{
|
impl Updatable<ModesUpdate> for Modes{
|
||||||
pub fn new(modes:Vec<NormalizedMode>)->Self{
|
fn update(&mut self,update:ModesUpdate){
|
||||||
Self{modes}
|
for (mode,mode_update) in update.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)->Result<(),ExistingEntryError>{
|
|
||||||
error_if_exists(self.modes.insert(mode_id,mode))
|
|
||||||
}
|
|
||||||
pub fn insert_stage(&mut self,mode_id:ModeId,stage_id:StageId,stage:Stage)->Result<(),ExistingEntryError>{
|
|
||||||
error_if_exists(self.stages.entry(mode_id).or_insert(HashMap::new()).insert(stage_id,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));
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct ExistingEntryError;
|
|
||||||
fn error_if_exists<T>(value:Option<T>)->Result<(),ExistingEntryError>{
|
|
||||||
match value{
|
|
||||||
Some(_)=>Err(ExistingEntryError),
|
|
||||||
None=>Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -63,22 +63,22 @@ 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().clamp_1()),
|
&JumpImpulse::Time(time)=>velocity-(*gravity*time).map(|t|t.divide().fix_1()),
|
||||||
&JumpImpulse::Height(height)=>{
|
&JumpImpulse::Height(height)=>{
|
||||||
//height==-v.y*v.y/(2*g.y);
|
//height==-v.y*v.y/(2*g.y);
|
||||||
//use energy to determine max height
|
//use energy to determine max height
|
||||||
let gg=gravity.length_squared();
|
let gg=gravity.length_squared();
|
||||||
let g=gg.sqrt();
|
let g=gg.sqrt().fix_1();
|
||||||
let v_g=gravity.dot(velocity);
|
let v_g=gravity.dot(velocity);
|
||||||
//do it backwards
|
//do it backwards
|
||||||
let radicand=v_g*v_g+(g*height*2).widen_4();
|
let radicand=v_g*v_g+(g*height*2).fix_4();
|
||||||
velocity-(*gravity*(radicand.sqrt().wrap_2()+v_g)/gg).divide().clamp_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().clamp_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
|
||||||
@ -91,10 +91,10 @@ impl JumpImpulse{
|
|||||||
pub fn get_jump_deltav(&self,gravity:&Planar64Vec3,mass:Planar64)->Planar64{
|
pub fn get_jump_deltav(&self,gravity:&Planar64Vec3,mass:Planar64)->Planar64{
|
||||||
//gravity.length() is actually the proper calculation because the jump is always opposite the gravity direction
|
//gravity.length() is actually the proper calculation because the jump is always opposite the gravity direction
|
||||||
match self{
|
match self{
|
||||||
&JumpImpulse::Time(time)=>(gravity.length().wrap_1()*time/2).divide().clamp_1(),
|
&JumpImpulse::Time(time)=>(gravity.length().fix_1()*time/2).divide().fix_1(),
|
||||||
&JumpImpulse::Height(height)=>(gravity.length()*height*2).sqrt().wrap_1(),
|
&JumpImpulse::Height(height)=>(gravity.length()*height*2).sqrt().fix_1(),
|
||||||
&JumpImpulse::Linear(deltav)=>deltav,
|
&JumpImpulse::Linear(deltav)=>deltav,
|
||||||
&JumpImpulse::Energy(energy)=>(energy.sqrt()*2/mass.sqrt()).divide().clamp_1(),
|
&JumpImpulse::Energy(energy)=>(energy.sqrt()*2/mass.sqrt()).divide().fix_1(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -126,10 +126,10 @@ impl JumpSettings{
|
|||||||
None=>rel_velocity,
|
None=>rel_velocity,
|
||||||
};
|
};
|
||||||
let j=boost_vel.dot(jump_dir);
|
let j=boost_vel.dot(jump_dir);
|
||||||
let js=jump_speed.widen_2();
|
let js=jump_speed.fix_2();
|
||||||
if j<js{
|
if j<js{
|
||||||
//weak booster: just do a regular jump
|
//weak booster: just do a regular jump
|
||||||
boost_vel+jump_dir.with_length(js-j).divide().wrap_1()
|
boost_vel+jump_dir.with_length(js-j).divide().fix_1()
|
||||||
}else{
|
}else{
|
||||||
//activate booster normally, jump does nothing
|
//activate booster normally, jump does nothing
|
||||||
boost_vel
|
boost_vel
|
||||||
@ -142,13 +142,13 @@ impl JumpSettings{
|
|||||||
None=>rel_velocity,
|
None=>rel_velocity,
|
||||||
};
|
};
|
||||||
let j=boost_vel.dot(jump_dir);
|
let j=boost_vel.dot(jump_dir);
|
||||||
let js=jump_speed.widen_2();
|
let js=jump_speed.fix_2();
|
||||||
if j<js{
|
if j<js{
|
||||||
//speed in direction of jump cannot be lower than amount
|
//speed in direction of jump cannot be lower than amount
|
||||||
boost_vel+jump_dir.with_length(js-j).divide().wrap_1()
|
boost_vel+jump_dir.with_length(js-j).divide().fix_1()
|
||||||
}else{
|
}else{
|
||||||
//boost and jump add together
|
//boost and jump add together
|
||||||
boost_vel+jump_dir.with_length(js).divide().wrap_1()
|
boost_vel+jump_dir.with_length(js).divide().fix_1()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
(false,JumpCalculation::Max)=>{
|
(false,JumpCalculation::Max)=>{
|
||||||
@ -159,10 +159,10 @@ impl JumpSettings{
|
|||||||
None=>rel_velocity,
|
None=>rel_velocity,
|
||||||
};
|
};
|
||||||
let boost_dot=boost_vel.dot(jump_dir);
|
let boost_dot=boost_vel.dot(jump_dir);
|
||||||
let js=jump_speed.widen_2();
|
let js=jump_speed.fix_2();
|
||||||
if boost_dot<js{
|
if boost_dot<js{
|
||||||
//weak boost is extended to jump speed
|
//weak boost is extended to jump speed
|
||||||
boost_vel+jump_dir.with_length(js-boost_dot).divide().wrap_1()
|
boost_vel+jump_dir.with_length(js-boost_dot).divide().fix_1()
|
||||||
}else{
|
}else{
|
||||||
//activate booster normally, jump does nothing
|
//activate booster normally, jump does nothing
|
||||||
boost_vel
|
boost_vel
|
||||||
@ -174,7 +174,7 @@ impl JumpSettings{
|
|||||||
Some(booster)=>booster.boost(rel_velocity),
|
Some(booster)=>booster.boost(rel_velocity),
|
||||||
None=>rel_velocity,
|
None=>rel_velocity,
|
||||||
};
|
};
|
||||||
boost_vel+jump_dir.with_length(jump_speed).divide().wrap_1()
|
boost_vel+jump_dir.with_length(jump_speed).divide().fix_1()
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -267,9 +267,9 @@ pub struct StrafeSettings{
|
|||||||
impl StrafeSettings{
|
impl StrafeSettings{
|
||||||
pub fn tick_velocity(&self,velocity:Planar64Vec3,control_dir:Planar64Vec3)->Option<Planar64Vec3>{
|
pub fn tick_velocity(&self,velocity:Planar64Vec3,control_dir:Planar64Vec3)->Option<Planar64Vec3>{
|
||||||
let d=velocity.dot(control_dir);
|
let d=velocity.dot(control_dir);
|
||||||
let mv=self.mv.widen_2();
|
let mv=self.mv.fix_2();
|
||||||
match d<mv{
|
match d<mv{
|
||||||
true=>Some(velocity+(control_dir*self.air_accel_limit.map_or(mv-d,|limit|limit.widen_2().min(mv-d))).wrap_1()),
|
true=>Some(velocity+(control_dir*self.air_accel_limit.map_or(mv-d,|limit|limit.fix_2().min(mv-d))).fix_1()),
|
||||||
false=>None,
|
false=>None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -290,7 +290,7 @@ pub struct PropulsionSettings{
|
|||||||
}
|
}
|
||||||
impl PropulsionSettings{
|
impl PropulsionSettings{
|
||||||
pub fn acceleration(&self,control_dir:Planar64Vec3)->Planar64Vec3{
|
pub fn acceleration(&self,control_dir:Planar64Vec3)->Planar64Vec3{
|
||||||
(control_dir*self.magnitude).clamp_1()
|
(control_dir*self.magnitude).fix_1()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -310,13 +310,13 @@ pub struct WalkSettings{
|
|||||||
impl WalkSettings{
|
impl WalkSettings{
|
||||||
pub fn accel(&self,target_diff:Planar64Vec3,gravity:Planar64Vec3)->Planar64{
|
pub fn accel(&self,target_diff:Planar64Vec3,gravity:Planar64Vec3)->Planar64{
|
||||||
//TODO: fallible walk accel
|
//TODO: fallible walk accel
|
||||||
let diff_len=target_diff.length().wrap_1();
|
let diff_len=target_diff.length().fix_1();
|
||||||
let friction=if diff_len<self.accelerate.topspeed{
|
let friction=if diff_len<self.accelerate.topspeed{
|
||||||
self.static_friction
|
self.static_friction
|
||||||
}else{
|
}else{
|
||||||
self.kinetic_friction
|
self.kinetic_friction
|
||||||
};
|
};
|
||||||
self.accelerate.accel.min((-gravity.y*friction).clamp_1())
|
self.accelerate.accel.min((-gravity.y*friction).fix_1())
|
||||||
}
|
}
|
||||||
pub fn get_walk_target_velocity(&self,control_dir:Planar64Vec3,normal:Planar64Vec3)->Planar64Vec3{
|
pub fn get_walk_target_velocity(&self,control_dir:Planar64Vec3,normal:Planar64Vec3)->Planar64Vec3{
|
||||||
if control_dir==crate::integer::vec3::ZERO{
|
if control_dir==crate::integer::vec3::ZERO{
|
||||||
@ -332,7 +332,7 @@ impl WalkSettings{
|
|||||||
if cr==crate::integer::vec3::ZERO_2{
|
if cr==crate::integer::vec3::ZERO_2{
|
||||||
crate::integer::vec3::ZERO
|
crate::integer::vec3::ZERO
|
||||||
}else{
|
}else{
|
||||||
(cr.cross(normal)*self.accelerate.topspeed/((nn*(nnmm-dd)).sqrt())).divide().clamp_1()
|
(cr.cross(normal)*self.accelerate.topspeed/((nn*(nnmm-dd)).sqrt())).divide().fix_1()
|
||||||
}
|
}
|
||||||
}else{
|
}else{
|
||||||
crate::integer::vec3::ZERO
|
crate::integer::vec3::ZERO
|
||||||
@ -341,7 +341,7 @@ impl WalkSettings{
|
|||||||
pub fn is_slope_walkable(&self,normal:Planar64Vec3,up:Planar64Vec3)->bool{
|
pub fn is_slope_walkable(&self,normal:Planar64Vec3,up:Planar64Vec3)->bool{
|
||||||
//normal is not guaranteed to be unit length
|
//normal is not guaranteed to be unit length
|
||||||
let ny=normal.dot(up);
|
let ny=normal.dot(up);
|
||||||
let h=normal.length().wrap_1();
|
let h=normal.length().fix_1();
|
||||||
//remember this is a normal vector
|
//remember this is a normal vector
|
||||||
ny.is_positive()&&h*self.surf_dot<ny
|
ny.is_positive()&&h*self.surf_dot<ny
|
||||||
}
|
}
|
||||||
@ -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
|
||||||
}
|
}
|
||||||
@ -368,13 +368,13 @@ impl LadderSettings{
|
|||||||
let nnmm=nn*mm;
|
let nnmm=nn*mm;
|
||||||
let d=normal.dot(control_dir);
|
let d=normal.dot(control_dir);
|
||||||
let mut dd=d*d;
|
let mut dd=d*d;
|
||||||
if (self.dot*self.dot*nnmm).clamp_4()<dd{
|
if (self.dot*self.dot*nnmm).fix_4()<dd{
|
||||||
if d.is_negative(){
|
if d.is_negative(){
|
||||||
control_dir=Planar64Vec3::new([Planar64::ZERO,mm.clamp_1(),Planar64::ZERO]);
|
control_dir=Planar64Vec3::new([Planar64::ZERO,mm.fix_1(),Planar64::ZERO]);
|
||||||
}else{
|
}else{
|
||||||
control_dir=Planar64Vec3::new([Planar64::ZERO,-mm.clamp_1(),Planar64::ZERO]);
|
control_dir=Planar64Vec3::new([Planar64::ZERO,-mm.fix_1(),Planar64::ZERO]);
|
||||||
}
|
}
|
||||||
dd=(normal.y*normal.y).widen_4();
|
dd=(normal.y*normal.y).fix_4();
|
||||||
}
|
}
|
||||||
//n=d if you are standing on top of a ladder and press E.
|
//n=d if you are standing on top of a ladder and press E.
|
||||||
//two fixes:
|
//two fixes:
|
||||||
@ -385,7 +385,7 @@ impl LadderSettings{
|
|||||||
if cr==crate::integer::vec3::ZERO_2{
|
if cr==crate::integer::vec3::ZERO_2{
|
||||||
crate::integer::vec3::ZERO
|
crate::integer::vec3::ZERO
|
||||||
}else{
|
}else{
|
||||||
(cr.cross(normal)*self.accelerate.topspeed/((nn*(nnmm-dd)).sqrt())).divide().clamp_1()
|
(cr.cross(normal)*self.accelerate.topspeed/((nn*(nnmm-dd)).sqrt())).divide().fix_1()
|
||||||
}
|
}
|
||||||
}else{
|
}else{
|
||||||
crate::integer::vec3::ZERO
|
crate::integer::vec3::ZERO
|
||||||
@ -417,7 +417,7 @@ impl Hitbox{
|
|||||||
}
|
}
|
||||||
pub fn source()->Self{
|
pub fn source()->Self{
|
||||||
Self{
|
Self{
|
||||||
halfsize:((int3(33,73,33)>>1)*VALVE_SCALE).narrow_1().unwrap(),
|
halfsize:((int3(33,73,33)>>1)*VALVE_SCALE).fix_1(),
|
||||||
mesh:HitboxMesh::Box,
|
mesh:HitboxMesh::Box,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -529,20 +529,20 @@ impl StyleModifiers{
|
|||||||
|
|
||||||
pub fn source_bhop()->Self{
|
pub fn source_bhop()->Self{
|
||||||
Self{
|
Self{
|
||||||
controls_mask:Controls::all(),
|
controls_mask:Controls::all()-Controls::MoveUp-Controls::MoveDown,
|
||||||
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<<28),
|
mv:(Planar64::raw(30)*VALVE_SCALE).fix_1(),
|
||||||
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{
|
||||||
impulse:JumpImpulse::Height((int(52)*VALVE_SCALE).narrow_1().unwrap()),
|
impulse:JumpImpulse::Height((int(52)*VALVE_SCALE).fix_1()),
|
||||||
calculation:JumpCalculation::JumpThenBoost,
|
calculation:JumpCalculation::JumpThenBoost,
|
||||||
limit_minimum:true,
|
limit_minimum:true,
|
||||||
}),
|
}),
|
||||||
gravity:(int3(0,-800,0)*VALVE_SCALE).narrow_1().unwrap(),
|
gravity:(int3(0,-800,0)*VALVE_SCALE).fix_1(),
|
||||||
mass:int(1),
|
mass:int(1),
|
||||||
rocket:None,
|
rocket:None,
|
||||||
walk:Some(WalkSettings{
|
walk:Some(WalkSettings{
|
||||||
@ -565,25 +565,25 @@ impl StyleModifiers{
|
|||||||
magnitude:int(12),//?
|
magnitude:int(12),//?
|
||||||
}),
|
}),
|
||||||
hitbox:Hitbox::source(),
|
hitbox:Hitbox::source(),
|
||||||
camera_offset:((int3(0,64,0)-(int3(0,73,0)>>1))*VALVE_SCALE).narrow_1().unwrap(),
|
camera_offset:((int3(0,64,0)-(int3(0,73,0)>>1))*VALVE_SCALE).fix_1(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn source_surf()->Self{
|
pub fn source_surf()->Self{
|
||||||
Self{
|
Self{
|
||||||
controls_mask:Controls::all(),
|
controls_mask:Controls::all()-Controls::MoveUp-Controls::MoveDown,
|
||||||
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).narrow_1().unwrap()),
|
air_accel_limit:Some((int(150)*66*VALVE_SCALE).fix_1()),
|
||||||
mv:Planar64::raw(30<<28),
|
mv:(int(30)*VALVE_SCALE).fix_1(),
|
||||||
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{
|
||||||
impulse:JumpImpulse::Height((int(52)*VALVE_SCALE).narrow_1().unwrap()),
|
impulse:JumpImpulse::Height((int(52)*VALVE_SCALE).fix_1()),
|
||||||
calculation:JumpCalculation::JumpThenBoost,
|
calculation:JumpCalculation::JumpThenBoost,
|
||||||
limit_minimum:true,
|
limit_minimum:true,
|
||||||
}),
|
}),
|
||||||
gravity:(int3(0,-800,0)*VALVE_SCALE).narrow_1().unwrap(),
|
gravity:(int3(0,-800,0)*VALVE_SCALE).fix_1(),
|
||||||
mass:int(1),
|
mass:int(1),
|
||||||
rocket:None,
|
rocket:None,
|
||||||
walk:Some(WalkSettings{
|
walk:Some(WalkSettings{
|
||||||
@ -606,7 +606,7 @@ impl StyleModifiers{
|
|||||||
magnitude:int(12),//?
|
magnitude:int(12),//?
|
||||||
}),
|
}),
|
||||||
hitbox:Hitbox::source(),
|
hitbox:Hitbox::source(),
|
||||||
camera_offset:((int3(0,64,0)-(int3(0,73,0)>>1))*VALVE_SCALE).narrow_1().unwrap(),
|
camera_offset:((int3(0,64,0)-(int3(0,73,0)>>1))*VALVE_SCALE).fix_1(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,35 +1,27 @@
|
|||||||
#[derive(Clone,Debug)]
|
use crate::integer::Time;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
pub struct TimedInstruction<I,T>{
|
pub struct TimedInstruction<I,T>{
|
||||||
pub time:T,
|
pub time:Time<T>,
|
||||||
pub instruction:I,
|
pub instruction:I,
|
||||||
}
|
}
|
||||||
impl<I,T> TimedInstruction<I,T>{
|
|
||||||
#[inline]
|
|
||||||
pub fn set_time<T2>(self,new_time:T2)->TimedInstruction<I,T2>{
|
|
||||||
TimedInstruction{
|
|
||||||
time:new_time,
|
|
||||||
instruction:self.instruction,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 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 Time;
|
type TimeInner;
|
||||||
fn next_instruction(&self,time_limit:Self::Time)->Option<TimedInstruction<I,Self::Time>>;
|
fn next_instruction(&self,time_limit:Time<Self::TimeInner>)->Option<TimedInstruction<I,Self::TimeInner>>;
|
||||||
}
|
}
|
||||||
/// Apply an atomic state update
|
/// Apply an atomic state update
|
||||||
pub trait InstructionConsumer<I>{
|
pub trait InstructionConsumer<I>{
|
||||||
type Time;
|
type TimeInner;
|
||||||
fn process_instruction(&mut self,instruction:TimedInstruction<I,Self::Time>);
|
fn process_instruction(&mut self,instruction:TimedInstruction<I,Self::TimeInner>);
|
||||||
}
|
}
|
||||||
/// 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,Time=T>+InstructionConsumer<I,Time=T>
|
pub trait InstructionFeedback<I,T>:InstructionEmitter<I,TimeInner=T>+InstructionConsumer<I,TimeInner=T>
|
||||||
where
|
where
|
||||||
T:Copy,
|
Time<T>:Copy,
|
||||||
{
|
{
|
||||||
#[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);
|
||||||
}
|
}
|
||||||
@ -37,46 +29,83 @@ pub trait InstructionFeedback<I,T>:InstructionEmitter<I,Time=T>+InstructionConsu
|
|||||||
}
|
}
|
||||||
impl<I,T,X> InstructionFeedback<I,T> for X
|
impl<I,T,X> InstructionFeedback<I,T> for X
|
||||||
where
|
where
|
||||||
T:Copy,
|
Time<T>:Copy,
|
||||||
X:InstructionEmitter<I,Time=T>+InstructionConsumer<I,Time=T>,
|
X:InstructionEmitter<I,TimeInner=T>+InstructionConsumer<I,TimeInner=T>,
|
||||||
{}
|
{}
|
||||||
|
|
||||||
|
pub struct InstructionCache<S,I,T>{
|
||||||
|
instruction_machine:S,
|
||||||
|
cached_instruction:Option<TimedInstruction<I,T>>,
|
||||||
|
time_limit:Time<T>,
|
||||||
|
}
|
||||||
|
impl<S,I,T> InstructionCache<S,I,T>
|
||||||
|
where
|
||||||
|
Time<T>:Copy+Ord,
|
||||||
|
Option<TimedInstruction<I,T>>:Clone,
|
||||||
|
S:InstructionEmitter<I,TimeInner=T>+InstructionConsumer<I,TimeInner=T>
|
||||||
|
{
|
||||||
|
pub fn new(
|
||||||
|
instruction_machine:S,
|
||||||
|
)->Self{
|
||||||
|
Self{
|
||||||
|
instruction_machine,
|
||||||
|
cached_instruction:None,
|
||||||
|
time_limit:Time::MIN,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn next_instruction_cached(&mut self,time_limit:Time<T>)->Option<TimedInstruction<I,T>>{
|
||||||
|
if time_limit<self.time_limit{
|
||||||
|
return self.cached_instruction.clone();
|
||||||
|
}
|
||||||
|
let next_instruction=self.instruction_machine.next_instruction(time_limit);
|
||||||
|
self.cached_instruction=next_instruction.clone();
|
||||||
|
self.time_limit=time_limit;
|
||||||
|
next_instruction
|
||||||
|
}
|
||||||
|
pub fn process_instruction(&mut self,instruction:TimedInstruction<I,T>){
|
||||||
|
// invalidate cache
|
||||||
|
self.time_limit=Time::MIN;
|
||||||
|
self.instruction_machine.process_instruction(instruction);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
//PROPER PRIVATE FIELDS!!!
|
//PROPER PRIVATE FIELDS!!!
|
||||||
pub struct InstructionCollector<I,T>{
|
pub struct InstructionCollector<I,T>{
|
||||||
time:T,
|
time:Time<T>,
|
||||||
instruction:Option<I>,
|
instruction:Option<I>,
|
||||||
}
|
}
|
||||||
impl<I,T> InstructionCollector<I,T>{
|
impl<I,T> InstructionCollector<I,T>
|
||||||
#[inline]
|
where Time<T>:Copy+PartialOrd,
|
||||||
pub const fn new(time:T)->Self{
|
{
|
||||||
|
pub const fn new(time:Time<T>)->Self{
|
||||||
Self{
|
Self{
|
||||||
time,
|
time,
|
||||||
instruction:None
|
instruction:None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn take(self)->Option<TimedInstruction<I,T>>{
|
pub const fn time(&self)->Time<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{
|
match instruction{
|
||||||
if ins.time<self.time{
|
Some(unwrap_instruction)=>{
|
||||||
self.time=ins.time;
|
if unwrap_instruction.time<self.time {
|
||||||
self.instruction=Some(ins.instruction);
|
self.time=unwrap_instruction.time;
|
||||||
|
self.instruction=Some(unwrap_instruction.instruction);
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
None=>(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn instruction(self)->Option<TimedInstruction<I,T>>{
|
||||||
|
//STEAL INSTRUCTION AND DESTROY INSTRUCTIONCOLLECTOR
|
||||||
|
match self.instruction{
|
||||||
|
Some(instruction)=>Some(TimedInstruction{
|
||||||
|
time:self.time,
|
||||||
|
instruction
|
||||||
|
}),
|
||||||
|
None=>None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,20 +1,19 @@
|
|||||||
pub use fixed_wide::fixed::*;
|
pub use fixed_wide::fixed::{Fixed,Fix};
|
||||||
pub use ratio_ops::ratio::{Ratio,Divide};
|
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,Ord,PartialOrd,Debug)]
|
#[derive(Clone,Copy,Hash,Eq,PartialEq,PartialOrd,Debug)]
|
||||||
pub enum TimeInner{}
|
pub enum TimeInner{}
|
||||||
pub type AbsoluteTime=Time<TimeInner>;
|
pub type AbsoluteTime=Time<TimeInner>;
|
||||||
|
|
||||||
#[derive(Clone,Copy,Hash,Eq,PartialEq,Ord,PartialOrd,Debug)]
|
#[derive(Clone,Copy,Hash,Eq,PartialEq,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);
|
||||||
@ -60,24 +59,18 @@ impl<T> Time<T>{
|
|||||||
impl<T> From<Planar64> for Time<T>{
|
impl<T> From<Planar64> for Time<T>{
|
||||||
#[inline]
|
#[inline]
|
||||||
fn from(value:Planar64)->Self{
|
fn from(value:Planar64)->Self{
|
||||||
Self::raw((value*Planar64::raw(1_000_000_000)).clamp_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>,
|
||||||
N1:Divide<Den,Output=T1>,
|
N1:Divide<Den,Output=T1>,
|
||||||
T1:Clamp<Planar64>,
|
T1:Fix<Planar64>,
|
||||||
{
|
{
|
||||||
#[inline]
|
#[inline]
|
||||||
fn from(value:Ratio<Num,Den>)->Self{
|
fn from(value:Ratio<Num,Den>)->Self{
|
||||||
Self::raw((value*Planar64::raw(1_000_000_000)).divide().clamp().to_raw())
|
Self::raw((value*Planar64::raw(1_000_000_000)).divide().fix().to_raw())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl<T> std::fmt::Display for Time<T>{
|
impl<T> std::fmt::Display for Time<T>{
|
||||||
@ -401,10 +394,6 @@ impl Angle32{
|
|||||||
pub const NEG_FRAC_PI_2:Self=Self(-1<<30);
|
pub const NEG_FRAC_PI_2:Self=Self(-1<<30);
|
||||||
pub const PI:Self=Self(-1<<31);
|
pub const PI:Self=Self(-1<<31);
|
||||||
#[inline]
|
#[inline]
|
||||||
pub const fn raw(num:i32)->Self{
|
|
||||||
Self(num)
|
|
||||||
}
|
|
||||||
#[inline]
|
|
||||||
pub const fn wrap_from_i64(theta:i64)->Self{
|
pub const fn wrap_from_i64(theta:i64)->Self{
|
||||||
//take lower bits
|
//take lower bits
|
||||||
//note: this was checked on compiler explorer and compiles to 1 instruction!
|
//note: this was checked on compiler explorer and compiles to 1 instruction!
|
||||||
@ -519,8 +508,8 @@ fn angle_sin_cos(){
|
|||||||
println!("cordic s={} c={}",(s/h).divide(),(c/h).divide());
|
println!("cordic s={} c={}",(s/h).divide(),(c/h).divide());
|
||||||
let (fs,fc)=f.sin_cos();
|
let (fs,fc)=f.sin_cos();
|
||||||
println!("float s={} c={}",fs,fc);
|
println!("float s={} c={}",fs,fc);
|
||||||
assert!(close_enough((c/h).divide().wrap_1(),Planar64::raw((fc*((1u64<<32) as f64)) as i64)));
|
assert!(close_enough((c/h).divide().fix_1(),Planar64::raw((fc*((1u64<<32) as f64)) as i64)));
|
||||||
assert!(close_enough((s/h).divide().wrap_1(),Planar64::raw((fs*((1u64<<32) as f64)) as i64)));
|
assert!(close_enough((s/h).divide().fix_1(),Planar64::raw((fs*((1u64<<32) as f64)) as i64)));
|
||||||
}
|
}
|
||||||
test_angle(1.0);
|
test_angle(1.0);
|
||||||
test_angle(std::f64::consts::PI/4.0);
|
test_angle(std::f64::consts::PI/4.0);
|
||||||
@ -562,10 +551,6 @@ pub mod vec3{
|
|||||||
pub const MAX:Planar64Vec3=Planar64Vec3::new([Planar64::MAX;3]);
|
pub const MAX:Planar64Vec3=Planar64Vec3::new([Planar64::MAX;3]);
|
||||||
pub const ZERO:Planar64Vec3=Planar64Vec3::new([Planar64::ZERO;3]);
|
pub const ZERO:Planar64Vec3=Planar64Vec3::new([Planar64::ZERO;3]);
|
||||||
pub const ZERO_2:linear_ops::types::Vector3<Fixed::<2,64>>=linear_ops::types::Vector3::new([Fixed::<2,64>::ZERO;3]);
|
pub const ZERO_2:linear_ops::types::Vector3<Fixed::<2,64>>=linear_ops::types::Vector3::new([Fixed::<2,64>::ZERO;3]);
|
||||||
pub const ZERO_3:linear_ops::types::Vector3<Fixed::<3,96>>=linear_ops::types::Vector3::new([Fixed::<3,96>::ZERO;3]);
|
|
||||||
pub const ZERO_4:linear_ops::types::Vector3<Fixed::<4,128>>=linear_ops::types::Vector3::new([Fixed::<4,128>::ZERO;3]);
|
|
||||||
pub const ZERO_5:linear_ops::types::Vector3<Fixed::<5,160>>=linear_ops::types::Vector3::new([Fixed::<5,160>::ZERO;3]);
|
|
||||||
pub const ZERO_6:linear_ops::types::Vector3<Fixed::<6,192>>=linear_ops::types::Vector3::new([Fixed::<6,192>::ZERO;3]);
|
|
||||||
pub const X:Planar64Vec3=Planar64Vec3::new([Planar64::ONE,Planar64::ZERO,Planar64::ZERO]);
|
pub const X:Planar64Vec3=Planar64Vec3::new([Planar64::ONE,Planar64::ZERO,Planar64::ZERO]);
|
||||||
pub const Y:Planar64Vec3=Planar64Vec3::new([Planar64::ZERO,Planar64::ONE,Planar64::ZERO]);
|
pub const Y:Planar64Vec3=Planar64Vec3::new([Planar64::ZERO,Planar64::ONE,Planar64::ZERO]);
|
||||||
pub const Z:Planar64Vec3=Planar64Vec3::new([Planar64::ZERO,Planar64::ZERO,Planar64::ONE]);
|
pub const Z:Planar64Vec3=Planar64Vec3::new([Planar64::ZERO,Planar64::ZERO,Planar64::ONE]);
|
||||||
@ -633,8 +618,8 @@ pub mod mat3{
|
|||||||
let (yc,ys)=y.cos_sin();
|
let (yc,ys)=y.cos_sin();
|
||||||
Planar64Mat3::from_cols([
|
Planar64Mat3::from_cols([
|
||||||
Planar64Vec3::new([xc,Planar64::ZERO,-xs]),
|
Planar64Vec3::new([xc,Planar64::ZERO,-xs]),
|
||||||
Planar64Vec3::new([(xs*ys).wrap_1(),yc,(xc*ys).wrap_1()]),
|
Planar64Vec3::new([(xs*ys).fix_1(),yc,(xc*ys).fix_1()]),
|
||||||
Planar64Vec3::new([(xs*yc).wrap_1(),-ys,(xc*yc).wrap_1()]),
|
Planar64Vec3::new([(xs*yc).fix_1(),-ys,(xc*yc).fix_1()]),
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
#[inline]
|
#[inline]
|
||||||
@ -662,21 +647,13 @@ 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.widen_2()+self.matrix3*point
|
self.translation.fix_2()+self.matrix3*point
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl Into<glam::Mat4> for Planar64Affine3{
|
impl Into<glam::Mat4> for Planar64Affine3{
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
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;
|
||||||
@ -9,6 +8,7 @@ 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::NormalizedModes,
|
pub modes:gameplay_modes::Modes,
|
||||||
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,5 +1,3 @@
|
|||||||
use std::collections::HashMap;
|
|
||||||
|
|
||||||
use crate::integer::{Planar64Vec3,Planar64Affine3};
|
use crate::integer::{Planar64Vec3,Planar64Affine3};
|
||||||
use crate::gameplay_attributes;
|
use crate::gameplay_attributes;
|
||||||
|
|
||||||
@ -125,87 +123,6 @@ 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);
|
||||||
pub struct Model{
|
pub struct Model{
|
||||||
|
@ -1,21 +1,22 @@
|
|||||||
use crate::mouse::MouseState;
|
use crate::mouse::MouseState;
|
||||||
use crate::gameplay_modes::{ModeId,StageId};
|
|
||||||
|
|
||||||
#[derive(Clone,Copy,Hash,Eq,PartialEq,Ord,PartialOrd,Debug)]
|
#[derive(Clone,Copy,Hash,Eq,PartialEq,PartialOrd,Debug)]
|
||||||
pub enum TimeInner{}
|
pub enum TimeInner{}
|
||||||
pub type Time=crate::integer::Time<TimeInner>;
|
pub type Time=crate::integer::Time<TimeInner>;
|
||||||
|
|
||||||
#[derive(Clone,Debug)]
|
#[derive(Clone,Debug)]
|
||||||
pub enum Instruction{
|
pub enum Instruction{
|
||||||
Mouse(MouseInstruction),
|
Mouse(MouseInstruction),
|
||||||
SetControl(SetControlInstruction),
|
Other(OtherInstruction),
|
||||||
Mode(ModeInstruction),
|
|
||||||
Misc(MiscInstruction),
|
|
||||||
/// Idle: there were no input events, but the simulation is safe to advance to this timestep
|
|
||||||
Idle,
|
|
||||||
}
|
}
|
||||||
impl Instruction{
|
impl Instruction{
|
||||||
pub const IDLE:Self=Self::Idle;
|
pub const IDLE:Self=Self::Other(OtherInstruction::Other(OtherOtherInstruction::Idle));
|
||||||
|
}
|
||||||
|
#[derive(Clone,Debug)]
|
||||||
|
pub enum OtherInstruction{
|
||||||
|
SetControl(SetControlInstruction),
|
||||||
|
Mode(ModeInstruction),
|
||||||
|
Other(OtherOtherInstruction),
|
||||||
}
|
}
|
||||||
#[derive(Clone,Debug)]
|
#[derive(Clone,Debug)]
|
||||||
pub enum MouseInstruction{
|
pub enum MouseInstruction{
|
||||||
@ -43,14 +44,15 @@ pub enum ModeInstruction{
|
|||||||
/// This forgets all inputs and settings which need to be reapplied.
|
/// This forgets all inputs and settings which need to be reapplied.
|
||||||
Reset,
|
Reset,
|
||||||
/// Restart: Teleport to the start zone.
|
/// Restart: Teleport to the start zone.
|
||||||
/// This runs when you press R or teleport to a bonus
|
Restart,
|
||||||
Restart(ModeId),
|
|
||||||
/// Spawn: Teleport to a specific mode's spawn
|
/// Spawn: Teleport to a specific mode's spawn
|
||||||
/// This runs when the map loads to put you at the map lobby
|
/// Sets current mode & spawn
|
||||||
Spawn(ModeId,StageId),
|
Spawn(crate::gameplay_modes::ModeId,crate::gameplay_modes::StageId),
|
||||||
}
|
}
|
||||||
#[derive(Clone,Debug)]
|
#[derive(Clone,Debug)]
|
||||||
pub enum MiscInstruction{
|
pub enum OtherOtherInstruction{
|
||||||
|
/// Idle: there were no input events, but the simulation is safe to advance to this timestep
|
||||||
|
Idle,
|
||||||
PracticeFly,
|
PracticeFly,
|
||||||
SetSensitivity(crate::integer::Ratio64Vec2),
|
SetSensitivity(crate::integer::Ratio64Vec2),
|
||||||
}
|
}
|
||||||
|
@ -1,20 +0,0 @@
|
|||||||
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::Clamp<Planar64>,
|
|
||||||
{
|
|
||||||
self.origin+self.direction.map(|elem|(t*elem).divide().clamp())
|
|
||||||
}
|
|
||||||
}
|
|
@ -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,Ord,PartialOrd,Debug)]
|
#[derive(Clone,Copy,Hash,Eq,PartialEq,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(),
|
RunState::Finished{timer}=>timer.time(time),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn start(&mut self,time:PhysicsTime)->Result<(),Error>{
|
pub fn start(&mut self,time:PhysicsTime)->Result<(),Error>{
|
||||||
@ -110,10 +110,4 @@ 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,Ord,PartialOrd,Debug)]
|
#[derive(Clone,Copy,Hash,Eq,PartialEq,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)]
|
||||||
pub enum Inner{}
|
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();
|
let new_time=self.time(time);
|
||||||
let mut timer=TimerFixed{
|
let mut timer=TimerFixed{
|
||||||
state:self.state,
|
state:self.state,
|
||||||
_paused:Unpaused,
|
_paused:Unpaused,
|
||||||
@ -165,9 +165,6 @@ 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,
|
||||||
@ -181,9 +178,6 @@ 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
|
||||||
@ -205,6 +199,12 @@ 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(),
|
Self::Paused(timer)=>timer.time(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!(0));
|
assert_eq!(timer.time(sec!(1)),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!(5));
|
assert_eq!(timer.time(sec!(20)),sec!(5));
|
||||||
}
|
}
|
||||||
#[test]
|
#[test]
|
||||||
fn test_timer()->Result<(),Error>{
|
fn test_timer()->Result<(),Error>{
|
||||||
|
56
lib/common/src/updatable.rs
Normal file
56
lib/common/src/updatable.rs
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
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.5.0"
|
version = "0.4.1"
|
||||||
edition = "2024"
|
edition = "2021"
|
||||||
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,5 +9,13 @@ 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 = { version = "0.6.0", path = "../common", registry = "strafesnet" }
|
strafesnet_common = { path = "../common", registry = "strafesnet" }
|
||||||
|
url = { version = "2.5.2", optional = true }
|
||||||
|
vbsp = { version = "0.6.0", optional = true }
|
||||||
|
@ -1,116 +0,0 @@
|
|||||||
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<'a,L:Loader<Resource=Texture,Index<'a>=H>+'a>(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<'a,L:Loader<Resource=Mesh,Index<'a>=H>+'a>(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,5 +1,34 @@
|
|||||||
|
#[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;
|
||||||
pub mod deferred_loader;
|
#[cfg(any(feature="source",feature="legacy"))]
|
||||||
|
pub mod valve_mesh;
|
||||||
|
#[cfg(any(feature="roblox",feature="legacy"))]
|
||||||
|
pub mod roblox_mesh;
|
||||||
|
|
||||||
|
#[cfg(feature="legacy")]
|
||||||
|
pub fn roblox_legacy()->roblox_legacy::Loader{
|
||||||
|
roblox_legacy::Loader::new()
|
||||||
|
}
|
||||||
|
#[cfg(feature="legacy")]
|
||||||
|
pub fn source_legacy()->source_legacy::Loader{
|
||||||
|
source_legacy::Loader::new()
|
||||||
|
}
|
||||||
|
#[cfg(feature="roblox")]
|
||||||
|
pub fn roblox()->roblox::Loader{
|
||||||
|
roblox::Loader::new()
|
||||||
|
}
|
||||||
|
#[cfg(feature="source")]
|
||||||
|
pub fn source()->source::Loader{
|
||||||
|
source::Loader::new()
|
||||||
|
}
|
||||||
|
@ -1,8 +0,0 @@
|
|||||||
use std::error::Error;
|
|
||||||
|
|
||||||
pub trait Loader{
|
|
||||||
type Error:Error;
|
|
||||||
type Index<'a> where Self:'a;
|
|
||||||
type Resource;
|
|
||||||
fn load<'a>(&mut self,index:Self::Index<'a>)->Result<Self::Resource,Self::Error>;
|
|
||||||
}
|
|
@ -1,17 +0,0 @@
|
|||||||
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))
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
48
lib/deferred_loader/src/rbxassetid.rs
Normal file
48
lib/deferred_loader/src/rbxassetid.rs
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
#[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)))?))
|
||||||
|
}
|
||||||
|
}
|
0
lib/deferred_loader/src/roblox.rs
Normal file
0
lib/deferred_loader/src/roblox.rs
Normal file
112
lib/deferred_loader/src/roblox_legacy.rs
Normal file
112
lib/deferred_loader/src/roblox_legacy.rs
Normal file
@ -0,0 +1,112 @@
|
|||||||
|
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,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
30
lib/deferred_loader/src/roblox_mesh.rs
Normal file
30
lib/deferred_loader/src/roblox_mesh.rs
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
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))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
102
lib/deferred_loader/src/source_legacy.rs
Normal file
102
lib/deferred_loader/src/source_legacy.rs
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
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,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
60
lib/deferred_loader/src/valve_mesh.rs
Normal file
60
lib/deferred_loader/src/valve_mesh.rs
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
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.2.0"
|
version = "0.1.1"
|
||||||
edition = "2024"
|
edition = "2021"
|
||||||
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.13.0"
|
bnum = "0.12.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 = { version = "0.1.0", path = "../ratio_ops", registry = "strafesnet", optional = true }
|
ratio_ops = { path = "../ratio_ops", registry = "strafesnet", optional = true }
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
use bnum::{BInt,cast::As};
|
use bnum::{BInt,cast::As};
|
||||||
|
|
||||||
#[derive(Clone,Copy,Debug,Default,Hash,PartialEq,Eq,PartialOrd,Ord)]
|
#[derive(Clone,Copy,Debug,Default,Hash,PartialEq,PartialOrd,Ord)]
|
||||||
/// A Fixed point number for which multiply operations widen the bits in the output. (when the wide-mul feature is enabled)
|
/// A Fixed point number for which multiply operations widen the bits in the output. (when the wide-mul feature is enabled)
|
||||||
/// N is the number of u64s to use
|
/// N is the number of u64s to use
|
||||||
/// F is the number of fractional bits (always N*32 lol)
|
/// F is the number of fractional bits (always N*32 lol)
|
||||||
@ -33,14 +33,6 @@ 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;
|
||||||
@ -64,10 +56,6 @@ 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
|
||||||
@ -109,6 +97,7 @@ where
|
|||||||
self.bits.eq(&other.into())
|
self.bits.eq(&other.into())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
impl<const N:usize,const F:usize> Eq for Fixed<N,F>{}
|
||||||
|
|
||||||
impl<const N:usize,const F:usize,T> PartialOrd<T> for Fixed<N,F>
|
impl<const N:usize,const F:usize,T> PartialOrd<T> for Fixed<N,F>
|
||||||
where
|
where
|
||||||
@ -663,94 +652,74 @@ macro_repeated!(
|
|||||||
1,2,3,4,5,6,7,8
|
1,2,3,4,5,6,7,8
|
||||||
);
|
);
|
||||||
|
|
||||||
#[derive(Debug,Eq,PartialEq)]
|
pub trait Fix<Out>{
|
||||||
pub enum NarrowError{
|
fn fix(self)->Out;
|
||||||
Overflow,
|
|
||||||
Underflow,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait Wrap<Output>{
|
macro_rules! impl_fix_rhs_lt_lhs_not_const_generic{
|
||||||
fn wrap(self)->Output;
|
|
||||||
}
|
|
||||||
pub trait Clamp<Output>{
|
|
||||||
fn clamp(self)->Output;
|
|
||||||
}
|
|
||||||
impl<const N:usize,const F:usize> Clamp<Fixed<N,F>> for Result<Fixed<N,F>,NarrowError>{
|
|
||||||
fn clamp(self)->Fixed<N,F>{
|
|
||||||
match self{
|
|
||||||
Ok(fixed)=>fixed,
|
|
||||||
Err(NarrowError::Overflow)=>Fixed::MAX,
|
|
||||||
Err(NarrowError::Underflow)=>Fixed::MIN,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
macro_rules! impl_narrow_not_const_generic{
|
|
||||||
(
|
(
|
||||||
(),
|
(),
|
||||||
($lhs:expr,$rhs:expr)
|
($lhs:expr,$rhs:expr)
|
||||||
)=>{
|
)=>{
|
||||||
paste::item!{
|
|
||||||
impl Fixed<$lhs,{$lhs*32}>
|
impl Fixed<$lhs,{$lhs*32}>
|
||||||
{
|
{
|
||||||
|
paste::item!{
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn [<wrap_ $rhs>](self)->Fixed<$rhs,{$rhs*32}>{
|
pub fn [<fix_ $rhs>](self)->Fixed<$rhs,{$rhs*32}>{
|
||||||
Fixed::from_bits(bnum::cast::As::as_::<BInt::<$rhs>>(self.bits.shr(($lhs-$rhs)*32)))
|
Fixed::from_bits(bnum::cast::As::as_::<BInt::<$rhs>>(self.bits.shr(($lhs-$rhs)*32)))
|
||||||
}
|
}
|
||||||
#[inline]
|
|
||||||
pub fn [<narrow_ $rhs>](self)->Result<Fixed<$rhs,{$rhs*32}>,NarrowError>{
|
|
||||||
if Fixed::<$rhs,{$rhs*32}>::MAX.[<widen_ $lhs>]().bits<self.bits{
|
|
||||||
return Err(NarrowError::Overflow);
|
|
||||||
}
|
|
||||||
if self.bits<Fixed::<$rhs,{$rhs*32}>::MIN.[<widen_ $lhs>]().bits{
|
|
||||||
return Err(NarrowError::Underflow);
|
|
||||||
}
|
|
||||||
Ok(self.[<wrap_ $rhs>]())
|
|
||||||
}
|
|
||||||
#[inline]
|
|
||||||
pub fn [<clamp_ $rhs>](self)->Fixed<$rhs,{$rhs*32}>{
|
|
||||||
self.[<narrow_ $rhs>]().clamp()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl Wrap<Fixed<$rhs,{$rhs*32}>> for Fixed<$lhs,{$lhs*32}>{
|
impl Fix<Fixed<$rhs,{$rhs*32}>> for Fixed<$lhs,{$lhs*32}>{
|
||||||
#[inline]
|
fn fix(self)->Fixed<$rhs,{$rhs*32}>{
|
||||||
fn wrap(self)->Fixed<$rhs,{$rhs*32}>{
|
paste::item!{
|
||||||
self.[<wrap_ $rhs>]()
|
self.[<fix_ $rhs>]()
|
||||||
}
|
|
||||||
}
|
|
||||||
impl TryInto<Fixed<$rhs,{$rhs*32}>> for Fixed<$lhs,{$lhs*32}>{
|
|
||||||
type Error=NarrowError;
|
|
||||||
#[inline]
|
|
||||||
fn try_into(self)->Result<Fixed<$rhs,{$rhs*32}>,Self::Error>{
|
|
||||||
self.[<narrow_ $rhs>]()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl Clamp<Fixed<$rhs,{$rhs*32}>> for Fixed<$lhs,{$lhs*32}>{
|
|
||||||
#[inline]
|
|
||||||
fn clamp(self)->Fixed<$rhs,{$rhs*32}>{
|
|
||||||
self.[<clamp_ $rhs>]()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
macro_rules! impl_widen_not_const_generic{
|
macro_rules! impl_fix_lhs_lt_rhs_not_const_generic{
|
||||||
(
|
(
|
||||||
(),
|
(),
|
||||||
($lhs:expr,$rhs:expr)
|
($lhs:expr,$rhs:expr)
|
||||||
)=>{
|
)=>{
|
||||||
paste::item!{
|
|
||||||
impl Fixed<$lhs,{$lhs*32}>
|
impl Fixed<$lhs,{$lhs*32}>
|
||||||
{
|
{
|
||||||
|
paste::item!{
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn [<widen_ $rhs>](self)->Fixed<$rhs,{$rhs*32}>{
|
pub fn [<fix_ $rhs>](self)->Fixed<$rhs,{$rhs*32}>{
|
||||||
Fixed::from_bits(bnum::cast::As::as_::<BInt::<$rhs>>(self.bits).shl(($rhs-$lhs)*32))
|
Fixed::from_bits(bnum::cast::As::as_::<BInt::<$rhs>>(self.bits).shl(($rhs-$lhs)*32))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl Into<Fixed<$rhs,{$rhs*32}>> for Fixed<$lhs,{$lhs*32}>{
|
}
|
||||||
|
impl Fix<Fixed<$rhs,{$rhs*32}>> for Fixed<$lhs,{$lhs*32}>{
|
||||||
|
fn fix(self)->Fixed<$rhs,{$rhs*32}>{
|
||||||
|
paste::item!{
|
||||||
|
self.[<fix_ $rhs>]()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
macro_rules! impl_fix_lhs_eq_rhs_not_const_generic{
|
||||||
|
(
|
||||||
|
(),
|
||||||
|
($lhs:expr,$rhs:expr)
|
||||||
|
)=>{
|
||||||
|
impl Fixed<$lhs,{$lhs*32}>
|
||||||
|
{
|
||||||
|
paste::item!{
|
||||||
#[inline]
|
#[inline]
|
||||||
fn into(self)->Fixed<$rhs,{$rhs*32}>{
|
pub fn [<fix_ $rhs>](self)->Fixed<$rhs,{$rhs*32}>{
|
||||||
self.[<widen_ $rhs>]()
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Fix<Fixed<$rhs,{$rhs*32}>> for Fixed<$lhs,{$lhs*32}>{
|
||||||
|
fn fix(self)->Fixed<$rhs,{$rhs*32}>{
|
||||||
|
paste::item!{
|
||||||
|
self.[<fix_ $rhs>]()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -760,7 +729,7 @@ macro_rules! impl_widen_not_const_generic{
|
|||||||
// I LOVE NOT BEING ABLE TO USE CONST GENERICS
|
// I LOVE NOT BEING ABLE TO USE CONST GENERICS
|
||||||
|
|
||||||
macro_repeated!(
|
macro_repeated!(
|
||||||
impl_narrow_not_const_generic,(),
|
impl_fix_rhs_lt_lhs_not_const_generic,(),
|
||||||
(2,1),(3,1),(4,1),(5,1),(6,1),(7,1),(8,1),(9,1),(10,1),(11,1),(12,1),(13,1),(14,1),(15,1),(16,1),(17,1),
|
(2,1),(3,1),(4,1),(5,1),(6,1),(7,1),(8,1),(9,1),(10,1),(11,1),(12,1),(13,1),(14,1),(15,1),(16,1),(17,1),
|
||||||
(3,2),(4,2),(5,2),(6,2),(7,2),(8,2),(9,2),(10,2),(11,2),(12,2),(13,2),(14,2),(15,2),(16,2),
|
(3,2),(4,2),(5,2),(6,2),(7,2),(8,2),(9,2),(10,2),(11,2),(12,2),(13,2),(14,2),(15,2),(16,2),
|
||||||
(4,3),(5,3),(6,3),(7,3),(8,3),(9,3),(10,3),(11,3),(12,3),(13,3),(14,3),(15,3),(16,3),
|
(4,3),(5,3),(6,3),(7,3),(8,3),(9,3),(10,3),(11,3),(12,3),(13,3),(14,3),(15,3),(16,3),
|
||||||
@ -778,7 +747,7 @@ macro_repeated!(
|
|||||||
(16,15)
|
(16,15)
|
||||||
);
|
);
|
||||||
macro_repeated!(
|
macro_repeated!(
|
||||||
impl_widen_not_const_generic,(),
|
impl_fix_lhs_lt_rhs_not_const_generic,(),
|
||||||
(1,2),
|
(1,2),
|
||||||
(1,3),(2,3),
|
(1,3),(2,3),
|
||||||
(1,4),(2,4),(3,4),
|
(1,4),(2,4),(3,4),
|
||||||
@ -793,8 +762,11 @@ macro_repeated!(
|
|||||||
(1,13),(2,13),(3,13),(4,13),(5,13),(6,13),(7,13),(8,13),(9,13),(10,13),(11,13),(12,13),
|
(1,13),(2,13),(3,13),(4,13),(5,13),(6,13),(7,13),(8,13),(9,13),(10,13),(11,13),(12,13),
|
||||||
(1,14),(2,14),(3,14),(4,14),(5,14),(6,14),(7,14),(8,14),(9,14),(10,14),(11,14),(12,14),(13,14),
|
(1,14),(2,14),(3,14),(4,14),(5,14),(6,14),(7,14),(8,14),(9,14),(10,14),(11,14),(12,14),(13,14),
|
||||||
(1,15),(2,15),(3,15),(4,15),(5,15),(6,15),(7,15),(8,15),(9,15),(10,15),(11,15),(12,15),(13,15),(14,15),
|
(1,15),(2,15),(3,15),(4,15),(5,15),(6,15),(7,15),(8,15),(9,15),(10,15),(11,15),(12,15),(13,15),(14,15),
|
||||||
(1,16),(2,16),(3,16),(4,16),(5,16),(6,16),(7,16),(8,16),(9,16),(10,16),(11,16),(12,16),(13,16),(14,16),(15,16),
|
(1,16),(2,16),(3,16),(4,16),(5,16),(6,16),(7,16),(8,16),(9,16),(10,16),(11,16),(12,16),(13,16),(14,16),(15,16)
|
||||||
(1,17)
|
);
|
||||||
|
macro_repeated!(
|
||||||
|
impl_fix_lhs_eq_rhs_not_const_generic,(),
|
||||||
|
(1,1),(2,2),(3,3),(4,4),(5,5),(6,6),(7,7),(8,8),(9,9),(10,10),(11,11),(12,12),(13,13),(14,14),(15,15),(16,16)
|
||||||
);
|
);
|
||||||
|
|
||||||
macro_rules! impl_not_const_generic{
|
macro_rules! impl_not_const_generic{
|
||||||
@ -814,13 +786,16 @@ macro_rules! impl_not_const_generic{
|
|||||||
let mut result=Self::ZERO;
|
let mut result=Self::ZERO;
|
||||||
|
|
||||||
//resize self to match the wide mul output
|
//resize self to match the wide mul output
|
||||||
let wide_self=self.[<widen_ $_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(){
|
||||||
result.as_bits_mut().as_bits_mut().set_bit(shift,true);
|
let new_result={
|
||||||
if wide_self<result.[<wide_mul_ $n _ $n>](result){
|
let mut bits=result.to_bits().to_bits();
|
||||||
// put it back lol
|
bits.set_bit(shift,true);
|
||||||
result.as_bits_mut().as_bits_mut().set_bit(shift,false);
|
Self::from_bits(BInt::from_bits(bits))
|
||||||
|
};
|
||||||
|
if new_result.[<wide_mul_ $n _ $n>](new_result)<=wide_self{
|
||||||
|
result=new_result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
result
|
result
|
||||||
|
@ -57,11 +57,11 @@ 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
|
||||||
let b:Result<I32F32,_>=Into::<f32>::into(-I32F32::MIN.widen_2()).try_into();
|
let b:Result<I32F32,_>=Into::<f32>::into(-I32F32::MIN.fix_2()).try_into();
|
||||||
assert_eq!(b,Err(crate::fixed::FixedFromFloatError::Overflow));
|
assert_eq!(b,Err(crate::fixed::FixedFromFloatError::Overflow));
|
||||||
let b:Result<I32F32,_>=f32::MIN_POSITIVE.try_into();
|
let b:Result<I32F32,_>=f32::MIN_POSITIVE.try_into();
|
||||||
assert_eq!(b,Err(crate::fixed::FixedFromFloatError::Underflow));
|
assert_eq!(b,Err(crate::fixed::FixedFromFloatError::Underflow));
|
||||||
@ -136,24 +136,11 @@ fn test_bint(){
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_wrap(){
|
fn test_fix(){
|
||||||
assert_eq!(I32F32::ONE,I256F256::ONE.wrap_1());
|
assert_eq!(I32F32::ONE.fix_8(),I256F256::ONE);
|
||||||
assert_eq!(I32F32::NEG_ONE,I256F256::NEG_ONE.wrap_1());
|
assert_eq!(I32F32::ONE,I256F256::ONE.fix_1());
|
||||||
}
|
assert_eq!(I32F32::NEG_ONE.fix_8(),I256F256::NEG_ONE);
|
||||||
#[test]
|
assert_eq!(I32F32::NEG_ONE,I256F256::NEG_ONE.fix_1());
|
||||||
fn test_narrow(){
|
|
||||||
assert_eq!(Ok(I32F32::ONE),I256F256::ONE.narrow_1());
|
|
||||||
assert_eq!(Ok(I32F32::NEG_ONE),I256F256::NEG_ONE.narrow_1());
|
|
||||||
}
|
|
||||||
#[test]
|
|
||||||
fn test_widen(){
|
|
||||||
assert_eq!(I32F32::ONE.widen_8(),I256F256::ONE);
|
|
||||||
assert_eq!(I32F32::NEG_ONE.widen_8(),I256F256::NEG_ONE);
|
|
||||||
}
|
|
||||||
#[test]
|
|
||||||
fn test_clamp(){
|
|
||||||
assert_eq!(I32F32::ONE,I256F256::ONE.clamp_1());
|
|
||||||
assert_eq!(I32F32::NEG_ONE,I256F256::NEG_ONE.clamp_1());
|
|
||||||
}
|
}
|
||||||
#[test]
|
#[test]
|
||||||
fn test_sqrt(){
|
fn test_sqrt(){
|
||||||
|
@ -15,10 +15,8 @@ macro_rules! impl_zeroes{
|
|||||||
let radicand=a1*a1-a2*a0*4;
|
let radicand=a1*a1-a2*a0*4;
|
||||||
match radicand.cmp(&<Self as core::ops::Mul>::Output::ZERO){
|
match radicand.cmp(&<Self as core::ops::Mul>::Output::ZERO){
|
||||||
Ordering::Greater=>{
|
Ordering::Greater=>{
|
||||||
// using wrap because sqrt always halves the number of leading digits.
|
|
||||||
// clamp would be more defensive, but is slower.
|
|
||||||
paste::item!{
|
paste::item!{
|
||||||
let planar_radicand=radicand.sqrt().[<wrap_ $n>]();
|
let planar_radicand=radicand.sqrt().[<fix_ $n>]();
|
||||||
}
|
}
|
||||||
//sort roots ascending and avoid taking the difference of large numbers
|
//sort roots ascending and avoid taking the difference of large numbers
|
||||||
let zeroes=match (a2pos,Self::ZERO<a1){
|
let zeroes=match (a2pos,Self::ZERO<a1){
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "linear_ops"
|
name = "linear_ops"
|
||||||
version = "0.1.1"
|
version = "0.1.0"
|
||||||
edition = "2024"
|
edition = "2021"
|
||||||
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 = { version = "0.1.0", path = "../ratio_ops", registry = "strafesnet", optional = true }
|
ratio_ops = { path = "../ratio_ops", registry = "strafesnet", optional = true }
|
||||||
fixed_wide = { version = "0.2.0", path = "../fixed_wide", registry = "strafesnet", optional = true }
|
fixed_wide = { 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]
|
||||||
|
@ -38,95 +38,40 @@ macro_rules! impl_fixed_wide_vector {
|
|||||||
$crate::macro_4!(impl_fixed_wide_vector_not_const_generic,());
|
$crate::macro_4!(impl_fixed_wide_vector_not_const_generic,());
|
||||||
// I LOVE NOT BEING ABLE TO USE CONST GENERICS
|
// I LOVE NOT BEING ABLE TO USE CONST GENERICS
|
||||||
$crate::macro_repeated!(
|
$crate::macro_repeated!(
|
||||||
impl_narrow_not_const_generic,(),
|
impl_fix_not_const_generic,(),
|
||||||
(2,1),(3,1),(4,1),(5,1),(6,1),(7,1),(8,1),(9,1),(10,1),(11,1),(12,1),(13,1),(14,1),(15,1),(16,1),(17,1),
|
(1,1),(2,1),(3,1),(4,1),(5,1),(6,1),(7,1),(8,1),(9,1),(10,1),(11,1),(12,1),(13,1),(14,1),(15,1),(16,1),
|
||||||
(3,2),(4,2),(5,2),(6,2),(7,2),(8,2),(9,2),(10,2),(11,2),(12,2),(13,2),(14,2),(15,2),(16,2),
|
(1,2),(2,2),(3,2),(4,2),(5,2),(6,2),(7,2),(8,2),(9,2),(10,2),(11,2),(12,2),(13,2),(14,2),(15,2),(16,2),
|
||||||
(4,3),(5,3),(6,3),(7,3),(8,3),(9,3),(10,3),(11,3),(12,3),(13,3),(14,3),(15,3),(16,3),
|
(1,3),(2,3),(3,3),(4,3),(5,3),(6,3),(7,3),(8,3),(9,3),(10,3),(11,3),(12,3),(13,3),(14,3),(15,3),(16,3),
|
||||||
(5,4),(6,4),(7,4),(8,4),(9,4),(10,4),(11,4),(12,4),(13,4),(14,4),(15,4),(16,4),
|
(1,4),(2,4),(3,4),(4,4),(5,4),(6,4),(7,4),(8,4),(9,4),(10,4),(11,4),(12,4),(13,4),(14,4),(15,4),(16,4),
|
||||||
(6,5),(7,5),(8,5),(9,5),(10,5),(11,5),(12,5),(13,5),(14,5),(15,5),(16,5),
|
(1,5),(2,5),(3,5),(4,5),(5,5),(6,5),(7,5),(8,5),(9,5),(10,5),(11,5),(12,5),(13,5),(14,5),(15,5),(16,5),
|
||||||
(7,6),(8,6),(9,6),(10,6),(11,6),(12,6),(13,6),(14,6),(15,6),(16,6),
|
(1,6),(2,6),(3,6),(4,6),(5,6),(6,6),(7,6),(8,6),(9,6),(10,6),(11,6),(12,6),(13,6),(14,6),(15,6),(16,6),
|
||||||
(8,7),(9,7),(10,7),(11,7),(12,7),(13,7),(14,7),(15,7),(16,7),
|
(1,7),(2,7),(3,7),(4,7),(5,7),(6,7),(7,7),(8,7),(9,7),(10,7),(11,7),(12,7),(13,7),(14,7),(15,7),(16,7),
|
||||||
(9,8),(10,8),(11,8),(12,8),(13,8),(14,8),(15,8),(16,8),
|
(1,8),(2,8),(3,8),(4,8),(5,8),(6,8),(7,8),(8,8),(9,8),(10,8),(11,8),(12,8),(13,8),(14,8),(15,8),(16,8),
|
||||||
(10,9),(11,9),(12,9),(13,9),(14,9),(15,9),(16,9),
|
(1,9),(2,9),(3,9),(4,9),(5,9),(6,9),(7,9),(8,9),(9,9),(10,9),(11,9),(12,9),(13,9),(14,9),(15,9),(16,9),
|
||||||
(11,10),(12,10),(13,10),(14,10),(15,10),(16,10),
|
(1,10),(2,10),(3,10),(4,10),(5,10),(6,10),(7,10),(8,10),(9,10),(10,10),(11,10),(12,10),(13,10),(14,10),(15,10),(16,10),
|
||||||
(12,11),(13,11),(14,11),(15,11),(16,11),
|
(1,11),(2,11),(3,11),(4,11),(5,11),(6,11),(7,11),(8,11),(9,11),(10,11),(11,11),(12,11),(13,11),(14,11),(15,11),(16,11),
|
||||||
(13,12),(14,12),(15,12),(16,12),
|
(1,12),(2,12),(3,12),(4,12),(5,12),(6,12),(7,12),(8,12),(9,12),(10,12),(11,12),(12,12),(13,12),(14,12),(15,12),(16,12),
|
||||||
(14,13),(15,13),(16,13),
|
(1,13),(2,13),(3,13),(4,13),(5,13),(6,13),(7,13),(8,13),(9,13),(10,13),(11,13),(12,13),(13,13),(14,13),(15,13),(16,13),
|
||||||
(15,14),(16,14),
|
(1,14),(2,14),(3,14),(4,14),(5,14),(6,14),(7,14),(8,14),(9,14),(10,14),(11,14),(12,14),(13,14),(14,14),(15,14),(16,14),
|
||||||
(16,15)
|
(1,15),(2,15),(3,15),(4,15),(5,15),(6,15),(7,15),(8,15),(9,15),(10,15),(11,15),(12,15),(13,15),(14,15),(15,15),(16,15),
|
||||||
|
(1,16),(2,16),(3,16),(4,16),(5,16),(6,16),(7,16),(8,16),(9,16),(10,16),(11,16),(12,16),(13,16),(14,16),(15,16),(16,16)
|
||||||
);
|
);
|
||||||
$crate::macro_repeated!(
|
|
||||||
impl_widen_not_const_generic,(),
|
|
||||||
(1,2),
|
|
||||||
(1,3),(2,3),
|
|
||||||
(1,4),(2,4),(3,4),
|
|
||||||
(1,5),(2,5),(3,5),(4,5),
|
|
||||||
(1,6),(2,6),(3,6),(4,6),(5,6),
|
|
||||||
(1,7),(2,7),(3,7),(4,7),(5,7),(6,7),
|
|
||||||
(1,8),(2,8),(3,8),(4,8),(5,8),(6,8),(7,8),
|
|
||||||
(1,9),(2,9),(3,9),(4,9),(5,9),(6,9),(7,9),(8,9),
|
|
||||||
(1,10),(2,10),(3,10),(4,10),(5,10),(6,10),(7,10),(8,10),(9,10),
|
|
||||||
(1,11),(2,11),(3,11),(4,11),(5,11),(6,11),(7,11),(8,11),(9,11),(10,11),
|
|
||||||
(1,12),(2,12),(3,12),(4,12),(5,12),(6,12),(7,12),(8,12),(9,12),(10,12),(11,12),
|
|
||||||
(1,13),(2,13),(3,13),(4,13),(5,13),(6,13),(7,13),(8,13),(9,13),(10,13),(11,13),(12,13),
|
|
||||||
(1,14),(2,14),(3,14),(4,14),(5,14),(6,14),(7,14),(8,14),(9,14),(10,14),(11,14),(12,14),(13,14),
|
|
||||||
(1,15),(2,15),(3,15),(4,15),(5,15),(6,15),(7,15),(8,15),(9,15),(10,15),(11,15),(12,15),(13,15),(14,15),
|
|
||||||
(1,16),(2,16),(3,16),(4,16),(5,16),(6,16),(7,16),(8,16),(9,16),(10,16),(11,16),(12,16),(13,16),(14,16),(15,16),
|
|
||||||
(1,17)
|
|
||||||
);
|
|
||||||
impl<const N:usize,T:fixed_wide::fixed::Wrap<U>,U> fixed_wide::fixed::Wrap<Vector<N,U>> for Vector<N,T>
|
|
||||||
{
|
|
||||||
#[inline]
|
|
||||||
fn wrap(self)->Vector<N,U>{
|
|
||||||
self.map(|t|t.wrap())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl<const N:usize,T:fixed_wide::fixed::Clamp<U>,U> fixed_wide::fixed::Clamp<Vector<N,U>> for Vector<N,T>
|
|
||||||
{
|
|
||||||
#[inline]
|
|
||||||
fn clamp(self)->Vector<N,U>{
|
|
||||||
self.map(|t|t.clamp())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
#[macro_export(local_inner_macros)]
|
#[macro_export(local_inner_macros)]
|
||||||
macro_rules! impl_narrow_not_const_generic{
|
macro_rules! impl_fix_not_const_generic{
|
||||||
(
|
(
|
||||||
(),
|
(),
|
||||||
($lhs:expr,$rhs:expr)
|
($lhs:expr,$rhs:expr)
|
||||||
)=>{
|
)=>{
|
||||||
|
impl<const N:usize> Vector<N,fixed_wide::fixed::Fixed<$lhs,{$lhs*32}>>
|
||||||
|
{
|
||||||
paste::item!{
|
paste::item!{
|
||||||
impl<const N:usize> Vector<N,fixed_wide::fixed::Fixed<$lhs,{$lhs*32}>>{
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn [<wrap_ $rhs>](self)->Vector<N,fixed_wide::fixed::Fixed<$rhs,{$rhs*32}>>{
|
pub fn [<fix_ $rhs>](self)->Vector<N,fixed_wide::fixed::Fixed<$rhs,{$rhs*32}>>{
|
||||||
self.map(|t|t.[<wrap_ $rhs>]())
|
self.map(|t|t.[<fix_ $rhs>]())
|
||||||
}
|
|
||||||
#[inline]
|
|
||||||
pub fn [<narrow_ $rhs>](self)->Vector<N,Result<fixed_wide::fixed::Fixed<$rhs,{$rhs*32}>,fixed_wide::fixed::NarrowError>>{
|
|
||||||
self.map(|t|t.[<narrow_ $rhs>]())
|
|
||||||
}
|
|
||||||
#[inline]
|
|
||||||
pub fn [<clamp_ $rhs>](self)->Vector<N,fixed_wide::fixed::Fixed<$rhs,{$rhs*32}>>{
|
|
||||||
self.map(|t|t.[<clamp_ $rhs>]())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#[doc(hidden)]
|
|
||||||
#[macro_export(local_inner_macros)]
|
|
||||||
macro_rules! impl_widen_not_const_generic{
|
|
||||||
(
|
|
||||||
(),
|
|
||||||
($lhs:expr,$rhs:expr)
|
|
||||||
)=>{
|
|
||||||
paste::item!{
|
|
||||||
impl<const N:usize> Vector<N,fixed_wide::fixed::Fixed<$lhs,{$lhs*32}>>{
|
|
||||||
#[inline]
|
|
||||||
pub fn [<widen_ $rhs>](self)->Vector<N,fixed_wide::fixed::Fixed<$rhs,{$rhs*32}>>{
|
|
||||||
self.map(|t|t.[<widen_ $rhs>]())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -204,19 +204,13 @@ macro_rules! impl_matrix_named_fields_shape {
|
|||||||
type Target=$struct_outer<Vector<$size_inner,T>>;
|
type Target=$struct_outer<Vector<$size_inner,T>>;
|
||||||
#[inline]
|
#[inline]
|
||||||
fn deref(&self)->&Self::Target{
|
fn deref(&self)->&Self::Target{
|
||||||
// This cast is valid because Matrix has #[repr(transparent)]
|
unsafe{core::mem::transmute(&self.array)}
|
||||||
let ptr=&self.array as *const [[T;$size_inner];$size_outer] as *const Self::Target;
|
|
||||||
// SAFETY: this pointer is non-null because it comes from a reference
|
|
||||||
unsafe{&*ptr}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl<T> core::ops::DerefMut for Matrix<$size_outer,$size_inner,T>{
|
impl<T> core::ops::DerefMut for Matrix<$size_outer,$size_inner,T>{
|
||||||
#[inline]
|
#[inline]
|
||||||
fn deref_mut(&mut self)->&mut Self::Target{
|
fn deref_mut(&mut self)->&mut Self::Target{
|
||||||
// This cast is valid because Matrix has #[repr(transparent)]
|
unsafe{core::mem::transmute(&mut self.array)}
|
||||||
let ptr=&mut self.array as *mut [[T;$size_inner];$size_outer] as *mut Self::Target;
|
|
||||||
// SAFETY: this pointer is non-null because it comes from a reference
|
|
||||||
unsafe{&mut*ptr}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -58,15 +58,6 @@ macro_rules! impl_vector {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<const N:usize,T,E:std::fmt::Debug> Vector<N,Result<T,E>>{
|
|
||||||
#[inline]
|
|
||||||
pub fn unwrap(self)->Vector<N,T>{
|
|
||||||
Vector{
|
|
||||||
array:self.array.map(Result::unwrap)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<const N:usize,T:Ord> Vector<N,T>{
|
impl<const N:usize,T:Ord> Vector<N,T>{
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn min(self,rhs:Self)->Self{
|
pub fn min(self,rhs:Self)->Self{
|
||||||
@ -330,19 +321,13 @@ macro_rules! impl_vector_named_fields {
|
|||||||
type Target=$struct<T>;
|
type Target=$struct<T>;
|
||||||
#[inline]
|
#[inline]
|
||||||
fn deref(&self)->&Self::Target{
|
fn deref(&self)->&Self::Target{
|
||||||
// This cast is valid because Vector has #[repr(transparent)]
|
unsafe{core::mem::transmute(&self.array)}
|
||||||
let ptr=&self.array as *const [T;$size] as *const Self::Target;
|
|
||||||
// SAFETY: this pointer is non-null because it comes from a reference
|
|
||||||
unsafe{&*ptr}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl<T> core::ops::DerefMut for Vector<$size,T>{
|
impl<T> core::ops::DerefMut for Vector<$size,T>{
|
||||||
#[inline]
|
#[inline]
|
||||||
fn deref_mut(&mut self)->&mut Self::Target{
|
fn deref_mut(&mut self)->&mut Self::Target{
|
||||||
// This cast is valid because Vector has #[repr(transparent)]
|
unsafe{core::mem::transmute(&mut self.array)}
|
||||||
let ptr=&mut self.array as *mut [T;$size] as *mut Self::Target;
|
|
||||||
// SAFETY: this pointer is non-null because it comes from a reference
|
|
||||||
unsafe{&mut*ptr}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
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,7 +3,6 @@
|
|||||||
/// 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.1"
|
version = "0.1.0"
|
||||||
edition = "2024"
|
edition = "2021"
|
||||||
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,35 +268,30 @@ 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+Parity,
|
LhsDen:Copy,
|
||||||
RhsNum:Copy,
|
RhsNum:Copy,
|
||||||
RhsDen:Copy+Parity,
|
RhsDen:Copy,
|
||||||
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>,
|
||||||
RhsDen:core::ops::Mul<LhsNum,Output=U>,
|
T:PartialOrd<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.partial_cmp_ratio(other)
|
(self.num*other.den).partial_cmp(&(other.num*self.den))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
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+Parity,
|
Den:Copy,
|
||||||
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.cmp_ratio(other)
|
(self.num*other.den).cmp(&(other.num*self.den))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "strafesnet_rbx_loader"
|
name = "strafesnet_rbx_loader"
|
||||||
version = "0.6.0"
|
version = "0.5.2"
|
||||||
edition = "2024"
|
edition = "2021"
|
||||||
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,14 +11,12 @@ authors = ["Rhys Lloyd <krakow20@gmail.com>"]
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
bytemuck = "1.14.3"
|
bytemuck = "1.14.3"
|
||||||
glam = "0.30.0"
|
glam = "0.29.0"
|
||||||
lazy-regex = "3.1.0"
|
lazy-regex = "3.1.0"
|
||||||
rbx_binary = { version = "1.1.0-sn4", registry = "strafesnet" }
|
rbx_binary = { version = "0.7.4", registry = "strafesnet" }
|
||||||
rbx_dom_weak = { version = "3.1.0-sn4", registry = "strafesnet", features = ["instance-userdata"] }
|
rbx_dom_weak = { version = "2.7.0", registry = "strafesnet" }
|
||||||
rbx_mesh = "0.3.1"
|
rbx_mesh = "0.1.2"
|
||||||
rbx_reflection_database = "1.0.0"
|
rbx_reflection_database = { version = "0.2.10", registry = "strafesnet" }
|
||||||
rbx_xml = { version = "1.1.0-sn4", registry = "strafesnet" }
|
rbx_xml = { version = "0.13.3", registry = "strafesnet" }
|
||||||
rbxassetid = { version = "0.1.0", path = "../rbxassetid", registry = "strafesnet" }
|
roblox_emulator = { path = "../roblox_emulator", registry = "strafesnet" }
|
||||||
roblox_emulator = { version = "0.5.0", path = "../roblox_emulator", default-features = false, registry = "strafesnet" }
|
strafesnet_common = { path = "../common", 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,12 +1,8 @@
|
|||||||
use std::io::Read;
|
use std::io::Read;
|
||||||
use rbx_dom_weak::WeakDom;
|
use rbx_dom_weak::WeakDom;
|
||||||
use roblox_emulator::context::Context;
|
|
||||||
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{
|
||||||
@ -28,8 +24,11 @@ impl Model{
|
|||||||
fn new(dom:WeakDom)->Self{
|
fn new(dom:WeakDom)->Self{
|
||||||
Self{dom}
|
Self{dom}
|
||||||
}
|
}
|
||||||
pub fn to_snf(&self,failure_mode:LoadFailureMode)->Result<strafesnet_common::map::CompleteMap,LoadError>{
|
pub fn into_place(self)->Place{
|
||||||
to_snf(self,failure_mode)
|
let Self{mut dom}=self;
|
||||||
|
let context=roblox_emulator::context::Context::from_mut(&mut dom);
|
||||||
|
let services=context.convert_into_place();
|
||||||
|
Place{dom,services}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl AsRef<WeakDom> for Model{
|
impl AsRef<WeakDom> for Model{
|
||||||
@ -39,41 +38,33 @@ impl AsRef<WeakDom> for Model{
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub struct Place{
|
pub struct Place{
|
||||||
context:Context,
|
dom:WeakDom,
|
||||||
|
services:roblox_emulator::context::Services,
|
||||||
}
|
}
|
||||||
impl Place{
|
impl Place{
|
||||||
pub fn new(dom:WeakDom)->Result<Self,roblox_emulator::context::ServicesError>{
|
fn new(dom:WeakDom)->Option<Self>{
|
||||||
let context=Context::from_place(dom)?;
|
let context=roblox_emulator::context::Context::from_ref(&dom);
|
||||||
Ok(Self{
|
Some(Self{
|
||||||
context,
|
services:context.find_services()?,
|
||||||
|
dom,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
pub fn run_scripts(&mut self){
|
pub fn run_scripts(&mut self){
|
||||||
let Place{context}=self;
|
let Place{dom,services}=self;
|
||||||
let runner=roblox_emulator::runner::Runner::new().unwrap();
|
let runner=roblox_emulator::runner::Runner::new().unwrap();
|
||||||
|
let context=roblox_emulator::context::Context::from_mut(dom);
|
||||||
let scripts=context.scripts();
|
let scripts=context.scripts();
|
||||||
let runnable=runner.runnable_context(context).unwrap();
|
let runnable=runner.runnable_context_with_services(context,services).unwrap();
|
||||||
for script in scripts{
|
for script in scripts{
|
||||||
if let Err(e)=runnable.run_script(script){
|
if let Err(e)=runnable.run_script(script){
|
||||||
println!("runner error: {e}");
|
println!("runner error: {e}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
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{
|
||||||
self.context.as_ref()
|
&self.dom
|
||||||
}
|
|
||||||
}
|
|
||||||
impl From<Model> for Place{
|
|
||||||
fn from(model:Model)->Self{
|
|
||||||
let context=Context::from_model(model.dom);
|
|
||||||
Self{
|
|
||||||
context,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -94,56 +85,23 @@ impl std::error::Error for ReadError{}
|
|||||||
pub fn read<R:Read>(input:R)->Result<Model,ReadError>{
|
pub fn read<R:Read>(input:R)->Result<Model,ReadError>{
|
||||||
let mut buf=std::io::BufReader::new(input);
|
let mut buf=std::io::BufReader::new(input);
|
||||||
let peek=std::io::BufRead::fill_buf(&mut buf).map_err(ReadError::Io)?;
|
let peek=std::io::BufRead::fill_buf(&mut buf).map_err(ReadError::Io)?;
|
||||||
match peek.get(0..8){
|
match &peek[0..8]{
|
||||||
Some(b"<roblox!")=>rbx_binary::from_reader(buf).map(Model::new).map_err(ReadError::RbxBinary),
|
b"<roblox!"=>rbx_binary::from_reader(buf).map(Model::new).map_err(ReadError::RbxBinary),
|
||||||
Some(b"<roblox ")=>rbx_xml::from_reader_default(buf).map(Model::new).map_err(ReadError::RbxXml),
|
b"<roblox "=>rbx_xml::from_reader_default(buf).map(Model::new).map_err(ReadError::RbxXml),
|
||||||
_=>Err(ReadError::UnknownFileFormat),
|
_=>Err(ReadError::UnknownFileFormat),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
//ConvertError
|
||||||
pub enum LoadError{
|
|
||||||
Texture(loader::TextureError),
|
pub fn convert<AcquireRenderConfigId,AcquireMeshId>(
|
||||||
Mesh(loader::MeshError),
|
dom:impl AsRef<WeakDom>,
|
||||||
}
|
acquire_render_config_id:AcquireRenderConfigId,
|
||||||
impl std::fmt::Display for LoadError{
|
acquire_mesh_id:AcquireMeshId
|
||||||
fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{
|
)->rbx::PartialMap1
|
||||||
write!(f,"{self:?}")
|
where
|
||||||
}
|
AcquireRenderConfigId:FnMut(Option<&str>)->strafesnet_common::model::RenderConfigId,
|
||||||
}
|
AcquireMeshId:FnMut(&str)->strafesnet_common::model::MeshId,
|
||||||
impl std::error::Error for LoadError{}
|
{
|
||||||
impl From<loader::TextureError> for LoadError{
|
rbx::convert(&dom.as_ref(),acquire_render_config_id,acquire_mesh_id)
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
@ -1,196 +0,0 @@
|
|||||||
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::RobloxPartDescription;
|
|
||||||
|
|
||||||
// disallow non-static lifetimes
|
|
||||||
fn static_ustr(s:&'static str)->rbx_dom_weak::Ustr{
|
|
||||||
rbx_dom_weak::ustr(s)
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
impl TextureLoader{
|
|
||||||
pub fn new()->Self{
|
|
||||||
Self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl Loader for TextureLoader{
|
|
||||||
type Error=TextureError;
|
|
||||||
type Index<'a>=&'a str;
|
|
||||||
type Resource=Texture;
|
|
||||||
fn load<'a>(&mut self,index:Self::Index<'a>)->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:RobloxPartDescription,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
#[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: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;
|
|
||||||
impl MeshLoader{
|
|
||||||
pub fn new()->Self{
|
|
||||||
Self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl Loader for MeshLoader{
|
|
||||||
type Error=MeshError;
|
|
||||||
type Index<'a>=MeshIndex<'a>;
|
|
||||||
type Resource=Mesh;
|
|
||||||
fn load<'a>(&mut self,index:Self::Index<'a>)->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(&static_ustr("PhysicsData")){
|
|
||||||
physics_data=data.as_ref();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if mesh_data.is_empty(){
|
|
||||||
if let Some(rbx_dom_weak::types::Variant::BinaryString(data))=instance.properties.get(&static_ustr("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,9 +1,8 @@
|
|||||||
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,RenderConfigId,TextureCoordinateId,VertexId}};
|
use strafesnet_common::{integer::vec3,model::{self, ColorId, IndexedVertex, NormalId, PolygonGroup, PolygonList, PositionId, 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),
|
||||||
@ -84,13 +83,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:&[rbx_mesh::mesh::Face2],
|
faces:&Vec<rbx_mesh::mesh::Face2>,
|
||||||
lods:&[rbx_mesh::mesh::Lod3],
|
lods:&Vec<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(|rbx_mesh::mesh::Face2(v0,v1,v2)|
|
PolygonGroup::PolygonList(PolygonList::new(faces[lod_pair[0].0 as usize..lod_pair[1].0 as usize].iter().map(|face|
|
||||||
vec![vertex_id_map[&v0],vertex_id_map[&v1],vertex_id_map[&v2]]
|
vec![vertex_id_map[&face.0],vertex_id_map[&face.1],vertex_id_map[&face.2]]
|
||||||
).collect()))
|
).collect()))
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
@ -205,13 +204,7 @@ 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...
|
||||||
//but what if models want to use the same texture
|
graphics_groups:Vec::new(),
|
||||||
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,6 +1,5 @@
|
|||||||
use crate::rbx::{RobloxPartDescription,RobloxWedgeDescription,RobloxCornerWedgeDescription};
|
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,MeshBuilder,IndexedGraphicsGroup,IndexedPhysicsGroup,IndexedVertex,PolygonGroupId,PolygonGroup,PolygonList,PositionId,TextureCoordinateId,NormalId,ColorId,VertexId,RenderConfigId};
|
use strafesnet_common::integer::{vec3,Planar64Vec3};
|
||||||
use strafesnet_common::integer::{vec3,Planar64,Planar64Vec3};
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum Primitives{
|
pub enum Primitives{
|
||||||
@ -10,22 +9,7 @@ pub enum Primitives{
|
|||||||
Wedge,
|
Wedge,
|
||||||
CornerWedge,
|
CornerWedge,
|
||||||
}
|
}
|
||||||
#[derive(Debug)]
|
#[derive(Hash,PartialEq,Eq)]
|
||||||
pub struct PrimitivesError;
|
|
||||||
impl TryFrom<u32> for Primitives{
|
|
||||||
type Error=PrimitivesError;
|
|
||||||
fn try_from(value:u32)->Result<Self,Self::Error>{
|
|
||||||
match value{
|
|
||||||
0=>Ok(Primitives::Sphere),
|
|
||||||
1=>Ok(Primitives::Cube),
|
|
||||||
2=>Ok(Primitives::Cylinder),
|
|
||||||
3=>Ok(Primitives::Wedge),
|
|
||||||
4=>Ok(Primitives::CornerWedge),
|
|
||||||
_=>Err(PrimitivesError),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#[derive(Clone,Copy,Hash,PartialEq,Eq)]
|
|
||||||
pub enum CubeFace{
|
pub enum CubeFace{
|
||||||
Right,
|
Right,
|
||||||
Top,
|
Top,
|
||||||
@ -34,22 +18,6 @@ pub enum CubeFace{
|
|||||||
Bottom,
|
Bottom,
|
||||||
Front,
|
Front,
|
||||||
}
|
}
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct CubeFaceError;
|
|
||||||
impl TryFrom<u32> for CubeFace{
|
|
||||||
type Error=CubeFaceError;
|
|
||||||
fn try_from(value:u32)->Result<Self,Self::Error>{
|
|
||||||
match value{
|
|
||||||
0=>Ok(CubeFace::Right),
|
|
||||||
1=>Ok(CubeFace::Top),
|
|
||||||
2=>Ok(CubeFace::Back),
|
|
||||||
3=>Ok(CubeFace::Left),
|
|
||||||
4=>Ok(CubeFace::Bottom),
|
|
||||||
5=>Ok(CubeFace::Front),
|
|
||||||
_=>Err(CubeFaceError),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const CUBE_DEFAULT_TEXTURE_COORDS:[TextureCoordinate;4]=[
|
const CUBE_DEFAULT_TEXTURE_COORDS:[TextureCoordinate;4]=[
|
||||||
TextureCoordinate::new(0.0,0.0),
|
TextureCoordinate::new(0.0,0.0),
|
||||||
TextureCoordinate::new(1.0,0.0),
|
TextureCoordinate::new(1.0,0.0),
|
||||||
@ -74,36 +42,154 @@ 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],
|
||||||
|
],
|
||||||
|
];
|
||||||
|
|
||||||
pub struct CubeFaceDescription([FaceDescription;Self::FACES]);
|
#[derive(Hash,PartialEq,Eq)]
|
||||||
|
pub enum WedgeFace{
|
||||||
|
Right,
|
||||||
|
TopFront,
|
||||||
|
Back,
|
||||||
|
Left,
|
||||||
|
Bottom,
|
||||||
|
}
|
||||||
|
const WEDGE_DEFAULT_NORMALS:[Planar64Vec3;5]=[
|
||||||
|
vec3::int( 1, 0, 0),//Wedge::Right
|
||||||
|
vec3::int( 0, 1,-1),//Wedge::TopFront
|
||||||
|
vec3::int( 0, 0, 1),//Wedge::Back
|
||||||
|
vec3::int(-1, 0, 0),//Wedge::Left
|
||||||
|
vec3::int( 0,-1, 0),//Wedge::Bottom
|
||||||
|
];
|
||||||
|
/*
|
||||||
|
local cornerWedgeVerticies = {
|
||||||
|
Vector3.new(-1/2,-1/2,-1/2),7
|
||||||
|
Vector3.new(-1/2,-1/2, 1/2),0
|
||||||
|
Vector3.new( 1/2,-1/2,-1/2),6
|
||||||
|
Vector3.new( 1/2,-1/2, 1/2),1
|
||||||
|
Vector3.new( 1/2, 1/2,-1/2),5
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
#[derive(Hash,PartialEq,Eq)]
|
||||||
|
pub enum CornerWedgeFace{
|
||||||
|
Right,
|
||||||
|
TopBack,
|
||||||
|
TopLeft,
|
||||||
|
Bottom,
|
||||||
|
Front,
|
||||||
|
}
|
||||||
|
const CORNERWEDGE_DEFAULT_NORMALS:[Planar64Vec3;5]=[
|
||||||
|
vec3::int( 1, 0, 0),//CornerWedge::Right
|
||||||
|
vec3::int( 0, 1, 1),//CornerWedge::BackTop
|
||||||
|
vec3::int(-1, 1, 0),//CornerWedge::LeftTop
|
||||||
|
vec3::int( 0,-1, 0),//CornerWedge::Bottom
|
||||||
|
vec3::int( 0, 0,-1),//CornerWedge::Front
|
||||||
|
];
|
||||||
|
pub fn unit_sphere(render:RenderConfigId)->Mesh{
|
||||||
|
unit_cube(render)
|
||||||
|
}
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct CubeFaceDescription([Option<FaceDescription>;6]);
|
||||||
impl CubeFaceDescription{
|
impl CubeFaceDescription{
|
||||||
pub const FACES:usize=6;
|
pub fn insert(&mut self,index:CubeFace,value:FaceDescription){
|
||||||
pub fn new(RobloxPartDescription(part_description):RobloxPartDescription,textureless_render_id:RenderConfigId)->Self{
|
self.0[index as usize]=Some(value);
|
||||||
Self(part_description.map(|face_description|match face_description{
|
}
|
||||||
Some(roblox_texture_transform)=>roblox_texture_transform.to_face_description(),
|
pub fn pairs(self)->std::iter::FilterMap<std::iter::Enumerate<std::array::IntoIter<Option<FaceDescription>,6>>,impl FnMut((usize,Option<FaceDescription>))->Option<(usize,FaceDescription)>>{
|
||||||
None=>FaceDescription::new_with_render_id(textureless_render_id),
|
self.0.into_iter().enumerate().filter_map(|v|v.1.map(|u|(v.0,u)))
|
||||||
}))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub struct WedgeFaceDescription([FaceDescription;Self::FACES]);
|
pub fn unit_cube(render:RenderConfigId)->Mesh{
|
||||||
|
let mut t=CubeFaceDescription::default();
|
||||||
|
t.insert(CubeFace::Right,FaceDescription::new_with_render_id(render));
|
||||||
|
t.insert(CubeFace::Top,FaceDescription::new_with_render_id(render));
|
||||||
|
t.insert(CubeFace::Back,FaceDescription::new_with_render_id(render));
|
||||||
|
t.insert(CubeFace::Left,FaceDescription::new_with_render_id(render));
|
||||||
|
t.insert(CubeFace::Bottom,FaceDescription::new_with_render_id(render));
|
||||||
|
t.insert(CubeFace::Front,FaceDescription::new_with_render_id(render));
|
||||||
|
generate_partial_unit_cube(t)
|
||||||
|
}
|
||||||
|
pub fn unit_cylinder(render:RenderConfigId)->Mesh{
|
||||||
|
//lmao
|
||||||
|
unit_cube(render)
|
||||||
|
}
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct WedgeFaceDescription([Option<FaceDescription>;5]);
|
||||||
impl WedgeFaceDescription{
|
impl WedgeFaceDescription{
|
||||||
pub const FACES:usize=5;
|
pub fn insert(&mut self,index:WedgeFace,value:FaceDescription){
|
||||||
pub fn new(RobloxWedgeDescription(part_description):RobloxWedgeDescription,textureless_render_id:RenderConfigId)->Self{
|
self.0[index as usize]=Some(value);
|
||||||
Self(part_description.map(|face_description|match face_description{
|
}
|
||||||
Some(roblox_texture_transform)=>roblox_texture_transform.to_face_description(),
|
pub fn pairs(self)->std::iter::FilterMap<std::iter::Enumerate<std::array::IntoIter<Option<FaceDescription>,5>>,impl FnMut((usize,Option<FaceDescription>))->Option<(usize,FaceDescription)>>{
|
||||||
None=>FaceDescription::new_with_render_id(textureless_render_id),
|
self.0.into_iter().enumerate().filter_map(|v|v.1.map(|u|(v.0,u)))
|
||||||
}))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub struct CornerWedgeFaceDescription([FaceDescription;Self::FACES]);
|
pub fn unit_wedge(render:RenderConfigId)->Mesh{
|
||||||
|
let mut t=WedgeFaceDescription::default();
|
||||||
|
t.insert(WedgeFace::Right,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::Left,FaceDescription::new_with_render_id(render));
|
||||||
|
t.insert(WedgeFace::Bottom,FaceDescription::new_with_render_id(render));
|
||||||
|
generate_partial_unit_wedge(t)
|
||||||
|
}
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct CornerWedgeFaceDescription([Option<FaceDescription>;5]);
|
||||||
impl CornerWedgeFaceDescription{
|
impl CornerWedgeFaceDescription{
|
||||||
pub const FACES:usize=5;
|
pub fn insert(&mut self,index:CornerWedgeFace,value:FaceDescription){
|
||||||
pub fn new(RobloxCornerWedgeDescription(part_description):RobloxCornerWedgeDescription,textureless_render_id:RenderConfigId)->Self{
|
self.0[index as usize]=Some(value);
|
||||||
Self(part_description.map(|face_description|match face_description{
|
|
||||||
Some(roblox_texture_transform)=>roblox_texture_transform.to_face_description(),
|
|
||||||
None=>FaceDescription::new_with_render_id(textureless_render_id),
|
|
||||||
}))
|
|
||||||
}
|
}
|
||||||
|
pub fn pairs(self)->std::iter::FilterMap<std::iter::Enumerate<std::array::IntoIter<Option<FaceDescription>,5>>,impl FnMut((usize,Option<FaceDescription>))->Option<(usize,FaceDescription)>>{
|
||||||
|
self.0.into_iter().enumerate().filter_map(|v|v.1.map(|u|(v.0,u)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn unit_cornerwedge(render:RenderConfigId)->Mesh{
|
||||||
|
let mut t=CornerWedgeFaceDescription::default();
|
||||||
|
t.insert(CornerWedgeFace::Right,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::Bottom,FaceDescription::new_with_render_id(render));
|
||||||
|
t.insert(CornerWedgeFace::Front,FaceDescription::new_with_render_id(render));
|
||||||
|
generate_partial_unit_cornerwedge(t)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
@ -113,7 +199,7 @@ pub struct FaceDescription{
|
|||||||
pub color:Color4,
|
pub color:Color4,
|
||||||
}
|
}
|
||||||
impl FaceDescription{
|
impl FaceDescription{
|
||||||
pub fn new_with_render_id(render:RenderConfigId)->Self{
|
pub fn new_with_render_id(render:RenderConfigId)->Self {
|
||||||
Self{
|
Self{
|
||||||
render,
|
render,
|
||||||
transform:glam::Affine2::IDENTITY,
|
transform:glam::Affine2::IDENTITY,
|
||||||
@ -121,51 +207,7 @@ impl FaceDescription{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn unit_cube(CubeFaceDescription(face_descriptions):CubeFaceDescription)->Mesh{
|
pub fn generate_partial_unit_cube(face_descriptions:CubeFaceDescription)->Mesh{
|
||||||
const CUBE_DEFAULT_POLYS:[[[u32;2];4];6]=[
|
|
||||||
// right (1, 0, 0)
|
|
||||||
[
|
|
||||||
[6,2],//[vertex,tex]
|
|
||||||
[5,1],
|
|
||||||
[2,0],
|
|
||||||
[1,3],
|
|
||||||
],
|
|
||||||
// top (0, 1, 0)
|
|
||||||
[
|
|
||||||
[5,3],
|
|
||||||
[4,2],
|
|
||||||
[3,1],
|
|
||||||
[2,0],
|
|
||||||
],
|
|
||||||
// back (0, 0, 1)
|
|
||||||
[
|
|
||||||
[0,3],
|
|
||||||
[1,2],
|
|
||||||
[2,1],
|
|
||||||
[3,0],
|
|
||||||
],
|
|
||||||
// left (-1, 0, 0)
|
|
||||||
[
|
|
||||||
[0,2],
|
|
||||||
[3,1],
|
|
||||||
[4,0],
|
|
||||||
[7,3],
|
|
||||||
],
|
|
||||||
// bottom (0,-1, 0)
|
|
||||||
[
|
|
||||||
[1,1],
|
|
||||||
[0,0],
|
|
||||||
[7,3],
|
|
||||||
[6,2],
|
|
||||||
],
|
|
||||||
// front (0, 0,-1)
|
|
||||||
[
|
|
||||||
[4,1],
|
|
||||||
[5,0],
|
|
||||||
[6,3],
|
|
||||||
[7,2],
|
|
||||||
],
|
|
||||||
];
|
|
||||||
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();
|
||||||
@ -176,7 +218,7 @@ pub fn unit_cube(CubeFaceDescription(face_descriptions):CubeFaceDescription)->Me
|
|||||||
let mut physics_group=IndexedPhysicsGroup::default();
|
let mut physics_group=IndexedPhysicsGroup::default();
|
||||||
let mut transforms=Vec::new();
|
let mut transforms=Vec::new();
|
||||||
//note that on a cube every vertex is guaranteed to be unique, so there's no need to hash them against existing vertices.
|
//note that on a cube every vertex is guaranteed to be unique, so there's no need to hash them against existing vertices.
|
||||||
for (face_id,face_description) in face_descriptions.into_iter().enumerate(){
|
for (face_id,face_description) in face_descriptions.pairs(){
|
||||||
//assume that scanning short lists is faster than hashing.
|
//assume that scanning short lists is faster than hashing.
|
||||||
let transform_index=if let Some(transform_index)=transforms.iter().position(|&transform|transform==face_description.transform){
|
let transform_index=if let Some(transform_index)=transforms.iter().position(|&transform|transform==face_description.transform){
|
||||||
transform_index
|
transform_index
|
||||||
@ -203,8 +245,8 @@ pub fn unit_cube(CubeFaceDescription(face_descriptions):CubeFaceDescription)->Me
|
|||||||
//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![
|
||||||
CUBE_DEFAULT_POLYS[face_id].map(|[pos_id,tex_id]|{
|
CUBE_DEFAULT_POLYS[face_id].map(|tup|{
|
||||||
let pos=CUBE_DEFAULT_VERTICES[pos_id 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
|
||||||
}else{
|
}else{
|
||||||
@ -216,7 +258,7 @@ pub fn unit_cube(CubeFaceDescription(face_descriptions):CubeFaceDescription)->Me
|
|||||||
//always push vertex
|
//always push vertex
|
||||||
let vertex=IndexedVertex{
|
let vertex=IndexedVertex{
|
||||||
pos:PositionId::new(pos_index),
|
pos:PositionId::new(pos_index),
|
||||||
tex:TextureCoordinateId::new(tex_id+4*transform_index),
|
tex:TextureCoordinateId::new(tup[1]+4*transform_index),
|
||||||
normal:NormalId::new(normal_index),
|
normal:NormalId::new(normal_index),
|
||||||
color:ColorId::new(color_index),
|
color:ColorId::new(color_index),
|
||||||
};
|
};
|
||||||
@ -243,49 +285,42 @@ pub fn unit_cube(CubeFaceDescription(face_descriptions):CubeFaceDescription)->Me
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
//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 unit_wedge(WedgeFaceDescription(face_descriptions):WedgeFaceDescription)->Mesh{
|
pub fn generate_partial_unit_wedge(face_descriptions:WedgeFaceDescription)->Mesh{
|
||||||
const WEDGE_DEFAULT_POLYS:[&[[u32;2]];5]=[
|
let wedge_default_polys=[
|
||||||
// right (1, 0, 0)
|
// right (1, 0, 0)
|
||||||
&[
|
vec![
|
||||||
[6,2],//[vertex,tex]
|
[6,2,0],//[vertex,tex,norm]
|
||||||
[2,0],
|
[2,0,0],
|
||||||
[1,3],
|
[1,3,0],
|
||||||
],
|
],
|
||||||
// FrontTop (0, 1, -1)
|
// FrontTop (0, 1, -1)
|
||||||
&[
|
vec![
|
||||||
[3,1],
|
[3,1,1],
|
||||||
[2,0],
|
[2,0,1],
|
||||||
[6,3],
|
[6,3,1],
|
||||||
[7,2],
|
[7,2,1],
|
||||||
],
|
],
|
||||||
// back (0, 0, 1)
|
// back (0, 0, 1)
|
||||||
&[
|
vec![
|
||||||
[0,3],
|
[0,3,2],
|
||||||
[1,2],
|
[1,2,2],
|
||||||
[2,1],
|
[2,1,2],
|
||||||
[3,0],
|
[3,0,2],
|
||||||
],
|
],
|
||||||
// left (-1, 0, 0)
|
// left (-1, 0, 0)
|
||||||
&[
|
vec![
|
||||||
[0,2],
|
[0,2,3],
|
||||||
[3,1],
|
[3,1,3],
|
||||||
[7,3],
|
[7,3,3],
|
||||||
],
|
],
|
||||||
// bottom (0,-1, 0)
|
// bottom (0,-1, 0)
|
||||||
&[
|
vec![
|
||||||
[1,1],
|
[1,1,4],
|
||||||
[0,0],
|
[0,0,4],
|
||||||
[7,3],
|
[7,3,4],
|
||||||
[6,2],
|
[6,2,4],
|
||||||
],
|
],
|
||||||
];
|
];
|
||||||
const WEDGE_DEFAULT_NORMALS:[Planar64Vec3;5]=[
|
|
||||||
vec3::int( 1, 0, 0),//Wedge::Right
|
|
||||||
vec3::int( 0, 1,-1),//Wedge::TopFront
|
|
||||||
vec3::int( 0, 0, 1),//Wedge::Back
|
|
||||||
vec3::int(-1, 0, 0),//Wedge::Left
|
|
||||||
vec3::int( 0,-1, 0),//Wedge::Bottom
|
|
||||||
];
|
|
||||||
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();
|
||||||
@ -296,7 +331,7 @@ pub fn unit_wedge(WedgeFaceDescription(face_descriptions):WedgeFaceDescription)-
|
|||||||
let mut physics_group=IndexedPhysicsGroup::default();
|
let mut physics_group=IndexedPhysicsGroup::default();
|
||||||
let mut transforms=Vec::new();
|
let mut transforms=Vec::new();
|
||||||
//note that on a cube every vertex is guaranteed to be unique, so there's no need to hash them against existing vertices.
|
//note that on a cube every vertex is guaranteed to be unique, so there's no need to hash them against existing vertices.
|
||||||
for (face_id,face_description) in face_descriptions.into_iter().enumerate(){
|
for (face_id,face_description) in face_descriptions.pairs(){
|
||||||
//assume that scanning short lists is faster than hashing.
|
//assume that scanning short lists is faster than hashing.
|
||||||
let transform_index=if let Some(transform_index)=transforms.iter().position(|&transform|transform==face_description.transform){
|
let transform_index=if let Some(transform_index)=transforms.iter().position(|&transform|transform==face_description.transform){
|
||||||
transform_index
|
transform_index
|
||||||
@ -323,8 +358,8 @@ pub fn unit_wedge(WedgeFaceDescription(face_descriptions):WedgeFaceDescription)-
|
|||||||
//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(|&[pos_id,tex_id]|{
|
wedge_default_polys[face_id].iter().map(|tup|{
|
||||||
let pos=CUBE_DEFAULT_VERTICES[pos_id 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
|
||||||
}else{
|
}else{
|
||||||
@ -336,7 +371,7 @@ pub fn unit_wedge(WedgeFaceDescription(face_descriptions):WedgeFaceDescription)-
|
|||||||
//always push vertex
|
//always push vertex
|
||||||
let vertex=IndexedVertex{
|
let vertex=IndexedVertex{
|
||||||
pos:PositionId::new(pos_index),
|
pos:PositionId::new(pos_index),
|
||||||
tex:TextureCoordinateId::new(tex_id+4*transform_index),
|
tex:TextureCoordinateId::new(tup[1]+4*transform_index),
|
||||||
normal:NormalId::new(normal_index),
|
normal:NormalId::new(normal_index),
|
||||||
color:ColorId::new(color_index),
|
color:ColorId::new(color_index),
|
||||||
};
|
};
|
||||||
@ -363,47 +398,40 @@ pub fn unit_wedge(WedgeFaceDescription(face_descriptions):WedgeFaceDescription)-
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn unit_cornerwedge(CornerWedgeFaceDescription(face_descriptions):CornerWedgeFaceDescription)->Mesh{
|
pub fn generate_partial_unit_cornerwedge(face_descriptions:CornerWedgeFaceDescription)->Mesh{
|
||||||
const CORNERWEDGE_DEFAULT_POLYS:[&[[u32;2]];5]=[
|
let cornerwedge_default_polys=[
|
||||||
// right (1, 0, 0)
|
// right (1, 0, 0)
|
||||||
&[
|
vec![
|
||||||
[6,2],//[vertex,tex]
|
[6,2,0],//[vertex,tex,norm]
|
||||||
[5,1],
|
[5,1,0],
|
||||||
[1,3],
|
[1,3,0],
|
||||||
],
|
],
|
||||||
// BackTop (0, 1, 1)
|
// BackTop (0, 1, 1)
|
||||||
&[
|
vec![
|
||||||
[5,3],
|
[5,3,1],
|
||||||
[0,1],
|
[0,1,1],
|
||||||
[1,0],
|
[1,0,1],
|
||||||
],
|
],
|
||||||
// LeftTop (-1, 1, 0)
|
// LeftTop (-1, 1, 0)
|
||||||
&[
|
vec![
|
||||||
[5,3],
|
[5,3,2],
|
||||||
[7,2],
|
[7,2,2],
|
||||||
[0,1],
|
[0,1,2],
|
||||||
],
|
],
|
||||||
// bottom (0,-1, 0)
|
// bottom (0,-1, 0)
|
||||||
&[
|
vec![
|
||||||
[1,1],
|
[1,1,3],
|
||||||
[0,0],
|
[0,0,3],
|
||||||
[7,3],
|
[7,3,3],
|
||||||
[6,2],
|
[6,2,3],
|
||||||
],
|
],
|
||||||
// front (0, 0,-1)
|
// front (0, 0,-1)
|
||||||
&[
|
vec![
|
||||||
[5,0],
|
[5,0,4],
|
||||||
[6,3],
|
[6,3,4],
|
||||||
[7,2],
|
[7,2,4],
|
||||||
],
|
],
|
||||||
];
|
];
|
||||||
const CORNERWEDGE_DEFAULT_NORMALS:[Planar64Vec3;5]=[
|
|
||||||
vec3::int( 1, 0, 0),//CornerWedge::Right
|
|
||||||
vec3::int( 0, 1, 1),//CornerWedge::BackTop
|
|
||||||
vec3::int(-1, 1, 0),//CornerWedge::LeftTop
|
|
||||||
vec3::int( 0,-1, 0),//CornerWedge::Bottom
|
|
||||||
vec3::int( 0, 0,-1),//CornerWedge::Front
|
|
||||||
];
|
|
||||||
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();
|
||||||
@ -414,7 +442,7 @@ pub fn unit_cornerwedge(CornerWedgeFaceDescription(face_descriptions):CornerWedg
|
|||||||
let mut physics_group=IndexedPhysicsGroup::default();
|
let mut physics_group=IndexedPhysicsGroup::default();
|
||||||
let mut transforms=Vec::new();
|
let mut transforms=Vec::new();
|
||||||
//note that on a cube every vertex is guaranteed to be unique, so there's no need to hash them against existing vertices.
|
//note that on a cube every vertex is guaranteed to be unique, so there's no need to hash them against existing vertices.
|
||||||
for (face_id,face_description) in face_descriptions.into_iter().enumerate(){
|
for (face_id,face_description) in face_descriptions.pairs(){
|
||||||
//assume that scanning short lists is faster than hashing.
|
//assume that scanning short lists is faster than hashing.
|
||||||
let transform_index=if let Some(transform_index)=transforms.iter().position(|&transform|transform==face_description.transform){
|
let transform_index=if let Some(transform_index)=transforms.iter().position(|&transform|transform==face_description.transform){
|
||||||
transform_index
|
transform_index
|
||||||
@ -441,8 +469,8 @@ pub fn unit_cornerwedge(CornerWedgeFaceDescription(face_descriptions):CornerWedg
|
|||||||
//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(|&[pos_id,tex_id]|{
|
cornerwedge_default_polys[face_id].iter().map(|tup|{
|
||||||
let pos=CUBE_DEFAULT_VERTICES[pos_id 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
|
||||||
}else{
|
}else{
|
||||||
@ -454,7 +482,7 @@ pub fn unit_cornerwedge(CornerWedgeFaceDescription(face_descriptions):CornerWedg
|
|||||||
//always push vertex
|
//always push vertex
|
||||||
let vertex=IndexedVertex{
|
let vertex=IndexedVertex{
|
||||||
pos:PositionId::new(pos_index),
|
pos:PositionId::new(pos_index),
|
||||||
tex:TextureCoordinateId::new(tex_id+4*transform_index),
|
tex:TextureCoordinateId::new(tup[1]+4*transform_index),
|
||||||
normal:NormalId::new(normal_index),
|
normal:NormalId::new(normal_index),
|
||||||
color:ColorId::new(color_index),
|
color:ColorId::new(color_index),
|
||||||
};
|
};
|
||||||
@ -480,133 +508,3 @@ pub fn unit_cornerwedge(CornerWedgeFaceDescription(face_descriptions):CornerWedg
|
|||||||
physics_groups:vec![physics_group],
|
physics_groups:vec![physics_group],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: fix face texture orientation
|
|
||||||
pub fn unit_cylinder(face_descriptions:CubeFaceDescription)->Mesh{
|
|
||||||
// cylinder is oriented about the x axis
|
|
||||||
// roblox cylinders use projected grid coordinates
|
|
||||||
/// how many grid coordinates to use (positive and negative)
|
|
||||||
const GON:i32=3;
|
|
||||||
/// grid perimeter
|
|
||||||
const POINTS:[[i32;2];4*2*GON as usize]=const{
|
|
||||||
let mut points=[[0;2];{4*2*GON as usize}];
|
|
||||||
let mut i=-GON;
|
|
||||||
while i<GON{
|
|
||||||
points[(i+GON) as usize]=[i,GON];
|
|
||||||
points[(i+GON+1*2*GON) as usize]=[GON,-i];
|
|
||||||
points[(i+GON+2*2*GON) as usize]=[-i,-GON];
|
|
||||||
points[(i+GON+3*2*GON) as usize]=[-GON,i];
|
|
||||||
i+=1;
|
|
||||||
}
|
|
||||||
points
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut mb=MeshBuilder::new();
|
|
||||||
let mut polygon_groups=Vec::with_capacity(CubeFaceDescription::FACES);
|
|
||||||
let mut graphics_groups=Vec::with_capacity(CubeFaceDescription::FACES);
|
|
||||||
let mut physics_group=IndexedPhysicsGroup{groups:Vec::with_capacity(CubeFaceDescription::FACES)};
|
|
||||||
let CubeFaceDescription([right,top,back,left,bottom,front])=face_descriptions;
|
|
||||||
|
|
||||||
macro_rules! end_face{
|
|
||||||
($face_description:expr,$end:expr,$iter:expr)=>{
|
|
||||||
let normal=mb.acquire_normal_id($end);
|
|
||||||
let color=mb.acquire_color_id($face_description.color);
|
|
||||||
|
|
||||||
// single polygon for physics
|
|
||||||
let polygon:Vec<_>=$iter.map(|[x,y]|{
|
|
||||||
let tex=mb.acquire_tex_id(
|
|
||||||
$face_description.transform.transform_point2(
|
|
||||||
(glam::vec2(-x as f32,y as f32).normalize()+1.0)/2.0
|
|
||||||
)
|
|
||||||
);
|
|
||||||
let pos=mb.acquire_pos_id($end+vec3::int(0,-x,y).with_length(Planar64::ONE).divide().wrap_1());
|
|
||||||
mb.acquire_vertex_id(IndexedVertex{pos,tex,normal,color})
|
|
||||||
}).collect();
|
|
||||||
|
|
||||||
// fanned polygons for graphics
|
|
||||||
let pos=mb.acquire_pos_id($end);
|
|
||||||
let tex=mb.acquire_tex_id($face_description.transform.transform_point2(glam::Vec2::ONE/2.0));
|
|
||||||
let center=mb.acquire_vertex_id(IndexedVertex{pos,tex,normal,color});
|
|
||||||
let polygon_list=(0..POINTS.len()).map(|i|
|
|
||||||
vec![center,polygon[i],polygon[(i+1)%POINTS.len()]]
|
|
||||||
).collect();
|
|
||||||
|
|
||||||
// end face graphics
|
|
||||||
let group_id=PolygonGroupId::new(polygon_groups.len() as u32);
|
|
||||||
polygon_groups.push(PolygonGroup::PolygonList(PolygonList::new(polygon_list)));
|
|
||||||
graphics_groups.push(IndexedGraphicsGroup{
|
|
||||||
render:$face_description.render,
|
|
||||||
groups:vec![group_id],
|
|
||||||
});
|
|
||||||
|
|
||||||
// end face physics
|
|
||||||
let polygon_list=vec![polygon];
|
|
||||||
|
|
||||||
let group_id=PolygonGroupId::new(polygon_groups.len() as u32);
|
|
||||||
polygon_groups.push(PolygonGroup::PolygonList(PolygonList::new(polygon_list)));
|
|
||||||
physics_group.groups.push(group_id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
macro_rules! tex{
|
|
||||||
($face_description:expr,$tex:expr)=>{{
|
|
||||||
let [x,y]=$tex;
|
|
||||||
$face_description.transform.transform_point2(
|
|
||||||
glam::vec2((x+GON) as f32,(y+GON) as f32)/(2*GON) as f32
|
|
||||||
)
|
|
||||||
}};
|
|
||||||
}
|
|
||||||
macro_rules! barrel_face{
|
|
||||||
($face_description:expr,$loop:ident,$lo_dir:expr,$hi_dir:expr,$tex_0:expr,$tex_1:expr,$tex_2:expr,$tex_3:expr)=>{
|
|
||||||
let mut polygon_list=Vec::with_capacity(CubeFaceDescription::FACES);
|
|
||||||
for $loop in -GON..GON{
|
|
||||||
// lo Z
|
|
||||||
let lz_dir=$lo_dir.with_length(Planar64::ONE).divide().wrap_1();
|
|
||||||
// hi Z
|
|
||||||
let hz_dir=$hi_dir.with_length(Planar64::ONE).divide().wrap_1();
|
|
||||||
|
|
||||||
// pos
|
|
||||||
let lx_lz_pos=mb.acquire_pos_id(vec3::NEG_X+lz_dir);
|
|
||||||
let lx_hz_pos=mb.acquire_pos_id(vec3::NEG_X+hz_dir);
|
|
||||||
let hx_hz_pos=mb.acquire_pos_id(vec3::X+hz_dir);
|
|
||||||
let hx_lz_pos=mb.acquire_pos_id(vec3::X+lz_dir);
|
|
||||||
// tex
|
|
||||||
let lx_lz_tex=mb.acquire_tex_id(tex!($face_description,$tex_0));
|
|
||||||
let lx_hz_tex=mb.acquire_tex_id(tex!($face_description,$tex_1));
|
|
||||||
let hx_hz_tex=mb.acquire_tex_id(tex!($face_description,$tex_2));
|
|
||||||
let hx_lz_tex=mb.acquire_tex_id(tex!($face_description,$tex_3));
|
|
||||||
// norm
|
|
||||||
let lz_norm=mb.acquire_normal_id(lz_dir);
|
|
||||||
let hz_norm=mb.acquire_normal_id(hz_dir);
|
|
||||||
// color
|
|
||||||
let color=mb.acquire_color_id($face_description.color);
|
|
||||||
|
|
||||||
polygon_list.push(vec![
|
|
||||||
mb.acquire_vertex_id(IndexedVertex{pos:lx_lz_pos,tex:lx_lz_tex,normal:lz_norm,color}),
|
|
||||||
mb.acquire_vertex_id(IndexedVertex{pos:lx_hz_pos,tex:lx_hz_tex,normal:hz_norm,color}),
|
|
||||||
mb.acquire_vertex_id(IndexedVertex{pos:hx_hz_pos,tex:hx_hz_tex,normal:hz_norm,color}),
|
|
||||||
mb.acquire_vertex_id(IndexedVertex{pos:hx_lz_pos,tex:hx_lz_tex,normal:lz_norm,color}),
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
// push face
|
|
||||||
let group_id=PolygonGroupId::new(polygon_groups.len() as u32);
|
|
||||||
polygon_groups.push(PolygonGroup::PolygonList(PolygonList::new(polygon_list)));
|
|
||||||
graphics_groups.push(IndexedGraphicsGroup{
|
|
||||||
render:$face_description.render,
|
|
||||||
groups:vec![group_id],
|
|
||||||
});
|
|
||||||
physics_group.groups.push(group_id);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
end_face!(right, vec3::X,POINTS.into_iter());
|
|
||||||
barrel_face!(top, z,vec3::int(0,GON,z),vec3::int(0,GON,z+1), [GON,z],[GON,z+1],[-GON,z+1],[-GON,z]);
|
|
||||||
barrel_face!(back, y,vec3::int(0,y+1,GON),vec3::int(0,y,GON), [GON,y+1],[GON,y],[-GON,y],[-GON,y+1]);
|
|
||||||
end_face!(left, vec3::NEG_X,POINTS.into_iter().rev());
|
|
||||||
barrel_face!(bottom, z,vec3::int(0,-GON,z+1),vec3::int(0,-GON,z), [-GON,z+1],[-GON,z],[GON,z],[GON,z+1]);
|
|
||||||
barrel_face!(front, y,vec3::int(0,y,-GON),vec3::int(0,y+1,-GON), [-GON,y],[-GON,y+1],[GON,y+1],[GON,y]);
|
|
||||||
|
|
||||||
let physics_groups=vec![physics_group];
|
|
||||||
|
|
||||||
mb.build(polygon_groups,graphics_groups,physics_groups)
|
|
||||||
}
|
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -1,178 +0,0 @@
|
|||||||
use rbx_mesh::mesh_data::{NormalId2 as MeshDataNormalId2,VertexId as MeshDataVertexId};
|
|
||||||
use rbx_mesh::physics_data::VertexId as PhysicsDataVertexId;
|
|
||||||
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,
|
|
||||||
crate::rbx::RobloxPartDescription(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 [MeshDataVertexId(vertex_id0),MeshDataVertexId(vertex_id1),MeshDataVertexId(vertex_id2)] in graphics_mesh.faces{
|
|
||||||
let face=[
|
|
||||||
graphics_mesh.vertices.get(vertex_id0 as usize).ok_or(Error::MissingVertexId(vertex_id0))?,
|
|
||||||
graphics_mesh.vertices.get(vertex_id1 as usize).ok_or(Error::MissingVertexId(vertex_id1))?,
|
|
||||||
graphics_mesh.vertices.get(vertex_id2 as usize).ok_or(Error::MissingVertexId(vertex_id2))?,
|
|
||||||
];
|
|
||||||
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(|[PhysicsDataVertexId(vertex_id0),PhysicsDataVertexId(vertex_id1),PhysicsDataVertexId(vertex_id2)]|{
|
|
||||||
let face=[
|
|
||||||
mesh.vertices.get(vertex_id0 as usize).ok_or(Error::MissingVertexId(vertex_id0))?,
|
|
||||||
mesh.vertices.get(vertex_id1 as usize).ok_or(Error::MissingVertexId(vertex_id1))?,
|
|
||||||
mesh.vertices.get(vertex_id2 as usize).ok_or(Error::MissingVertexId(vertex_id2))?,
|
|
||||||
].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,
|
|
||||||
))
|
|
||||||
}
|
|
@ -1,11 +0,0 @@
|
|||||||
[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"
|
|
@ -1,176 +0,0 @@
|
|||||||
Apache License
|
|
||||||
Version 2.0, January 2004
|
|
||||||
http://www.apache.org/licenses/
|
|
||||||
|
|
||||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
|
||||||
|
|
||||||
1. Definitions.
|
|
||||||
|
|
||||||
"License" shall mean the terms and conditions for use, reproduction,
|
|
||||||
and distribution as defined by Sections 1 through 9 of this document.
|
|
||||||
|
|
||||||
"Licensor" shall mean the copyright owner or entity authorized by
|
|
||||||
the copyright owner that is granting the License.
|
|
||||||
|
|
||||||
"Legal Entity" shall mean the union of the acting entity and all
|
|
||||||
other entities that control, are controlled by, or are under common
|
|
||||||
control with that entity. For the purposes of this definition,
|
|
||||||
"control" means (i) the power, direct or indirect, to cause the
|
|
||||||
direction or management of such entity, whether by contract or
|
|
||||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
|
||||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
|
||||||
|
|
||||||
"You" (or "Your") shall mean an individual or Legal Entity
|
|
||||||
exercising permissions granted by this License.
|
|
||||||
|
|
||||||
"Source" form shall mean the preferred form for making modifications,
|
|
||||||
including but not limited to software source code, documentation
|
|
||||||
source, and configuration files.
|
|
||||||
|
|
||||||
"Object" form shall mean any form resulting from mechanical
|
|
||||||
transformation or translation of a Source form, including but
|
|
||||||
not limited to compiled object code, generated documentation,
|
|
||||||
and conversions to other media types.
|
|
||||||
|
|
||||||
"Work" shall mean the work of authorship, whether in Source or
|
|
||||||
Object form, made available under the License, as indicated by a
|
|
||||||
copyright notice that is included in or attached to the work
|
|
||||||
(an example is provided in the Appendix below).
|
|
||||||
|
|
||||||
"Derivative Works" shall mean any work, whether in Source or Object
|
|
||||||
form, that is based on (or derived from) the Work and for which the
|
|
||||||
editorial revisions, annotations, elaborations, or other modifications
|
|
||||||
represent, as a whole, an original work of authorship. For the purposes
|
|
||||||
of this License, Derivative Works shall not include works that remain
|
|
||||||
separable from, or merely link (or bind by name) to the interfaces of,
|
|
||||||
the Work and Derivative Works thereof.
|
|
||||||
|
|
||||||
"Contribution" shall mean any work of authorship, including
|
|
||||||
the original version of the Work and any modifications or additions
|
|
||||||
to that Work or Derivative Works thereof, that is intentionally
|
|
||||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
|
||||||
or by an individual or Legal Entity authorized to submit on behalf of
|
|
||||||
the copyright owner. For the purposes of this definition, "submitted"
|
|
||||||
means any form of electronic, verbal, or written communication sent
|
|
||||||
to the Licensor or its representatives, including but not limited to
|
|
||||||
communication on electronic mailing lists, source code control systems,
|
|
||||||
and issue tracking systems that are managed by, or on behalf of, the
|
|
||||||
Licensor for the purpose of discussing and improving the Work, but
|
|
||||||
excluding communication that is conspicuously marked or otherwise
|
|
||||||
designated in writing by the copyright owner as "Not a Contribution."
|
|
||||||
|
|
||||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
|
||||||
on behalf of whom a Contribution has been received by Licensor and
|
|
||||||
subsequently incorporated within the Work.
|
|
||||||
|
|
||||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
|
||||||
this License, each Contributor hereby grants to You a perpetual,
|
|
||||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
||||||
copyright license to reproduce, prepare Derivative Works of,
|
|
||||||
publicly display, publicly perform, sublicense, and distribute the
|
|
||||||
Work and such Derivative Works in Source or Object form.
|
|
||||||
|
|
||||||
3. Grant of Patent License. Subject to the terms and conditions of
|
|
||||||
this License, each Contributor hereby grants to You a perpetual,
|
|
||||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
||||||
(except as stated in this section) patent license to make, have made,
|
|
||||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
|
||||||
where such license applies only to those patent claims licensable
|
|
||||||
by such Contributor that are necessarily infringed by their
|
|
||||||
Contribution(s) alone or by combination of their Contribution(s)
|
|
||||||
with the Work to which such Contribution(s) was submitted. If You
|
|
||||||
institute patent litigation against any entity (including a
|
|
||||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
|
||||||
or a Contribution incorporated within the Work constitutes direct
|
|
||||||
or contributory patent infringement, then any patent licenses
|
|
||||||
granted to You under this License for that Work shall terminate
|
|
||||||
as of the date such litigation is filed.
|
|
||||||
|
|
||||||
4. Redistribution. You may reproduce and distribute copies of the
|
|
||||||
Work or Derivative Works thereof in any medium, with or without
|
|
||||||
modifications, and in Source or Object form, provided that You
|
|
||||||
meet the following conditions:
|
|
||||||
|
|
||||||
(a) You must give any other recipients of the Work or
|
|
||||||
Derivative Works a copy of this License; and
|
|
||||||
|
|
||||||
(b) You must cause any modified files to carry prominent notices
|
|
||||||
stating that You changed the files; and
|
|
||||||
|
|
||||||
(c) You must retain, in the Source form of any Derivative Works
|
|
||||||
that You distribute, all copyright, patent, trademark, and
|
|
||||||
attribution notices from the Source form of the Work,
|
|
||||||
excluding those notices that do not pertain to any part of
|
|
||||||
the Derivative Works; and
|
|
||||||
|
|
||||||
(d) If the Work includes a "NOTICE" text file as part of its
|
|
||||||
distribution, then any Derivative Works that You distribute must
|
|
||||||
include a readable copy of the attribution notices contained
|
|
||||||
within such NOTICE file, excluding those notices that do not
|
|
||||||
pertain to any part of the Derivative Works, in at least one
|
|
||||||
of the following places: within a NOTICE text file distributed
|
|
||||||
as part of the Derivative Works; within the Source form or
|
|
||||||
documentation, if provided along with the Derivative Works; or,
|
|
||||||
within a display generated by the Derivative Works, if and
|
|
||||||
wherever such third-party notices normally appear. The contents
|
|
||||||
of the NOTICE file are for informational purposes only and
|
|
||||||
do not modify the License. You may add Your own attribution
|
|
||||||
notices within Derivative Works that You distribute, alongside
|
|
||||||
or as an addendum to the NOTICE text from the Work, provided
|
|
||||||
that such additional attribution notices cannot be construed
|
|
||||||
as modifying the License.
|
|
||||||
|
|
||||||
You may add Your own copyright statement to Your modifications and
|
|
||||||
may provide additional or different license terms and conditions
|
|
||||||
for use, reproduction, or distribution of Your modifications, or
|
|
||||||
for any such Derivative Works as a whole, provided Your use,
|
|
||||||
reproduction, and distribution of the Work otherwise complies with
|
|
||||||
the conditions stated in this License.
|
|
||||||
|
|
||||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
|
||||||
any Contribution intentionally submitted for inclusion in the Work
|
|
||||||
by You to the Licensor shall be under the terms and conditions of
|
|
||||||
this License, without any additional terms or conditions.
|
|
||||||
Notwithstanding the above, nothing herein shall supersede or modify
|
|
||||||
the terms of any separate license agreement you may have executed
|
|
||||||
with Licensor regarding such Contributions.
|
|
||||||
|
|
||||||
6. Trademarks. This License does not grant permission to use the trade
|
|
||||||
names, trademarks, service marks, or product names of the Licensor,
|
|
||||||
except as required for reasonable and customary use in describing the
|
|
||||||
origin of the Work and reproducing the content of the NOTICE file.
|
|
||||||
|
|
||||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
|
||||||
agreed to in writing, Licensor provides the Work (and each
|
|
||||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
||||||
implied, including, without limitation, any warranties or conditions
|
|
||||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
|
||||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
|
||||||
appropriateness of using or redistributing the Work and assume any
|
|
||||||
risks associated with Your exercise of permissions under this License.
|
|
||||||
|
|
||||||
8. Limitation of Liability. In no event and under no legal theory,
|
|
||||||
whether in tort (including negligence), contract, or otherwise,
|
|
||||||
unless required by applicable law (such as deliberate and grossly
|
|
||||||
negligent acts) or agreed to in writing, shall any Contributor be
|
|
||||||
liable to You for damages, including any direct, indirect, special,
|
|
||||||
incidental, or consequential damages of any character arising as a
|
|
||||||
result of this License or out of the use or inability to use the
|
|
||||||
Work (including but not limited to damages for loss of goodwill,
|
|
||||||
work stoppage, computer failure or malfunction, or any and all
|
|
||||||
other commercial damages or losses), even if such Contributor
|
|
||||||
has been advised of the possibility of such damages.
|
|
||||||
|
|
||||||
9. Accepting Warranty or Additional Liability. While redistributing
|
|
||||||
the Work or Derivative Works thereof, You may choose to offer,
|
|
||||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
|
||||||
or other liability obligations and/or rights consistent with this
|
|
||||||
License. However, in accepting such obligations, You may act only
|
|
||||||
on Your own behalf and on Your sole responsibility, not on behalf
|
|
||||||
of any other Contributor, and only if You agree to indemnify,
|
|
||||||
defend, and hold each Contributor harmless for any liability
|
|
||||||
incurred by, or claims asserted against, such Contributor by reason
|
|
||||||
of your accepting any such warranty or additional liability.
|
|
||||||
|
|
||||||
END OF TERMS AND CONDITIONS
|
|
@ -1,23 +0,0 @@
|
|||||||
Permission is hereby granted, free of charge, to any
|
|
||||||
person obtaining a copy of this software and associated
|
|
||||||
documentation files (the "Software"), to deal in the
|
|
||||||
Software without restriction, including without
|
|
||||||
limitation the rights to use, copy, modify, merge,
|
|
||||||
publish, distribute, sublicense, and/or sell copies of
|
|
||||||
the Software, and to permit persons to whom the Software
|
|
||||||
is furnished to do so, subject to the following
|
|
||||||
conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice
|
|
||||||
shall be included in all copies or substantial portions
|
|
||||||
of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
|
|
||||||
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
|
|
||||||
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
|
|
||||||
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
|
|
||||||
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
|
||||||
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
||||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
|
|
||||||
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
|
||||||
DEALINGS IN THE SOFTWARE.
|
|
@ -1,26 +0,0 @@
|
|||||||
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>
|
|
@ -1,41 +0,0 @@
|
|||||||
#[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.5.0"
|
version = "0.4.7"
|
||||||
edition = "2024"
|
edition = "2021"
|
||||||
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,10 +12,10 @@ default=["run-service"]
|
|||||||
run-service=[]
|
run-service=[]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
glam = "0.30.0"
|
glam = "0.29.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 = "3.1.0-sn4", registry = "strafesnet", features = ["instance-userdata"] }
|
rbx_dom_weak = { version = "2.7.0", registry = "strafesnet" }
|
||||||
rbx_reflection = "5.0.0"
|
rbx_reflection = { version = "4.7.0", registry = "strafesnet" }
|
||||||
rbx_reflection_database = "1.0.0"
|
rbx_reflection_database = { version = "0.2.10", registry = "strafesnet" }
|
||||||
rbx_types = "2.0.0"
|
rbx_types = { version = "1.10.0", registry = "strafesnet" }
|
||||||
|
@ -1,113 +1,93 @@
|
|||||||
use crate::util::static_ustr;
|
|
||||||
use rbx_dom_weak::{types::Ref,InstanceBuilder,WeakDom};
|
use rbx_dom_weak::{types::Ref,InstanceBuilder,WeakDom};
|
||||||
|
|
||||||
#[derive(Debug)]
|
pub fn class_is_a(class:&str,superclass:&str)->bool{
|
||||||
pub enum ServicesError{
|
class==superclass
|
||||||
WorkspaceNotFound,
|
||rbx_reflection_database::get().classes.get(class)
|
||||||
}
|
.is_some_and(|descriptor|
|
||||||
impl std::fmt::Display for ServicesError{
|
descriptor.superclass.as_ref().is_some_and(|class_super|
|
||||||
fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{
|
class_is_a(class_super,superclass)
|
||||||
write!(f,"{self:?}")
|
)
|
||||||
}
|
)
|
||||||
}
|
|
||||||
impl std::error::Error for ServicesError{}
|
|
||||||
|
|
||||||
pub struct Services{
|
|
||||||
pub(crate) game:Ref,
|
|
||||||
pub(crate) workspace:Ref,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Services{
|
#[repr(transparent)]
|
||||||
fn find_services(dom:&WeakDom)->Result<Services,ServicesError>{
|
|
||||||
Ok(Services{
|
|
||||||
workspace:*dom.root().children().iter().find(|&&r|
|
|
||||||
dom.get_by_ref(r).is_some_and(|instance|instance.class=="Workspace")
|
|
||||||
).ok_or(ServicesError::WorkspaceNotFound)?,
|
|
||||||
game:dom.root_ref(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub type LuaAppData=&'static mut WeakDom;
|
|
||||||
pub struct Context{
|
pub struct Context{
|
||||||
pub(crate)dom:WeakDom,
|
pub(crate)dom:WeakDom,
|
||||||
pub(crate)services:Services,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Context{
|
impl Context{
|
||||||
pub fn from_place(dom:WeakDom)->Result<Context,ServicesError>{
|
pub const fn new(dom:WeakDom)->Self{
|
||||||
let services=Services::find_services(&dom)?;
|
Self{dom}
|
||||||
Ok(Self{dom,services})
|
|
||||||
}
|
}
|
||||||
pub fn script_singleton(source:String)->(Context,crate::runner::instance::Instance){
|
pub fn script_singleton(source:String)->(Context,crate::runner::instance::Instance,Services){
|
||||||
let script=InstanceBuilder::new("Script")
|
let script=InstanceBuilder::new("Script")
|
||||||
.with_property("Source",rbx_types::Variant::String(source));
|
.with_property("Source",rbx_types::Variant::String(source));
|
||||||
let script_ref=script.referent();
|
let script_ref=script.referent();
|
||||||
let dom=WeakDom::new(
|
let mut context=Self::new(WeakDom::new(
|
||||||
InstanceBuilder::new("DataModel")
|
InstanceBuilder::new("DataModel")
|
||||||
.with_child(script)
|
.with_child(script)
|
||||||
);
|
));
|
||||||
let context=Self::from_model(dom);
|
let services=context.convert_into_place();
|
||||||
(context,crate::runner::instance::Instance::new_unchecked(script_ref))
|
(context,crate::runner::instance::Instance::new(script_ref),services)
|
||||||
|
}
|
||||||
|
pub fn from_ref(dom:&WeakDom)->&Context{
|
||||||
|
unsafe{&*(dom as *const WeakDom as *const Context)}
|
||||||
|
}
|
||||||
|
pub fn from_mut(dom:&mut WeakDom)->&mut Context{
|
||||||
|
unsafe{&mut *(dom as *mut WeakDom as *mut Context)}
|
||||||
}
|
}
|
||||||
/// Creates an iterator over all items of a particular class.
|
/// Creates an iterator over all items of a particular class.
|
||||||
pub fn superclass_iter<'a>(&'a self,superclass:&'a str)->impl Iterator<Item=Ref>+'a{
|
pub fn superclass_iter<'a>(&'a self,superclass:&'a str)->impl Iterator<Item=Ref>+'a{
|
||||||
let db=rbx_reflection_database::get();
|
self.dom.descendants().filter(|&instance|
|
||||||
let Some(superclass)=db.classes.get(superclass)else{
|
class_is_a(instance.class.as_ref(),superclass)
|
||||||
panic!("Invalid class");
|
).map(|instance|instance.referent())
|
||||||
};
|
|
||||||
self.dom.descendants().filter_map(|instance|{
|
|
||||||
let class=db.classes.get(instance.class.as_str())?;
|
|
||||||
db.has_superclass(class,superclass).then(||instance.referent())
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
pub fn scripts(&self)->Vec<crate::runner::instance::Instance>{
|
pub fn scripts(&self)->Vec<crate::runner::instance::Instance>{
|
||||||
self.superclass_iter("Script")
|
self.superclass_iter("LuaSourceContainer").map(crate::runner::instance::Instance::new).collect()
|
||||||
.filter_map(|script_ref|{
|
|
||||||
let script=self.dom.get_by_ref(script_ref)?;
|
|
||||||
if let None|Some(rbx_dom_weak::types::Variant::Bool(false))=script.properties.get(&static_ustr("Disabled")){
|
|
||||||
return Some(crate::runner::instance::Instance::new_unchecked(script_ref));
|
|
||||||
}
|
|
||||||
None
|
|
||||||
})
|
|
||||||
.collect()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn from_model(mut dom:WeakDom)->Context{
|
pub fn find_services(&self)->Option<Services>{
|
||||||
|
Some(Services{
|
||||||
|
workspace:*self.dom.root().children().iter().find(|&&r|
|
||||||
|
self.dom.get_by_ref(r).is_some_and(|instance|instance.class=="Workspace")
|
||||||
|
)?,
|
||||||
|
game:self.dom.root_ref(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
pub fn convert_into_place(&mut self)->Services{
|
||||||
//snapshot root instances
|
//snapshot root instances
|
||||||
let children=dom.root().children().to_owned();
|
let children=self.dom.root().children().to_owned();
|
||||||
|
|
||||||
//insert services
|
//insert services
|
||||||
let game=dom.root_ref();
|
let game=self.dom.root_ref();
|
||||||
let terrain_bldr=InstanceBuilder::new("Terrain");
|
let terrain_bldr=InstanceBuilder::new("Terrain");
|
||||||
let workspace=dom.insert(game,
|
let workspace=self.dom.insert(game,
|
||||||
InstanceBuilder::new("Workspace")
|
InstanceBuilder::new("Workspace")
|
||||||
//Set Workspace.Terrain property equal to Terrain
|
//Set Workspace.Terrain property equal to Terrain
|
||||||
.with_property("Terrain",terrain_bldr.referent())
|
.with_property("Terrain",terrain_bldr.referent())
|
||||||
.with_child(terrain_bldr)
|
.with_child(terrain_bldr)
|
||||||
);
|
);
|
||||||
|
{
|
||||||
|
//Lowercase and upper case workspace property!
|
||||||
|
let game=self.dom.root_mut();
|
||||||
|
game.properties.insert("workspace".to_owned(),rbx_types::Variant::Ref(workspace));
|
||||||
|
game.properties.insert("Workspace".to_owned(),rbx_types::Variant::Ref(workspace));
|
||||||
|
}
|
||||||
|
self.dom.insert(game,InstanceBuilder::new("Lighting"));
|
||||||
|
|
||||||
//transfer original root instances into workspace
|
//transfer original root instances into workspace
|
||||||
for instance in children{
|
for instance in children{
|
||||||
dom.transfer_within(instance,workspace);
|
self.dom.transfer_within(instance,workspace);
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
Services{
|
||||||
//Lowercase and upper case workspace property!
|
game,
|
||||||
let game=dom.root_mut();
|
workspace,
|
||||||
// TODO: DELETE THIS!
|
|
||||||
game.properties.insert(static_ustr("workspace"),rbx_types::Variant::Ref(workspace));
|
|
||||||
game.properties.insert(static_ustr("Workspace"),rbx_types::Variant::Ref(workspace));
|
|
||||||
}
|
}
|
||||||
dom.insert(game,InstanceBuilder::new("Lighting"));
|
|
||||||
|
|
||||||
let services=Services{game,workspace};
|
|
||||||
Self{dom,services}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AsRef<WeakDom> for Context{
|
pub struct Services{
|
||||||
fn as_ref(&self)->&WeakDom{
|
pub game:Ref,
|
||||||
&self.dom
|
pub workspace:Ref,
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
mod util;
|
|
||||||
pub mod runner;
|
pub mod runner;
|
||||||
pub mod context;
|
pub mod context;
|
||||||
#[cfg(feature="run-service")]
|
#[cfg(feature="run-service")]
|
||||||
@ -6,7 +5,3 @@ pub(crate) mod scheduler;
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests;
|
mod tests;
|
||||||
|
|
||||||
pub mod mlua{
|
|
||||||
pub use mlua::{Result,Error};
|
|
||||||
}
|
|
||||||
|
@ -1,59 +0,0 @@
|
|||||||
use super::color3::Color3;
|
|
||||||
|
|
||||||
#[derive(Clone,Copy)]
|
|
||||||
pub struct BrickColor(rbx_types::BrickColor);
|
|
||||||
impl BrickColor{
|
|
||||||
pub fn from_name(name:&str)->Option<Self>{
|
|
||||||
Some(BrickColor(rbx_types::BrickColor::from_name(name)?))
|
|
||||||
}
|
|
||||||
pub fn from_number(number:u16)->Option<Self>{
|
|
||||||
Some(BrickColor(rbx_types::BrickColor::from_number(number)?))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_globals(lua:&mlua::Lua,globals:&mlua::Table)->Result<(),mlua::Error>{
|
|
||||||
let table=lua.create_table()?;
|
|
||||||
|
|
||||||
table.raw_set("new",
|
|
||||||
lua.create_function(|_,r:mlua::Value|
|
|
||||||
match r{
|
|
||||||
mlua::Value::String(name)=>Ok(BrickColor::from_name(&*name.to_str()?)),
|
|
||||||
mlua::Value::Integer(number)=>Ok(BrickColor::from_number(number as u16)),
|
|
||||||
_=>Err(mlua::Error::runtime("Unsupported arguments"))
|
|
||||||
}
|
|
||||||
|
|
||||||
)?
|
|
||||||
)?;
|
|
||||||
|
|
||||||
macro_rules! brickcolor_constructor{
|
|
||||||
($fname:expr,$internal:ident)=>{
|
|
||||||
table.raw_set($fname,
|
|
||||||
lua.create_function(|_,_:()|
|
|
||||||
Ok(BrickColor(rbx_types::BrickColor::$internal))
|
|
||||||
)?
|
|
||||||
)?;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
brickcolor_constructor!("White",White);
|
|
||||||
brickcolor_constructor!("Gray",MediumStoneGrey);
|
|
||||||
brickcolor_constructor!("DarkGray",DarkStoneGrey);
|
|
||||||
brickcolor_constructor!("Black",Black);
|
|
||||||
brickcolor_constructor!("Red",BrightRed);
|
|
||||||
brickcolor_constructor!("Yellow",BrightYellow);
|
|
||||||
brickcolor_constructor!("Green",DarkGreen);
|
|
||||||
brickcolor_constructor!("Blue",BrightBlue);
|
|
||||||
|
|
||||||
globals.set("BrickColor",table)?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
impl mlua::UserData for BrickColor{
|
|
||||||
fn add_fields<F:mlua::UserDataFields<Self>>(fields:&mut F){
|
|
||||||
fields.add_field_method_get("Color",|_,BrickColor(this)|{
|
|
||||||
let rbx_types::Color3uint8{r,g,b}=this.to_color3uint8();
|
|
||||||
Ok(Color3::from_rgb(r,g,b))
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
type_from_lua_userdata!(BrickColor);
|
|
@ -1,10 +1,7 @@
|
|||||||
use mlua::FromLua;
|
|
||||||
|
|
||||||
use super::number::Number;
|
|
||||||
use super::vector3::Vector3;
|
use super::vector3::Vector3;
|
||||||
|
|
||||||
#[derive(Clone,Copy)]
|
#[derive(Clone,Copy)]
|
||||||
pub struct CFrame(glam::Affine3A);
|
pub struct CFrame(pub(crate)glam::Affine3A);
|
||||||
|
|
||||||
impl CFrame{
|
impl CFrame{
|
||||||
pub fn new(
|
pub fn new(
|
||||||
@ -37,14 +34,14 @@ fn vec3_from_glam(v:rbx_types::Vector3)->glam::Vec3A{
|
|||||||
glam::vec3a(v.x,v.y,v.z)
|
glam::vec3a(v.x,v.y,v.z)
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<CFrame> for rbx_types::CFrame{
|
impl Into<rbx_types::CFrame> for CFrame{
|
||||||
fn from(CFrame(cf):CFrame)->rbx_types::CFrame{
|
fn into(self)->rbx_types::CFrame{
|
||||||
rbx_types::CFrame::new(
|
rbx_types::CFrame::new(
|
||||||
vec3_to_glam(cf.translation),
|
vec3_to_glam(self.0.translation),
|
||||||
rbx_types::Matrix3::new(
|
rbx_types::Matrix3::new(
|
||||||
vec3_to_glam(cf.matrix3.x_axis),
|
vec3_to_glam(self.0.matrix3.x_axis),
|
||||||
vec3_to_glam(cf.matrix3.y_axis),
|
vec3_to_glam(self.0.matrix3.y_axis),
|
||||||
vec3_to_glam(cf.matrix3.z_axis),
|
vec3_to_glam(self.0.matrix3.z_axis),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -63,25 +60,16 @@ impl From<rbx_types::CFrame> for CFrame{
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_globals(lua:&mlua::Lua,globals:&mlua::Table)->Result<(),mlua::Error>{
|
pub fn set_globals(lua:&mlua::Lua,globals:&mlua::Table)->Result<(),mlua::Error>{
|
||||||
let table=lua.create_table()?;
|
let cframe_table=lua.create_table()?;
|
||||||
|
|
||||||
//CFrame.new
|
//CFrame.new
|
||||||
table.raw_set("new",
|
cframe_table.raw_set("new",
|
||||||
lua.create_function(|lua,tuple:(
|
lua.create_function(|_,tuple:(
|
||||||
mlua::Value,mlua::Value,Option<Number>,
|
mlua::Value,mlua::Value,Option<f32>,
|
||||||
Option<Number>,Option<Number>,Option<Number>,
|
Option<f32>,Option<f32>,Option<f32>,
|
||||||
Option<Number>,Option<Number>,Option<Number>,
|
Option<f32>,Option<f32>,Option<f32>,
|
||||||
Option<Number>,Option<Number>,Option<Number>,
|
Option<f32>,Option<f32>,Option<f32>,
|
||||||
)|match tuple{
|
)|match tuple{
|
||||||
//CFrame.new()
|
|
||||||
(
|
|
||||||
mlua::Value::Nil,mlua::Value::Nil,None,
|
|
||||||
None,None,None,
|
|
||||||
None,None,None,
|
|
||||||
None,None,None,
|
|
||||||
)=>{
|
|
||||||
Ok(CFrame(glam::Affine3A::IDENTITY))
|
|
||||||
},
|
|
||||||
//CFrame.new(pos)
|
//CFrame.new(pos)
|
||||||
(
|
(
|
||||||
mlua::Value::UserData(pos),mlua::Value::Nil,None,
|
mlua::Value::UserData(pos),mlua::Value::Nil,None,
|
||||||
@ -89,8 +77,8 @@ pub fn set_globals(lua:&mlua::Lua,globals:&mlua::Table)->Result<(),mlua::Error>{
|
|||||||
None,None,None,
|
None,None,None,
|
||||||
None,None,None,
|
None,None,None,
|
||||||
)=>{
|
)=>{
|
||||||
let Vector3(pos):&Vector3=&*pos.borrow()?;
|
let pos:Vector3=pos.take()?;
|
||||||
Ok(CFrame::point(pos.x,pos.y,pos.z))
|
Ok(CFrame::point(pos.0.x,pos.0.y,pos.0.z))
|
||||||
},
|
},
|
||||||
//TODO: CFrame.new(pos,look)
|
//TODO: CFrame.new(pos,look)
|
||||||
(
|
(
|
||||||
@ -99,99 +87,85 @@ pub fn set_globals(lua:&mlua::Lua,globals:&mlua::Table)->Result<(),mlua::Error>{
|
|||||||
None,None,None,
|
None,None,None,
|
||||||
None,None,None,
|
None,None,None,
|
||||||
)=>{
|
)=>{
|
||||||
let _pos:&Vector3=&*pos.borrow()?;
|
let _pos:Vector3=pos.take()?;
|
||||||
let _look:&Vector3=&*look.borrow()?;
|
let _look:Vector3=look.take()?;
|
||||||
Err(mlua::Error::runtime("Not yet implemented"))
|
Err(mlua::Error::runtime("Not yet implemented"))
|
||||||
},
|
},
|
||||||
//CFrame.new(x,y,z)
|
//CFrame.new(x,y,z)
|
||||||
(
|
(
|
||||||
x,y,Some(z),
|
mlua::Value::Number(x),mlua::Value::Number(y),Some(z),
|
||||||
None,None,None,
|
None,None,None,
|
||||||
None,None,None,
|
None,None,None,
|
||||||
None,None,None,
|
None,None,None,
|
||||||
)=>Ok(CFrame::point(Number::from_lua(x,lua)?.into(),Number::from_lua(y,lua)?.into(),z.into())),
|
)=>Ok(CFrame::point(x as f32,y as f32,z)),
|
||||||
//CFrame.new(x,y,z,xx,yx,zx,xy,yy,zy,xz,yz,zz)
|
//CFrame.new(x,y,z,xx,yx,zx,xy,yy,zy,xz,yz,zz)
|
||||||
(
|
(
|
||||||
x,y,Some(z),
|
mlua::Value::Number(x),mlua::Value::Number(y),Some(z),
|
||||||
Some(xx),Some(yx),Some(zx),
|
Some(xx),Some(yx),Some(zx),
|
||||||
Some(xy),Some(yy),Some(zy),
|
Some(xy),Some(yy),Some(zy),
|
||||||
Some(xz),Some(yz),Some(zz),
|
Some(xz),Some(yz),Some(zz),
|
||||||
)=>Ok(CFrame::new(Number::from_lua(x,lua)?.into(),Number::from_lua(y,lua)?.into(),z.into(),
|
)=>Ok(CFrame::new(x as f32,y as f32,z,
|
||||||
xx.into(),yx.into(),zx.into(),
|
xx,yx,zx,
|
||||||
xy.into(),yy.into(),zy.into(),
|
xy,yy,zy,
|
||||||
xz.into(),yz.into(),zz.into(),
|
xz,yz,zz,
|
||||||
)),
|
)),
|
||||||
_=>Err(mlua::Error::runtime("Invalid arguments"))
|
_=>Err(mlua::Error::runtime("Invalid arguments"))
|
||||||
})?
|
})?
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
//CFrame.Angles
|
//CFrame.Angles
|
||||||
let from_euler_angles=lua.create_function(|_,(x,y,z):(Number,Number,Number)|
|
cframe_table.raw_set("Angles",
|
||||||
Ok(CFrame::angles(x.into(),y.into(),z.into()))
|
lua.create_function(|_,(x,y,z):(f32,f32,f32)|
|
||||||
|
Ok(CFrame::angles(x,y,z))
|
||||||
|
)?
|
||||||
)?;
|
)?;
|
||||||
table.raw_set("Angles",from_euler_angles.clone())?;
|
|
||||||
table.raw_set("fromEulerAnglesXYZ",from_euler_angles.clone())?;
|
|
||||||
table.raw_set("FromEulerAnglesXYZ",from_euler_angles)?;
|
|
||||||
|
|
||||||
globals.set("CFrame",table)?;
|
globals.set("CFrame",cframe_table)?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
impl mlua::UserData for CFrame{
|
impl mlua::UserData for CFrame{
|
||||||
fn add_fields<F:mlua::UserDataFields<Self>>(fields:&mut F){
|
fn add_fields<F:mlua::UserDataFields<Self>>(fields:&mut F){
|
||||||
fields.add_field_method_get("p",|_,CFrame(this)|Ok(Vector3(this.translation)));
|
//CFrame.p
|
||||||
fields.add_field_method_get("x",|_,CFrame(this)|Ok(this.translation.x));
|
fields.add_field_method_get("p",|_,this|Ok(Vector3(this.0.translation)));
|
||||||
fields.add_field_method_get("X",|_,CFrame(this)|Ok(this.translation.x));
|
|
||||||
fields.add_field_method_get("y",|_,CFrame(this)|Ok(this.translation.y));
|
|
||||||
fields.add_field_method_get("Y",|_,CFrame(this)|Ok(this.translation.y));
|
|
||||||
fields.add_field_method_get("z",|_,CFrame(this)|Ok(this.translation.z));
|
|
||||||
fields.add_field_method_get("Z",|_,CFrame(this)|Ok(this.translation.z));
|
|
||||||
fields.add_field_method_get("rightVector",|_,CFrame(this)|Ok(Vector3(this.matrix3.x_axis)));
|
|
||||||
fields.add_field_method_get("RightVector",|_,CFrame(this)|Ok(Vector3(this.matrix3.x_axis)));
|
|
||||||
fields.add_field_method_get("upVector",|_,CFrame(this)|Ok(Vector3(this.matrix3.y_axis)));
|
|
||||||
fields.add_field_method_get("UpVector",|_,CFrame(this)|Ok(Vector3(this.matrix3.y_axis)));
|
|
||||||
fields.add_field_method_get("lookVector",|_,CFrame(this)|Ok(Vector3(-this.matrix3.z_axis)));
|
|
||||||
fields.add_field_method_get("LookVector",|_,CFrame(this)|Ok(Vector3(-this.matrix3.z_axis)));
|
|
||||||
fields.add_field_method_get("XVector",|_,CFrame(this)|Ok(Vector3(this.matrix3.row(0))));
|
|
||||||
fields.add_field_method_get("YVector",|_,CFrame(this)|Ok(Vector3(this.matrix3.row(1))));
|
|
||||||
fields.add_field_method_get("ZVector",|_,CFrame(this)|Ok(Vector3(this.matrix3.row(2))));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn add_methods<M:mlua::UserDataMethods<Self>>(methods:&mut M){
|
fn add_methods<M:mlua::UserDataMethods<Self>>(methods:&mut M){
|
||||||
methods.add_method("components",|_,CFrame(this),()|Ok((
|
methods.add_method("components",|_,this,()|Ok((
|
||||||
this.translation.x,
|
this.0.translation.x,
|
||||||
this.translation.y,
|
this.0.translation.y,
|
||||||
this.translation.z,
|
this.0.translation.z,
|
||||||
this.matrix3.x_axis.x,
|
this.0.matrix3.x_axis.x,
|
||||||
this.matrix3.y_axis.x,
|
this.0.matrix3.y_axis.x,
|
||||||
this.matrix3.z_axis.x,
|
this.0.matrix3.z_axis.x,
|
||||||
this.matrix3.x_axis.y,
|
this.0.matrix3.x_axis.y,
|
||||||
this.matrix3.y_axis.y,
|
this.0.matrix3.y_axis.y,
|
||||||
this.matrix3.z_axis.y,
|
this.0.matrix3.z_axis.y,
|
||||||
this.matrix3.x_axis.z,
|
this.0.matrix3.x_axis.z,
|
||||||
this.matrix3.y_axis.z,
|
this.0.matrix3.y_axis.z,
|
||||||
this.matrix3.z_axis.z,
|
this.0.matrix3.z_axis.z,
|
||||||
)));
|
)));
|
||||||
methods.add_method("VectorToWorldSpace",|_,CFrame(this),Vector3(v):Vector3|
|
methods.add_method("VectorToWorldSpace",|_,this,v:Vector3|
|
||||||
Ok(Vector3(this.transform_vector3a(v)))
|
Ok(Vector3(this.0.transform_vector3a(v.0)))
|
||||||
);
|
);
|
||||||
|
|
||||||
methods.add_meta_function(mlua::MetaMethod::Mul,|_,(CFrame(this),CFrame(val)):(Self,Self)|Ok(Self(this*val)));
|
//methods.add_meta_method(mlua::MetaMethod::Mul,|_,this,val:&Vector3|Ok(Vector3(this.0.matrix3*val.0+this.0.translation)));
|
||||||
methods.add_meta_function(mlua::MetaMethod::ToString,|_,CFrame(this):Self|
|
methods.add_meta_function(mlua::MetaMethod::Mul,|_,(this,val):(Self,Self)|Ok(Self(this.0*val.0)));
|
||||||
|
methods.add_meta_function(mlua::MetaMethod::ToString,|_,this:Self|
|
||||||
Ok(format!("CFrame.new({},{},{},{},{},{},{},{},{},{},{},{})",
|
Ok(format!("CFrame.new({},{},{},{},{},{},{},{},{},{},{},{})",
|
||||||
this.translation.x,
|
this.0.translation.x,
|
||||||
this.translation.y,
|
this.0.translation.y,
|
||||||
this.translation.z,
|
this.0.translation.z,
|
||||||
this.matrix3.x_axis.x,
|
this.0.matrix3.x_axis.x,
|
||||||
this.matrix3.y_axis.x,
|
this.0.matrix3.y_axis.x,
|
||||||
this.matrix3.z_axis.x,
|
this.0.matrix3.z_axis.x,
|
||||||
this.matrix3.x_axis.y,
|
this.0.matrix3.x_axis.y,
|
||||||
this.matrix3.y_axis.y,
|
this.0.matrix3.y_axis.y,
|
||||||
this.matrix3.z_axis.y,
|
this.0.matrix3.z_axis.y,
|
||||||
this.matrix3.x_axis.z,
|
this.0.matrix3.x_axis.z,
|
||||||
this.matrix3.y_axis.z,
|
this.0.matrix3.y_axis.z,
|
||||||
this.matrix3.z_axis.z,
|
this.0.matrix3.z_axis.z,
|
||||||
))
|
))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
use super::number::Number;
|
|
||||||
|
|
||||||
#[derive(Clone,Copy)]
|
#[derive(Clone,Copy)]
|
||||||
pub struct Color3{
|
pub struct Color3{
|
||||||
r:f32,
|
r:f32,
|
||||||
@ -10,37 +8,28 @@ impl Color3{
|
|||||||
pub const fn new(r:f32,g:f32,b:f32)->Self{
|
pub const fn new(r:f32,g:f32,b:f32)->Self{
|
||||||
Self{r,g,b}
|
Self{r,g,b}
|
||||||
}
|
}
|
||||||
pub const fn from_rgb(r:u8,g:u8,b:u8)->Self{
|
|
||||||
Color3::new(r as f32/255.0,g as f32/255.0,b as f32/255.0)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
impl From<rbx_types::Color3> for Color3{
|
impl Into<rbx_types::Color3> for Color3{
|
||||||
fn from(value:rbx_types::Color3)->Color3{
|
fn into(self)->rbx_types::Color3{
|
||||||
Color3::new(value.r,value.g,value.b)
|
rbx_types::Color3::new(self.r,self.g,self.b)
|
||||||
}
|
|
||||||
}
|
|
||||||
impl From<Color3> for rbx_types::Color3{
|
|
||||||
fn from(value:Color3)->rbx_types::Color3{
|
|
||||||
rbx_types::Color3::new(value.r,value.g,value.b)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_globals(lua:&mlua::Lua,globals:&mlua::Table)->Result<(),mlua::Error>{
|
pub fn set_globals(lua:&mlua::Lua,globals:&mlua::Table)->Result<(),mlua::Error>{
|
||||||
let table=lua.create_table()?;
|
let color3_table=lua.create_table()?;
|
||||||
|
|
||||||
table.raw_set("new",
|
color3_table.raw_set("new",
|
||||||
lua.create_function(|_,(r,g,b):(Number,Number,Number)|
|
lua.create_function(|_,(r,g,b):(f32,f32,f32)|
|
||||||
Ok(Color3::new(r.into(),g.into(),b.into()))
|
Ok(Color3::new(r,g,b))
|
||||||
|
)?
|
||||||
|
)?;
|
||||||
|
color3_table.raw_set("fromRGB",
|
||||||
|
lua.create_function(|_,(r,g,b):(u8,u8,u8)|
|
||||||
|
Ok(Color3::new(r as f32/255.0,g as f32/255.0,b as f32/255.0))
|
||||||
)?
|
)?
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
let from_rgb=lua.create_function(|_,(r,g,b):(u8,u8,u8)|
|
globals.set("Color3",color3_table)?;
|
||||||
Ok(Color3::from_rgb(r,g,b))
|
|
||||||
)?;
|
|
||||||
table.raw_set("fromRGB",from_rgb.clone())?;
|
|
||||||
table.raw_set("FromRGB",from_rgb)?;
|
|
||||||
|
|
||||||
globals.set("Color3",table)?;
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -1,29 +1,31 @@
|
|||||||
#[derive(Clone)]
|
#[derive(Clone,Copy)]
|
||||||
pub struct ColorSequence(rbx_types::ColorSequence);
|
pub struct ColorSequence{}
|
||||||
impl ColorSequence{
|
impl ColorSequence{
|
||||||
pub const fn new(keypoints:Vec<rbx_types::ColorSequenceKeypoint>)->Self{
|
pub const fn new()->Self{
|
||||||
Self(rbx_types::ColorSequence{keypoints})
|
Self{}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl From<ColorSequence> for rbx_types::ColorSequence{
|
impl Into<rbx_types::ColorSequence> for ColorSequence{
|
||||||
fn from(ColorSequence(value):ColorSequence)->rbx_types::ColorSequence{
|
fn into(self)->rbx_types::ColorSequence{
|
||||||
value
|
rbx_types::ColorSequence{
|
||||||
|
keypoints:Vec::new()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_globals(lua:&mlua::Lua,globals:&mlua::Table)->Result<(),mlua::Error>{
|
pub fn set_globals(lua:&mlua::Lua,globals:&mlua::Table)->Result<(),mlua::Error>{
|
||||||
let table=lua.create_table()?;
|
let number_sequence_table=lua.create_table()?;
|
||||||
|
|
||||||
table.raw_set("new",
|
number_sequence_table.raw_set("new",
|
||||||
lua.create_function(|_,_:mlua::MultiValue|
|
lua.create_function(|_,_:mlua::MultiValue|
|
||||||
Ok(ColorSequence::new(Vec::new()))
|
Ok(ColorSequence::new())
|
||||||
)?
|
)?
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
globals.set("ColorSequence",table)?;
|
globals.set("ColorSequence",number_sequence_table)?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
impl mlua::UserData for ColorSequence{}
|
impl mlua::UserData for ColorSequence{}
|
||||||
type_from_lua_userdata_clone!(ColorSequence);
|
type_from_lua_userdata!(ColorSequence);
|
||||||
|
@ -1,136 +1,63 @@
|
|||||||
#[derive(Clone,Copy)]
|
use mlua::IntoLua;
|
||||||
pub struct EnumItem<'a>{
|
|
||||||
name:Option<&'a str>,
|
|
||||||
value:u32,
|
|
||||||
}
|
|
||||||
impl<'a> EnumItem<'a>{
|
|
||||||
fn known_name((name,&value):(&'a std::borrow::Cow<'a,str>,&u32))->Self{
|
|
||||||
Self{name:Some(name.as_ref()),value}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl<'a> From<rbx_types::Enum> for EnumItem<'a>{
|
|
||||||
fn from(e:rbx_types::Enum)->Self{
|
|
||||||
EnumItem{
|
|
||||||
name:None,
|
|
||||||
value:e.to_u32(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl From<EnumItem<'_>> for rbx_types::Enum{
|
|
||||||
fn from(e:EnumItem)->rbx_types::Enum{
|
|
||||||
rbx_types::Enum::from_u32(e.value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl PartialEq for EnumItem<'_>{
|
|
||||||
fn eq(&self,other:&EnumItem<'_>)->bool{
|
|
||||||
self.value==other.value&&{
|
|
||||||
// if both names are known, they must match, otherwise whatever
|
|
||||||
match (self.name,other.name){
|
|
||||||
(Some(lhs),Some(rhs))=>lhs==rhs,
|
|
||||||
_=>true,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone,Copy)]
|
#[derive(Clone,Copy)]
|
||||||
pub struct Enums;
|
pub struct Enum(u32);
|
||||||
impl Enums{
|
|
||||||
pub fn get(&self,index:&str)->Option<EnumItems<'static>>{
|
|
||||||
let db=rbx_reflection_database::get();
|
|
||||||
db.enums.get(index).map(|ed|EnumItems{ed})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#[derive(Clone,Copy)]
|
#[derive(Clone,Copy)]
|
||||||
pub struct EnumItems<'a>{
|
pub struct EnumItems;
|
||||||
|
#[derive(Clone,Copy)]
|
||||||
|
pub struct EnumItem<'a>{
|
||||||
ed:&'a rbx_reflection::EnumDescriptor<'a>,
|
ed:&'a rbx_reflection::EnumDescriptor<'a>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> EnumItems<'a>{
|
impl Into<rbx_types::Enum> for Enum{
|
||||||
pub fn from_value(&self,value:u32)->Option<EnumItem<'a>>{
|
fn into(self)->rbx_types::Enum{
|
||||||
self.ed.items.iter().find(|&(_,&v)|v==value).map(EnumItem::known_name)
|
rbx_types::Enum::from_u32(self.0)
|
||||||
}
|
|
||||||
pub fn from_name(&self,name:&str)->Option<EnumItem<'a>>{
|
|
||||||
self.ed.items.get_key_value(name).map(EnumItem::known_name)
|
|
||||||
}
|
|
||||||
pub fn from_enum(&self,enum_item:EnumItem)->Option<EnumItem<'a>>{
|
|
||||||
match enum_item.name{
|
|
||||||
Some(s)=>{
|
|
||||||
let got=self.from_name(s)?;
|
|
||||||
(got.value==enum_item.value).then_some(got)
|
|
||||||
},
|
|
||||||
None=>self.from_value(enum_item.value)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub enum CoerceEnum<'a>{
|
impl<'a> EnumItem<'a>{
|
||||||
Integer(i32),
|
const fn new(ed:&'a rbx_reflection::EnumDescriptor)->Self{
|
||||||
String(mlua::String),
|
Self{ed}
|
||||||
Enum(EnumItem<'a>),
|
|
||||||
}
|
|
||||||
impl CoerceEnum<'_>{
|
|
||||||
pub fn coerce_to<'a>(self,enum_items:EnumItems<'a>)->mlua::Result<EnumItem<'a>>{
|
|
||||||
match self{
|
|
||||||
CoerceEnum::Integer(int)=>enum_items.from_value(int as u32),
|
|
||||||
CoerceEnum::String(s)=>enum_items.from_name(&*s.to_str()?),
|
|
||||||
CoerceEnum::Enum(enum_item)=>enum_items.from_enum(enum_item),
|
|
||||||
}.ok_or_else(||mlua::Error::runtime(format!("Bad {} EnumItem",enum_items.ed.name)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl mlua::FromLua for CoerceEnum<'_>{
|
|
||||||
fn from_lua(value:mlua::Value,_lua:&mlua::Lua)->Result<Self,mlua::Error>{
|
|
||||||
match value{
|
|
||||||
mlua::Value::Integer(int)=>Ok(CoerceEnum::Integer(int)),
|
|
||||||
mlua::Value::String(s)=>Ok(CoerceEnum::String(s)),
|
|
||||||
mlua::Value::UserData(ud)=>Ok(CoerceEnum::Enum(*ud.borrow()?)),
|
|
||||||
other=>Err(mlua::Error::runtime(format!("Expected {} got {:?}",stringify!(Enum),other))),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_globals(_lua:&mlua::Lua,globals:&mlua::Table)->Result<(),mlua::Error>{
|
pub fn set_globals(_lua:&mlua::Lua,globals:&mlua::Table)->Result<(),mlua::Error>{
|
||||||
globals.set("Enum",Enums)
|
globals.set("Enum",EnumItems)
|
||||||
}
|
}
|
||||||
|
|
||||||
impl mlua::UserData for EnumItems<'static>{
|
|
||||||
fn add_fields<F:mlua::UserDataFields<Self>>(_fields:&mut F){
|
|
||||||
}
|
|
||||||
fn add_methods<M:mlua::UserDataMethods<Self>>(methods:&mut M){
|
|
||||||
methods.add_method("FromName",|_,this:&EnumItems,name:mlua::String|Ok(this.from_name(&*name.to_str()?)));
|
|
||||||
methods.add_method("FromValue",|_,this:&EnumItems,value:u32|Ok(this.from_value(value)));
|
|
||||||
methods.add_method("GetEnumItems",|_,this:&EnumItems,()|->mlua::Result<Vec<EnumItem>>{
|
|
||||||
Ok(this.ed.items.iter().map(EnumItem::known_name).collect())
|
|
||||||
});
|
|
||||||
methods.add_meta_function(mlua::MetaMethod::Index,|_,(this,val):(EnumItems,mlua::String)|{
|
|
||||||
let index=&*val.to_str()?;
|
|
||||||
Ok(this.ed.items.get_key_value(index).map(EnumItem::known_name))
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
type_from_lua_userdata_lua_lifetime!(EnumItems);
|
|
||||||
|
|
||||||
impl mlua::UserData for Enums{
|
|
||||||
fn add_fields<F:mlua::UserDataFields<Self>>(_fields:&mut F){
|
|
||||||
}
|
|
||||||
fn add_methods<M:mlua::UserDataMethods<Self>>(methods:&mut M){
|
|
||||||
methods.add_meta_function(mlua::MetaMethod::Index,|_,(enums,val):(Self,mlua::String)|{
|
|
||||||
Ok(enums.get(&*val.to_str()?))
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
type_from_lua_userdata!(Enums);
|
|
||||||
|
|
||||||
impl mlua::UserData for EnumItem<'_>{
|
impl mlua::UserData for EnumItem<'_>{
|
||||||
fn add_fields<F:mlua::UserDataFields<Self>>(fields:&mut F){
|
fn add_fields<F:mlua::UserDataFields<Self>>(_fields:&mut F){
|
||||||
fields.add_field_method_get("Name",|_,this|Ok(this.name));
|
|
||||||
fields.add_field_method_get("Value",|_,this|Ok(this.value));
|
|
||||||
}
|
}
|
||||||
fn add_methods<M:mlua::UserDataMethods<Self>>(methods:&mut M){
|
fn add_methods<M:mlua::UserDataMethods<Self>>(methods:&mut M){
|
||||||
methods.add_meta_function(mlua::MetaMethod::Eq,|_,(lhs,rhs):(EnumItem<'_>,EnumItem<'_>)|{
|
methods.add_meta_function(mlua::MetaMethod::Index,|lua,(this,val):(EnumItem<'_>,mlua::String)|{
|
||||||
Ok(lhs==rhs)
|
match this.ed.items.get(&*val.to_str()?){
|
||||||
|
Some(&id)=>Enum(id).into_lua(lua),
|
||||||
|
None=>mlua::Value::Nil.into_lua(lua),
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
type_from_lua_userdata_lua_lifetime!(EnumItem);
|
type_from_lua_userdata_lua_lifetime!(EnumItem);
|
||||||
|
|
||||||
|
impl mlua::UserData for EnumItems{
|
||||||
|
fn add_fields<F:mlua::UserDataFields<Self>>(_fields:&mut F){
|
||||||
|
}
|
||||||
|
fn add_methods<M:mlua::UserDataMethods<Self>>(methods:&mut M){
|
||||||
|
methods.add_meta_function(mlua::MetaMethod::Index,|lua,(_,val):(Self,mlua::String)|{
|
||||||
|
let db=rbx_reflection_database::get();
|
||||||
|
match db.enums.get(&*val.to_str()?){
|
||||||
|
Some(ed)=>EnumItem::new(ed).into_lua(lua),
|
||||||
|
None=>mlua::Value::Nil.into_lua(lua),
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
type_from_lua_userdata!(EnumItems);
|
||||||
|
|
||||||
|
impl mlua::UserData for Enum{
|
||||||
|
fn add_fields<F:mlua::UserDataFields<Self>>(_fields:&mut F){
|
||||||
|
}
|
||||||
|
fn add_methods<M:mlua::UserDataMethods<Self>>(_methods:&mut M){
|
||||||
|
}
|
||||||
|
}
|
||||||
|
type_from_lua_userdata!(Enum);
|
||||||
|
@ -2,47 +2,48 @@ use std::collections::{hash_map::Entry,HashMap};
|
|||||||
|
|
||||||
use mlua::{FromLua,FromLuaMulti,IntoLua,IntoLuaMulti};
|
use mlua::{FromLua,FromLuaMulti,IntoLua,IntoLuaMulti};
|
||||||
use rbx_types::Ref;
|
use rbx_types::Ref;
|
||||||
use rbx_dom_weak::{Ustr,InstanceBuilder,WeakDom};
|
use rbx_dom_weak::{InstanceBuilder,WeakDom};
|
||||||
|
|
||||||
use crate::util::static_ustr;
|
|
||||||
use crate::runner::vector3::Vector3;
|
use crate::runner::vector3::Vector3;
|
||||||
use crate::runner::number::Number;
|
|
||||||
|
|
||||||
pub fn set_globals(lua:&mlua::Lua,globals:&mlua::Table)->Result<(),mlua::Error>{
|
pub fn set_globals(lua:&mlua::Lua,globals:&mlua::Table)->Result<(),mlua::Error>{
|
||||||
//class functions store
|
//class functions store
|
||||||
lua.set_app_data(ClassMethodsStore::default());
|
lua.set_app_data(ClassMethodsStore::default());
|
||||||
|
lua.set_app_data(InstanceValueStore::default());
|
||||||
|
|
||||||
let table=lua.create_table()?;
|
let instance_table=lua.create_table()?;
|
||||||
|
|
||||||
//Instance.new
|
//Instance.new
|
||||||
table.raw_set("new",
|
instance_table.raw_set("new",
|
||||||
lua.create_function(|lua,(class_name,parent):(mlua::String,Option<Instance>)|{
|
lua.create_function(|lua,(class_name,parent):(mlua::String,Option<Instance>)|{
|
||||||
let class_name_str=&*class_name.to_str()?;
|
let class_name_str=&*class_name.to_str()?;
|
||||||
let parent_ref=parent.map_or(Ref::none(),|instance|instance.referent);
|
let parent=parent.ok_or_else(||mlua::Error::runtime("Nil Parent not yet supported"))?;
|
||||||
dom_mut(lua,|dom|{
|
dom_mut(lua,|dom|{
|
||||||
Ok(Instance::new_unchecked(dom.insert(parent_ref,InstanceBuilder::new(class_name_str))))
|
//TODO: Nil instances
|
||||||
|
Ok(Instance::new(dom.insert(parent.referent,InstanceBuilder::new(class_name_str))))
|
||||||
})
|
})
|
||||||
})?
|
})?
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
globals.set("Instance",table)?;
|
globals.set("Instance",instance_table)?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
// LMAO look at this function!
|
// LMAO look at this function!
|
||||||
pub fn dom_mut<T>(lua:&mlua::Lua,mut f:impl FnMut(&mut WeakDom)->mlua::Result<T>)->mlua::Result<T>{
|
pub fn dom_mut<T>(lua:&mlua::Lua,mut f:impl FnMut(&mut WeakDom)->mlua::Result<T>)->mlua::Result<T>{
|
||||||
let mut dom=lua.app_data_mut::<crate::context::LuaAppData>().ok_or_else(||mlua::Error::runtime("DataModel missing"))?;
|
let mut dom=lua.app_data_mut::<&'static mut WeakDom>().ok_or_else(||mlua::Error::runtime("DataModel missing"))?;
|
||||||
f(*dom)
|
f(*dom)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn class_is_a(class:&str,superclass:&str)->bool{
|
fn coerce_float32(value:&mlua::Value)->Option<f32>{
|
||||||
let db=rbx_reflection_database::get();
|
match value{
|
||||||
let (Some(class),Some(superclass))=(db.classes.get(class),db.classes.get(superclass))else{
|
&mlua::Value::Integer(i)=>Some(i as f32),
|
||||||
return false;
|
&mlua::Value::Number(f)=>Some(f as f32),
|
||||||
};
|
_=>None,
|
||||||
db.has_superclass(class,superclass)
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_full_name(dom:&rbx_dom_weak::WeakDom,instance:&rbx_dom_weak::Instance)->String{
|
fn get_full_name(dom:&rbx_dom_weak::WeakDom,instance:&rbx_dom_weak::Instance)->String{
|
||||||
let mut full_name=instance.name.clone();
|
let mut full_name=instance.name.clone();
|
||||||
let mut pref=instance.parent();
|
let mut pref=instance.parent();
|
||||||
@ -57,7 +58,7 @@ fn get_full_name(dom:&rbx_dom_weak::WeakDom,instance:&rbx_dom_weak::Instance)->S
|
|||||||
pub fn get_name_source(lua:&mlua::Lua,script:Instance)->Result<(String,String),mlua::Error>{
|
pub fn get_name_source(lua:&mlua::Lua,script:Instance)->Result<(String,String),mlua::Error>{
|
||||||
dom_mut(lua,|dom|{
|
dom_mut(lua,|dom|{
|
||||||
let instance=script.get(dom)?;
|
let instance=script.get(dom)?;
|
||||||
let source=match instance.properties.get(&static_ustr("Source")){
|
let source=match instance.properties.get("Source"){
|
||||||
Some(rbx_dom_weak::types::Variant::String(s))=>s.clone(),
|
Some(rbx_dom_weak::types::Variant::String(s))=>s.clone(),
|
||||||
_=>Err(mlua::Error::external("Missing script.Source"))?,
|
_=>Err(mlua::Error::external("Missing script.Source"))?,
|
||||||
};
|
};
|
||||||
@ -79,32 +80,14 @@ pub fn find_first_descendant_of_class<'a>(dom:&'a rbx_dom_weak::WeakDom,instance
|
|||||||
dom.descendants_of(instance.referent()).find(|&inst|inst.class==class)
|
dom.descendants_of(instance.referent()).find(|&inst|inst.class==class)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn find_first_child_which_is_a<'a>(dom:&'a rbx_dom_weak::WeakDom,instance:&rbx_dom_weak::Instance,superclass:&str)->Option<&'a rbx_dom_weak::Instance>{
|
|
||||||
let db=rbx_reflection_database::get();
|
|
||||||
let superclass_descriptor=db.classes.get(superclass)?;
|
|
||||||
instance.children().iter().filter_map(|&r|dom.get_by_ref(r)).find(|inst|{
|
|
||||||
db.classes.get(inst.class.as_str()).is_some_and(|descriptor|db.has_superclass(descriptor,superclass_descriptor))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
pub fn find_first_descendant_which_is_a<'a>(dom:&'a rbx_dom_weak::WeakDom,instance:&rbx_dom_weak::Instance,superclass:&str)->Option<&'a rbx_dom_weak::Instance>{
|
|
||||||
let db=rbx_reflection_database::get();
|
|
||||||
let superclass_descriptor=db.classes.get(superclass)?;
|
|
||||||
dom.descendants_of(instance.referent()).find(|inst|{
|
|
||||||
db.classes.get(inst.class.as_str()).is_some_and(|descriptor|db.has_superclass(descriptor,superclass_descriptor))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone,Copy)]
|
#[derive(Clone,Copy)]
|
||||||
pub struct Instance{
|
pub struct Instance{
|
||||||
referent:Ref,
|
referent:Ref,
|
||||||
}
|
}
|
||||||
impl Instance{
|
impl Instance{
|
||||||
pub const fn new_unchecked(referent:Ref)->Self{
|
pub const fn new(referent:Ref)->Self{
|
||||||
Self{referent}
|
Self{referent}
|
||||||
}
|
}
|
||||||
pub fn new(referent:Ref)->Option<Self>{
|
|
||||||
referent.is_some().then_some(Self{referent})
|
|
||||||
}
|
|
||||||
pub fn get<'a>(&self,dom:&'a WeakDom)->mlua::Result<&'a rbx_dom_weak::Instance>{
|
pub fn get<'a>(&self,dom:&'a WeakDom)->mlua::Result<&'a rbx_dom_weak::Instance>{
|
||||||
dom.get_by_ref(self.referent).ok_or_else(||mlua::Error::runtime("Instance missing"))
|
dom.get_by_ref(self.referent).ok_or_else(||mlua::Error::runtime("Instance missing"))
|
||||||
}
|
}
|
||||||
@ -114,25 +97,40 @@ impl Instance{
|
|||||||
}
|
}
|
||||||
type_from_lua_userdata!(Instance);
|
type_from_lua_userdata!(Instance);
|
||||||
|
|
||||||
|
//TODO: update rbx_reflection and use dom.superclasses_iter
|
||||||
|
pub struct SuperClassIter<'a> {
|
||||||
|
database: &'a rbx_reflection::ReflectionDatabase<'a>,
|
||||||
|
descriptor: Option<&'a rbx_reflection::ClassDescriptor<'a>>,
|
||||||
|
}
|
||||||
|
impl<'a> SuperClassIter<'a> {
|
||||||
|
fn next_descriptor(&self) -> Option<&'a rbx_reflection::ClassDescriptor<'a>> {
|
||||||
|
let superclass = self.descriptor?.superclass.as_ref()?;
|
||||||
|
self.database.classes.get(superclass)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<'a> Iterator for SuperClassIter<'a> {
|
||||||
|
type Item = &'a rbx_reflection::ClassDescriptor<'a>;
|
||||||
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
|
let next_descriptor = self.next_descriptor();
|
||||||
|
std::mem::replace(&mut self.descriptor, next_descriptor)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl mlua::UserData for Instance{
|
impl mlua::UserData for Instance{
|
||||||
fn add_fields<F:mlua::UserDataFields<Self>>(fields:&mut F){
|
fn add_fields<F:mlua::UserDataFields<Self>>(fields:&mut F){
|
||||||
fn get_parent(lua:&mlua::Lua,this:&Instance)->mlua::Result<Option<Instance>>{
|
fields.add_field_method_get("Parent",|lua,this|{
|
||||||
dom_mut(lua,|dom|{
|
dom_mut(lua,|dom|{
|
||||||
let instance=this.get(dom)?;
|
let instance=this.get(dom)?;
|
||||||
Ok(Instance::new(instance.parent()))
|
Ok(Instance::new(instance.parent()))
|
||||||
})
|
})
|
||||||
}
|
});
|
||||||
fields.add_field_method_get("parent",get_parent);
|
fields.add_field_method_set("Parent",|lua,this,val:Option<Instance>|{
|
||||||
fields.add_field_method_get("Parent",get_parent);
|
let parent=val.ok_or_else(||mlua::Error::runtime("Nil Parent not yet supported"))?;
|
||||||
fn set_parent(lua:&mlua::Lua,this:&mut Instance,new_parent:Option<Instance>)->mlua::Result<()>{
|
|
||||||
let parent_ref=new_parent.map_or(Ref::none(),|instance|instance.referent);
|
|
||||||
dom_mut(lua,|dom|{
|
dom_mut(lua,|dom|{
|
||||||
dom.transfer_within(this.referent,parent_ref);
|
dom.transfer_within(this.referent,parent.referent);
|
||||||
Ok(())
|
Ok(())
|
||||||
})
|
})
|
||||||
}
|
});
|
||||||
fields.add_field_method_set("parent",set_parent);
|
|
||||||
fields.add_field_method_set("Parent",set_parent);
|
|
||||||
fields.add_field_method_get("Name",|lua,this|{
|
fields.add_field_method_get("Name",|lua,this|{
|
||||||
dom_mut(lua,|dom|{
|
dom_mut(lua,|dom|{
|
||||||
let instance=this.get(dom)?;
|
let instance=this.get(dom)?;
|
||||||
@ -150,38 +148,22 @@ impl mlua::UserData for Instance{
|
|||||||
fields.add_field_method_get("ClassName",|lua,this|{
|
fields.add_field_method_get("ClassName",|lua,this|{
|
||||||
dom_mut(lua,|dom|{
|
dom_mut(lua,|dom|{
|
||||||
let instance=this.get(dom)?;
|
let instance=this.get(dom)?;
|
||||||
Ok(instance.class.to_owned())
|
Ok(instance.class.clone())
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
fn add_methods<M:mlua::UserDataMethods<Self>>(methods:&mut M){
|
fn add_methods<M:mlua::UserDataMethods<Self>>(methods:&mut M){
|
||||||
fn clone(lua:&mlua::Lua,this:&Instance,_:())->mlua::Result<Instance>{
|
methods.add_method("GetChildren",|lua,this,_:()|
|
||||||
dom_mut(lua,|dom|{
|
|
||||||
let instance_ref=dom.clone_within(this.referent);
|
|
||||||
Ok(Instance::new_unchecked(instance_ref))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
methods.add_method("clone",clone);
|
|
||||||
methods.add_method("Clone",clone);
|
|
||||||
fn get_children(lua:&mlua::Lua,this:&Instance,_:())->mlua::Result<Vec<Instance>>{
|
|
||||||
dom_mut(lua,|dom|{
|
dom_mut(lua,|dom|{
|
||||||
let instance=this.get(dom)?;
|
let instance=this.get(dom)?;
|
||||||
let children:Vec<_>=instance
|
let children:Vec<_>=instance
|
||||||
.children()
|
.children()
|
||||||
.iter()
|
.iter()
|
||||||
.copied()
|
.copied()
|
||||||
.map(Instance::new_unchecked)
|
.map(Instance::new)
|
||||||
.collect();
|
.collect();
|
||||||
Ok(children)
|
Ok(children)
|
||||||
})
|
})
|
||||||
}
|
|
||||||
methods.add_method("children",get_children);
|
|
||||||
methods.add_method("GetChildren",get_children);
|
|
||||||
methods.add_method("GetFullName",|lua,this,()|
|
|
||||||
dom_mut(lua,|dom|{
|
|
||||||
let instance=this.get(dom)?;
|
|
||||||
Ok(get_full_name(dom,instance))
|
|
||||||
})
|
|
||||||
);
|
);
|
||||||
fn ffc(lua:&mlua::Lua,this:&Instance,(name,search_descendants):(mlua::String,Option<bool>))->mlua::Result<Option<Instance>>{
|
fn ffc(lua:&mlua::Lua,this:&Instance,(name,search_descendants):(mlua::String,Option<bool>))->mlua::Result<Option<Instance>>{
|
||||||
let name_str=&*name.to_str()?;
|
let name_str=&*name.to_str()?;
|
||||||
@ -193,7 +175,7 @@ impl mlua::UserData for Instance{
|
|||||||
false=>find_first_child(dom,instance,name_str),
|
false=>find_first_child(dom,instance,name_str),
|
||||||
}
|
}
|
||||||
.map(|instance|
|
.map(|instance|
|
||||||
Instance::new_unchecked(instance.referent())
|
Instance::new(instance.referent())
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
@ -203,29 +185,13 @@ impl mlua::UserData for Instance{
|
|||||||
methods.add_method("FindFirstChildOfClass",|lua,this,(class,search_descendants):(mlua::String,Option<bool>)|{
|
methods.add_method("FindFirstChildOfClass",|lua,this,(class,search_descendants):(mlua::String,Option<bool>)|{
|
||||||
let class_str=&*class.to_str()?;
|
let class_str=&*class.to_str()?;
|
||||||
dom_mut(lua,|dom|{
|
dom_mut(lua,|dom|{
|
||||||
let inst=this.get(dom)?;
|
|
||||||
Ok(
|
Ok(
|
||||||
match search_descendants.unwrap_or(false){
|
match search_descendants.unwrap_or(false){
|
||||||
true=>find_first_descendant_of_class(dom,inst,class_str),
|
true=>find_first_descendant_of_class(dom,this.get(dom)?,class_str),
|
||||||
false=>find_first_child_of_class(dom,inst,class_str),
|
false=>find_first_child_of_class(dom,this.get(dom)?,class_str),
|
||||||
}
|
}
|
||||||
.map(|instance|
|
.map(|instance|
|
||||||
Instance::new_unchecked(instance.referent())
|
Instance::new(instance.referent())
|
||||||
)
|
|
||||||
)
|
|
||||||
})
|
|
||||||
});
|
|
||||||
methods.add_method("FindFirstChildWhichIsA",|lua,this,(class,search_descendants):(mlua::String,Option<bool>)|{
|
|
||||||
let class_str=&*class.to_str()?;
|
|
||||||
dom_mut(lua,|dom|{
|
|
||||||
let inst=this.get(dom)?;
|
|
||||||
Ok(
|
|
||||||
match search_descendants.unwrap_or(false){
|
|
||||||
true=>find_first_descendant_which_is_a(dom,inst,class_str),
|
|
||||||
false=>find_first_child_which_is_a(dom,inst,class_str),
|
|
||||||
}
|
|
||||||
.map(|instance|
|
|
||||||
Instance::new_unchecked(instance.referent())
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
@ -235,42 +201,24 @@ impl mlua::UserData for Instance{
|
|||||||
let children:Vec<_>=dom
|
let children:Vec<_>=dom
|
||||||
.descendants_of(this.referent)
|
.descendants_of(this.referent)
|
||||||
.map(|instance|
|
.map(|instance|
|
||||||
Instance::new_unchecked(instance.referent())
|
Instance::new(instance.referent())
|
||||||
)
|
)
|
||||||
.collect();
|
.collect();
|
||||||
Ok(children)
|
Ok(children)
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
methods.add_method("IsAncestorOf",|lua,this,descendant:Instance|
|
methods.add_method("IsA",|lua,this,classname:mlua::String|
|
||||||
dom_mut(lua,|dom|{
|
|
||||||
let instance=descendant.get(dom)?;
|
|
||||||
Ok(std::iter::successors(Some(instance),|inst|dom.get_by_ref(inst.parent())).any(|inst|inst.referent()==this.referent))
|
|
||||||
})
|
|
||||||
);
|
|
||||||
methods.add_method("IsDescendantOf",|lua,this,ancestor:Instance|
|
|
||||||
dom_mut(lua,|dom|{
|
dom_mut(lua,|dom|{
|
||||||
let instance=this.get(dom)?;
|
let instance=this.get(dom)?;
|
||||||
Ok(std::iter::successors(Some(instance),|inst|dom.get_by_ref(inst.parent())).any(|inst|inst.referent()==ancestor.referent))
|
Ok(crate::context::class_is_a(instance.class.as_str(),&*classname.to_str()?))
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
fn is_a(lua:&mlua::Lua,this:&Instance,classname:mlua::String)->mlua::Result<bool>{
|
methods.add_method("Destroy",|lua,this,()|
|
||||||
dom_mut(lua,|dom|{
|
dom_mut(lua,|dom|{
|
||||||
let instance=this.get(dom)?;
|
dom.destroy(this.referent);
|
||||||
Ok(class_is_a(instance.class.as_str(),&*classname.to_str()?))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
methods.add_method("isA",is_a);
|
|
||||||
methods.add_method("IsA",is_a);
|
|
||||||
fn destroy(lua:&mlua::Lua,this:&Instance,_:())->mlua::Result<()>{
|
|
||||||
dom_mut(lua,|dom|{
|
|
||||||
dom.transfer_within(this.referent,Ref::none());
|
|
||||||
Ok(())
|
Ok(())
|
||||||
})
|
})
|
||||||
}
|
);
|
||||||
methods.add_method("remove",destroy);
|
|
||||||
methods.add_method("Remove",destroy);
|
|
||||||
methods.add_method("destroy",destroy);
|
|
||||||
methods.add_method("Destroy",destroy);
|
|
||||||
methods.add_meta_function(mlua::MetaMethod::ToString,|lua,this:Instance|{
|
methods.add_meta_function(mlua::MetaMethod::ToString,|lua,this:Instance|{
|
||||||
dom_mut(lua,|dom|{
|
dom_mut(lua,|dom|{
|
||||||
let instance=this.get(dom)?;
|
let instance=this.get(dom)?;
|
||||||
@ -284,38 +232,39 @@ impl mlua::UserData for Instance{
|
|||||||
//println!("__index t={} i={index:?}",instance.name);
|
//println!("__index t={} i={index:?}",instance.name);
|
||||||
let db=rbx_reflection_database::get();
|
let db=rbx_reflection_database::get();
|
||||||
let class=db.classes.get(instance.class.as_str()).ok_or_else(||mlua::Error::runtime("Class missing"))?;
|
let class=db.classes.get(instance.class.as_str()).ok_or_else(||mlua::Error::runtime("Class missing"))?;
|
||||||
// Find existing property
|
//Find existing property
|
||||||
// Interestingly, ustr can know ahead of time if
|
match instance.properties.get(index_str)
|
||||||
// a property does not exist in any runtime instance
|
.cloned()
|
||||||
match Ustr::from_existing(index_str)
|
|
||||||
.and_then(|index_ustr|
|
|
||||||
instance.properties.get(&index_ustr).cloned()
|
|
||||||
)
|
|
||||||
//Find default value
|
//Find default value
|
||||||
.or_else(||db.find_default_property(class,index_str).cloned())
|
.or_else(||db.find_default_property(class,index_str).cloned())
|
||||||
//Find virtual property
|
//Find virtual property
|
||||||
.or_else(||db.superclasses_iter(class).find_map(|class|
|
.or_else(||{
|
||||||
|
SuperClassIter{
|
||||||
|
database:db,
|
||||||
|
descriptor:Some(class),
|
||||||
|
}
|
||||||
|
.find_map(|class|
|
||||||
find_virtual_property(&instance.properties,class,index_str)
|
find_virtual_property(&instance.properties,class,index_str)
|
||||||
))
|
)
|
||||||
|
})
|
||||||
{
|
{
|
||||||
Some(rbx_types::Variant::Bool(val))=>return val.into_lua(lua),
|
|
||||||
Some(rbx_types::Variant::Int32(val))=>return val.into_lua(lua),
|
Some(rbx_types::Variant::Int32(val))=>return val.into_lua(lua),
|
||||||
Some(rbx_types::Variant::Int64(val))=>return val.into_lua(lua),
|
Some(rbx_types::Variant::Int64(val))=>return val.into_lua(lua),
|
||||||
Some(rbx_types::Variant::Float32(val))=>return val.into_lua(lua),
|
Some(rbx_types::Variant::Float32(val))=>return val.into_lua(lua),
|
||||||
Some(rbx_types::Variant::Float64(val))=>return val.into_lua(lua),
|
Some(rbx_types::Variant::Float64(val))=>return val.into_lua(lua),
|
||||||
Some(rbx_types::Variant::String(val))=>return val.into_lua(lua),
|
Some(rbx_types::Variant::Ref(val))=>return Instance::new(val).into_lua(lua),
|
||||||
Some(rbx_types::Variant::Ref(val))=>return Instance::new_unchecked(val).into_lua(lua),
|
Some(rbx_types::Variant::CFrame(cf))=>return Into::<crate::runner::cframe::CFrame>::into(cf).into_lua(lua),
|
||||||
Some(rbx_types::Variant::Enum(e))=>return crate::runner::r#enum::EnumItem::from(e).into_lua(lua),
|
Some(rbx_types::Variant::Vector3(v))=>return Into::<crate::runner::vector3::Vector3>::into(v).into_lua(lua),
|
||||||
Some(rbx_types::Variant::Color3(c))=>return crate::runner::color3::Color3::from(c).into_lua(lua),
|
|
||||||
Some(rbx_types::Variant::CFrame(cf))=>return crate::runner::cframe::CFrame::from(cf).into_lua(lua),
|
|
||||||
Some(rbx_types::Variant::Vector2(v))=>return crate::runner::vector2::Vector2::from(v).into_lua(lua),
|
|
||||||
Some(rbx_types::Variant::Vector3(v))=>return crate::runner::vector3::Vector3::from(v).into_lua(lua),
|
|
||||||
None=>(),
|
None=>(),
|
||||||
other=>return Err(mlua::Error::runtime(format!("Instance.__index Unsupported property type instance={} index={index_str} value={other:?}",instance.name))),
|
other=>return Err(mlua::Error::runtime(format!("Instance.__index Unsupported property type instance={} index={index_str} value={other:?}",instance.name))),
|
||||||
}
|
}
|
||||||
//find a function with a matching name
|
//find a function with a matching name
|
||||||
if let Some(function)=class_methods_store_mut(lua,|cf|{
|
if let Some(function)=class_methods_store_mut(lua,|cf|{
|
||||||
db.superclasses_iter(class).find_map(|class|{
|
let mut iter=SuperClassIter{
|
||||||
|
database:db,
|
||||||
|
descriptor:Some(class),
|
||||||
|
};
|
||||||
|
iter.find_map(|class|{
|
||||||
let mut class_methods=cf.get_or_create_class_methods(&class.name)?;
|
let mut class_methods=cf.get_or_create_class_methods(&class.name)?;
|
||||||
class_methods.get_or_create_function(lua,index_str)
|
class_methods.get_or_create_function(lua,index_str)
|
||||||
.transpose()
|
.transpose()
|
||||||
@ -325,33 +274,41 @@ impl mlua::UserData for Instance{
|
|||||||
}
|
}
|
||||||
|
|
||||||
//find or create an associated userdata object
|
//find or create an associated userdata object
|
||||||
let instance=this.get_mut(dom)?;
|
if let Some(value)=instance_value_store_mut(lua,|ivs|{
|
||||||
if let Some(value)=get_or_create_userdata(instance,lua,index_str)?{
|
//TODO: walk class tree somehow
|
||||||
|
match ivs.get_or_create_instance_values(&instance){
|
||||||
|
Some(mut instance_values)=>instance_values.get_or_create_value(lua,index_str),
|
||||||
|
None=>Ok(None)
|
||||||
|
}
|
||||||
|
})?{
|
||||||
return value.into_lua(lua);
|
return value.into_lua(lua);
|
||||||
}
|
}
|
||||||
// drop mutable borrow
|
|
||||||
//find a child with a matching name
|
//find a child with a matching name
|
||||||
let instance=this.get(dom)?;
|
|
||||||
find_first_child(dom,instance,index_str)
|
find_first_child(dom,instance,index_str)
|
||||||
.map(|instance|Instance::new_unchecked(instance.referent()))
|
.map(|instance|Instance::new(instance.referent()))
|
||||||
.into_lua(lua)
|
.into_lua(lua)
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
methods.add_meta_function(mlua::MetaMethod::NewIndex,|lua,(this,index,value):(Instance,mlua::String,mlua::Value)|{
|
methods.add_meta_function(mlua::MetaMethod::NewIndex,|lua,(this,index,value):(Instance,mlua::String,mlua::Value)|{
|
||||||
let index_str=&*index.to_str()?;
|
|
||||||
dom_mut(lua,|dom|{
|
dom_mut(lua,|dom|{
|
||||||
let instance=this.get_mut(dom)?;
|
let instance=this.get_mut(dom)?;
|
||||||
|
//println!("__newindex t={} i={index:?} v={value:?}",instance.name);
|
||||||
|
let index_str=&*index.to_str()?;
|
||||||
let db=rbx_reflection_database::get();
|
let db=rbx_reflection_database::get();
|
||||||
let class=db.classes.get(instance.class.as_str()).ok_or_else(||mlua::Error::runtime("Class missing"))?;
|
let class=db.classes.get(instance.class.as_str()).ok_or_else(||mlua::Error::runtime("Class missing"))?;
|
||||||
let property=db.superclasses_iter(class).find_map(|cls|
|
let mut iter=SuperClassIter{
|
||||||
cls.properties.get(index_str)
|
database:db,
|
||||||
).ok_or_else(||
|
descriptor:Some(class),
|
||||||
mlua::Error::runtime(format!("Property '{index_str}' missing on class '{}'",class.name))
|
};
|
||||||
)?;
|
let property=iter.find_map(|cls|cls.properties.get(index_str)).ok_or_else(||mlua::Error::runtime(format!("Property '{index_str}' missing on class '{}'",class.name)))?;
|
||||||
let value=match &property.data_type{
|
match &property.data_type{
|
||||||
|
rbx_reflection::DataType::Value(rbx_types::VariantType::Vector3)=>{
|
||||||
|
let typed_value:Vector3=*value.as_userdata().ok_or_else(||mlua::Error::runtime("Expected Userdata"))?.borrow()?;
|
||||||
|
instance.properties.insert(index_str.to_owned(),rbx_types::Variant::Vector3(typed_value.into()));
|
||||||
|
},
|
||||||
rbx_reflection::DataType::Value(rbx_types::VariantType::Float32)=>{
|
rbx_reflection::DataType::Value(rbx_types::VariantType::Float32)=>{
|
||||||
let typed_value=Number::from_lua(value.clone(),lua)?.to_f32();
|
let typed_value:f32=coerce_float32(&value).ok_or_else(||mlua::Error::runtime("Expected f32"))?;
|
||||||
rbx_types::Variant::Float32(typed_value)
|
instance.properties.insert(index_str.to_owned(),rbx_types::Variant::Float32(typed_value));
|
||||||
},
|
},
|
||||||
rbx_reflection::DataType::Enum(enum_name)=>{
|
rbx_reflection::DataType::Enum(enum_name)=>{
|
||||||
let typed_value=match &value{
|
let typed_value=match &value{
|
||||||
@ -362,77 +319,35 @@ impl mlua::UserData for Instance{
|
|||||||
Ok(rbx_types::Enum::from_u32(*e.items.get(&*s.to_str()?).ok_or_else(||mlua::Error::runtime("Invalid enum item"))?))
|
Ok(rbx_types::Enum::from_u32(*e.items.get(&*s.to_str()?).ok_or_else(||mlua::Error::runtime("Invalid enum item"))?))
|
||||||
},
|
},
|
||||||
mlua::Value::UserData(any_user_data)=>{
|
mlua::Value::UserData(any_user_data)=>{
|
||||||
let e:crate::runner::r#enum::EnumItem=*any_user_data.borrow()?;
|
let e:crate::runner::r#enum::Enum=*any_user_data.borrow()?;
|
||||||
Ok(e.into())
|
Ok(e.into())
|
||||||
},
|
},
|
||||||
_=>Err(mlua::Error::runtime("Expected Enum")),
|
_=>Err(mlua::Error::runtime("Expected Enum")),
|
||||||
}?;
|
}?;
|
||||||
rbx_types::Variant::Enum(typed_value)
|
instance.properties.insert(index_str.to_owned(),rbx_types::Variant::Enum(typed_value));
|
||||||
},
|
},
|
||||||
rbx_reflection::DataType::Value(rbx_types::VariantType::Color3)=>{
|
rbx_reflection::DataType::Value(rbx_types::VariantType::Color3)=>{
|
||||||
let typed_value:crate::runner::color3::Color3=*value.as_userdata().ok_or_else(||mlua::Error::runtime("Expected Color3"))?.borrow()?;
|
let typed_value:crate::runner::color3::Color3=*value.as_userdata().ok_or_else(||mlua::Error::runtime("Expected Color3"))?.borrow()?;
|
||||||
rbx_types::Variant::Color3(typed_value.into())
|
instance.properties.insert(index_str.to_owned(),rbx_types::Variant::Color3(typed_value.into()));
|
||||||
},
|
},
|
||||||
rbx_reflection::DataType::Value(rbx_types::VariantType::Bool)=>{
|
rbx_reflection::DataType::Value(rbx_types::VariantType::Bool)=>{
|
||||||
let typed_value=value.as_boolean().ok_or_else(||mlua::Error::runtime("Expected boolean"))?;
|
let typed_value=value.as_boolean().ok_or_else(||mlua::Error::runtime("Expected boolean"))?;
|
||||||
rbx_types::Variant::Bool(typed_value)
|
instance.properties.insert(index_str.to_owned(),rbx_types::Variant::Bool(typed_value));
|
||||||
},
|
|
||||||
rbx_reflection::DataType::Value(rbx_types::VariantType::Int32)=>{
|
|
||||||
let typed_value=value.as_i32().ok_or_else(||mlua::Error::runtime("Expected Int32"))?;
|
|
||||||
rbx_types::Variant::Int32(typed_value)
|
|
||||||
},
|
},
|
||||||
rbx_reflection::DataType::Value(rbx_types::VariantType::String)=>{
|
rbx_reflection::DataType::Value(rbx_types::VariantType::String)=>{
|
||||||
let typed_value=match &value{
|
let typed_value=value.as_str().ok_or_else(||mlua::Error::runtime("Expected boolean"))?;
|
||||||
mlua::Value::Integer(i)=>i.to_string(),
|
instance.properties.insert(index_str.to_owned(),rbx_types::Variant::String(typed_value.to_owned()));
|
||||||
mlua::Value::Number(n)=>n.to_string(),
|
|
||||||
mlua::Value::String(s)=>s.to_str()?.to_owned(),
|
|
||||||
_=>return Err(mlua::Error::runtime("Expected string")),
|
|
||||||
};
|
|
||||||
rbx_types::Variant::String(typed_value)
|
|
||||||
},
|
|
||||||
rbx_reflection::DataType::Value(rbx_types::VariantType::UDim2)=>{
|
|
||||||
let typed_value:&crate::runner::udim2::UDim2=&*value.as_userdata().ok_or_else(||mlua::Error::runtime("Expected UDim2"))?.borrow()?;
|
|
||||||
rbx_types::Variant::UDim2(typed_value.clone().into())
|
|
||||||
},
|
|
||||||
rbx_reflection::DataType::Value(rbx_types::VariantType::NumberRange)=>{
|
|
||||||
let typed_value:&crate::runner::number_range::NumberRange=&*value.as_userdata().ok_or_else(||mlua::Error::runtime("Expected NumberRange"))?.borrow()?;
|
|
||||||
rbx_types::Variant::NumberRange(typed_value.clone().into())
|
|
||||||
},
|
},
|
||||||
rbx_reflection::DataType::Value(rbx_types::VariantType::NumberSequence)=>{
|
rbx_reflection::DataType::Value(rbx_types::VariantType::NumberSequence)=>{
|
||||||
let typed_value:&crate::runner::number_sequence::NumberSequence=&*value.as_userdata().ok_or_else(||mlua::Error::runtime("Expected NumberSequence"))?.borrow()?;
|
let typed_value:crate::runner::number_sequence::NumberSequence=*value.as_userdata().ok_or_else(||mlua::Error::runtime("Expected NumberSequence"))?.borrow()?;
|
||||||
rbx_types::Variant::NumberSequence(typed_value.clone().into())
|
instance.properties.insert(index_str.to_owned(),rbx_types::Variant::NumberSequence(typed_value.into()));
|
||||||
},
|
},
|
||||||
rbx_reflection::DataType::Value(rbx_types::VariantType::ColorSequence)=>{
|
rbx_reflection::DataType::Value(rbx_types::VariantType::ColorSequence)=>{
|
||||||
let typed_value:&crate::runner::color_sequence::ColorSequence=&*value.as_userdata().ok_or_else(||mlua::Error::runtime("Expected ColorSequence"))?.borrow()?;
|
let typed_value:crate::runner::color_sequence::ColorSequence=*value.as_userdata().ok_or_else(||mlua::Error::runtime("Expected ColorSequence"))?.borrow()?;
|
||||||
rbx_types::Variant::ColorSequence(typed_value.clone().into())
|
instance.properties.insert(index_str.to_owned(),rbx_types::Variant::ColorSequence(typed_value.into()));
|
||||||
},
|
|
||||||
rbx_reflection::DataType::Value(rbx_types::VariantType::Vector2)=>{
|
|
||||||
let typed_value:crate::runner::vector2::Vector2=*value.as_userdata().ok_or_else(||mlua::Error::runtime("Expected Vector2"))?.borrow()?;
|
|
||||||
rbx_types::Variant::Vector2(typed_value.clone().into())
|
|
||||||
},
|
|
||||||
rbx_reflection::DataType::Value(rbx_types::VariantType::Vector3)=>{
|
|
||||||
let typed_value:crate::runner::vector3::Vector3=*value.as_userdata().ok_or_else(||mlua::Error::runtime("Expected Vector3"))?.borrow()?;
|
|
||||||
rbx_types::Variant::Vector3(typed_value.clone().into())
|
|
||||||
},
|
|
||||||
rbx_reflection::DataType::Value(rbx_types::VariantType::CFrame)=>{
|
|
||||||
let typed_value:crate::runner::cframe::CFrame=*value.as_userdata().ok_or_else(||mlua::Error::runtime("Expected CFrame"))?.borrow()?;
|
|
||||||
rbx_types::Variant::CFrame(typed_value.clone().into())
|
|
||||||
},
|
|
||||||
rbx_reflection::DataType::Value(rbx_types::VariantType::ContentId)=>{
|
|
||||||
let typed_value=value.as_str().ok_or_else(||mlua::Error::runtime("Expected string"))?.to_owned();
|
|
||||||
rbx_types::Variant::ContentId(typed_value.into())
|
|
||||||
},
|
|
||||||
rbx_reflection::DataType::Value(rbx_types::VariantType::Ref)=>{
|
|
||||||
// why clone?
|
|
||||||
let typed_value=Option::<Instance>::from_lua(value.clone(),lua)?;
|
|
||||||
rbx_types::Variant::Ref(typed_value.map_or(Ref::none(),|instance|instance.referent))
|
|
||||||
},
|
},
|
||||||
other=>return Err(mlua::Error::runtime(format!("Unimplemented property type: {other:?}"))),
|
other=>return Err(mlua::Error::runtime(format!("Unimplemented property type: {other:?}"))),
|
||||||
};
|
}
|
||||||
// the index is known to be a real property at this point
|
|
||||||
// allow creating a permanent ustr (memory leak)
|
|
||||||
let index_ustr=rbx_dom_weak::ustr(index_str);
|
|
||||||
instance.properties.insert(index_ustr,value);
|
|
||||||
Ok(())
|
Ok(())
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
@ -457,53 +372,28 @@ type CFD=phf::Map<&'static str,// Class name
|
|||||||
ClassFunctionPointer
|
ClassFunctionPointer
|
||||||
>
|
>
|
||||||
>;
|
>;
|
||||||
const GET_SERVICE:ClassFunctionPointer=cf!(|lua,_this,service:mlua::String|{
|
static CLASS_FUNCTION_DATABASE:CFD=phf::phf_map!{
|
||||||
|
"DataModel"=>phf::phf_map!{
|
||||||
|
"GetService"=>cf!(|lua,_this,service:mlua::String|{
|
||||||
dom_mut(lua,|dom|{
|
dom_mut(lua,|dom|{
|
||||||
//dom.root_ref()==this.referent ?
|
//dom.root_ref()==this.referent ?
|
||||||
let service=&*service.to_str()?;
|
let service=&*service.to_str()?;
|
||||||
match service{
|
match service{
|
||||||
"Lighting"|"RunService"|"Players"|"Workspace"|"MaterialService"|"TweenService"=>{
|
"Lighting"|"RunService"=>{
|
||||||
let referent=find_first_child_of_class(dom,dom.root(),service)
|
let referent=find_first_child_of_class(dom,dom.root(),service)
|
||||||
.map(|instance|instance.referent())
|
.map(|instance|instance.referent())
|
||||||
.unwrap_or_else(||
|
.unwrap_or_else(||
|
||||||
dom.insert(dom.root_ref(),InstanceBuilder::new(service))
|
dom.insert(dom.root_ref(),InstanceBuilder::new(service))
|
||||||
);
|
);
|
||||||
Ok(Instance::new_unchecked(referent))
|
Ok(Instance::new(referent))
|
||||||
},
|
},
|
||||||
other=>Err(mlua::Error::runtime(format!("Service '{other}' not supported"))),
|
other=>Err::<Instance,_>(mlua::Error::runtime(format!("Service '{other}' not supported"))),
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
});
|
}),
|
||||||
const GET_PLAYERS:ClassFunctionPointer=cf!(|_lua,_this,()|->mlua::Result<_>{
|
|
||||||
Ok(Vec::<Instance>::new())
|
|
||||||
});
|
|
||||||
const NO_OP:ClassFunctionPointer=cf!(|_lua,_this,_:mlua::MultiValue|->mlua::Result<_>{Ok(())});
|
|
||||||
static CLASS_FUNCTION_DATABASE:CFD=phf::phf_map!{
|
|
||||||
"DataModel"=>phf::phf_map!{
|
|
||||||
"service"=>GET_SERVICE,
|
|
||||||
"GetService"=>GET_SERVICE,
|
|
||||||
},
|
},
|
||||||
"Terrain"=>phf::phf_map!{
|
"Terrain"=>phf::phf_map!{
|
||||||
"FillBall"=>cf!(|_lua,_,_:(Vector3,Number,crate::runner::r#enum::CoerceEnum)|mlua::Result::Ok(())),
|
"FillBlock"=>cf!(|_lua,_,_:(crate::runner::cframe::CFrame,Vector3,crate::runner::r#enum::Enum)|mlua::Result::Ok(()))
|
||||||
"FillBlock"=>cf!(|_lua,_,_:(crate::runner::cframe::CFrame,Vector3,crate::runner::r#enum::CoerceEnum)|mlua::Result::Ok(())),
|
|
||||||
"FillCylinder"=>cf!(|_lua,_,_:(crate::runner::cframe::CFrame,Number,Number,crate::runner::r#enum::CoerceEnum)|mlua::Result::Ok(())),
|
|
||||||
"SetMaterialColor"=>cf!(|_lua,_,_:(crate::runner::r#enum::CoerceEnum,crate::runner::color3::Color3)|mlua::Result::Ok(())),
|
|
||||||
},
|
|
||||||
"Players"=>phf::phf_map!{
|
|
||||||
"players"=>GET_PLAYERS,
|
|
||||||
"GetPlayers"=>GET_PLAYERS,
|
|
||||||
},
|
|
||||||
"Sound"=>phf::phf_map!{
|
|
||||||
"Play"=>NO_OP,
|
|
||||||
},
|
|
||||||
"TweenService"=>phf::phf_map!{
|
|
||||||
"Create"=>cf!(|_lua,_,(instance,tween_info,goal):(Instance,crate::runner::tween_info::TweenInfo,mlua::Table)|->mlua::Result<_>{
|
|
||||||
Ok(crate::runner::tween::Tween::create(
|
|
||||||
instance,
|
|
||||||
tween_info,
|
|
||||||
goal,
|
|
||||||
))
|
|
||||||
}),
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -590,70 +480,78 @@ static VIRTUAL_PROPERTY_DATABASE:VPD=phf::phf_map!{
|
|||||||
};
|
};
|
||||||
|
|
||||||
fn find_virtual_property(
|
fn find_virtual_property(
|
||||||
properties:&rbx_dom_weak::UstrMap<rbx_types::Variant>,
|
properties:&HashMap<String,rbx_types::Variant>,
|
||||||
class:&rbx_reflection::ClassDescriptor,
|
class:&rbx_reflection::ClassDescriptor,
|
||||||
index:&str,
|
index:&str
|
||||||
)->Option<rbx_types::Variant>{
|
)->Option<rbx_types::Variant>{
|
||||||
//Find virtual property
|
//Find virtual property
|
||||||
let class_virtual_properties=VIRTUAL_PROPERTY_DATABASE.get(&class.name)?;
|
let class_virtual_properties=VIRTUAL_PROPERTY_DATABASE.get(&class.name)?;
|
||||||
let virtual_property=class_virtual_properties.get(index)?;
|
let virtual_property=class_virtual_properties.get(index)?;
|
||||||
|
|
||||||
//Get source property
|
//Get source property
|
||||||
let variant=properties.get(&static_ustr(virtual_property.property))?;
|
let variant=properties.get(virtual_property.property)?;
|
||||||
|
|
||||||
//Transform Source property with provided function
|
//Transform Source property with provided function
|
||||||
(virtual_property.pointer)(variant)
|
(virtual_property.pointer)(variant)
|
||||||
}
|
}
|
||||||
|
|
||||||
// lazy-loaded per-instance userdata values
|
// lazy-loaded per-instance userdata values
|
||||||
|
// This whole thing is a bad idea and a garbage collection nightmare.
|
||||||
|
// TODO: recreate rbx_dom_weak with my own instance type that owns this data.
|
||||||
type CreateUserData=fn(&mlua::Lua)->mlua::Result<mlua::AnyUserData>;
|
type CreateUserData=fn(&mlua::Lua)->mlua::Result<mlua::AnyUserData>;
|
||||||
type LUD=phf::Map<&'static str,// Class name
|
type LUD=phf::Map<&'static str,// Class name
|
||||||
phf::Map<&'static str,// Value name
|
phf::Map<&'static str,// Value name
|
||||||
CreateUserData
|
CreateUserData
|
||||||
>
|
>
|
||||||
>;
|
>;
|
||||||
fn create_script_signal(lua:&mlua::Lua)->mlua::Result<mlua::AnyUserData>{
|
|
||||||
lua.create_any_userdata(crate::runner::script_signal::ScriptSignal::new())
|
|
||||||
}
|
|
||||||
static LAZY_USER_DATA:LUD=phf::phf_map!{
|
static LAZY_USER_DATA:LUD=phf::phf_map!{
|
||||||
"RunService"=>phf::phf_map!{
|
"RunService"=>phf::phf_map!{
|
||||||
"Stepped"=>create_script_signal,
|
"RenderStepped"=>|lua|{
|
||||||
"Heartbeat"=>create_script_signal,
|
lua.create_any_userdata(crate::runner::script_signal::ScriptSignal::new())
|
||||||
"RenderStepped"=>create_script_signal,
|
|
||||||
},
|
},
|
||||||
"Players"=>phf::phf_map!{
|
|
||||||
"PlayerAdded"=>create_script_signal,
|
|
||||||
},
|
|
||||||
"BasePart"=>phf::phf_map!{
|
|
||||||
"Touched"=>create_script_signal,
|
|
||||||
"TouchEnded"=>create_script_signal,
|
|
||||||
},
|
|
||||||
"Instance"=>phf::phf_map!{
|
|
||||||
"ChildAdded"=>create_script_signal,
|
|
||||||
"ChildRemoved"=>create_script_signal,
|
|
||||||
"DescendantAdded"=>create_script_signal,
|
|
||||||
"DescendantRemoved"=>create_script_signal,
|
|
||||||
},
|
|
||||||
"ClickDetector"=>phf::phf_map!{
|
|
||||||
"MouseClick"=>create_script_signal,
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
fn get_or_create_userdata(instance:&mut rbx_dom_weak::Instance,lua:&mlua::Lua,index:&str)->mlua::Result<Option<mlua::AnyUserData>>{
|
#[derive(Default)]
|
||||||
use std::collections::hash_map::Entry;
|
pub struct InstanceValueStore{
|
||||||
let db=rbx_reflection_database::get();
|
values:HashMap<Ref,
|
||||||
let Some(class)=db.classes.get(instance.class.as_str())else{
|
HashMap<&'static str,
|
||||||
return Ok(None)
|
mlua::AnyUserData
|
||||||
};
|
>
|
||||||
if let Some((&static_str,create_userdata))=db.superclasses_iter(class).find_map(|superclass|
|
>,
|
||||||
// find pair (class,index)
|
}
|
||||||
LAZY_USER_DATA.get(&superclass.name)
|
pub struct InstanceValues<'a>{
|
||||||
.and_then(|map|map.get_entry(index))
|
named_values:&'static phf::Map<&'static str,CreateUserData>,
|
||||||
){
|
values:&'a mut HashMap<&'static str,mlua::AnyUserData>,
|
||||||
let index_ustr=static_ustr(static_str);
|
}
|
||||||
return Ok(Some(match instance.userdata.entry(index_ustr){
|
impl InstanceValueStore{
|
||||||
Entry::Occupied(entry)=>entry.get().clone(),
|
pub fn get_or_create_instance_values(&mut self,instance:&rbx_dom_weak::Instance)->Option<InstanceValues>{
|
||||||
Entry::Vacant(entry)=>entry.insert(create_userdata(lua)?).clone(),
|
LAZY_USER_DATA.get(instance.class.as_str())
|
||||||
}));
|
.map(|named_values|
|
||||||
}
|
InstanceValues{
|
||||||
Ok(None)
|
named_values,
|
||||||
|
values:self.values.entry(instance.referent())
|
||||||
|
.or_insert_with(||HashMap::new()),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl InstanceValues<'_>{
|
||||||
|
pub fn get_or_create_value(&mut self,lua:&mlua::Lua,index:&str)->mlua::Result<Option<mlua::AnyUserData>>{
|
||||||
|
Ok(match self.named_values.get_entry(index){
|
||||||
|
Some((&static_index_str,&function_pointer))=>Some(
|
||||||
|
match self.values.entry(static_index_str){
|
||||||
|
Entry::Occupied(entry)=>entry.get().clone(),
|
||||||
|
Entry::Vacant(entry)=>entry.insert(
|
||||||
|
function_pointer(lua)?
|
||||||
|
).clone(),
|
||||||
|
}
|
||||||
|
),
|
||||||
|
None=>None,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn instance_value_store_mut<T>(lua:&mlua::Lua,mut f:impl FnMut(&mut InstanceValueStore)->mlua::Result<T>)->mlua::Result<T>{
|
||||||
|
let mut cf=lua.app_data_mut::<InstanceValueStore>().ok_or_else(||mlua::Error::runtime("InstanceValueStore missing"))?;
|
||||||
|
f(&mut *cf)
|
||||||
}
|
}
|
||||||
|
@ -1,34 +1,22 @@
|
|||||||
macro_rules! type_from_lua_userdata{
|
macro_rules! type_from_lua_userdata{
|
||||||
($ty:ident)=>{
|
($asd:ident)=>{
|
||||||
impl mlua::FromLua for $ty{
|
impl mlua::FromLua for $asd{
|
||||||
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!($ty),other))),
|
other=>Err(mlua::Error::runtime(format!("Expected {} got {:?}",stringify!($asd),other))),
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
macro_rules! type_from_lua_userdata_clone{
|
|
||||||
($ty:ident)=>{
|
|
||||||
impl mlua::FromLua for $ty{
|
|
||||||
fn from_lua(value:mlua::Value,_lua:&mlua::Lua)->Result<Self,mlua::Error>{
|
|
||||||
match value{
|
|
||||||
mlua::Value::UserData(ud)=>Ok(ud.borrow::<Self>()?.clone()),
|
|
||||||
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{
|
||||||
($ty:ident)=>{
|
($asd:ident)=>{
|
||||||
impl mlua::FromLua for $ty<'static>{
|
impl mlua::FromLua for $asd<'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!($ty),other))),
|
other=>Err(mlua::Error::runtime(format!("Expected {} got {:?}",stringify!($asd),other))),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,19 +3,10 @@ mod macros;
|
|||||||
mod runner;
|
mod runner;
|
||||||
|
|
||||||
mod r#enum;
|
mod r#enum;
|
||||||
mod task;
|
|
||||||
mod udim;
|
|
||||||
mod tween;
|
|
||||||
mod udim2;
|
|
||||||
mod color3;
|
mod color3;
|
||||||
mod cframe;
|
mod cframe;
|
||||||
mod number;
|
|
||||||
mod vector2;
|
|
||||||
mod vector3;
|
mod vector3;
|
||||||
mod brickcolor;
|
|
||||||
mod tween_info;
|
|
||||||
pub mod instance;
|
pub mod instance;
|
||||||
mod number_range;
|
|
||||||
mod script_signal;
|
mod script_signal;
|
||||||
mod color_sequence;
|
mod color_sequence;
|
||||||
mod number_sequence;
|
mod number_sequence;
|
||||||
|
@ -1,43 +0,0 @@
|
|||||||
// the goal of this module is to provide an intermediate type
|
|
||||||
// that is guaranteed to be some kind of number, and provide
|
|
||||||
// methods to coerce it into various more specific types.
|
|
||||||
|
|
||||||
#[derive(Clone,Copy)]
|
|
||||||
pub enum Number{
|
|
||||||
Integer(i32),
|
|
||||||
Number(f64),
|
|
||||||
}
|
|
||||||
macro_rules! impl_ty{
|
|
||||||
($ident:ident,$ty:ty)=>{
|
|
||||||
impl Number{
|
|
||||||
#[inline]
|
|
||||||
pub fn $ident(self)->$ty{
|
|
||||||
match self{
|
|
||||||
Self::Integer(int)=>int as $ty,
|
|
||||||
Self::Number(num)=>num as $ty,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl From<Number> for $ty{
|
|
||||||
fn from(value:Number)->$ty{
|
|
||||||
value.$ident()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
impl_ty!(to_u32,u32);
|
|
||||||
impl_ty!(to_i32,i32);
|
|
||||||
impl_ty!(to_f32,f32);
|
|
||||||
impl_ty!(to_u64,u64);
|
|
||||||
impl_ty!(to_i64,i64);
|
|
||||||
impl_ty!(to_f64,f64);
|
|
||||||
|
|
||||||
impl mlua::FromLua for Number{
|
|
||||||
fn from_lua(value:mlua::Value,_lua:&mlua::Lua)->Result<Self,mlua::Error>{
|
|
||||||
match value{
|
|
||||||
mlua::Value::Integer(int)=>Ok(Number::Integer(int)),
|
|
||||||
mlua::Value::Number(num)=>Ok(Number::Number(num)),
|
|
||||||
other=>Err(mlua::Error::runtime(format!("Expected {} got {:?}",stringify!(Number),other))),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,34 +0,0 @@
|
|||||||
use super::number::Number;
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct NumberRange(rbx_types::NumberRange);
|
|
||||||
impl NumberRange{
|
|
||||||
pub const fn new(min:f32,max:f32)->Self{
|
|
||||||
Self(rbx_types::NumberRange{min,max})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl From<NumberRange> for rbx_types::NumberRange{
|
|
||||||
fn from(NumberRange(value):NumberRange)->rbx_types::NumberRange{
|
|
||||||
value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_globals(lua:&mlua::Lua,globals:&mlua::Table)->Result<(),mlua::Error>{
|
|
||||||
let table=lua.create_table()?;
|
|
||||||
|
|
||||||
table.raw_set("new",
|
|
||||||
lua.create_function(|_,(min,max):(Number,Option<Number>)|{
|
|
||||||
Ok(match max{
|
|
||||||
Some(max)=>NumberRange::new(min.into(),max.into()),
|
|
||||||
None=>NumberRange::new(min.into(),min.into()),
|
|
||||||
})
|
|
||||||
})?
|
|
||||||
)?;
|
|
||||||
|
|
||||||
globals.set("NumberRange",table)?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
impl mlua::UserData for NumberRange{}
|
|
||||||
type_from_lua_userdata_clone!(NumberRange);
|
|
@ -1,36 +1,31 @@
|
|||||||
#[derive(Clone)]
|
#[derive(Clone,Copy)]
|
||||||
pub struct NumberSequence(rbx_types::NumberSequence);
|
pub struct NumberSequence{}
|
||||||
impl NumberSequence{
|
impl NumberSequence{
|
||||||
pub const fn new(keypoints:Vec<rbx_types::NumberSequenceKeypoint>)->Self{
|
pub const fn new()->Self{
|
||||||
Self(rbx_types::NumberSequence{keypoints})
|
Self{}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl From<NumberSequence> for rbx_types::NumberSequence{
|
impl Into<rbx_types::NumberSequence> for NumberSequence{
|
||||||
fn from(NumberSequence(value):NumberSequence)->rbx_types::NumberSequence{
|
fn into(self)->rbx_types::NumberSequence{
|
||||||
value
|
rbx_types::NumberSequence{
|
||||||
|
keypoints:Vec::new()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_globals(lua:&mlua::Lua,globals:&mlua::Table)->Result<(),mlua::Error>{
|
pub fn set_globals(lua:&mlua::Lua,globals:&mlua::Table)->Result<(),mlua::Error>{
|
||||||
let table=lua.create_table()?;
|
let number_sequence_table=lua.create_table()?;
|
||||||
|
|
||||||
table.raw_set("new",
|
number_sequence_table.raw_set("new",
|
||||||
lua.create_function(|_,_:mlua::MultiValue|
|
lua.create_function(|_,_:mlua::MultiValue|
|
||||||
Ok(NumberSequence::new(Vec::new()))
|
Ok(NumberSequence::new())
|
||||||
)?
|
)?
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
globals.set("NumberSequence",table)?;
|
globals.set("NumberSequence",number_sequence_table)?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
impl mlua::UserData for NumberSequence{}
|
impl mlua::UserData for NumberSequence{}
|
||||||
impl mlua::FromLua for NumberSequence{
|
type_from_lua_userdata!(NumberSequence);
|
||||||
fn from_lua(value:mlua::Value,_lua:&mlua::Lua)->Result<Self,mlua::Error>{
|
|
||||||
match value{
|
|
||||||
mlua::Value::UserData(ud)=>Ok(ud.borrow::<Self>()?.clone()),
|
|
||||||
other=>Err(mlua::Error::runtime(format!("Expected {} got {:?}",stringify!(NumberSequence),other))),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
use crate::context::Context;
|
use crate::context::Context;
|
||||||
use crate::util::static_ustr;
|
|
||||||
#[cfg(feature="run-service")]
|
#[cfg(feature="run-service")]
|
||||||
use crate::scheduler::scheduler_mut;
|
use crate::scheduler::scheduler_mut;
|
||||||
|
|
||||||
@ -13,7 +12,7 @@ pub enum Error{
|
|||||||
error:mlua::Error
|
error:mlua::Error
|
||||||
},
|
},
|
||||||
RustLua(mlua::Error),
|
RustLua(mlua::Error),
|
||||||
Services(crate::context::ServicesError),
|
NoServices,
|
||||||
}
|
}
|
||||||
impl std::fmt::Display for Error{
|
impl std::fmt::Display for Error{
|
||||||
fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{
|
fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{
|
||||||
@ -32,19 +31,14 @@ fn init(lua:&mlua::Lua)->mlua::Result<()>{
|
|||||||
//global environment
|
//global environment
|
||||||
let globals=lua.globals();
|
let globals=lua.globals();
|
||||||
|
|
||||||
super::task::set_globals(lua,&globals)?;
|
#[cfg(feature="run-service")]
|
||||||
|
crate::scheduler::set_globals(lua,&globals)?;
|
||||||
super::script_signal::set_globals(lua,&globals)?;
|
super::script_signal::set_globals(lua,&globals)?;
|
||||||
super::r#enum::set_globals(lua,&globals)?;
|
super::r#enum::set_globals(lua,&globals)?;
|
||||||
super::udim::set_globals(lua,&globals)?;
|
|
||||||
super::udim2::set_globals(lua,&globals)?;
|
|
||||||
super::color3::set_globals(lua,&globals)?;
|
super::color3::set_globals(lua,&globals)?;
|
||||||
super::brickcolor::set_globals(lua,&globals)?;
|
|
||||||
super::vector2::set_globals(lua,&globals)?;
|
|
||||||
super::vector3::set_globals(lua,&globals)?;
|
super::vector3::set_globals(lua,&globals)?;
|
||||||
super::cframe::set_globals(lua,&globals)?;
|
super::cframe::set_globals(lua,&globals)?;
|
||||||
super::instance::instance::set_globals(lua,&globals)?;
|
super::instance::instance::set_globals(lua,&globals)?;
|
||||||
super::tween_info::set_globals(lua,&globals)?;
|
|
||||||
super::number_range::set_globals(lua,&globals)?;
|
|
||||||
super::number_sequence::set_globals(lua,&globals)?;
|
super::number_sequence::set_globals(lua,&globals)?;
|
||||||
super::color_sequence::set_globals(lua,&globals)?;
|
super::color_sequence::set_globals(lua,&globals)?;
|
||||||
|
|
||||||
@ -60,21 +54,22 @@ impl Runner{
|
|||||||
Ok(runner)
|
Ok(runner)
|
||||||
}
|
}
|
||||||
pub fn runnable_context<'a>(self,context:&'a mut Context)->Result<Runnable<'a>,Error>{
|
pub fn runnable_context<'a>(self,context:&'a mut Context)->Result<Runnable<'a>,Error>{
|
||||||
|
let services=context.find_services().ok_or(Error::NoServices)?;
|
||||||
|
self.runnable_context_with_services(context,&services)
|
||||||
|
}
|
||||||
|
pub fn runnable_context_with_services<'a>(self,context:&'a mut Context,services:&crate::context::Services)->Result<Runnable<'a>,Error>{
|
||||||
{
|
{
|
||||||
let globals=self.lua.globals();
|
let globals=self.lua.globals();
|
||||||
globals.set("game",super::instance::Instance::new_unchecked(context.services.game)).map_err(Error::RustLua)?;
|
globals.set("game",super::instance::Instance::new(services.game)).map_err(Error::RustLua)?;
|
||||||
globals.set("workspace",super::instance::Instance::new_unchecked(context.services.workspace)).map_err(Error::RustLua)?;
|
globals.set("workspace",super::instance::Instance::new(services.workspace)).map_err(Error::RustLua)?;
|
||||||
}
|
}
|
||||||
// SAFETY: This is not a &'static mut WeakDom,
|
//this makes set_app_data shut up about the lifetime
|
||||||
// but as long as Runnable<'a> holds the lifetime of &'a mut Context
|
self.lua.set_app_data::<&'static mut rbx_dom_weak::WeakDom>(unsafe{core::mem::transmute(&mut context.dom)});
|
||||||
// it is a valid unique reference.
|
|
||||||
let ptr=&mut context.dom as *mut rbx_dom_weak::WeakDom;
|
|
||||||
self.lua.set_app_data::<crate::context::LuaAppData>(unsafe{&mut*ptr});
|
|
||||||
#[cfg(feature="run-service")]
|
#[cfg(feature="run-service")]
|
||||||
self.lua.set_app_data::<crate::scheduler::Scheduler>(crate::scheduler::Scheduler::default());
|
self.lua.set_app_data::<crate::scheduler::Scheduler>(crate::scheduler::Scheduler::default());
|
||||||
Ok(Runnable{
|
Ok(Runnable{
|
||||||
lua:self.lua,
|
lua:self.lua,
|
||||||
_lifetime:std::marker::PhantomData
|
_lifetime:&std::marker::PhantomData
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -82,11 +77,11 @@ impl Runner{
|
|||||||
//Runnable is the same thing but has context set, which it holds the lifetime for.
|
//Runnable is the same thing but has context set, which it holds the lifetime for.
|
||||||
pub struct Runnable<'a>{
|
pub struct Runnable<'a>{
|
||||||
lua:mlua::Lua,
|
lua:mlua::Lua,
|
||||||
_lifetime:std::marker::PhantomData<&'a ()>
|
_lifetime:&'a std::marker::PhantomData<()>
|
||||||
}
|
}
|
||||||
impl Runnable<'_>{
|
impl Runnable<'_>{
|
||||||
pub fn drop_context(self)->Runner{
|
pub fn drop_context(self)->Runner{
|
||||||
self.lua.remove_app_data::<crate::context::LuaAppData>();
|
self.lua.remove_app_data::<&'static mut rbx_dom_weak::WeakDom>();
|
||||||
#[cfg(feature="run-service")]
|
#[cfg(feature="run-service")]
|
||||||
self.lua.remove_app_data::<crate::scheduler::Scheduler>();
|
self.lua.remove_app_data::<crate::scheduler::Scheduler>();
|
||||||
Runner{
|
Runner{
|
||||||
@ -128,15 +123,20 @@ impl Runnable<'_>{
|
|||||||
}
|
}
|
||||||
#[cfg(feature="run-service")]
|
#[cfg(feature="run-service")]
|
||||||
pub fn run_service_step(&self)->Result<(),mlua::Error>{
|
pub fn run_service_step(&self)->Result<(),mlua::Error>{
|
||||||
let render_stepped_signal=super::instance::instance::dom_mut(&self.lua,|dom|{
|
let render_stepped=super::instance::instance::dom_mut(&self.lua,|dom|{
|
||||||
let run_service=super::instance::instance::find_first_child_of_class(dom,dom.root(),"RunService").ok_or_else(||mlua::Error::runtime("RunService missing"))?;
|
let run_service=super::instance::instance::find_first_child_of_class(dom,dom.root(),"RunService").ok_or_else(||mlua::Error::runtime("RunService missing"))?;
|
||||||
Ok(match run_service.userdata.get(&static_ustr("RenderStepped")){
|
super::instance::instance::instance_value_store_mut(&self.lua,|instance_value_store|{
|
||||||
Some(render_stepped)=>Some(render_stepped.borrow::<super::script_signal::ScriptSignal>()?.clone()),
|
//unwrap because I trust my find_first_child_of_class function to
|
||||||
None=>None
|
let mut instance_values=instance_value_store.get_or_create_instance_values(run_service).ok_or_else(||mlua::Error::runtime("RunService InstanceValues missing"))?;
|
||||||
|
let render_stepped=instance_values.get_or_create_value(&self.lua,"RenderStepped")?;
|
||||||
|
//let stepped=instance_values.get_or_create_value(&self.lua,"Stepped")?;
|
||||||
|
//let heartbeat=instance_values.get_or_create_value(&self.lua,"Heartbeat")?;
|
||||||
|
Ok(render_stepped)
|
||||||
})
|
})
|
||||||
})?;
|
})?;
|
||||||
if let Some(render_stepped_signal)=render_stepped_signal{
|
if let Some(render_stepped)=render_stepped{
|
||||||
render_stepped_signal.fire(&mlua::MultiValue::new());
|
let signal:&super::script_signal::ScriptSignal=&*render_stepped.borrow()?;
|
||||||
|
signal.fire(&mlua::MultiValue::new());
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -98,9 +98,12 @@ impl ScriptConnection{
|
|||||||
|
|
||||||
impl mlua::UserData for ScriptSignal{
|
impl mlua::UserData for ScriptSignal{
|
||||||
fn add_methods<M:mlua::UserDataMethods<Self>>(methods:&mut M){
|
fn add_methods<M:mlua::UserDataMethods<Self>>(methods:&mut M){
|
||||||
methods.add_method("connect",|_lua,this,f:mlua::Function|Ok(this.connect(f)));
|
methods.add_method("Connect",|_lua,this,f:mlua::Function|
|
||||||
methods.add_method("Connect",|_lua,this,f:mlua::Function|Ok(this.connect(f)));
|
Ok(this.connect(f))
|
||||||
methods.add_method("Once",|_lua,this,f:mlua::Function|Ok(this.once(f)));
|
);
|
||||||
|
methods.add_method("Once",|_lua,this,f:mlua::Function|
|
||||||
|
Ok(this.once(f))
|
||||||
|
);
|
||||||
// Fire is not allowed to be called from Lua
|
// Fire is not allowed to be called from Lua
|
||||||
// methods.add_method("Fire",|_lua,this,args:mlua::MultiValue|
|
// methods.add_method("Fire",|_lua,this,args:mlua::MultiValue|
|
||||||
// Ok(this.fire(args))
|
// Ok(this.fire(args))
|
||||||
@ -161,7 +164,6 @@ pub fn set_globals(lua:&mlua::Lua,globals:&mlua::Table)->Result<(),mlua::Error>{
|
|||||||
.call::<mlua::Function>(())?;
|
.call::<mlua::Function>(())?;
|
||||||
|
|
||||||
lua.register_userdata_type::<ScriptSignal>(|reg|{
|
lua.register_userdata_type::<ScriptSignal>(|reg|{
|
||||||
reg.add_field("wait",wait.clone());
|
|
||||||
reg.add_field("Wait",wait);
|
reg.add_field("Wait",wait);
|
||||||
mlua::UserData::register(reg);
|
mlua::UserData::register(reg);
|
||||||
})?;
|
})?;
|
||||||
|
@ -1,42 +0,0 @@
|
|||||||
#[cfg(not(feature="run-service"))]
|
|
||||||
fn no_op(_lua:&mlua::Lua,_time:Option<super::number::Number>)->mlua::Result<f64>{
|
|
||||||
Ok(0.0)
|
|
||||||
}
|
|
||||||
fn tick(_lua:&mlua::Lua,_:())->mlua::Result<f64>{
|
|
||||||
Ok(0.0)
|
|
||||||
}
|
|
||||||
// This is used to avoid calling coroutine.yield from the rust side.
|
|
||||||
const LUA_WAIT:&str=
|
|
||||||
"local coroutine_yield=coroutine.yield
|
|
||||||
local schedule_thread=schedule_thread
|
|
||||||
return function(dt)
|
|
||||||
schedule_thread(dt)
|
|
||||||
return coroutine_yield()
|
|
||||||
end";
|
|
||||||
|
|
||||||
pub fn set_globals(lua:&mlua::Lua,globals:&mlua::Table)->Result<(),mlua::Error>{
|
|
||||||
let coroutine_table=globals.get::<mlua::Table>("coroutine")?;
|
|
||||||
#[cfg(feature="run-service")]
|
|
||||||
let schedule_thread=lua.create_function(crate::scheduler::schedule_thread)?;
|
|
||||||
#[cfg(not(feature="run-service"))]
|
|
||||||
let schedule_thread=lua.create_function(no_op)?;
|
|
||||||
|
|
||||||
//create wait function environment
|
|
||||||
let wait_env=lua.create_table()?;
|
|
||||||
wait_env.raw_set("coroutine",coroutine_table)?;
|
|
||||||
wait_env.raw_set("schedule_thread",schedule_thread)?;
|
|
||||||
|
|
||||||
//construct wait function from Lua code
|
|
||||||
let wait=lua.load(LUA_WAIT)
|
|
||||||
.set_name("wait")
|
|
||||||
.set_environment(wait_env)
|
|
||||||
.call::<mlua::Function>(())?;
|
|
||||||
|
|
||||||
globals.raw_set("wait",wait)?;
|
|
||||||
|
|
||||||
// TODO: move this somewhere it belongs
|
|
||||||
let tick=lua.create_function(tick)?;
|
|
||||||
globals.raw_set("tick",tick)?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
@ -1,35 +0,0 @@
|
|||||||
use super::instance::Instance;
|
|
||||||
use super::tween_info::TweenInfo;
|
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct Tween{
|
|
||||||
instance:Instance,
|
|
||||||
tween_info:TweenInfo,
|
|
||||||
goal:mlua::Table,
|
|
||||||
playback_state:rbx_types::Enum,
|
|
||||||
}
|
|
||||||
impl Tween{
|
|
||||||
pub fn create(
|
|
||||||
instance:Instance,
|
|
||||||
tween_info:TweenInfo,
|
|
||||||
goal:mlua::Table,
|
|
||||||
)->Self{
|
|
||||||
Self{
|
|
||||||
instance,
|
|
||||||
tween_info,
|
|
||||||
goal,
|
|
||||||
// Enum.PlaybackState.Begin
|
|
||||||
playback_state:rbx_types::Enum::from_u32(0),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub fn play(&mut self){
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl mlua::UserData for Tween{
|
|
||||||
fn add_methods<M:mlua::UserDataMethods<Self>>(methods:&mut M){
|
|
||||||
methods.add_method_mut("Play",|_,this,()|Ok(this.play()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
type_from_lua_userdata_clone!(Tween);
|
|
@ -1,45 +0,0 @@
|
|||||||
use super::number::Number;
|
|
||||||
use super::r#enum::{CoerceEnum,Enums};
|
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct TweenInfo{
|
|
||||||
time:f64,
|
|
||||||
easing_style:rbx_types::Enum,
|
|
||||||
easing_direction:rbx_types::Enum,
|
|
||||||
repeat_count:u32,
|
|
||||||
reverses:bool,
|
|
||||||
delay_time:f64,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_globals(lua:&mlua::Lua,globals:&mlua::Table)->Result<(),mlua::Error>{
|
|
||||||
let table=lua.create_table()?;
|
|
||||||
|
|
||||||
table.raw_set("new",
|
|
||||||
lua.create_function(|_,(time,easing_style,easing_direction,repeat_count,reverses,delay_time):(Option<Number>,Option<CoerceEnum>,Option<CoerceEnum>,Option<u32>,Option<bool>,Option<Number>)|{
|
|
||||||
Ok(TweenInfo{
|
|
||||||
time:time.map_or(1.0,Number::to_f64),
|
|
||||||
easing_style:match easing_style{
|
|
||||||
// Enum.EasingStyle.Quad
|
|
||||||
None=>rbx_types::Enum::from_u32(3),
|
|
||||||
Some(e)=>e.coerce_to(Enums.get("EasingStyle").unwrap())?.into(),
|
|
||||||
},
|
|
||||||
easing_direction:match easing_direction{
|
|
||||||
// Enum.EasingDirection.Out
|
|
||||||
None=>rbx_types::Enum::from_u32(1),
|
|
||||||
Some(e)=>e.coerce_to(Enums.get("EasingDirection").unwrap())?.into(),
|
|
||||||
},
|
|
||||||
repeat_count:repeat_count.unwrap_or(0),
|
|
||||||
reverses:reverses.unwrap_or(false),
|
|
||||||
delay_time:delay_time.map_or(0.0,Number::to_f64),
|
|
||||||
})
|
|
||||||
})?
|
|
||||||
)?;
|
|
||||||
|
|
||||||
globals.set("TweenInfo",table)?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
impl mlua::UserData for TweenInfo{}
|
|
||||||
type_from_lua_userdata_clone!(TweenInfo);
|
|
@ -1,36 +0,0 @@
|
|||||||
use super::number::Number;
|
|
||||||
|
|
||||||
#[derive(Clone,Copy)]
|
|
||||||
pub struct UDim(rbx_types::UDim);
|
|
||||||
impl UDim{
|
|
||||||
pub fn new(scale:f32,offset:i32)->Self{
|
|
||||||
UDim(rbx_types::UDim::new(scale,offset))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl From<rbx_types::UDim> for UDim{
|
|
||||||
fn from(value:rbx_types::UDim)->Self{
|
|
||||||
Self(value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_globals(lua:&mlua::Lua,globals:&mlua::Table)->Result<(),mlua::Error>{
|
|
||||||
let table=lua.create_table()?;
|
|
||||||
|
|
||||||
table.raw_set("new",
|
|
||||||
lua.create_function(|_,(scale,offset):(Number,i32)|
|
|
||||||
Ok(UDim::new(scale.into(),offset))
|
|
||||||
)?
|
|
||||||
)?;
|
|
||||||
|
|
||||||
globals.set("UDim",table)?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
impl mlua::UserData for UDim{
|
|
||||||
fn add_fields<F:mlua::UserDataFields<Self>>(fields:&mut F){
|
|
||||||
fields.add_field_method_get("Scale",|_,UDim(this)|Ok(this.scale));
|
|
||||||
fields.add_field_method_get("Offset",|_,UDim(this)|Ok(this.offset));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
type_from_lua_userdata!(UDim);
|
|
@ -1,40 +0,0 @@
|
|||||||
use super::udim::UDim;
|
|
||||||
use super::number::Number;
|
|
||||||
|
|
||||||
#[derive(Clone,Copy)]
|
|
||||||
pub struct UDim2(rbx_types::UDim2);
|
|
||||||
impl UDim2{
|
|
||||||
pub fn new(sx:f32,ox:i32,sy:f32,oy:i32)->Self{
|
|
||||||
UDim2(rbx_types::UDim2::new(
|
|
||||||
rbx_types::UDim::new(sx,ox),
|
|
||||||
rbx_types::UDim::new(sy,oy),
|
|
||||||
))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl From<UDim2> for rbx_types::UDim2{
|
|
||||||
fn from(UDim2(value):UDim2)->rbx_types::UDim2{
|
|
||||||
value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_globals(lua:&mlua::Lua,globals:&mlua::Table)->Result<(),mlua::Error>{
|
|
||||||
let table=lua.create_table()?;
|
|
||||||
|
|
||||||
table.raw_set("new",
|
|
||||||
lua.create_function(|_,(sx,ox,sy,oy):(Number,i32,Number,i32)|
|
|
||||||
Ok(UDim2::new(sx.into(),ox,sy.into(),oy))
|
|
||||||
)?
|
|
||||||
)?;
|
|
||||||
|
|
||||||
globals.set("UDim2",table)?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
impl mlua::UserData for UDim2{
|
|
||||||
fn add_fields<F:mlua::UserDataFields<Self>>(fields:&mut F){
|
|
||||||
fields.add_field_method_get("X",|_,UDim2(this)|Ok(UDim::from(this.x)));
|
|
||||||
fields.add_field_method_get("Y",|_,UDim2(this)|Ok(UDim::from(this.y)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
type_from_lua_userdata!(UDim2);
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user