Compare commits
29 Commits
refactor-s
...
debug
| Author | SHA1 | Date | |
|---|---|---|---|
|
f360bb19f5
|
|||
|
fed0c3afc5
|
|||
|
20e1163468
|
|||
|
67d5953471
|
|||
|
d2ded2f53d
|
|||
|
1d6d24e838
|
|||
|
14f8a9be45
|
|||
|
7b80b8dd43
|
|||
|
48c235d73d
|
|||
|
16835e0d36
|
|||
|
f8996c958c
|
|||
|
f91fcf6b6f
|
|||
|
4593514954
|
|||
|
31a3e31e70
|
|||
|
4873e0298c
|
|||
|
637fb38131
|
|||
|
ae624f90dc
|
|||
|
1d17e6acf0
|
|||
|
a53cf8a8c7
|
|||
|
9007de1a2d
|
|||
|
6df057de17
|
|||
|
4fe2eed922
|
|||
|
e83d0e5ff9
|
|||
|
4587d8161d
|
|||
|
7b56dacb73
|
|||
|
f19e846e0f
|
|||
|
6240b0ae86
|
|||
|
8d1ec94ac2
|
|||
|
f82d860822
|
211
Cargo.lock
generated
211
Cargo.lock
generated
@@ -73,6 +73,56 @@ dependencies = [
|
|||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "anstream"
|
||||||
|
version = "0.6.21"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a"
|
||||||
|
dependencies = [
|
||||||
|
"anstyle",
|
||||||
|
"anstyle-parse",
|
||||||
|
"anstyle-query",
|
||||||
|
"anstyle-wincon",
|
||||||
|
"colorchoice",
|
||||||
|
"is_terminal_polyfill",
|
||||||
|
"utf8parse",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "anstyle"
|
||||||
|
version = "1.0.13"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "anstyle-parse"
|
||||||
|
version = "0.2.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2"
|
||||||
|
dependencies = [
|
||||||
|
"utf8parse",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "anstyle-query"
|
||||||
|
version = "1.1.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc"
|
||||||
|
dependencies = [
|
||||||
|
"windows-sys 0.61.2",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "anstyle-wincon"
|
||||||
|
version = "3.0.11"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d"
|
||||||
|
dependencies = [
|
||||||
|
"anstyle",
|
||||||
|
"once_cell_polyfill",
|
||||||
|
"windows-sys 0.61.2",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "array-init"
|
name = "array-init"
|
||||||
version = "2.1.0"
|
version = "2.1.0"
|
||||||
@@ -290,6 +340,46 @@ version = "0.2.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
|
checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "clap"
|
||||||
|
version = "4.5.60"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2797f34da339ce31042b27d23607e051786132987f595b02ba4f6a6dffb7030a"
|
||||||
|
dependencies = [
|
||||||
|
"clap_builder",
|
||||||
|
"clap_derive",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "clap_builder"
|
||||||
|
version = "4.5.60"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "24a241312cea5059b13574bb9b3861cabf758b879c15190b37b6d6fd63ab6876"
|
||||||
|
dependencies = [
|
||||||
|
"anstream",
|
||||||
|
"anstyle",
|
||||||
|
"clap_lex",
|
||||||
|
"strsim",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "clap_derive"
|
||||||
|
version = "4.5.55"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a92793da1a46a5f2a02a6f4c46c6496b28c43638adea8306fcb0caa1634f24e5"
|
||||||
|
dependencies = [
|
||||||
|
"heck",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.117",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "clap_lex"
|
||||||
|
version = "1.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3a822ea5bc7590f9d40f1ba12c0dc3c2760f3482c6984db1573ad11031420831"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "codespan-reporting"
|
name = "codespan-reporting"
|
||||||
version = "0.12.0"
|
version = "0.12.0"
|
||||||
@@ -301,6 +391,12 @@ dependencies = [
|
|||||||
"unicode-width",
|
"unicode-width",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "colorchoice"
|
||||||
|
version = "1.0.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "combine"
|
name = "combine"
|
||||||
version = "4.6.7"
|
version = "4.6.7"
|
||||||
@@ -717,6 +813,12 @@ dependencies = [
|
|||||||
"foldhash 0.2.0",
|
"foldhash 0.2.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "heck"
|
||||||
|
version = "0.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hermit-abi"
|
name = "hermit-abi"
|
||||||
version = "0.5.2"
|
version = "0.5.2"
|
||||||
@@ -756,6 +858,21 @@ dependencies = [
|
|||||||
"hashbrown 0.16.1",
|
"hashbrown 0.16.1",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "integration-tests"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"strafesnet_common",
|
||||||
|
"strafesnet_roblox_bot_file",
|
||||||
|
"strafesnet_roblox_bot_player",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "is_terminal_polyfill"
|
||||||
|
version = "1.70.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "itertools"
|
name = "itertools"
|
||||||
version = "0.14.0"
|
version = "0.14.0"
|
||||||
@@ -765,6 +882,12 @@ dependencies = [
|
|||||||
"either",
|
"either",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "itoa"
|
||||||
|
version = "1.0.17"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "jni"
|
name = "jni"
|
||||||
version = "0.21.1"
|
version = "0.21.1"
|
||||||
@@ -941,6 +1064,20 @@ dependencies = [
|
|||||||
"paste",
|
"paste",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "mp4"
|
||||||
|
version = "0.14.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c9ef834d5ed55e494a2ae350220314dc4aacd1c43a9498b00e320e0ea352a5c3"
|
||||||
|
dependencies = [
|
||||||
|
"byteorder",
|
||||||
|
"bytes",
|
||||||
|
"num-rational",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
"thiserror 1.0.69",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "mp4ra-rust"
|
name = "mp4ra-rust"
|
||||||
version = "0.3.0"
|
version = "0.3.0"
|
||||||
@@ -1012,6 +1149,37 @@ dependencies = [
|
|||||||
"jni-sys",
|
"jni-sys",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "num-bigint"
|
||||||
|
version = "0.4.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9"
|
||||||
|
dependencies = [
|
||||||
|
"num-integer",
|
||||||
|
"num-traits",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "num-integer"
|
||||||
|
version = "0.1.46"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f"
|
||||||
|
dependencies = [
|
||||||
|
"num-traits",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "num-rational"
|
||||||
|
version = "0.4.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824"
|
||||||
|
dependencies = [
|
||||||
|
"num-bigint",
|
||||||
|
"num-integer",
|
||||||
|
"num-traits",
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "num-traits"
|
name = "num-traits"
|
||||||
version = "0.2.19"
|
version = "0.2.19"
|
||||||
@@ -1262,6 +1430,12 @@ version = "1.21.3"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
|
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "once_cell_polyfill"
|
||||||
|
version = "1.70.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "orbclient"
|
name = "orbclient"
|
||||||
version = "0.3.50"
|
version = "0.3.50"
|
||||||
@@ -1627,6 +1801,19 @@ dependencies = [
|
|||||||
"syn 2.0.117",
|
"syn 2.0.117",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde_json"
|
||||||
|
version = "1.0.149"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86"
|
||||||
|
dependencies = [
|
||||||
|
"itoa",
|
||||||
|
"memchr",
|
||||||
|
"serde",
|
||||||
|
"serde_core",
|
||||||
|
"zmij",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "shlex"
|
name = "shlex"
|
||||||
version = "1.3.0"
|
version = "1.3.0"
|
||||||
@@ -1705,9 +1892,9 @@ checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "strafesnet_common"
|
name = "strafesnet_common"
|
||||||
version = "0.8.6"
|
version = "0.8.7"
|
||||||
source = "sparse+https://git.itzana.me/api/packages/strafesnet/cargo/"
|
source = "sparse+https://git.itzana.me/api/packages/strafesnet/cargo/"
|
||||||
checksum = "fb31424f16d189979d9f5781067ff29169a258c11da6ff46a4196bffd96d61dc"
|
checksum = "ac4eb613a8d86986b61aa6b52bd74ef605d370c149778fe96cfab16dc4377636"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"arrayvec",
|
"arrayvec",
|
||||||
"bitflags 2.11.0",
|
"bitflags 2.11.0",
|
||||||
@@ -1802,6 +1989,12 @@ version = "0.1.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6637bab7722d379c8b41ba849228d680cc12d0a45ba1fa2b48f2a30577a06731"
|
checksum = "6637bab7722d379c8b41ba849228d680cc12d0a45ba1fa2b48f2a30577a06731"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "strsim"
|
||||||
|
version = "0.11.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "syn"
|
name = "syn"
|
||||||
version = "1.0.109"
|
version = "1.0.109"
|
||||||
@@ -1983,6 +2176,12 @@ version = "0.2.2"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254"
|
checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "utf8parse"
|
||||||
|
version = "0.2.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "version_check"
|
name = "version_check"
|
||||||
version = "0.9.5"
|
version = "0.9.5"
|
||||||
@@ -1993,7 +2192,9 @@ checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
|
|||||||
name = "video-encoder"
|
name = "video-encoder"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"clap",
|
||||||
"glam",
|
"glam",
|
||||||
|
"mp4",
|
||||||
"strafesnet_common",
|
"strafesnet_common",
|
||||||
"strafesnet_graphics",
|
"strafesnet_graphics",
|
||||||
"strafesnet_roblox_bot_file",
|
"strafesnet_roblox_bot_file",
|
||||||
@@ -2825,3 +3026,9 @@ dependencies = [
|
|||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.117",
|
"syn 2.0.117",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "zmij"
|
||||||
|
version = "1.0.21"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa"
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
[workspace]
|
[workspace]
|
||||||
members = [
|
members = [
|
||||||
|
"integration-tests",
|
||||||
"lib",
|
"lib",
|
||||||
"native-player",
|
"native-player",
|
||||||
"video-encoder",
|
"video-encoder",
|
||||||
@@ -14,6 +15,7 @@ codegen-units = 1
|
|||||||
|
|
||||||
[workspace.dependencies]
|
[workspace.dependencies]
|
||||||
glam = "0.32.0"
|
glam = "0.32.0"
|
||||||
|
wgpu = "28.0.0"
|
||||||
|
|
||||||
strafesnet_common = { version = "0.8.6", registry = "strafesnet" }
|
strafesnet_common = { version = "0.8.6", registry = "strafesnet" }
|
||||||
strafesnet_graphics = { version = "0.0.7", registry = "strafesnet" }
|
strafesnet_graphics = { version = "0.0.7", registry = "strafesnet" }
|
||||||
|
|||||||
9
integration-tests/Cargo.toml
Normal file
9
integration-tests/Cargo.toml
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
[package]
|
||||||
|
name = "integration-tests"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2024"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
strafesnet_common.workspace = true
|
||||||
|
strafesnet_roblox_bot_file.workspace = true
|
||||||
|
strafesnet_roblox_bot_player.workspace = true
|
||||||
25
integration-tests/src/main.rs
Normal file
25
integration-tests/src/main.rs
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
use strafesnet_roblox_bot_file::v0;
|
||||||
|
use strafesnet_roblox_bot_player::{bot,bvh,head};
|
||||||
|
use head::Time as PlaybackTime;
|
||||||
|
use strafesnet_common::session::Time as SessionTime;
|
||||||
|
|
||||||
|
fn main(){
|
||||||
|
let bot=include_bytes!("../../web-demo/bhop_marble_7cf33a64-7120-4514-b9fa-4fe29d9523d.qbot");
|
||||||
|
let timelines=v0::read_all_to_block(std::io::Cursor::new(bot)).unwrap();
|
||||||
|
let bot=bot::CompleteBot::new(timelines);
|
||||||
|
let bvh=bvh::Bvh::new(&bot);
|
||||||
|
|
||||||
|
// sample the position at 0.24s
|
||||||
|
let mut playback0=head::PlaybackHead::new(&bot,SessionTime::ZERO);
|
||||||
|
for i in 0..10{
|
||||||
|
let sample_time=PlaybackTime::from_millis(6543+1*i);
|
||||||
|
playback0.set_time(&bot,SessionTime::ZERO,sample_time);
|
||||||
|
let pos=playback0.get_position(&bot,SessionTime::ZERO);
|
||||||
|
|
||||||
|
// get the closest time on the timeline (convert to PlaybackTime which starts at 0)
|
||||||
|
let closest_time=bot.playback_time(bvh.closest_time_to_point(&bot,pos).unwrap());
|
||||||
|
println!("time={sample_time} closest_time={closest_time}");
|
||||||
|
}
|
||||||
|
// let mut playback1=head::PlaybackHead::new(&bot,SessionTime::ZERO);
|
||||||
|
// playback1.set_time(&bot,SessionTime::ZERO,sample_time);
|
||||||
|
}
|
||||||
@@ -5,7 +5,7 @@ edition = "2024"
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
glam.workspace = true
|
glam.workspace = true
|
||||||
|
wgpu.workspace = true
|
||||||
strafesnet_common.workspace = true
|
strafesnet_common.workspace = true
|
||||||
strafesnet_graphics.workspace = true
|
strafesnet_graphics.workspace = true
|
||||||
strafesnet_roblox_bot_file.workspace = true
|
strafesnet_roblox_bot_file.workspace = true
|
||||||
wgpu = "28.0.0"
|
|
||||||
|
|||||||
@@ -34,6 +34,10 @@ impl CompleteBot{
|
|||||||
pub fn time(&self,time:PlaybackTime)->PhysicsTime{
|
pub fn time(&self,time:PlaybackTime)->PhysicsTime{
|
||||||
self.timer.time(time)
|
self.timer.time(time)
|
||||||
}
|
}
|
||||||
|
pub fn playback_time(&self,time:PhysicsTime)->PlaybackTime{
|
||||||
|
use strafesnet_common::timer::TimerState;
|
||||||
|
time.coerce()-self.timer.clone().into_state().get_offset().coerce()
|
||||||
|
}
|
||||||
pub const fn duration(&self)->PhysicsTime{
|
pub const fn duration(&self)->PhysicsTime{
|
||||||
self.duration
|
self.duration
|
||||||
}
|
}
|
||||||
|
|||||||
168
lib/src/bvh.rs
Normal file
168
lib/src/bvh.rs
Normal file
@@ -0,0 +1,168 @@
|
|||||||
|
use core::ops::Range;
|
||||||
|
use strafesnet_common::aabb::Aabb;
|
||||||
|
use strafesnet_common::bvh::generate_bvh;
|
||||||
|
use strafesnet_common::integer::vec3;
|
||||||
|
use strafesnet_common::integer::{Fixed,Planar64};
|
||||||
|
use strafesnet_common::physics::Time as PhysicsTime;
|
||||||
|
use crate::bot::CompleteBot;
|
||||||
|
use strafesnet_roblox_bot_file::v0;
|
||||||
|
|
||||||
|
const MAX_SLICE_LEN:usize=16;
|
||||||
|
struct EventSlice{
|
||||||
|
slice:Range<usize>,
|
||||||
|
inclusive:bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Bvh{
|
||||||
|
bvh:strafesnet_common::bvh::BvhNode<EventSlice>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Bvh{
|
||||||
|
pub fn new(bot:&CompleteBot)->Self{
|
||||||
|
let output_events=&bot.timelines().output_events;
|
||||||
|
// iterator over the event timeline and capture slices of contiguous output events.
|
||||||
|
// create an Aabb for each slice and then generate a BVH.
|
||||||
|
let mut bvh_nodes=Vec::new();
|
||||||
|
let it=output_events
|
||||||
|
.array_windows()
|
||||||
|
.enumerate()
|
||||||
|
// find discontinuities
|
||||||
|
.filter(|&(_,[event0,event1])|
|
||||||
|
event0.time==event1.time&&!(
|
||||||
|
event0.event.position.x==event1.event.position.x
|
||||||
|
&&event0.event.position.y==event1.event.position.y
|
||||||
|
&&event0.event.position.z==event1.event.position.z
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut last_index=0;
|
||||||
|
let mut push_slices=|index:usize|{
|
||||||
|
let len=index-last_index;
|
||||||
|
let count=len.div_ceil(MAX_SLICE_LEN);
|
||||||
|
let slice_len=MAX_SLICE_LEN;
|
||||||
|
println!("push_slices index={index} len={len} count={count} slice_len={slice_len}");
|
||||||
|
bvh_nodes.reserve(count);
|
||||||
|
// 0123456789
|
||||||
|
// split into groups of MAX_SLICE_LEN=4
|
||||||
|
// [0123][4567][89]
|
||||||
|
let mut push_slice=|slice:Range<usize>,inclusive:bool|{
|
||||||
|
let mut aabb=Aabb::default();
|
||||||
|
for event in &output_events[slice.start..slice.end]{
|
||||||
|
aabb.grow(vec3::try_from_f32_array([event.event.position.x,event.event.position.y,event.event.position.z]).unwrap());
|
||||||
|
}
|
||||||
|
if inclusive{
|
||||||
|
let event=&output_events[slice.end];
|
||||||
|
aabb.grow(vec3::try_from_f32_array([event.event.position.x,event.event.position.y,event.event.position.z]).unwrap());
|
||||||
|
}
|
||||||
|
println!("EventSlice slice={slice:?} {}",if inclusive{"inclusive"}else{"exclusive"});
|
||||||
|
bvh_nodes.push((EventSlice{slice,inclusive},aabb));
|
||||||
|
};
|
||||||
|
// push fixed-size groups
|
||||||
|
for i in 0..count-1{
|
||||||
|
push_slice((last_index+i*slice_len)..(last_index+(i+1)*slice_len),true);
|
||||||
|
}
|
||||||
|
// push last group which may be shorter
|
||||||
|
push_slice((last_index+(count-1)*slice_len)..index,false);
|
||||||
|
last_index=index;
|
||||||
|
};
|
||||||
|
// find discontinuities (teleports) and avoid forming a bvh node across them
|
||||||
|
for (split_index,_) in it{
|
||||||
|
// we want to use the index of event1
|
||||||
|
push_slices(split_index+1);
|
||||||
|
}
|
||||||
|
// there are no more discontinuities, push the remaining slices
|
||||||
|
push_slices(output_events.len());
|
||||||
|
let bvh=generate_bvh(bvh_nodes);
|
||||||
|
Self{bvh}
|
||||||
|
}
|
||||||
|
/// Find the exact timestamp on the bot timeline that is closest to the given point.
|
||||||
|
pub fn closest_time_to_point<'a>(&self,bot:&'a CompleteBot,point:glam::Vec3)->Option<PhysicsTime>{
|
||||||
|
let point=point+bot.world_offset();
|
||||||
|
let start_point=vec3::try_from_f32_array(point.to_array()).unwrap();
|
||||||
|
let output_events=&bot.timelines().output_events;
|
||||||
|
// grow a sphere starting at start_point until we find the closest point on the bot output events
|
||||||
|
let intersect_leaf=|event_slice:&EventSlice|{
|
||||||
|
// calculate the distance to the leaf contents
|
||||||
|
let mut best_distance=output_events[event_slice.slice.start..event_slice.slice.end].iter().map(|event|{
|
||||||
|
let p=event.event.position;
|
||||||
|
let p=vec3::try_from_f32_array([p.x,p.y,p.z]).unwrap();
|
||||||
|
(start_point-p).length_squared()
|
||||||
|
}).min()?;
|
||||||
|
let mut prev_event=&output_events[event_slice.slice.start];
|
||||||
|
let start_time=bot.playback_time(crate::time::from_float(prev_event.time).unwrap());
|
||||||
|
let mut f=|event:&'a v0::Timed<v0::OutputEvent>|{
|
||||||
|
let p0=vec3::try_from_f32_array([prev_event.event.position.x,prev_event.event.position.y,prev_event.event.position.z]).unwrap();
|
||||||
|
let p1=vec3::try_from_f32_array([event.event.position.x,event.event.position.y,event.event.position.z]).unwrap();
|
||||||
|
let d=p1-p0;
|
||||||
|
let d0=p0.dot(d);
|
||||||
|
let d1=p1.dot(d);
|
||||||
|
let sp_d=start_point.dot(d);
|
||||||
|
// must be on the segment
|
||||||
|
if d0<sp_d&&sp_d<d1{
|
||||||
|
let t0=d1-sp_d;
|
||||||
|
let t1=sp_d-d0;
|
||||||
|
let dt=d1-d0;
|
||||||
|
let distance=(((p0*t0+p1*t1)/dt).divide().wrap_1()-start_point).length_squared();
|
||||||
|
if distance<best_distance{
|
||||||
|
best_distance=distance;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
prev_event=event;
|
||||||
|
};
|
||||||
|
for event in &output_events[event_slice.slice.start+1..event_slice.slice.end]{
|
||||||
|
f(event);
|
||||||
|
}
|
||||||
|
if event_slice.inclusive{
|
||||||
|
f(&output_events[event_slice.slice.end]);
|
||||||
|
}else{
|
||||||
|
}
|
||||||
|
let end_time=bot.playback_time(crate::time::from_float(prev_event.time).unwrap());
|
||||||
|
println!("intersect_leaf {:?} {} start_time={} end_time={}",event_slice.slice,if event_slice.inclusive{"inclusive"}else{"exclusive"},start_time,end_time);
|
||||||
|
Some(best_distance)
|
||||||
|
};
|
||||||
|
let intersect_aabb=|aabb:&Aabb|{
|
||||||
|
// calculate the distance to the aabb
|
||||||
|
let clamped_point=start_point.min(aabb.max()).max(aabb.min());
|
||||||
|
Some((start_point-clamped_point).length_squared())
|
||||||
|
};
|
||||||
|
let (_,event_slice)=self.bvh.traverse(start_point,Fixed::ZERO,Fixed::MAX,intersect_leaf,intersect_aabb)?;
|
||||||
|
|
||||||
|
// find time at the closest point
|
||||||
|
let (best_time,mut best_distance)=output_events[event_slice.slice.start..event_slice.slice.end].iter().map(|event|{
|
||||||
|
let p=event.event.position;
|
||||||
|
let p=vec3::try_from_f32_array([p.x,p.y,p.z]).unwrap();
|
||||||
|
(event.time,(start_point-p).length_squared())
|
||||||
|
}).min_by_key(|&(_,distance)|distance)?;
|
||||||
|
let mut best_time=crate::time::from_float(best_time).unwrap();
|
||||||
|
let mut prev_event=&output_events[event_slice.slice.start];
|
||||||
|
let mut f=|event:&'a v0::Timed<v0::OutputEvent>|{
|
||||||
|
let p0=vec3::try_from_f32_array([prev_event.event.position.x,prev_event.event.position.y,prev_event.event.position.z]).unwrap();
|
||||||
|
let p1=vec3::try_from_f32_array([event.event.position.x,event.event.position.y,event.event.position.z]).unwrap();
|
||||||
|
let d=p1-p0;
|
||||||
|
let d0=p0.dot(d);
|
||||||
|
let d1=p1.dot(d);
|
||||||
|
let sp_d=start_point.dot(d);
|
||||||
|
// must be on the segment
|
||||||
|
if d0<sp_d&&sp_d<d1{
|
||||||
|
let t0=d1-sp_d;
|
||||||
|
let t1=sp_d-d0;
|
||||||
|
let dt=d1-d0;
|
||||||
|
let distance=(((p0*t0+p1*t1)/dt).divide().wrap_1()-start_point).length_squared();
|
||||||
|
if distance<best_distance{
|
||||||
|
best_distance=distance;
|
||||||
|
let p0:Planar64=prev_event.time.try_into().unwrap();
|
||||||
|
let p1:Planar64=event.time.try_into().unwrap();
|
||||||
|
best_time=((p0*t0+p1*t1)/dt).into();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
prev_event=event;
|
||||||
|
};
|
||||||
|
for event in &output_events[event_slice.slice.start+1..event_slice.slice.end]{
|
||||||
|
f(event);
|
||||||
|
}
|
||||||
|
if event_slice.inclusive{
|
||||||
|
f(&output_events[event_slice.slice.end]);
|
||||||
|
}
|
||||||
|
Some(best_time)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,7 +6,7 @@ pub struct Graphics{
|
|||||||
start_offset:glam::Vec3,
|
start_offset:glam::Vec3,
|
||||||
}
|
}
|
||||||
impl Graphics{
|
impl Graphics{
|
||||||
pub fn new(device:&wgpu::Device,queue:&wgpu::Queue,size:glam::UVec2,view_format: wgpu::TextureFormat)->Self{
|
pub fn new(device:&wgpu::Device,queue:&wgpu::Queue,size:glam::UVec2,view_format:wgpu::TextureFormat)->Self{
|
||||||
let graphics=strafesnet_graphics::graphics::GraphicsState::new(device,queue,size,view_format);
|
let graphics=strafesnet_graphics::graphics::GraphicsState::new(device,queue,size,view_format);
|
||||||
Self{
|
Self{
|
||||||
graphics,
|
graphics,
|
||||||
|
|||||||
@@ -119,6 +119,10 @@ impl PlaybackHead{
|
|||||||
|
|
||||||
(p-bot.world_offset()+CompleteBot::CAMERA_OFFSET,a.yx())
|
(p-bot.world_offset()+CompleteBot::CAMERA_OFFSET,a.yx())
|
||||||
}
|
}
|
||||||
|
pub fn get_position(&self,bot:&CompleteBot,time:SessionTime)->glam::Vec3{
|
||||||
|
let interp=self.interpolate_output(bot,time);
|
||||||
|
interp.position()-bot.world_offset()
|
||||||
|
}
|
||||||
pub fn get_velocity(&self,bot:&CompleteBot,time:SessionTime)->glam::Vec3{
|
pub fn get_velocity(&self,bot:&CompleteBot,time:SessionTime)->glam::Vec3{
|
||||||
let interp=self.interpolate_output(bot,time);
|
let interp=self.interpolate_output(bot,time);
|
||||||
interp.velocity()
|
interp.velocity()
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
pub mod bot;
|
pub mod bot;
|
||||||
|
pub mod bvh;
|
||||||
pub mod head;
|
pub mod head;
|
||||||
pub mod time;
|
pub mod time;
|
||||||
pub mod state;
|
pub mod state;
|
||||||
|
|||||||
@@ -4,10 +4,10 @@ version = "0.1.0"
|
|||||||
edition = "2024"
|
edition = "2024"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
glam.workspace = true
|
|
||||||
pollster = "0.4.0"
|
pollster = "0.4.0"
|
||||||
wgpu = "28.0.0"
|
|
||||||
winit = "0.30.12"
|
winit = "0.30.12"
|
||||||
|
glam.workspace = true
|
||||||
|
wgpu.workspace = true
|
||||||
strafesnet_roblox_bot_player.workspace = true
|
strafesnet_roblox_bot_player.workspace = true
|
||||||
strafesnet_common.workspace = true
|
strafesnet_common.workspace = true
|
||||||
strafesnet_graphics.workspace = true
|
strafesnet_graphics.workspace = true
|
||||||
|
|||||||
@@ -5,10 +5,12 @@ edition = "2024"
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
glam.workspace = true
|
glam.workspace = true
|
||||||
wgpu = "28.0.0"
|
wgpu.workspace = true
|
||||||
strafesnet_roblox_bot_player.workspace = true
|
strafesnet_roblox_bot_player.workspace = true
|
||||||
strafesnet_common.workspace = true
|
strafesnet_common.workspace = true
|
||||||
strafesnet_graphics.workspace = true
|
strafesnet_graphics.workspace = true
|
||||||
strafesnet_roblox_bot_file.workspace = true
|
strafesnet_roblox_bot_file.workspace = true
|
||||||
strafesnet_snf.workspace = true
|
strafesnet_snf.workspace = true
|
||||||
vk-video = "0.2.0"
|
vk-video = "0.2.0"
|
||||||
|
clap = { version = "4.5.60", features = ["derive"] }
|
||||||
|
mp4 = "0.14.0"
|
||||||
|
|||||||
4
video-encoder/README.md
Normal file
4
video-encoder/README.md
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
### How it works
|
||||||
|
- Render RGB to graphics_texture
|
||||||
|
- Convert RGB to YUV on video_texture
|
||||||
|
- Encode video frame
|
||||||
@@ -1,20 +1,25 @@
|
|||||||
struct VertexOutput {
|
struct VertexOutput {
|
||||||
@builtin(position) position: vec4<f32>,
|
@builtin(position) position: vec4<f32>,
|
||||||
|
@location(1) uv: vec2<f32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@vertex
|
@vertex
|
||||||
fn vs_main(@builtin(vertex_index) vertex_index: u32) -> VertexOutput {
|
fn vs_main(@builtin(vertex_index) vertex_index: u32) -> VertexOutput {
|
||||||
// hacky way to draw a large triangle
|
// hacky way to draw a large triangle
|
||||||
let tmp1 = i32(vertex_index) / 2;
|
let tmp1 = i32(vertex_index) / 2;
|
||||||
let tmp2 = i32(vertex_index) & 1;
|
let tmp2 = i32(vertex_index) & 1;
|
||||||
var result:VertexOutput;
|
var result:VertexOutput;
|
||||||
result.position=vec4<f32>(
|
result.position=vec4<f32>(
|
||||||
f32(tmp1) * 4.0 - 1.0,
|
f32(tmp1) * 4.0 - 1.0,
|
||||||
f32(tmp2) * 4.0 - 1.0,
|
f32(tmp2) * 4.0 - 1.0,
|
||||||
1.0,
|
1.0,
|
||||||
1.0
|
1.0
|
||||||
);
|
);
|
||||||
return result;
|
result.uv=vec2<f32>(
|
||||||
|
f32(tmp1) * 2.0,
|
||||||
|
1.0 - f32(tmp2) * 2.0
|
||||||
|
);
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@group(0)
|
@group(0)
|
||||||
@@ -24,23 +29,27 @@ var texture: texture_2d<f32>;
|
|||||||
@binding(1)
|
@binding(1)
|
||||||
var texture_sampler: sampler;
|
var texture_sampler: sampler;
|
||||||
|
|
||||||
|
const RGB_TO_Y:vec3<f32> =
|
||||||
|
vec3(0.2126,0.7152,0.0722);
|
||||||
|
const RGB_TO_UV:mat3x2<f32> = mat3x2<f32>(
|
||||||
|
-0.09991,0.615,
|
||||||
|
-0.33609,-0.55861,
|
||||||
|
0.436,-0.05639
|
||||||
|
);
|
||||||
|
const BIAS:vec2<f32> = vec2<f32>(0.5, 0.5);
|
||||||
|
|
||||||
@fragment
|
@fragment
|
||||||
fn fs_main_y(input: VertexOutput) -> @location(0) f32 {
|
fn fs_main_y(input: VertexOutput) -> @location(0) f32 {
|
||||||
let conversion_weights = vec3<f32>(0.2126, 0.7152, 0.0722);
|
let color = textureSample(texture, texture_sampler, input.uv).rgb;
|
||||||
let color = textureSample(texture, texture_sampler, input.position.xy).rgb;
|
let y = dot(RGB_TO_Y,color);
|
||||||
|
let y_limited = mix(16.0/255.0,240.0/255.0,y);
|
||||||
return clamp(dot(color, conversion_weights), 0.0, 1.0);
|
return clamp(y_limited, 0.0, 1.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
@fragment
|
@fragment
|
||||||
fn fs_main_uv(input: VertexOutput) -> @location(0) vec2<f32> {
|
fn fs_main_uv(input: VertexOutput) -> @location(0) vec2<f32> {
|
||||||
let conversion_weights = mat3x2<f32>(
|
let color = textureSample(texture, texture_sampler, input.uv).rgb;
|
||||||
-0.1146, 0.5,
|
let uv = RGB_TO_UV * color + BIAS;
|
||||||
-0.3854, -0.4542,
|
let uv_limited = mix(vec2(16.0/255.0),vec2(240.0/255.0),uv);
|
||||||
0.5, -0.0458,
|
return clamp(uv_limited, vec2(0.0, 0.0), vec2(1.0, 1.0));
|
||||||
);
|
|
||||||
let conversion_bias = vec2<f32>(0.5, 0.5);
|
|
||||||
let color = textureSample(texture, texture_sampler, input.position.xy).rgb;
|
|
||||||
|
|
||||||
return clamp(conversion_weights * color + conversion_bias, vec2(0.0, 0.0), vec2(1.0, 1.0));
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,28 +1,129 @@
|
|||||||
use std::io::Write;
|
use std::num::NonZeroU32;
|
||||||
|
use std::path::PathBuf;
|
||||||
use strafesnet_common::session::Time as SessionTime;
|
use strafesnet_common::session::Time as SessionTime;
|
||||||
|
|
||||||
pub fn setup_and_start(){
|
#[derive(clap::Subcommand)]
|
||||||
let vulkan_instance = vk_video::VulkanInstance::new().unwrap();
|
pub enum Commands{
|
||||||
let vulkan_adapter = vulkan_instance.create_adapter(None).unwrap();
|
Encode(EncodeSubcommand),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Commands{
|
||||||
|
pub fn run(self){
|
||||||
|
match self{
|
||||||
|
Commands::Encode(command)=>command.run().unwrap(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(clap::Args)]
|
||||||
|
pub struct EncodeSubcommand{
|
||||||
|
#[arg(long,short)]
|
||||||
|
map:PathBuf,
|
||||||
|
#[arg(long,short)]
|
||||||
|
bot:PathBuf,
|
||||||
|
#[arg(long,short)]
|
||||||
|
output_file:Option<PathBuf>,
|
||||||
|
#[arg(long,short)]
|
||||||
|
width:Option<NonZeroU32>,
|
||||||
|
#[arg(long,short)]
|
||||||
|
height:Option<NonZeroU32>,
|
||||||
|
#[arg(long)]
|
||||||
|
fps:Option<u32>,
|
||||||
|
#[arg(long)]
|
||||||
|
target_bitrate:Option<u64>,
|
||||||
|
#[arg(long)]
|
||||||
|
max_bitrate:Option<u64>,
|
||||||
|
#[arg(long)]
|
||||||
|
device:Option<String>,
|
||||||
|
}
|
||||||
|
impl EncodeSubcommand{
|
||||||
|
fn run(self)->Result<(),EncodeError>{
|
||||||
|
encode(EncodeParams{
|
||||||
|
width:self.width.unwrap_or(NonZeroU32::new(1920).unwrap()),
|
||||||
|
height:self.width.unwrap_or(NonZeroU32::new(1080).unwrap()),
|
||||||
|
target_framerate:self.fps.unwrap_or(60),
|
||||||
|
average_bitrate:self.target_bitrate.unwrap_or(6_000_000),
|
||||||
|
max_bitrate:self.max_bitrate.unwrap_or(6_000_000),
|
||||||
|
device:self.device,
|
||||||
|
output_file:self.output_file.unwrap_or_else(||{
|
||||||
|
let mut output_file:PathBuf=self.bot.file_stem().unwrap().into();
|
||||||
|
output_file.set_extension("mp4");
|
||||||
|
output_file
|
||||||
|
}),
|
||||||
|
map:self.map,
|
||||||
|
bot:self.bot,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[expect(dead_code)]
|
||||||
|
#[derive(Debug)]
|
||||||
|
enum EncodeError{
|
||||||
|
ReadMap(std::io::Error),
|
||||||
|
ReadBot(std::io::Error),
|
||||||
|
DecodeSNF(strafesnet_snf::Error),
|
||||||
|
DecodeMap(strafesnet_snf::map::Error),
|
||||||
|
DecodeBot(strafesnet_roblox_bot_file::v0::Error),
|
||||||
|
CreateInstance(vk_video::VulkanInitError),
|
||||||
|
CreateAdapter(vk_video::VulkanInitError),
|
||||||
|
NoAdapter,
|
||||||
|
CreateDevice(vk_video::VulkanInitError),
|
||||||
|
VideoEncodeParams(vk_video::VulkanEncoderError),
|
||||||
|
VideoCreateTextures(vk_video::VulkanEncoderError),
|
||||||
|
VideoEncodeFrame(vk_video::VulkanEncoderError),
|
||||||
|
OutputCreateFile(std::io::Error),
|
||||||
|
OutputMp4Start(mp4::Error),
|
||||||
|
OutputMp4AddTrack(mp4::Error),
|
||||||
|
OutputMp4WriteSample(mp4::Error),
|
||||||
|
OutputMp4End(mp4::Error),
|
||||||
|
}
|
||||||
|
|
||||||
|
struct EncodeParams{
|
||||||
|
width:NonZeroU32,
|
||||||
|
height:NonZeroU32,
|
||||||
|
target_framerate:u32,
|
||||||
|
average_bitrate:u64,
|
||||||
|
max_bitrate:u64,
|
||||||
|
device:Option<String>,
|
||||||
|
map:PathBuf,
|
||||||
|
bot:PathBuf,
|
||||||
|
output_file:PathBuf,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn encode(params:EncodeParams)->Result<(),EncodeError>{
|
||||||
|
let size = glam::uvec2(params.width.get(),params.height.get());
|
||||||
|
let target_framerate = params.target_framerate;
|
||||||
|
let average_bitrate = params.average_bitrate;
|
||||||
|
let max_bitrate = params.max_bitrate;
|
||||||
|
|
||||||
|
let map_file=std::fs::read(params.map).map_err(EncodeError::ReadMap)?;
|
||||||
|
let bot_file=std::fs::read(params.bot).map_err(EncodeError::ReadBot)?;
|
||||||
|
|
||||||
|
// read files
|
||||||
|
let map=strafesnet_snf::read_map(std::io::Cursor::new(map_file))
|
||||||
|
.map_err(EncodeError::DecodeSNF)?
|
||||||
|
.into_complete_map()
|
||||||
|
.map_err(EncodeError::DecodeMap)?;
|
||||||
|
let timelines=strafesnet_roblox_bot_file::v0::read_all_to_block(std::io::Cursor::new(bot_file))
|
||||||
|
.map_err(EncodeError::DecodeBot)?;
|
||||||
|
|
||||||
|
// vulkan init
|
||||||
|
let vulkan_instance = vk_video::VulkanInstance::new().map_err(EncodeError::CreateInstance)?;
|
||||||
|
let vulkan_adapter = if let Some(filter)=params.device.as_deref(){
|
||||||
|
vulkan_instance.iter_adapters(None)
|
||||||
|
.map_err(EncodeError::CreateAdapter)?
|
||||||
|
.find(|adapter|adapter.info().name.contains(filter))
|
||||||
|
.ok_or(EncodeError::NoAdapter)?
|
||||||
|
}else{
|
||||||
|
vulkan_instance.create_adapter(None).map_err(EncodeError::CreateAdapter)?
|
||||||
|
};
|
||||||
let vulkan_device = vulkan_adapter
|
let vulkan_device = vulkan_adapter
|
||||||
.create_device(
|
.create_device(
|
||||||
wgpu::Features::TEXTURE_COMPRESSION_BC,
|
wgpu::Features::TEXTURE_COMPRESSION_BC,
|
||||||
wgpu::ExperimentalFeatures::disabled(),
|
wgpu::ExperimentalFeatures::disabled(),
|
||||||
wgpu::Limits::defaults(),
|
wgpu::Limits::defaults(),
|
||||||
)
|
)
|
||||||
.unwrap();
|
.map_err(EncodeError::CreateDevice)?;
|
||||||
|
|
||||||
let size = glam::uvec2(1920,1080);
|
|
||||||
let target_framerate = 60;
|
|
||||||
let average_bitrate = 10_000_000;
|
|
||||||
let max_bitrate = 20_000_000;
|
|
||||||
|
|
||||||
let bot_file=include_bytes!("../../web-demo/bhop_marble_7cf33a64-7120-4514-b9fa-4fe29d9523d.qbot");
|
|
||||||
let map_file=include_bytes!("../../web-demo/bhop_marble_5692093612.snfm");
|
|
||||||
|
|
||||||
// decode
|
|
||||||
let timelines=strafesnet_roblox_bot_file::v0::read_all_to_block(std::io::Cursor::new(bot_file)).unwrap();
|
|
||||||
let map=strafesnet_snf::read_map(std::io::Cursor::new(map_file)).unwrap().into_complete_map().unwrap();
|
|
||||||
|
|
||||||
// playback
|
// playback
|
||||||
let bot=strafesnet_roblox_bot_player::bot::CompleteBot::new(timelines);
|
let bot=strafesnet_roblox_bot_player::bot::CompleteBot::new(timelines);
|
||||||
@@ -41,8 +142,8 @@ pub fn setup_and_start(){
|
|||||||
vulkan_device
|
vulkan_device
|
||||||
.encoder_parameters_high_quality(
|
.encoder_parameters_high_quality(
|
||||||
vk_video::parameters::VideoParameters {
|
vk_video::parameters::VideoParameters {
|
||||||
width:size.x.try_into().unwrap(),
|
width:params.width,
|
||||||
height:size.y.try_into().unwrap(),
|
height:params.height,
|
||||||
target_framerate:target_framerate.into(),
|
target_framerate:target_framerate.into(),
|
||||||
},
|
},
|
||||||
vk_video::parameters::RateControl::VariableBitrate {
|
vk_video::parameters::RateControl::VariableBitrate {
|
||||||
@@ -51,11 +152,44 @@ pub fn setup_and_start(){
|
|||||||
virtual_buffer_size: std::time::Duration::from_secs(2),
|
virtual_buffer_size: std::time::Duration::from_secs(2),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.unwrap(),
|
.map_err(EncodeError::VideoEncodeParams)?,
|
||||||
)
|
)
|
||||||
.unwrap();
|
.map_err(EncodeError::VideoCreateTextures)?;
|
||||||
|
|
||||||
let mut output_file = std::fs::File::create("output.h264").unwrap();
|
let output_file=std::fs::File::create(params.output_file)
|
||||||
|
.map_err(EncodeError::OutputCreateFile)?;
|
||||||
|
|
||||||
|
let mp4_config=mp4::Mp4Config{
|
||||||
|
major_brand: str::parse("isom").unwrap(),
|
||||||
|
minor_version: 512,
|
||||||
|
compatible_brands: vec![
|
||||||
|
str::parse("isom").unwrap(),
|
||||||
|
str::parse("iso2").unwrap(),
|
||||||
|
str::parse("avc1").unwrap(),
|
||||||
|
str::parse("mp41").unwrap(),
|
||||||
|
],
|
||||||
|
timescale:target_framerate,
|
||||||
|
};
|
||||||
|
let mut mp4=mp4::Mp4Writer::write_start(output_file,&mp4_config)
|
||||||
|
.map_err(EncodeError::OutputMp4Start)?;
|
||||||
|
|
||||||
|
let avc_config=mp4::AvcConfig{
|
||||||
|
width:params.width.get() as u16,
|
||||||
|
height:params.height.get() as u16,
|
||||||
|
// make up some data to prevent this underdeveloped library from crashing
|
||||||
|
seq_param_set:vec![0,0,0,0],
|
||||||
|
pic_param_set:vec![],
|
||||||
|
};
|
||||||
|
let track_config=mp4::TrackConfig{
|
||||||
|
track_type:mp4::TrackType::Video,
|
||||||
|
timescale:target_framerate,
|
||||||
|
language:"eng".to_owned(),
|
||||||
|
media_conf:mp4::MediaConfig::AvcConfig(avc_config),
|
||||||
|
};
|
||||||
|
|
||||||
|
const TRACK_ID:u32=1;
|
||||||
|
mp4.add_track(&track_config)
|
||||||
|
.map_err(EncodeError::OutputMp4AddTrack)?;
|
||||||
|
|
||||||
let duration = bot.duration();
|
let duration = bot.duration();
|
||||||
for i in 0..duration.get()*target_framerate as i64/SessionTime::ONE_SECOND.get() {
|
for i in 0..duration.get()*target_framerate as i64/SessionTime::ONE_SECOND.get() {
|
||||||
@@ -64,20 +198,28 @@ pub fn setup_and_start(){
|
|||||||
let (pos,angles)=playback_head.get_position_angles(&bot,time);
|
let (pos,angles)=playback_head.get_position_angles(&bot,time);
|
||||||
wgpu_state.render(pos,angles);
|
wgpu_state.render(pos,angles);
|
||||||
|
|
||||||
let res = unsafe {
|
let frame=vk_video::Frame{
|
||||||
encoder
|
data:wgpu_state.video_texture.clone(),
|
||||||
.encode(
|
pts:None,
|
||||||
vk_video::Frame {
|
|
||||||
data: wgpu_state.video_texture.clone(),
|
|
||||||
pts: None,
|
|
||||||
},
|
|
||||||
false,
|
|
||||||
)
|
|
||||||
.unwrap()
|
|
||||||
};
|
};
|
||||||
|
let res=unsafe{encoder.encode(frame,false)}
|
||||||
|
.map_err(EncodeError::VideoEncodeFrame)?;
|
||||||
|
|
||||||
output_file.write_all(&res.data).unwrap();
|
let mp4_sample=mp4::Mp4Sample{
|
||||||
|
start_time:i as u64,
|
||||||
|
duration:1,
|
||||||
|
rendering_offset:0,
|
||||||
|
is_sync:false,
|
||||||
|
bytes:res.data.into(),
|
||||||
|
};
|
||||||
|
mp4.write_sample(TRACK_ID,&mp4_sample)
|
||||||
|
.map_err(EncodeError::OutputMp4WriteSample)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mp4.write_end()
|
||||||
|
.map_err(EncodeError::OutputMp4End)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
struct WgpuState {
|
struct WgpuState {
|
||||||
@@ -101,7 +243,7 @@ impl WgpuState {
|
|||||||
queue: wgpu::Queue,
|
queue: wgpu::Queue,
|
||||||
size: glam::UVec2,
|
size: glam::UVec2,
|
||||||
) -> WgpuState {
|
) -> WgpuState {
|
||||||
const FORMAT: wgpu::TextureFormat = wgpu::TextureFormat::Bgra8UnormSrgb;
|
const FORMAT: wgpu::TextureFormat = wgpu::TextureFormat::Rgba8UnormSrgb;
|
||||||
let graphics = strafesnet_roblox_bot_player::graphics::Graphics::new(&device,&queue,size,FORMAT);
|
let graphics = strafesnet_roblox_bot_player::graphics::Graphics::new(&device,&queue,size,FORMAT);
|
||||||
|
|
||||||
let shader = wgpu::include_wgsl!("../shaders/rgb_to_yuv.wgsl");
|
let shader = wgpu::include_wgsl!("../shaders/rgb_to_yuv.wgsl");
|
||||||
@@ -302,7 +444,7 @@ impl PlaneRenderer {
|
|||||||
topology: wgpu::PrimitiveTopology::TriangleList,
|
topology: wgpu::PrimitiveTopology::TriangleList,
|
||||||
cull_mode: Some(wgpu::Face::Back),
|
cull_mode: Some(wgpu::Face::Back),
|
||||||
polygon_mode: wgpu::PolygonMode::Fill,
|
polygon_mode: wgpu::PolygonMode::Fill,
|
||||||
front_face: wgpu::FrontFace::Ccw,
|
front_face: wgpu::FrontFace::Cw,
|
||||||
conservative: false,
|
conservative: false,
|
||||||
unclipped_depth: false,
|
unclipped_depth: false,
|
||||||
strip_index_format: None,
|
strip_index_format: None,
|
||||||
@@ -1,5 +1,24 @@
|
|||||||
mod setup;
|
use clap::{Parser,Subcommand};
|
||||||
|
|
||||||
|
mod encode;
|
||||||
|
|
||||||
|
#[derive(Parser)]
|
||||||
|
#[command(author,version,about,long_about=None)]
|
||||||
|
#[command(propagate_version=true)]
|
||||||
|
struct Cli{
|
||||||
|
#[command(subcommand)]
|
||||||
|
command:Commands,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Subcommand)]
|
||||||
|
enum Commands{
|
||||||
|
#[command(flatten)]
|
||||||
|
Encode(encode::Commands),
|
||||||
|
}
|
||||||
|
|
||||||
fn main(){
|
fn main(){
|
||||||
setup::setup_and_start();
|
let cli=Cli::parse();
|
||||||
|
match cli.command{
|
||||||
|
Commands::Encode(commands)=>commands.run(),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ webgl = ["wgpu/webgl"]
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
glam.workspace = true
|
glam.workspace = true
|
||||||
|
wgpu.workspace = true
|
||||||
strafesnet_roblox_bot_player.workspace = true
|
strafesnet_roblox_bot_player.workspace = true
|
||||||
strafesnet_common.workspace = true
|
strafesnet_common.workspace = true
|
||||||
strafesnet_graphics.workspace = true
|
strafesnet_graphics.workspace = true
|
||||||
@@ -20,7 +21,6 @@ strafesnet_snf.workspace = true
|
|||||||
wasm-bindgen = "0.2.108"
|
wasm-bindgen = "0.2.108"
|
||||||
wasm-bindgen-futures = "0.4.58"
|
wasm-bindgen-futures = "0.4.58"
|
||||||
web-sys = { version = "0.3.85", features = ["HtmlCanvasElement"] }
|
web-sys = { version = "0.3.85", features = ["HtmlCanvasElement"] }
|
||||||
wgpu = { version = "28.0.0" }
|
|
||||||
|
|
||||||
[package.metadata.wasm-pack.profile.release]
|
[package.metadata.wasm-pack.profile.release]
|
||||||
wasm-opt = ["-Oz", "--enable-bulk-memory","--enable-nontrapping-float-to-int"]
|
wasm-opt = ["-Oz", "--enable-bulk-memory","--enable-nontrapping-float-to-int"]
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
use wasm_bindgen::prelude::wasm_bindgen;
|
use wasm_bindgen::prelude::wasm_bindgen;
|
||||||
use wasm_bindgen::JsError;
|
use wasm_bindgen::JsError;
|
||||||
use strafesnet_roblox_bot_file::v0;
|
use strafesnet_roblox_bot_file::v0;
|
||||||
use strafesnet_roblox_bot_player::{bot,head,time,graphics};
|
use strafesnet_roblox_bot_player::{bot,bvh,head,time,graphics};
|
||||||
use strafesnet_graphics::{setup,surface};
|
use strafesnet_graphics::{setup,surface};
|
||||||
|
|
||||||
// Hack to keep the code compiling,
|
// Hack to keep the code compiling,
|
||||||
@@ -193,12 +193,11 @@ impl PlaybackHead{
|
|||||||
pub fn get_game_controls(&self)->u32{
|
pub fn get_game_controls(&self)->u32{
|
||||||
self.head.state().get_controls().bits()
|
self.head.state().get_controls().bits()
|
||||||
}
|
}
|
||||||
/// Returns an array of [pitch, yaw, roll] in radians. Yaw is not restricted to any particular range.
|
|
||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
pub fn get_angles(&self,bot:&CompleteBot,time:f64)->Vec<f32>{
|
pub fn get_position(&self,bot:&CompleteBot,time:f64)->Vector3{
|
||||||
let time=time::from_float(time).unwrap();
|
let time=time::from_float(time).unwrap();
|
||||||
let angles=self.head.get_angles(&bot.bot,time);
|
let position=self.head.get_position(&bot.bot,time);
|
||||||
angles.to_array().to_vec()
|
Vector3(position)
|
||||||
}
|
}
|
||||||
/// Returns the camera angles yaw delta between the last game tick and the most recent game tick.
|
/// Returns the camera angles yaw delta between the last game tick and the most recent game tick.
|
||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
@@ -206,3 +205,31 @@ impl PlaybackHead{
|
|||||||
self.head.state().get_angles_delta().y
|
self.head.state().get_angles_delta().y
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[wasm_bindgen]
|
||||||
|
pub struct Vector3(glam::Vec3);
|
||||||
|
#[wasm_bindgen]
|
||||||
|
impl Vector3{
|
||||||
|
#[wasm_bindgen]
|
||||||
|
pub fn to_array(&self)->Vec<f32>{
|
||||||
|
self.0.to_array().to_vec()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[wasm_bindgen]
|
||||||
|
pub struct Bvh{
|
||||||
|
bvh:bvh::Bvh,
|
||||||
|
}
|
||||||
|
#[wasm_bindgen]
|
||||||
|
impl Bvh{
|
||||||
|
#[wasm_bindgen(constructor)]
|
||||||
|
pub fn new(bot:&CompleteBot)->Self{
|
||||||
|
Self{
|
||||||
|
bvh:bvh::Bvh::new(&bot.bot),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[wasm_bindgen]
|
||||||
|
pub fn closest_time_to_point(&self,bot:&CompleteBot,point:&Vector3)->Option<f64>{
|
||||||
|
Some(bot.bot.playback_time(self.bvh.closest_time_to_point(&bot.bot,point.0)?).into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -58,6 +58,8 @@
|
|||||||
<div class="hud">
|
<div class="hud">
|
||||||
<div id="hud_duration" class="timer">00:00:00</div>
|
<div id="hud_duration" class="timer">00:00:00</div>
|
||||||
<div id="hud_timer" class="timer">00:00:00</div>
|
<div id="hud_timer" class="timer">00:00:00</div>
|
||||||
|
<div id="diff_velocity" class="timer">-0.000 u/s</div>
|
||||||
|
<div id="diff_time" class="timer">-0.000s</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="controls">
|
<div class="controls">
|
||||||
<button id="control_reset">↪️</button>
|
<button id="control_reset">↪️</button>
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import init, {
|
|||||||
CompleteBot,
|
CompleteBot,
|
||||||
CompleteMap,
|
CompleteMap,
|
||||||
PlaybackHead,
|
PlaybackHead,
|
||||||
|
Bvh,
|
||||||
} from "./pkg/strafesnet_roblox_bot_player_wasm_module.js";
|
} from "./pkg/strafesnet_roblox_bot_player_wasm_module.js";
|
||||||
|
|
||||||
// Loading
|
// Loading
|
||||||
@@ -17,12 +18,16 @@ const graphics = await setup_graphics(canvas);
|
|||||||
const bot = new CompleteBot(new Uint8Array(await b.arrayBuffer()));
|
const bot = new CompleteBot(new Uint8Array(await b.arrayBuffer()));
|
||||||
const map = new CompleteMap(new Uint8Array(await m.arrayBuffer()));
|
const map = new CompleteMap(new Uint8Array(await m.arrayBuffer()));
|
||||||
const playback = new PlaybackHead(bot, 0);
|
const playback = new PlaybackHead(bot, 0);
|
||||||
|
const bvh_wr = new Bvh(bot);
|
||||||
|
const playback_wr = new PlaybackHead(bot, 0);
|
||||||
|
|
||||||
graphics.change_map(map);
|
graphics.change_map(map);
|
||||||
|
|
||||||
// HUD
|
// HUD
|
||||||
const hud_timer = document.getElementById("hud_timer");
|
const hud_timer = document.getElementById("hud_timer");
|
||||||
const hud_duration = document.getElementById("hud_duration");
|
const hud_duration = document.getElementById("hud_duration");
|
||||||
|
const diff_velocity = document.getElementById("diff_velocity");
|
||||||
|
const diff_time = document.getElementById("diff_time");
|
||||||
const MODE_MAIN = 0;
|
const MODE_MAIN = 0;
|
||||||
|
|
||||||
function timer_text(t) {
|
function timer_text(t) {
|
||||||
@@ -106,6 +111,22 @@ function animate(now) {
|
|||||||
const time = playback.get_run_time(bot, elapsedSec, MODE_MAIN);
|
const time = playback.get_run_time(bot, elapsedSec, MODE_MAIN);
|
||||||
hud_timer.textContent = timer_text(time);
|
hud_timer.textContent = timer_text(time);
|
||||||
|
|
||||||
|
// show diff
|
||||||
|
const pos = playback.get_position(bot, elapsedSec);
|
||||||
|
const wr_playback_time = bvh_wr.closest_time_to_point(bot, pos);
|
||||||
|
playback_wr.set_head_time(bot, elapsedSec, wr_playback_time);
|
||||||
|
const wr_time = playback_wr.get_run_time(bot, elapsedSec, MODE_MAIN);
|
||||||
|
const run_speed = playback.get_speed(bot, elapsedSec);
|
||||||
|
const wr_speed = playback_wr.get_speed(bot, elapsedSec);
|
||||||
|
const v_diff = run_speed - wr_speed;
|
||||||
|
const wholespeed = Math.floor(Math.abs(v_diff));
|
||||||
|
const millispeed = Math.floor((Math.abs(v_diff) % 1) * 1000);
|
||||||
|
diff_velocity.textContent = `${v_diff<0?"-":"+"}${String(wholespeed)}.${String(millispeed).padStart(3, "0")} u/s`;
|
||||||
|
const t_diff = time - wr_time;
|
||||||
|
const s = Math.floor(Math.abs(t_diff));
|
||||||
|
const ms = Math.floor((Math.abs(t_diff) % 1) * 1000);
|
||||||
|
diff_time.textContent = `${t_diff<0?"-":"+"}${String(s)}.${String(ms).padStart(3, "0")}s`;
|
||||||
|
|
||||||
// Render the frame that the bot is at that time
|
// Render the frame that the bot is at that time
|
||||||
graphics.render(bot, playback, elapsedSec);
|
graphics.render(bot, playback, elapsedSec);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user