summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.gitignore1
-rw-r--r--Cargo.lock3550
-rw-r--r--Cargo.toml20
-rw-r--r--Makefile7
-rw-r--r--assets/shaders/canvas.wgsl32
-rw-r--r--assets/shaders/tacbody.wgsl79
-rw-r--r--assets/systems/sol.csv3
-rw-r--r--rust-toolchain.toml2
-rw-r--r--shell.nix52
-rw-r--r--src/canvas.rs176
-rw-r--r--src/eguictx.rs143
-rw-r--r--src/known_stars.rs24
-rw-r--r--src/main.rs178
-rw-r--r--src/solar_system.rs161
-rw-r--r--src/tacmap.rs158
-rw-r--r--src/tacmap/camera.rs272
-rw-r--r--src/tacmap/render.rs233
-rw-r--r--src/texture.rs96
-rw-r--r--src/timeman.rs69
-rw-r--r--src/vertex.rs33
-rw-r--r--src/wgpuctx/mod.rs245
-rw-r--r--src/wgpuctx/pipeline.rs121
-rw-r--r--src/window.rs148
-rw-r--r--src/window/ui.rs96
24 files changed, 5899 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..ea8c4bf
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1 @@
+/target
diff --git a/Cargo.lock b/Cargo.lock
new file mode 100644
index 0000000..f18c765
--- /dev/null
+++ b/Cargo.lock
@@ -0,0 +1,3550 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 4
+
+[[package]]
+name = "ab_glyph"
+version = "0.2.32"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "01c0457472c38ea5bd1c3b5ada5e368271cb550be7a4ca4a0b4634e9913f6cc2"
+dependencies = [
+ "ab_glyph_rasterizer",
+ "owned_ttf_parser",
+]
+
+[[package]]
+name = "ab_glyph_rasterizer"
+version = "0.1.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "366ffbaa4442f4684d91e2cd7c5ea7c4ed8add41959a31447066e279e432b618"
+
+[[package]]
+name = "adler2"
+version = "2.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa"
+
+[[package]]
+name = "ahash"
+version = "0.8.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75"
+dependencies = [
+ "cfg-if",
+ "getrandom",
+ "once_cell",
+ "version_check",
+ "zerocopy",
+]
+
+[[package]]
+name = "aho-corasick"
+version = "1.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "android-activity"
+version = "0.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ef6978589202a00cd7e118380c448a08b6ed394c3a8df3a430d0898e3a42d046"
+dependencies = [
+ "android-properties",
+ "bitflags 2.11.0",
+ "cc",
+ "cesu8",
+ "jni",
+ "jni-sys",
+ "libc",
+ "log",
+ "ndk",
+ "ndk-context",
+ "ndk-sys",
+ "num_enum",
+ "thiserror 1.0.69",
+]
+
+[[package]]
+name = "android-properties"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fc7eb209b1518d6bb87b283c20095f5228ecda460da70b44f0802523dea6da04"
+
+[[package]]
+name = "android_system_properties"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
+dependencies = [
+ "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]]
+name = "approx"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3f2a05fd1bd10b2527e20a2cd32d8873d115b8b39fe219ee25f42a8aca6ba278"
+dependencies = [
+ "num-traits",
+]
+
+[[package]]
+name = "arboard"
+version = "3.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0348a1c054491f4bfe6ab86a7b6ab1e44e45d899005de92f58b3df180b36ddaf"
+dependencies = [
+ "clipboard-win",
+ "image",
+ "log",
+ "objc2 0.6.4",
+ "objc2-app-kit 0.3.2",
+ "objc2-core-foundation",
+ "objc2-core-graphics",
+ "objc2-foundation 0.3.2",
+ "parking_lot",
+ "percent-encoding",
+ "windows-sys 0.60.2",
+ "x11rb",
+]
+
+[[package]]
+name = "arrayref"
+version = "0.3.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb"
+
+[[package]]
+name = "arrayvec"
+version = "0.7.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50"
+
+[[package]]
+name = "as-raw-xcb-connection"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "175571dd1d178ced59193a6fc02dde1b972eb0bc56c892cde9beeceac5bf0f6b"
+
+[[package]]
+name = "ash"
+version = "0.38.0+1.3.281"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0bb44936d800fea8f016d7f2311c6a4f97aebd5dc86f09906139ec848cf3a46f"
+dependencies = [
+ "libloading",
+]
+
+[[package]]
+name = "atomic-waker"
+version = "1.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0"
+
+[[package]]
+name = "autocfg"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
+
+[[package]]
+name = "bit-set"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3"
+dependencies = [
+ "bit-vec",
+]
+
+[[package]]
+name = "bit-vec"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7"
+
+[[package]]
+name = "bitflags"
+version = "1.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
+
+[[package]]
+name = "bitflags"
+version = "2.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af"
+
+[[package]]
+name = "block"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a"
+
+[[package]]
+name = "block2"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2c132eebf10f5cad5289222520a4a058514204aed6d791f1cf4fe8088b82d15f"
+dependencies = [
+ "objc2 0.5.2",
+]
+
+[[package]]
+name = "bumpalo"
+version = "3.20.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb"
+
+[[package]]
+name = "bytemuck"
+version = "1.25.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c8efb64bd706a16a1bdde310ae86b351e4d21550d98d056f22f8a7f7a2183fec"
+dependencies = [
+ "bytemuck_derive",
+]
+
+[[package]]
+name = "bytemuck_derive"
+version = "1.10.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f9abbd1bc6865053c427f7198e6af43bfdedc55ab791faed4fbd361d789575ff"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "byteorder-lite"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495"
+
+[[package]]
+name = "bytes"
+version = "1.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33"
+
+[[package]]
+name = "calloop"
+version = "0.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b99da2f8558ca23c71f4fd15dc57c906239752dd27ff3c00a1d56b685b7cbfec"
+dependencies = [
+ "bitflags 2.11.0",
+ "log",
+ "polling",
+ "rustix 0.38.44",
+ "slab",
+ "thiserror 1.0.69",
+]
+
+[[package]]
+name = "calloop"
+version = "0.14.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4dbf9978365bac10f54d1d4b04f7ce4427e51f71d61f2fe15e3fed5166474df7"
+dependencies = [
+ "bitflags 2.11.0",
+ "polling",
+ "rustix 1.1.4",
+ "slab",
+ "tracing",
+]
+
+[[package]]
+name = "calloop-wayland-source"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "95a66a987056935f7efce4ab5668920b5d0dac4a7c99991a67395f13702ddd20"
+dependencies = [
+ "calloop 0.13.0",
+ "rustix 0.38.44",
+ "wayland-backend",
+ "wayland-client",
+]
+
+[[package]]
+name = "calloop-wayland-source"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "138efcf0940a02ebf0cc8d1eff41a1682a46b431630f4c52450d6265876021fa"
+dependencies = [
+ "calloop 0.14.4",
+ "rustix 1.1.4",
+ "wayland-backend",
+ "wayland-client",
+]
+
+[[package]]
+name = "cc"
+version = "1.2.56"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "aebf35691d1bfb0ac386a69bac2fde4dd276fb618cf8bf4f5318fe285e821bb2"
+dependencies = [
+ "find-msvc-tools",
+ "jobserver",
+ "libc",
+ "shlex",
+]
+
+[[package]]
+name = "cesu8"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c"
+
+[[package]]
+name = "cfg-if"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
+
+[[package]]
+name = "cfg_aliases"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
+
+[[package]]
+name = "cgmath"
+version = "0.18.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1a98d30140e3296250832bbaaff83b27dcd6fa3cc70fb6f1f3e5c9c0023b5317"
+dependencies = [
+ "approx",
+ "num-traits",
+]
+
+[[package]]
+name = "clipboard-win"
+version = "5.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bde03770d3df201d4fb868f2c9c59e66a3e4e2bd06692a0fe701e7103c7e84d4"
+dependencies = [
+ "error-code",
+]
+
+[[package]]
+name = "codespan-reporting"
+version = "0.12.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fe6d2e5af09e8c8ad56c969f2157a3d4238cebc7c55f0a517728c38f7b200f81"
+dependencies = [
+ "serde",
+ "termcolor",
+ "unicode-width",
+]
+
+[[package]]
+name = "colorchoice"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75"
+
+[[package]]
+name = "combine"
+version = "4.6.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd"
+dependencies = [
+ "bytes",
+ "memchr",
+]
+
+[[package]]
+name = "concurrent-queue"
+version = "2.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973"
+dependencies = [
+ "crossbeam-utils",
+]
+
+[[package]]
+name = "core-foundation"
+version = "0.9.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f"
+dependencies = [
+ "core-foundation-sys",
+ "libc",
+]
+
+[[package]]
+name = "core-foundation"
+version = "0.10.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6"
+dependencies = [
+ "core-foundation-sys",
+ "libc",
+]
+
+[[package]]
+name = "core-foundation-sys"
+version = "0.8.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
+
+[[package]]
+name = "core-graphics"
+version = "0.23.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c07782be35f9e1140080c6b96f0d44b739e2278479f64e02fdab4e32dfd8b081"
+dependencies = [
+ "bitflags 1.3.2",
+ "core-foundation 0.9.4",
+ "core-graphics-types 0.1.3",
+ "foreign-types",
+ "libc",
+]
+
+[[package]]
+name = "core-graphics-types"
+version = "0.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "45390e6114f68f718cc7a830514a96f903cccd70d02a8f6d9f643ac4ba45afaf"
+dependencies = [
+ "bitflags 1.3.2",
+ "core-foundation 0.9.4",
+ "libc",
+]
+
+[[package]]
+name = "core-graphics-types"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3d44a101f213f6c4cdc1853d4b78aef6db6bdfa3468798cc1d9912f4735013eb"
+dependencies = [
+ "bitflags 2.11.0",
+ "core-foundation 0.10.1",
+ "libc",
+]
+
+[[package]]
+name = "crc32fast"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511"
+dependencies = [
+ "cfg-if",
+]
+
+[[package]]
+name = "crossbeam-utils"
+version = "0.8.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
+
+[[package]]
+name = "crunchy"
+version = "0.2.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5"
+
+[[package]]
+name = "csv"
+version = "1.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "52cd9d68cf7efc6ddfaaee42e7288d3a99d613d4b50f76ce9827ae0c6e14f938"
+dependencies = [
+ "csv-core",
+ "itoa",
+ "ryu",
+ "serde_core",
+]
+
+[[package]]
+name = "csv-core"
+version = "0.1.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "704a3c26996a80471189265814dbc2c257598b96b8a7feae2d31ace646bb9782"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "cursor-icon"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f27ae1dd37df86211c42e150270f82743308803d90a6f6e6651cd730d5e1732f"
+
+[[package]]
+name = "dispatch"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b"
+
+[[package]]
+name = "dispatch2"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e0e367e4e7da84520dedcac1901e4da967309406d1e51017ae1abfb97adbd38"
+dependencies = [
+ "bitflags 2.11.0",
+ "objc2 0.6.4",
+]
+
+[[package]]
+name = "displaydoc"
+version = "0.2.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "dlib"
+version = "0.5.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ab8ecd87370524b461f8557c119c405552c396ed91fc0a8eec68679eab26f94a"
+dependencies = [
+ "libloading",
+]
+
+[[package]]
+name = "document-features"
+version = "0.2.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d4b8a88685455ed29a21542a33abd9cb6510b6b129abadabdcef0f4c55bc8f61"
+dependencies = [
+ "litrs",
+]
+
+[[package]]
+name = "downcast-rs"
+version = "1.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2"
+
+[[package]]
+name = "dpi"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d8b14ccef22fc6f5a8f4d7d768562a182c04ce9a3b3157b91390b52ddfdf1a76"
+
+[[package]]
+name = "ecolor"
+version = "0.33.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "71ddb8ac7643d1dba1bb02110e804406dd459a838efcb14011ced10556711a8e"
+dependencies = [
+ "bytemuck",
+ "emath",
+]
+
+[[package]]
+name = "egui"
+version = "0.33.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6a9b567d356674e9a5121ed3fedfb0a7c31e059fe71f6972b691bcd0bfc284e3"
+dependencies = [
+ "ahash",
+ "bitflags 2.11.0",
+ "emath",
+ "epaint",
+ "log",
+ "nohash-hasher",
+ "profiling",
+ "smallvec",
+ "unicode-segmentation",
+]
+
+[[package]]
+name = "egui-wgpu"
+version = "0.33.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5e4d209971c84b2352a06174abdba701af1e552ce56b144d96f2bd50a3c91236"
+dependencies = [
+ "ahash",
+ "bytemuck",
+ "document-features",
+ "egui",
+ "epaint",
+ "log",
+ "profiling",
+ "thiserror 2.0.18",
+ "type-map",
+ "web-time",
+ "wgpu",
+ "winit",
+]
+
+[[package]]
+name = "egui-winit"
+version = "0.33.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ec6687e5bb551702f4ad10ac428bab12acf9d53047ebb1082d4a0ed8c6251a29"
+dependencies = [
+ "arboard",
+ "bytemuck",
+ "egui",
+ "log",
+ "objc2 0.5.2",
+ "objc2-foundation 0.2.2",
+ "objc2-ui-kit",
+ "profiling",
+ "raw-window-handle",
+ "smithay-clipboard",
+ "web-time",
+ "webbrowser",
+ "winit",
+]
+
+[[package]]
+name = "emath"
+version = "0.33.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "491bdf728bf25ddd9ad60d4cf1c48588fa82c013a2440b91aa7fc43e34a07c32"
+dependencies = [
+ "bytemuck",
+]
+
+[[package]]
+name = "env_filter"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7a1c3cc8e57274ec99de65301228b537f1e4eedc1b8e0f9411c6caac8ae7308f"
+dependencies = [
+ "log",
+ "regex",
+]
+
+[[package]]
+name = "env_logger"
+version = "0.11.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b2daee4ea451f429a58296525ddf28b45a3b64f1acf6587e2067437bb11e218d"
+dependencies = [
+ "anstream",
+ "anstyle",
+ "env_filter",
+ "jiff",
+ "log",
+]
+
+[[package]]
+name = "epaint"
+version = "0.33.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "009d0dd3c2163823a0abdb899451ecbc78798dec545ee91b43aff1fa790bab62"
+dependencies = [
+ "ab_glyph",
+ "ahash",
+ "bytemuck",
+ "ecolor",
+ "emath",
+ "epaint_default_fonts",
+ "log",
+ "nohash-hasher",
+ "parking_lot",
+ "profiling",
+]
+
+[[package]]
+name = "epaint_default_fonts"
+version = "0.33.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c4fbe202b6578d3d56428fa185cdf114a05e49da05f477b3c7f0fbb221f1862"
+
+[[package]]
+name = "equivalent"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
+
+[[package]]
+name = "errno"
+version = "0.3.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
+dependencies = [
+ "libc",
+ "windows-sys 0.61.2",
+]
+
+[[package]]
+name = "error-code"
+version = "3.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dea2df4cf52843e0452895c455a1a2cfbb842a1e7329671acf418fdc53ed4c59"
+
+[[package]]
+name = "fastrand"
+version = "2.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be"
+
+[[package]]
+name = "fax"
+version = "0.2.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f05de7d48f37cd6730705cbca900770cab77a89f413d23e100ad7fad7795a0ab"
+dependencies = [
+ "fax_derive",
+]
+
+[[package]]
+name = "fax_derive"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a0aca10fb742cb43f9e7bb8467c91aa9bcb8e3ffbc6a6f7389bb93ffc920577d"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "fdeflate"
+version = "0.3.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e6853b52649d4ac5c0bd02320cddc5ba956bdb407c4b75a2c6b75bf51500f8c"
+dependencies = [
+ "simd-adler32",
+]
+
+[[package]]
+name = "find-msvc-tools"
+version = "0.1.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582"
+
+[[package]]
+name = "flate2"
+version = "1.1.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c"
+dependencies = [
+ "crc32fast",
+ "miniz_oxide",
+]
+
+[[package]]
+name = "foldhash"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
+
+[[package]]
+name = "foldhash"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb"
+
+[[package]]
+name = "foreign-types"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965"
+dependencies = [
+ "foreign-types-macros",
+ "foreign-types-shared",
+]
+
+[[package]]
+name = "foreign-types-macros"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "foreign-types-shared"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b"
+
+[[package]]
+name = "form_urlencoded"
+version = "1.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf"
+dependencies = [
+ "percent-encoding",
+]
+
+[[package]]
+name = "futures-core"
+version = "0.3.32"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d"
+
+[[package]]
+name = "futures-task"
+version = "0.3.32"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393"
+
+[[package]]
+name = "futures-util"
+version = "0.3.32"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6"
+dependencies = [
+ "futures-core",
+ "futures-task",
+ "pin-project-lite",
+ "slab",
+]
+
+[[package]]
+name = "gethostname"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1bd49230192a3797a9a4d6abe9b3eed6f7fa4c8a8a4947977c6f80025f92cbd8"
+dependencies = [
+ "rustix 1.1.4",
+ "windows-link",
+]
+
+[[package]]
+name = "getrandom"
+version = "0.3.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "r-efi",
+ "wasip2",
+]
+
+[[package]]
+name = "gl_generator"
+version = "0.14.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1a95dfc23a2b4a9a2f5ab41d194f8bfda3cabec42af4e39f08c339eb2a0c124d"
+dependencies = [
+ "khronos_api",
+ "log",
+ "xml-rs",
+]
+
+[[package]]
+name = "glow"
+version = "0.16.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c5e5ea60d70410161c8bf5da3fdfeaa1c72ed2c15f8bbb9d19fe3a4fad085f08"
+dependencies = [
+ "js-sys",
+ "slotmap",
+ "wasm-bindgen",
+ "web-sys",
+]
+
+[[package]]
+name = "glutin_wgl_sys"
+version = "0.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2c4ee00b289aba7a9e5306d57c2d05499b2e5dc427f84ac708bd2c090212cf3e"
+dependencies = [
+ "gl_generator",
+]
+
+[[package]]
+name = "gpu-alloc"
+version = "0.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fbcd2dba93594b227a1f57ee09b8b9da8892c34d55aa332e034a228d0fe6a171"
+dependencies = [
+ "bitflags 2.11.0",
+ "gpu-alloc-types",
+]
+
+[[package]]
+name = "gpu-alloc-types"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "98ff03b468aa837d70984d55f5d3f846f6ec31fe34bbb97c4f85219caeee1ca4"
+dependencies = [
+ "bitflags 2.11.0",
+]
+
+[[package]]
+name = "gpu-allocator"
+version = "0.27.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c151a2a5ef800297b4e79efa4f4bec035c5f51d5ae587287c9b952bdf734cacd"
+dependencies = [
+ "log",
+ "presser",
+ "thiserror 1.0.69",
+ "windows",
+]
+
+[[package]]
+name = "gpu-descriptor"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b89c83349105e3732062a895becfc71a8f921bb71ecbbdd8ff99263e3b53a0ca"
+dependencies = [
+ "bitflags 2.11.0",
+ "gpu-descriptor-types",
+ "hashbrown 0.15.5",
+]
+
+[[package]]
+name = "gpu-descriptor-types"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fdf242682df893b86f33a73828fb09ca4b2d3bb6cc95249707fc684d27484b91"
+dependencies = [
+ "bitflags 2.11.0",
+]
+
+[[package]]
+name = "half"
+version = "2.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b"
+dependencies = [
+ "cfg-if",
+ "crunchy",
+ "num-traits",
+ "zerocopy",
+]
+
+[[package]]
+name = "hashbrown"
+version = "0.15.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1"
+dependencies = [
+ "foldhash 0.1.5",
+]
+
+[[package]]
+name = "hashbrown"
+version = "0.16.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100"
+dependencies = [
+ "foldhash 0.2.0",
+]
+
+[[package]]
+name = "hermit-abi"
+version = "0.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c"
+
+[[package]]
+name = "hexf-parse"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dfa686283ad6dd069f105e5ab091b04c62850d3e4cf5d67debad1933f55023df"
+
+[[package]]
+name = "icu_collections"
+version = "2.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4c6b649701667bbe825c3b7e6388cb521c23d88644678e83c0c4d0a621a34b43"
+dependencies = [
+ "displaydoc",
+ "potential_utf",
+ "yoke",
+ "zerofrom",
+ "zerovec",
+]
+
+[[package]]
+name = "icu_locale_core"
+version = "2.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "edba7861004dd3714265b4db54a3c390e880ab658fec5f7db895fae2046b5bb6"
+dependencies = [
+ "displaydoc",
+ "litemap",
+ "tinystr",
+ "writeable",
+ "zerovec",
+]
+
+[[package]]
+name = "icu_normalizer"
+version = "2.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5f6c8828b67bf8908d82127b2054ea1b4427ff0230ee9141c54251934ab1b599"
+dependencies = [
+ "icu_collections",
+ "icu_normalizer_data",
+ "icu_properties",
+ "icu_provider",
+ "smallvec",
+ "zerovec",
+]
+
+[[package]]
+name = "icu_normalizer_data"
+version = "2.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a"
+
+[[package]]
+name = "icu_properties"
+version = "2.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "020bfc02fe870ec3a66d93e677ccca0562506e5872c650f893269e08615d74ec"
+dependencies = [
+ "icu_collections",
+ "icu_locale_core",
+ "icu_properties_data",
+ "icu_provider",
+ "zerotrie",
+ "zerovec",
+]
+
+[[package]]
+name = "icu_properties_data"
+version = "2.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "616c294cf8d725c6afcd8f55abc17c56464ef6211f9ed59cccffe534129c77af"
+
+[[package]]
+name = "icu_provider"
+version = "2.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "85962cf0ce02e1e0a629cc34e7ca3e373ce20dda4c4d7294bbd0bf1fdb59e614"
+dependencies = [
+ "displaydoc",
+ "icu_locale_core",
+ "writeable",
+ "yoke",
+ "zerofrom",
+ "zerotrie",
+ "zerovec",
+]
+
+[[package]]
+name = "idna"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de"
+dependencies = [
+ "idna_adapter",
+ "smallvec",
+ "utf8_iter",
+]
+
+[[package]]
+name = "idna_adapter"
+version = "1.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344"
+dependencies = [
+ "icu_normalizer",
+ "icu_properties",
+]
+
+[[package]]
+name = "image"
+version = "0.25.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e6506c6c10786659413faa717ceebcb8f70731c0a60cbae39795fdf114519c1a"
+dependencies = [
+ "bytemuck",
+ "byteorder-lite",
+ "moxcms",
+ "num-traits",
+ "png",
+ "tiff",
+]
+
+[[package]]
+name = "indexmap"
+version = "2.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017"
+dependencies = [
+ "equivalent",
+ "hashbrown 0.16.1",
+]
+
+[[package]]
+name = "is_terminal_polyfill"
+version = "1.70.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695"
+
+[[package]]
+name = "itoa"
+version = "1.0.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2"
+
+[[package]]
+name = "jiff"
+version = "0.2.22"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "819b44bc7c87d9117eb522f14d46e918add69ff12713c475946b0a29363ed1c2"
+dependencies = [
+ "jiff-static",
+ "log",
+ "portable-atomic",
+ "portable-atomic-util",
+ "serde_core",
+]
+
+[[package]]
+name = "jiff-static"
+version = "0.2.22"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "470252db18ecc35fd766c0891b1e3ec6cbbcd62507e85276c01bf75d8e94d4a1"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "jni"
+version = "0.21.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97"
+dependencies = [
+ "cesu8",
+ "cfg-if",
+ "combine",
+ "jni-sys",
+ "log",
+ "thiserror 1.0.69",
+ "walkdir",
+ "windows-sys 0.45.0",
+]
+
+[[package]]
+name = "jni-sys"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130"
+
+[[package]]
+name = "jobserver"
+version = "0.1.34"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33"
+dependencies = [
+ "getrandom",
+ "libc",
+]
+
+[[package]]
+name = "js-sys"
+version = "0.3.91"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b49715b7073f385ba4bc528e5747d02e66cb39c6146efb66b781f131f0fb399c"
+dependencies = [
+ "once_cell",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "khronos-egl"
+version = "6.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6aae1df220ece3c0ada96b8153459b67eebe9ae9212258bb0134ae60416fdf76"
+dependencies = [
+ "libc",
+ "libloading",
+ "pkg-config",
+]
+
+[[package]]
+name = "khronos_api"
+version = "3.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e2db585e1d738fc771bf08a151420d3ed193d9d895a36df7f6f8a9456b911ddc"
+
+[[package]]
+name = "libc"
+version = "0.2.182"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6800badb6cb2082ffd7b6a67e6125bb39f18782f793520caee8cb8846be06112"
+
+[[package]]
+name = "libloading"
+version = "0.8.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55"
+dependencies = [
+ "cfg-if",
+ "windows-link",
+]
+
+[[package]]
+name = "libm"
+version = "0.2.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981"
+
+[[package]]
+name = "libredox"
+version = "0.1.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1744e39d1d6a9948f4f388969627434e31128196de472883b39f148769bfe30a"
+dependencies = [
+ "bitflags 2.11.0",
+ "libc",
+ "plain",
+ "redox_syscall 0.7.3",
+]
+
+[[package]]
+name = "linux-raw-sys"
+version = "0.4.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab"
+
+[[package]]
+name = "linux-raw-sys"
+version = "0.12.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53"
+
+[[package]]
+name = "litemap"
+version = "0.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77"
+
+[[package]]
+name = "litrs"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "11d3d7f243d5c5a8b9bb5d6dd2b1602c0cb0b9db1621bafc7ed66e35ff9fe092"
+
+[[package]]
+name = "lock_api"
+version = "0.4.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965"
+dependencies = [
+ "scopeguard",
+]
+
+[[package]]
+name = "log"
+version = "0.4.29"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897"
+
+[[package]]
+name = "malloc_buf"
+version = "0.0.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "62bb907fe88d54d8d9ce32a3cceab4218ed2f6b7d35617cafe9adf84e43919cb"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "memchr"
+version = "2.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79"
+
+[[package]]
+name = "memmap2"
+version = "0.9.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "714098028fe011992e1c3962653c96b2d578c4b4bce9036e15ff220319b1e0e3"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "metal"
+version = "0.32.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "00c15a6f673ff72ddcc22394663290f870fb224c1bfce55734a75c414150e605"
+dependencies = [
+ "bitflags 2.11.0",
+ "block",
+ "core-graphics-types 0.2.0",
+ "foreign-types",
+ "log",
+ "objc",
+ "paste",
+]
+
+[[package]]
+name = "miniz_oxide"
+version = "0.8.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316"
+dependencies = [
+ "adler2",
+ "simd-adler32",
+]
+
+[[package]]
+name = "moxcms"
+version = "0.7.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ac9557c559cd6fc9867e122e20d2cbefc9ca29d80d027a8e39310920ed2f0a97"
+dependencies = [
+ "num-traits",
+ "pxfm",
+]
+
+[[package]]
+name = "naga"
+version = "27.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "066cf25f0e8b11ee0df221219010f213ad429855f57c494f995590c861a9a7d8"
+dependencies = [
+ "arrayvec",
+ "bit-set",
+ "bitflags 2.11.0",
+ "cfg-if",
+ "cfg_aliases",
+ "codespan-reporting",
+ "half",
+ "hashbrown 0.16.1",
+ "hexf-parse",
+ "indexmap",
+ "libm",
+ "log",
+ "num-traits",
+ "once_cell",
+ "rustc-hash 1.1.0",
+ "spirv",
+ "thiserror 2.0.18",
+ "unicode-ident",
+]
+
+[[package]]
+name = "ndk"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c3f42e7bbe13d351b6bead8286a43aac9534b82bd3cc43e47037f012ebfd62d4"
+dependencies = [
+ "bitflags 2.11.0",
+ "jni-sys",
+ "log",
+ "ndk-sys",
+ "num_enum",
+ "raw-window-handle",
+ "thiserror 1.0.69",
+]
+
+[[package]]
+name = "ndk-context"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b"
+
+[[package]]
+name = "ndk-sys"
+version = "0.6.0+11769913"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ee6cda3051665f1fb8d9e08fc35c96d5a244fb1be711a03b71118828afc9a873"
+dependencies = [
+ "jni-sys",
+]
+
+[[package]]
+name = "nohash-hasher"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2bf50223579dc7cdcfb3bfcacf7069ff68243f8c363f62ffa99cf000a6b9c451"
+
+[[package]]
+name = "num-traits"
+version = "0.2.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
+dependencies = [
+ "autocfg",
+ "libm",
+]
+
+[[package]]
+name = "num_enum"
+version = "0.7.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b1207a7e20ad57b847bbddc6776b968420d38292bbfe2089accff5e19e82454c"
+dependencies = [
+ "num_enum_derive",
+ "rustversion",
+]
+
+[[package]]
+name = "num_enum_derive"
+version = "0.7.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ff32365de1b6743cb203b710788263c44a03de03802daf96092f2da4fe6ba4d7"
+dependencies = [
+ "proc-macro-crate",
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "objc"
+version = "0.2.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1"
+dependencies = [
+ "malloc_buf",
+]
+
+[[package]]
+name = "objc-sys"
+version = "0.3.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cdb91bdd390c7ce1a8607f35f3ca7151b65afc0ff5ff3b34fa350f7d7c7e4310"
+
+[[package]]
+name = "objc2"
+version = "0.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "46a785d4eeff09c14c487497c162e92766fbb3e4059a71840cecc03d9a50b804"
+dependencies = [
+ "objc-sys",
+ "objc2-encode",
+]
+
+[[package]]
+name = "objc2"
+version = "0.6.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3a12a8ed07aefc768292f076dc3ac8c48f3781c8f2d5851dd3d98950e8c5a89f"
+dependencies = [
+ "objc2-encode",
+]
+
+[[package]]
+name = "objc2-app-kit"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e4e89ad9e3d7d297152b17d39ed92cd50ca8063a89a9fa569046d41568891eff"
+dependencies = [
+ "bitflags 2.11.0",
+ "block2",
+ "libc",
+ "objc2 0.5.2",
+ "objc2-core-data",
+ "objc2-core-image",
+ "objc2-foundation 0.2.2",
+ "objc2-quartz-core",
+]
+
+[[package]]
+name = "objc2-app-kit"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d49e936b501e5c5bf01fda3a9452ff86dc3ea98ad5f283e1455153142d97518c"
+dependencies = [
+ "bitflags 2.11.0",
+ "objc2 0.6.4",
+ "objc2-core-graphics",
+ "objc2-foundation 0.3.2",
+]
+
+[[package]]
+name = "objc2-cloud-kit"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "74dd3b56391c7a0596a295029734d3c1c5e7e510a4cb30245f8221ccea96b009"
+dependencies = [
+ "bitflags 2.11.0",
+ "block2",
+ "objc2 0.5.2",
+ "objc2-core-location",
+ "objc2-foundation 0.2.2",
+]
+
+[[package]]
+name = "objc2-contacts"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a5ff520e9c33812fd374d8deecef01d4a840e7b41862d849513de77e44aa4889"
+dependencies = [
+ "block2",
+ "objc2 0.5.2",
+ "objc2-foundation 0.2.2",
+]
+
+[[package]]
+name = "objc2-core-data"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "617fbf49e071c178c0b24c080767db52958f716d9eabdf0890523aeae54773ef"
+dependencies = [
+ "bitflags 2.11.0",
+ "block2",
+ "objc2 0.5.2",
+ "objc2-foundation 0.2.2",
+]
+
+[[package]]
+name = "objc2-core-foundation"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536"
+dependencies = [
+ "bitflags 2.11.0",
+ "dispatch2",
+ "objc2 0.6.4",
+]
+
+[[package]]
+name = "objc2-core-graphics"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e022c9d066895efa1345f8e33e584b9f958da2fd4cd116792e15e07e4720a807"
+dependencies = [
+ "bitflags 2.11.0",
+ "dispatch2",
+ "objc2 0.6.4",
+ "objc2-core-foundation",
+ "objc2-io-surface",
+]
+
+[[package]]
+name = "objc2-core-image"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "55260963a527c99f1819c4f8e3b47fe04f9650694ef348ffd2227e8196d34c80"
+dependencies = [
+ "block2",
+ "objc2 0.5.2",
+ "objc2-foundation 0.2.2",
+ "objc2-metal",
+]
+
+[[package]]
+name = "objc2-core-location"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "000cfee34e683244f284252ee206a27953279d370e309649dc3ee317b37e5781"
+dependencies = [
+ "block2",
+ "objc2 0.5.2",
+ "objc2-contacts",
+ "objc2-foundation 0.2.2",
+]
+
+[[package]]
+name = "objc2-encode"
+version = "4.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33"
+
+[[package]]
+name = "objc2-foundation"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0ee638a5da3799329310ad4cfa62fbf045d5f56e3ef5ba4149e7452dcf89d5a8"
+dependencies = [
+ "bitflags 2.11.0",
+ "block2",
+ "dispatch",
+ "libc",
+ "objc2 0.5.2",
+]
+
+[[package]]
+name = "objc2-foundation"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272"
+dependencies = [
+ "bitflags 2.11.0",
+ "objc2 0.6.4",
+ "objc2-core-foundation",
+]
+
+[[package]]
+name = "objc2-io-surface"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "180788110936d59bab6bd83b6060ffdfffb3b922ba1396b312ae795e1de9d81d"
+dependencies = [
+ "bitflags 2.11.0",
+ "objc2 0.6.4",
+ "objc2-core-foundation",
+]
+
+[[package]]
+name = "objc2-link-presentation"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a1a1ae721c5e35be65f01a03b6d2ac13a54cb4fa70d8a5da293d7b0020261398"
+dependencies = [
+ "block2",
+ "objc2 0.5.2",
+ "objc2-app-kit 0.2.2",
+ "objc2-foundation 0.2.2",
+]
+
+[[package]]
+name = "objc2-metal"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dd0cba1276f6023976a406a14ffa85e1fdd19df6b0f737b063b95f6c8c7aadd6"
+dependencies = [
+ "bitflags 2.11.0",
+ "block2",
+ "objc2 0.5.2",
+ "objc2-foundation 0.2.2",
+]
+
+[[package]]
+name = "objc2-quartz-core"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e42bee7bff906b14b167da2bac5efe6b6a07e6f7c0a21a7308d40c960242dc7a"
+dependencies = [
+ "bitflags 2.11.0",
+ "block2",
+ "objc2 0.5.2",
+ "objc2-foundation 0.2.2",
+ "objc2-metal",
+]
+
+[[package]]
+name = "objc2-symbols"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0a684efe3dec1b305badae1a28f6555f6ddd3bb2c2267896782858d5a78404dc"
+dependencies = [
+ "objc2 0.5.2",
+ "objc2-foundation 0.2.2",
+]
+
+[[package]]
+name = "objc2-ui-kit"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b8bb46798b20cd6b91cbd113524c490f1686f4c4e8f49502431415f3512e2b6f"
+dependencies = [
+ "bitflags 2.11.0",
+ "block2",
+ "objc2 0.5.2",
+ "objc2-cloud-kit",
+ "objc2-core-data",
+ "objc2-core-image",
+ "objc2-core-location",
+ "objc2-foundation 0.2.2",
+ "objc2-link-presentation",
+ "objc2-quartz-core",
+ "objc2-symbols",
+ "objc2-uniform-type-identifiers",
+ "objc2-user-notifications",
+]
+
+[[package]]
+name = "objc2-uniform-type-identifiers"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "44fa5f9748dbfe1ca6c0b79ad20725a11eca7c2218bceb4b005cb1be26273bfe"
+dependencies = [
+ "block2",
+ "objc2 0.5.2",
+ "objc2-foundation 0.2.2",
+]
+
+[[package]]
+name = "objc2-user-notifications"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "76cfcbf642358e8689af64cee815d139339f3ed8ad05103ed5eaf73db8d84cb3"
+dependencies = [
+ "bitflags 2.11.0",
+ "block2",
+ "objc2 0.5.2",
+ "objc2-core-location",
+ "objc2-foundation 0.2.2",
+]
+
+[[package]]
+name = "once_cell"
+version = "1.21.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
+
+[[package]]
+name = "once_cell_polyfill"
+version = "1.70.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe"
+
+[[package]]
+name = "orbclient"
+version = "0.3.50"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "52ad2c6bae700b7aa5d1cc30c59bdd3a1c180b09dbaea51e2ae2b8e1cf211fdd"
+dependencies = [
+ "libc",
+ "libredox",
+]
+
+[[package]]
+name = "ordered-float"
+version = "5.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7f4779c6901a562440c3786d08192c6fbda7c1c2060edd10006b05ee35d10f2d"
+dependencies = [
+ "num-traits",
+]
+
+[[package]]
+name = "owned_ttf_parser"
+version = "0.25.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "36820e9051aca1014ddc75770aab4d68bc1e9e632f0f5627c4086bc216fb583b"
+dependencies = [
+ "ttf-parser",
+]
+
+[[package]]
+name = "parking_lot"
+version = "0.12.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a"
+dependencies = [
+ "lock_api",
+ "parking_lot_core",
+]
+
+[[package]]
+name = "parking_lot_core"
+version = "0.9.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "redox_syscall 0.5.18",
+ "smallvec",
+ "windows-link",
+]
+
+[[package]]
+name = "paste"
+version = "1.0.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
+
+[[package]]
+name = "percent-encoding"
+version = "2.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220"
+
+[[package]]
+name = "phf"
+version = "0.13.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c1562dc717473dbaa4c1f85a36410e03c047b2e7df7f45ee938fbef64ae7fadf"
+dependencies = [
+ "phf_macros",
+ "phf_shared",
+ "serde",
+]
+
+[[package]]
+name = "phf_generator"
+version = "0.13.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "135ace3a761e564ec88c03a77317a7c6b80bb7f7135ef2544dbe054243b89737"
+dependencies = [
+ "fastrand",
+ "phf_shared",
+]
+
+[[package]]
+name = "phf_macros"
+version = "0.13.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "812f032b54b1e759ccd5f8b6677695d5268c588701effba24601f6932f8269ef"
+dependencies = [
+ "phf_generator",
+ "phf_shared",
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "phf_shared"
+version = "0.13.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e57fef6bc5981e38c2ce2d63bfa546861309f875b8a75f092d1d54ae2d64f266"
+dependencies = [
+ "siphasher",
+]
+
+[[package]]
+name = "pin-project"
+version = "1.1.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f1749c7ed4bcaf4c3d0a3efc28538844fb29bcdd7d2b67b2be7e20ba861ff517"
+dependencies = [
+ "pin-project-internal",
+]
+
+[[package]]
+name = "pin-project-internal"
+version = "1.1.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d9b20ed30f105399776b9c883e68e536ef602a16ae6f596d2c473591d6ad64c6"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "pin-project-lite"
+version = "0.2.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd"
+
+[[package]]
+name = "pkg-config"
+version = "0.3.32"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c"
+
+[[package]]
+name = "plain"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6"
+
+[[package]]
+name = "png"
+version = "0.18.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "60769b8b31b2a9f263dae2776c37b1b28ae246943cf719eb6946a1db05128a61"
+dependencies = [
+ "bitflags 2.11.0",
+ "crc32fast",
+ "fdeflate",
+ "flate2",
+ "miniz_oxide",
+]
+
+[[package]]
+name = "polling"
+version = "3.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5d0e4f59085d47d8241c88ead0f274e8a0cb551f3625263c05eb8dd897c34218"
+dependencies = [
+ "cfg-if",
+ "concurrent-queue",
+ "hermit-abi",
+ "pin-project-lite",
+ "rustix 1.1.4",
+ "windows-sys 0.61.2",
+]
+
+[[package]]
+name = "pollster"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2f3a9f18d041e6d0e102a0a46750538147e5e8992d3b4873aaafee2520b00ce3"
+
+[[package]]
+name = "portable-atomic"
+version = "1.13.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49"
+
+[[package]]
+name = "portable-atomic-util"
+version = "0.2.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7a9db96d7fa8782dd8c15ce32ffe8680bbd1e978a43bf51a34d39483540495f5"
+dependencies = [
+ "portable-atomic",
+]
+
+[[package]]
+name = "potential_utf"
+version = "0.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b73949432f5e2a09657003c25bca5e19a0e9c84f8058ca374f49e0ebe605af77"
+dependencies = [
+ "zerovec",
+]
+
+[[package]]
+name = "presser"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e8cf8e6a8aa66ce33f63993ffc4ea4271eb5b0530a9002db8455ea6050c77bfa"
+
+[[package]]
+name = "proc-macro-crate"
+version = "3.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "219cb19e96be00ab2e37d6e299658a0cfa83e52429179969b0f0121b4ac46983"
+dependencies = [
+ "toml_edit",
+]
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.106"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "profiling"
+version = "1.0.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3eb8486b569e12e2c32ad3e204dbaba5e4b5b216e9367044f25f1dba42341773"
+
+[[package]]
+name = "pxfm"
+version = "0.1.28"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b5a041e753da8b807c9255f28de81879c78c876392ff2469cde94799b2896b9d"
+
+[[package]]
+name = "quick-error"
+version = "2.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3"
+
+[[package]]
+name = "quick-xml"
+version = "0.38.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b66c2058c55a409d601666cffe35f04333cf1013010882cec174a7467cd4e21c"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.44"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4"
+dependencies = [
+ "proc-macro2",
+]
+
+[[package]]
+name = "r-efi"
+version = "5.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
+
+[[package]]
+name = "range-alloc"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ca45419789ae5a7899559e9512e58ca889e41f04f1f2445e9f4b290ceccd1d08"
+
+[[package]]
+name = "raw-window-handle"
+version = "0.6.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539"
+
+[[package]]
+name = "redox_syscall"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa"
+dependencies = [
+ "bitflags 1.3.2",
+]
+
+[[package]]
+name = "redox_syscall"
+version = "0.5.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d"
+dependencies = [
+ "bitflags 2.11.0",
+]
+
+[[package]]
+name = "redox_syscall"
+version = "0.7.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6ce70a74e890531977d37e532c34d45e9055d2409ed08ddba14529471ed0be16"
+dependencies = [
+ "bitflags 2.11.0",
+]
+
+[[package]]
+name = "regex"
+version = "1.12.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276"
+dependencies = [
+ "aho-corasick",
+ "memchr",
+ "regex-automata",
+ "regex-syntax",
+]
+
+[[package]]
+name = "regex-automata"
+version = "0.4.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f"
+dependencies = [
+ "aho-corasick",
+ "memchr",
+ "regex-syntax",
+]
+
+[[package]]
+name = "regex-syntax"
+version = "0.8.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a"
+
+[[package]]
+name = "renderdoc-sys"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "19b30a45b0cd0bcca8037f3d0dc3421eaf95327a17cad11964fb8179b4fc4832"
+
+[[package]]
+name = "rustc-hash"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
+
+[[package]]
+name = "rustc-hash"
+version = "2.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d"
+
+[[package]]
+name = "rustix"
+version = "0.38.44"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154"
+dependencies = [
+ "bitflags 2.11.0",
+ "errno",
+ "libc",
+ "linux-raw-sys 0.4.15",
+ "windows-sys 0.59.0",
+]
+
+[[package]]
+name = "rustix"
+version = "1.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190"
+dependencies = [
+ "bitflags 2.11.0",
+ "errno",
+ "libc",
+ "linux-raw-sys 0.12.1",
+ "windows-sys 0.61.2",
+]
+
+[[package]]
+name = "rustversion"
+version = "1.0.22"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
+
+[[package]]
+name = "ryu"
+version = "1.0.23"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f"
+
+[[package]]
+name = "same-file"
+version = "1.0.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
+dependencies = [
+ "winapi-util",
+]
+
+[[package]]
+name = "scoped-tls"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294"
+
+[[package]]
+name = "scopeguard"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
+
+[[package]]
+name = "sctk-adwaita"
+version = "0.10.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b6277f0217056f77f1d8f49f2950ac6c278c0d607c45f5ee99328d792ede24ec"
+dependencies = [
+ "ab_glyph",
+ "log",
+ "memmap2",
+ "smithay-client-toolkit 0.19.2",
+ "tiny-skia",
+]
+
+[[package]]
+name = "serde"
+version = "1.0.228"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
+dependencies = [
+ "serde_core",
+ "serde_derive",
+]
+
+[[package]]
+name = "serde_core"
+version = "1.0.228"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad"
+dependencies = [
+ "serde_derive",
+]
+
+[[package]]
+name = "serde_derive"
+version = "1.0.228"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "shlex"
+version = "1.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
+
+[[package]]
+name = "simd-adler32"
+version = "0.3.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2"
+
+[[package]]
+name = "siphasher"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b2aa850e253778c88a04c3d7323b043aeda9d3e30d5971937c1855769763678e"
+
+[[package]]
+name = "slab"
+version = "0.4.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5"
+
+[[package]]
+name = "slotmap"
+version = "1.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bdd58c3c93c3d278ca835519292445cb4b0d4dc59ccfdf7ceadaab3f8aeb4038"
+dependencies = [
+ "version_check",
+]
+
+[[package]]
+name = "smallvec"
+version = "1.15.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
+
+[[package]]
+name = "smithay-client-toolkit"
+version = "0.19.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3457dea1f0eb631b4034d61d4d8c32074caa6cd1ab2d59f2327bd8461e2c0016"
+dependencies = [
+ "bitflags 2.11.0",
+ "calloop 0.13.0",
+ "calloop-wayland-source 0.3.0",
+ "cursor-icon",
+ "libc",
+ "log",
+ "memmap2",
+ "rustix 0.38.44",
+ "thiserror 1.0.69",
+ "wayland-backend",
+ "wayland-client",
+ "wayland-csd-frame",
+ "wayland-cursor",
+ "wayland-protocols",
+ "wayland-protocols-wlr",
+ "wayland-scanner",
+ "xkeysym",
+]
+
+[[package]]
+name = "smithay-client-toolkit"
+version = "0.20.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0512da38f5e2b31201a93524adb8d3136276fa4fe4aafab4e1f727a82b534cc0"
+dependencies = [
+ "bitflags 2.11.0",
+ "calloop 0.14.4",
+ "calloop-wayland-source 0.4.1",
+ "cursor-icon",
+ "libc",
+ "log",
+ "memmap2",
+ "rustix 1.1.4",
+ "thiserror 2.0.18",
+ "wayland-backend",
+ "wayland-client",
+ "wayland-csd-frame",
+ "wayland-cursor",
+ "wayland-protocols",
+ "wayland-protocols-experimental",
+ "wayland-protocols-misc",
+ "wayland-protocols-wlr",
+ "wayland-scanner",
+ "xkeysym",
+]
+
+[[package]]
+name = "smithay-clipboard"
+version = "0.7.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "71704c03f739f7745053bde45fa203a46c58d25bc5c4efba1d9a60e9dba81226"
+dependencies = [
+ "libc",
+ "smithay-client-toolkit 0.20.0",
+ "wayland-backend",
+]
+
+[[package]]
+name = "smol_str"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dd538fb6910ac1099850255cf94a94df6551fbdd602454387d0adb2d1ca6dead"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "spirv"
+version = "0.3.0+sdk-1.3.268.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "eda41003dc44290527a59b13432d4a0379379fa074b70174882adfbdfd917844"
+dependencies = [
+ "bitflags 2.11.0",
+]
+
+[[package]]
+name = "stable_deref_trait"
+version = "1.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596"
+
+[[package]]
+name = "static_assertions"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
+
+[[package]]
+name = "strict-num"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6637bab7722d379c8b41ba849228d680cc12d0a45ba1fa2b48f2a30577a06731"
+
+[[package]]
+name = "stringreader"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "913e7b03d63752f6cdd2df77da36749d82669904798fe8944b9ec3d23f159905"
+
+[[package]]
+name = "syn"
+version = "2.0.117"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "synstructure"
+version = "0.13.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "systemic4x"
+version = "0.1.0"
+dependencies = [
+ "bytemuck",
+ "cgmath",
+ "csv",
+ "egui",
+ "egui-wgpu",
+ "egui-winit",
+ "env_logger",
+ "log",
+ "phf",
+ "pollster",
+ "serde",
+ "stringreader",
+ "wgpu",
+ "winit",
+]
+
+[[package]]
+name = "termcolor"
+version = "1.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755"
+dependencies = [
+ "winapi-util",
+]
+
+[[package]]
+name = "thiserror"
+version = "1.0.69"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52"
+dependencies = [
+ "thiserror-impl 1.0.69",
+]
+
+[[package]]
+name = "thiserror"
+version = "2.0.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4"
+dependencies = [
+ "thiserror-impl 2.0.18",
+]
+
+[[package]]
+name = "thiserror-impl"
+version = "1.0.69"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "thiserror-impl"
+version = "2.0.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "tiff"
+version = "0.10.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "af9605de7fee8d9551863fd692cce7637f548dbd9db9180fcc07ccc6d26c336f"
+dependencies = [
+ "fax",
+ "flate2",
+ "half",
+ "quick-error",
+ "weezl",
+ "zune-jpeg",
+]
+
+[[package]]
+name = "tiny-skia"
+version = "0.11.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "83d13394d44dae3207b52a326c0c85a8bf87f1541f23b0d143811088497b09ab"
+dependencies = [
+ "arrayref",
+ "arrayvec",
+ "bytemuck",
+ "cfg-if",
+ "log",
+ "tiny-skia-path",
+]
+
+[[package]]
+name = "tiny-skia-path"
+version = "0.11.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9c9e7fc0c2e86a30b117d0462aa261b72b7a99b7ebd7deb3a14ceda95c5bdc93"
+dependencies = [
+ "arrayref",
+ "bytemuck",
+ "strict-num",
+]
+
+[[package]]
+name = "tinystr"
+version = "0.8.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "42d3e9c45c09de15d06dd8acf5f4e0e399e85927b7f00711024eb7ae10fa4869"
+dependencies = [
+ "displaydoc",
+ "zerovec",
+]
+
+[[package]]
+name = "toml_datetime"
+version = "0.7.5+spec-1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "92e1cfed4a3038bc5a127e35a2d360f145e1f4b971b551a2ba5fd7aedf7e1347"
+dependencies = [
+ "serde_core",
+]
+
+[[package]]
+name = "toml_edit"
+version = "0.23.10+spec-1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "84c8b9f757e028cee9fa244aea147aab2a9ec09d5325a9b01e0a49730c2b5269"
+dependencies = [
+ "indexmap",
+ "toml_datetime",
+ "toml_parser",
+ "winnow",
+]
+
+[[package]]
+name = "toml_parser"
+version = "1.0.9+spec-1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "702d4415e08923e7e1ef96cd5727c0dfed80b4d2fa25db9647fe5eb6f7c5a4c4"
+dependencies = [
+ "winnow",
+]
+
+[[package]]
+name = "tracing"
+version = "0.1.44"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100"
+dependencies = [
+ "log",
+ "pin-project-lite",
+ "tracing-core",
+]
+
+[[package]]
+name = "tracing-core"
+version = "0.1.36"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a"
+
+[[package]]
+name = "ttf-parser"
+version = "0.25.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d2df906b07856748fa3f6e0ad0cbaa047052d4a7dd609e231c4f72cee8c36f31"
+
+[[package]]
+name = "type-map"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cb30dbbd9036155e74adad6812e9898d03ec374946234fbcebd5dfc7b9187b90"
+dependencies = [
+ "rustc-hash 2.1.1",
+]
+
+[[package]]
+name = "unicode-ident"
+version = "1.0.24"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
+
+[[package]]
+name = "unicode-segmentation"
+version = "1.12.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493"
+
+[[package]]
+name = "unicode-width"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254"
+
+[[package]]
+name = "url"
+version = "2.5.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed"
+dependencies = [
+ "form_urlencoded",
+ "idna",
+ "percent-encoding",
+ "serde",
+]
+
+[[package]]
+name = "utf8_iter"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"
+
+[[package]]
+name = "utf8parse"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
+
+[[package]]
+name = "version_check"
+version = "0.9.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
+
+[[package]]
+name = "walkdir"
+version = "2.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b"
+dependencies = [
+ "same-file",
+ "winapi-util",
+]
+
+[[package]]
+name = "wasip2"
+version = "1.0.2+wasi-0.2.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5"
+dependencies = [
+ "wit-bindgen",
+]
+
+[[package]]
+name = "wasm-bindgen"
+version = "0.2.114"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6532f9a5c1ece3798cb1c2cfdba640b9b3ba884f5db45973a6f442510a87d38e"
+dependencies = [
+ "cfg-if",
+ "once_cell",
+ "rustversion",
+ "wasm-bindgen-macro",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-futures"
+version = "0.4.64"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e9c5522b3a28661442748e09d40924dfb9ca614b21c00d3fd135720e48b67db8"
+dependencies = [
+ "cfg-if",
+ "futures-util",
+ "js-sys",
+ "once_cell",
+ "wasm-bindgen",
+ "web-sys",
+]
+
+[[package]]
+name = "wasm-bindgen-macro"
+version = "0.2.114"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "18a2d50fcf105fb33bb15f00e7a77b772945a2ee45dcf454961fd843e74c18e6"
+dependencies = [
+ "quote",
+ "wasm-bindgen-macro-support",
+]
+
+[[package]]
+name = "wasm-bindgen-macro-support"
+version = "0.2.114"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "03ce4caeaac547cdf713d280eda22a730824dd11e6b8c3ca9e42247b25c631e3"
+dependencies = [
+ "bumpalo",
+ "proc-macro2",
+ "quote",
+ "syn",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-shared"
+version = "0.2.114"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "75a326b8c223ee17883a4251907455a2431acc2791c98c26279376490c378c16"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "wayland-backend"
+version = "0.3.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fee64194ccd96bf648f42a65a7e589547096dfa702f7cadef84347b66ad164f9"
+dependencies = [
+ "cc",
+ "downcast-rs",
+ "rustix 1.1.4",
+ "scoped-tls",
+ "smallvec",
+ "wayland-sys",
+]
+
+[[package]]
+name = "wayland-client"
+version = "0.31.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b8e6faa537fbb6c186cb9f1d41f2f811a4120d1b57ec61f50da451a0c5122bec"
+dependencies = [
+ "bitflags 2.11.0",
+ "rustix 1.1.4",
+ "wayland-backend",
+ "wayland-scanner",
+]
+
+[[package]]
+name = "wayland-csd-frame"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "625c5029dbd43d25e6aa9615e88b829a5cad13b2819c4ae129fdbb7c31ab4c7e"
+dependencies = [
+ "bitflags 2.11.0",
+ "cursor-icon",
+ "wayland-backend",
+]
+
+[[package]]
+name = "wayland-cursor"
+version = "0.31.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5864c4b5b6064b06b1e8b74ead4a98a6c45a285fe7a0e784d24735f011fdb078"
+dependencies = [
+ "rustix 1.1.4",
+ "wayland-client",
+ "xcursor",
+]
+
+[[package]]
+name = "wayland-protocols"
+version = "0.32.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "baeda9ffbcfc8cd6ddaade385eaf2393bd2115a69523c735f12242353c3df4f3"
+dependencies = [
+ "bitflags 2.11.0",
+ "wayland-backend",
+ "wayland-client",
+ "wayland-scanner",
+]
+
+[[package]]
+name = "wayland-protocols-experimental"
+version = "20250721.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "40a1f863128dcaaec790d7b4b396cc9b9a7a079e878e18c47e6c2d2c5a8dcbb1"
+dependencies = [
+ "bitflags 2.11.0",
+ "wayland-backend",
+ "wayland-client",
+ "wayland-protocols",
+ "wayland-scanner",
+]
+
+[[package]]
+name = "wayland-protocols-misc"
+version = "0.3.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "791c58fdeec5406aa37169dd815327d1e47f334219b523444bc26d70ceb4c34e"
+dependencies = [
+ "bitflags 2.11.0",
+ "wayland-backend",
+ "wayland-client",
+ "wayland-protocols",
+ "wayland-scanner",
+]
+
+[[package]]
+name = "wayland-protocols-plasma"
+version = "0.3.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "aa98634619300a535a9a97f338aed9a5ff1e01a461943e8346ff4ae26007306b"
+dependencies = [
+ "bitflags 2.11.0",
+ "wayland-backend",
+ "wayland-client",
+ "wayland-protocols",
+ "wayland-scanner",
+]
+
+[[package]]
+name = "wayland-protocols-wlr"
+version = "0.3.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e9597cdf02cf0c34cd5823786dce6b5ae8598f05c2daf5621b6e178d4f7345f3"
+dependencies = [
+ "bitflags 2.11.0",
+ "wayland-backend",
+ "wayland-client",
+ "wayland-protocols",
+ "wayland-scanner",
+]
+
+[[package]]
+name = "wayland-scanner"
+version = "0.31.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5423e94b6a63e68e439803a3e153a9252d5ead12fd853334e2ad33997e3889e3"
+dependencies = [
+ "proc-macro2",
+ "quick-xml",
+ "quote",
+]
+
+[[package]]
+name = "wayland-sys"
+version = "0.31.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e6dbfc3ac5ef974c92a2235805cc0114033018ae1290a72e474aa8b28cbbdfd"
+dependencies = [
+ "dlib",
+ "log",
+ "once_cell",
+ "pkg-config",
+]
+
+[[package]]
+name = "web-sys"
+version = "0.3.91"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "854ba17bb104abfb26ba36da9729addc7ce7f06f5c0f90f3c391f8461cca21f9"
+dependencies = [
+ "js-sys",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "web-time"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb"
+dependencies = [
+ "js-sys",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "webbrowser"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3f00bb839c1cf1e3036066614cbdcd035ecf215206691ea646aa3c60a24f68f2"
+dependencies = [
+ "core-foundation 0.10.1",
+ "jni",
+ "log",
+ "ndk-context",
+ "objc2 0.6.4",
+ "objc2-foundation 0.3.2",
+ "url",
+ "web-sys",
+]
+
+[[package]]
+name = "weezl"
+version = "0.1.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a28ac98ddc8b9274cb41bb4d9d4d5c425b6020c50c46f25559911905610b4a88"
+
+[[package]]
+name = "wgpu"
+version = "27.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bfe68bac7cde125de7a731c3400723cadaaf1703795ad3f4805f187459cd7a77"
+dependencies = [
+ "arrayvec",
+ "bitflags 2.11.0",
+ "cfg-if",
+ "cfg_aliases",
+ "document-features",
+ "hashbrown 0.16.1",
+ "js-sys",
+ "log",
+ "naga",
+ "parking_lot",
+ "portable-atomic",
+ "profiling",
+ "raw-window-handle",
+ "smallvec",
+ "static_assertions",
+ "wasm-bindgen",
+ "wasm-bindgen-futures",
+ "web-sys",
+ "wgpu-core",
+ "wgpu-hal",
+ "wgpu-types",
+]
+
+[[package]]
+name = "wgpu-core"
+version = "27.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "27a75de515543b1897b26119f93731b385a19aea165a1ec5f0e3acecc229cae7"
+dependencies = [
+ "arrayvec",
+ "bit-set",
+ "bit-vec",
+ "bitflags 2.11.0",
+ "bytemuck",
+ "cfg_aliases",
+ "document-features",
+ "hashbrown 0.16.1",
+ "indexmap",
+ "log",
+ "naga",
+ "once_cell",
+ "parking_lot",
+ "portable-atomic",
+ "profiling",
+ "raw-window-handle",
+ "rustc-hash 1.1.0",
+ "smallvec",
+ "thiserror 2.0.18",
+ "wgpu-core-deps-apple",
+ "wgpu-core-deps-emscripten",
+ "wgpu-core-deps-windows-linux-android",
+ "wgpu-hal",
+ "wgpu-types",
+]
+
+[[package]]
+name = "wgpu-core-deps-apple"
+version = "27.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0772ae958e9be0c729561d5e3fd9a19679bcdfb945b8b1a1969d9bfe8056d233"
+dependencies = [
+ "wgpu-hal",
+]
+
+[[package]]
+name = "wgpu-core-deps-emscripten"
+version = "27.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b06ac3444a95b0813ecfd81ddb2774b66220b264b3e2031152a4a29fda4da6b5"
+dependencies = [
+ "wgpu-hal",
+]
+
+[[package]]
+name = "wgpu-core-deps-windows-linux-android"
+version = "27.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "71197027d61a71748e4120f05a9242b2ad142e3c01f8c1b47707945a879a03c3"
+dependencies = [
+ "wgpu-hal",
+]
+
+[[package]]
+name = "wgpu-hal"
+version = "27.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5b21cb61c57ee198bc4aff71aeadff4cbb80b927beb912506af9c780d64313ce"
+dependencies = [
+ "android_system_properties",
+ "arrayvec",
+ "ash",
+ "bit-set",
+ "bitflags 2.11.0",
+ "block",
+ "bytemuck",
+ "cfg-if",
+ "cfg_aliases",
+ "core-graphics-types 0.2.0",
+ "glow",
+ "glutin_wgl_sys",
+ "gpu-alloc",
+ "gpu-allocator",
+ "gpu-descriptor",
+ "hashbrown 0.16.1",
+ "js-sys",
+ "khronos-egl",
+ "libc",
+ "libloading",
+ "log",
+ "metal",
+ "naga",
+ "ndk-sys",
+ "objc",
+ "once_cell",
+ "ordered-float",
+ "parking_lot",
+ "portable-atomic",
+ "portable-atomic-util",
+ "profiling",
+ "range-alloc",
+ "raw-window-handle",
+ "renderdoc-sys",
+ "smallvec",
+ "thiserror 2.0.18",
+ "wasm-bindgen",
+ "web-sys",
+ "wgpu-types",
+ "windows",
+ "windows-core",
+]
+
+[[package]]
+name = "wgpu-types"
+version = "27.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "afdcf84c395990db737f2dd91628706cb31e86d72e53482320d368e52b5da5eb"
+dependencies = [
+ "bitflags 2.11.0",
+ "bytemuck",
+ "js-sys",
+ "log",
+ "thiserror 2.0.18",
+ "web-sys",
+]
+
+[[package]]
+name = "winapi-util"
+version = "0.1.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
+dependencies = [
+ "windows-sys 0.61.2",
+]
+
+[[package]]
+name = "windows"
+version = "0.58.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dd04d41d93c4992d421894c18c8b43496aa748dd4c081bac0dc93eb0489272b6"
+dependencies = [
+ "windows-core",
+ "windows-targets 0.52.6",
+]
+
+[[package]]
+name = "windows-core"
+version = "0.58.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6ba6d44ec8c2591c134257ce647b7ea6b20335bf6379a27dac5f1641fcf59f99"
+dependencies = [
+ "windows-implement",
+ "windows-interface",
+ "windows-result",
+ "windows-strings",
+ "windows-targets 0.52.6",
+]
+
+[[package]]
+name = "windows-implement"
+version = "0.58.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2bbd5b46c938e506ecbce286b6628a02171d56153ba733b6c741fc627ec9579b"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "windows-interface"
+version = "0.58.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "053c4c462dc91d3b1504c6fe5a726dd15e216ba718e84a0e46a88fbe5ded3515"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "windows-link"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
+
+[[package]]
+name = "windows-result"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e"
+dependencies = [
+ "windows-targets 0.52.6",
+]
+
+[[package]]
+name = "windows-strings"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10"
+dependencies = [
+ "windows-result",
+ "windows-targets 0.52.6",
+]
+
+[[package]]
+name = "windows-sys"
+version = "0.45.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0"
+dependencies = [
+ "windows-targets 0.42.2",
+]
+
+[[package]]
+name = "windows-sys"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
+dependencies = [
+ "windows-targets 0.52.6",
+]
+
+[[package]]
+name = "windows-sys"
+version = "0.59.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
+dependencies = [
+ "windows-targets 0.52.6",
+]
+
+[[package]]
+name = "windows-sys"
+version = "0.60.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb"
+dependencies = [
+ "windows-targets 0.53.5",
+]
+
+[[package]]
+name = "windows-sys"
+version = "0.61.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
+dependencies = [
+ "windows-link",
+]
+
+[[package]]
+name = "windows-targets"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071"
+dependencies = [
+ "windows_aarch64_gnullvm 0.42.2",
+ "windows_aarch64_msvc 0.42.2",
+ "windows_i686_gnu 0.42.2",
+ "windows_i686_msvc 0.42.2",
+ "windows_x86_64_gnu 0.42.2",
+ "windows_x86_64_gnullvm 0.42.2",
+ "windows_x86_64_msvc 0.42.2",
+]
+
+[[package]]
+name = "windows-targets"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
+dependencies = [
+ "windows_aarch64_gnullvm 0.52.6",
+ "windows_aarch64_msvc 0.52.6",
+ "windows_i686_gnu 0.52.6",
+ "windows_i686_gnullvm 0.52.6",
+ "windows_i686_msvc 0.52.6",
+ "windows_x86_64_gnu 0.52.6",
+ "windows_x86_64_gnullvm 0.52.6",
+ "windows_x86_64_msvc 0.52.6",
+]
+
+[[package]]
+name = "windows-targets"
+version = "0.53.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3"
+dependencies = [
+ "windows-link",
+ "windows_aarch64_gnullvm 0.53.1",
+ "windows_aarch64_msvc 0.53.1",
+ "windows_i686_gnu 0.53.1",
+ "windows_i686_gnullvm 0.53.1",
+ "windows_i686_msvc 0.53.1",
+ "windows_x86_64_gnu 0.53.1",
+ "windows_x86_64_gnullvm 0.53.1",
+ "windows_x86_64_msvc 0.53.1",
+]
+
+[[package]]
+name = "windows_aarch64_gnullvm"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8"
+
+[[package]]
+name = "windows_aarch64_gnullvm"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
+
+[[package]]
+name = "windows_aarch64_gnullvm"
+version = "0.53.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53"
+
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43"
+
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
+
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.53.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006"
+
+[[package]]
+name = "windows_i686_gnu"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f"
+
+[[package]]
+name = "windows_i686_gnu"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
+
+[[package]]
+name = "windows_i686_gnu"
+version = "0.53.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3"
+
+[[package]]
+name = "windows_i686_gnullvm"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
+
+[[package]]
+name = "windows_i686_gnullvm"
+version = "0.53.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c"
+
+[[package]]
+name = "windows_i686_msvc"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060"
+
+[[package]]
+name = "windows_i686_msvc"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
+
+[[package]]
+name = "windows_i686_msvc"
+version = "0.53.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2"
+
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36"
+
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
+
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.53.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499"
+
+[[package]]
+name = "windows_x86_64_gnullvm"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3"
+
+[[package]]
+name = "windows_x86_64_gnullvm"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
+
+[[package]]
+name = "windows_x86_64_gnullvm"
+version = "0.53.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1"
+
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0"
+
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
+
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.53.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650"
+
+[[package]]
+name = "winit"
+version = "0.30.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a6755fa58a9f8350bd1e472d4c3fcc25f824ec358933bba33306d0b63df5978d"
+dependencies = [
+ "ahash",
+ "android-activity",
+ "atomic-waker",
+ "bitflags 2.11.0",
+ "block2",
+ "bytemuck",
+ "calloop 0.13.0",
+ "cfg_aliases",
+ "concurrent-queue",
+ "core-foundation 0.9.4",
+ "core-graphics",
+ "cursor-icon",
+ "dpi",
+ "js-sys",
+ "libc",
+ "memmap2",
+ "ndk",
+ "objc2 0.5.2",
+ "objc2-app-kit 0.2.2",
+ "objc2-foundation 0.2.2",
+ "objc2-ui-kit",
+ "orbclient",
+ "percent-encoding",
+ "pin-project",
+ "raw-window-handle",
+ "redox_syscall 0.4.1",
+ "rustix 0.38.44",
+ "sctk-adwaita",
+ "smithay-client-toolkit 0.19.2",
+ "smol_str",
+ "tracing",
+ "unicode-segmentation",
+ "wasm-bindgen",
+ "wasm-bindgen-futures",
+ "wayland-backend",
+ "wayland-client",
+ "wayland-protocols",
+ "wayland-protocols-plasma",
+ "web-sys",
+ "web-time",
+ "windows-sys 0.52.0",
+ "x11-dl",
+ "x11rb",
+ "xkbcommon-dl",
+]
+
+[[package]]
+name = "winnow"
+version = "0.7.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5a5364e9d77fcdeeaa6062ced926ee3381faa2ee02d3eb83a5c27a8825540829"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "wit-bindgen"
+version = "0.51.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5"
+
+[[package]]
+name = "writeable"
+version = "0.6.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9"
+
+[[package]]
+name = "x11-dl"
+version = "2.21.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "38735924fedd5314a6e548792904ed8c6de6636285cb9fec04d5b1db85c1516f"
+dependencies = [
+ "libc",
+ "once_cell",
+ "pkg-config",
+]
+
+[[package]]
+name = "x11rb"
+version = "0.13.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9993aa5be5a26815fe2c3eacfc1fde061fc1a1f094bf1ad2a18bf9c495dd7414"
+dependencies = [
+ "as-raw-xcb-connection",
+ "gethostname",
+ "libc",
+ "libloading",
+ "once_cell",
+ "rustix 1.1.4",
+ "x11rb-protocol",
+]
+
+[[package]]
+name = "x11rb-protocol"
+version = "0.13.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ea6fc2961e4ef194dcbfe56bb845534d0dc8098940c7e5c012a258bfec6701bd"
+
+[[package]]
+name = "xcursor"
+version = "0.3.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bec9e4a500ca8864c5b47b8b482a73d62e4237670e5b5f1d6b9e3cae50f28f2b"
+
+[[package]]
+name = "xkbcommon-dl"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d039de8032a9a8856a6be89cea3e5d12fdd82306ab7c94d74e6deab2460651c5"
+dependencies = [
+ "bitflags 2.11.0",
+ "dlib",
+ "log",
+ "once_cell",
+ "xkeysym",
+]
+
+[[package]]
+name = "xkeysym"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b9cc00251562a284751c9973bace760d86c0276c471b4be569fe6b068ee97a56"
+
+[[package]]
+name = "xml-rs"
+version = "0.8.28"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3ae8337f8a065cfc972643663ea4279e04e7256de865aa66fe25cec5fb912d3f"
+
+[[package]]
+name = "yoke"
+version = "0.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "72d6e5c6afb84d73944e5cedb052c4680d5657337201555f9f2a16b7406d4954"
+dependencies = [
+ "stable_deref_trait",
+ "yoke-derive",
+ "zerofrom",
+]
+
+[[package]]
+name = "yoke-derive"
+version = "0.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+ "synstructure",
+]
+
+[[package]]
+name = "zerocopy"
+version = "0.8.40"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a789c6e490b576db9f7e6b6d661bcc9799f7c0ac8352f56ea20193b2681532e5"
+dependencies = [
+ "zerocopy-derive",
+]
+
+[[package]]
+name = "zerocopy-derive"
+version = "0.8.40"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f65c489a7071a749c849713807783f70672b28094011623e200cb86dcb835953"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "zerofrom"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5"
+dependencies = [
+ "zerofrom-derive",
+]
+
+[[package]]
+name = "zerofrom-derive"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+ "synstructure",
+]
+
+[[package]]
+name = "zerotrie"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2a59c17a5562d507e4b54960e8569ebee33bee890c70aa3fe7b97e85a9fd7851"
+dependencies = [
+ "displaydoc",
+ "yoke",
+ "zerofrom",
+]
+
+[[package]]
+name = "zerovec"
+version = "0.11.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6c28719294829477f525be0186d13efa9a3c602f7ec202ca9e353d310fb9a002"
+dependencies = [
+ "yoke",
+ "zerofrom",
+ "zerovec-derive",
+]
+
+[[package]]
+name = "zerovec-derive"
+version = "0.11.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "zune-core"
+version = "0.4.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3f423a2c17029964870cfaabb1f13dfab7d092a62a29a89264f4d36990ca414a"
+
+[[package]]
+name = "zune-jpeg"
+version = "0.4.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "29ce2c8a9384ad323cf564b67da86e21d3cfdff87908bc1223ed5c99bc792713"
+dependencies = [
+ "zune-core",
+]
diff --git a/Cargo.toml b/Cargo.toml
new file mode 100644
index 0000000..95edc86
--- /dev/null
+++ b/Cargo.toml
@@ -0,0 +1,20 @@
+[package]
+name = "systemic4x"
+version = "0.1.0"
+edition = "2024"
+
+[dependencies]
+bytemuck = { version = "1.25.0", features = ["derive"]}
+cgmath = "0.18.0"
+csv = "1.4.0"
+egui = "0.33.3"
+egui-wgpu = { version = "0.33.3", features = [ "winit" ]}
+egui-winit = "0.33.3"
+env_logger = "0.11.8"
+log = "0.4.29"
+phf = { version = "0.13.1", features = ["macros"]}
+pollster = "0.4.0"
+serde = { version = "1.0.228", features = ["derive"]}
+stringreader = "0.1.1"
+wgpu = "27.0.1"
+winit = { version = "0.30.12", features = ["wayland"]}
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..4967214
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,7 @@
+all: run
+
+run:
+ cargo run
+
+build:
+ cargo build
diff --git a/assets/shaders/canvas.wgsl b/assets/shaders/canvas.wgsl
new file mode 100644
index 0000000..3be1a77
--- /dev/null
+++ b/assets/shaders/canvas.wgsl
@@ -0,0 +1,32 @@
+struct VertexInput {
+ @location(0) position: vec3<f32>,
+ @location(1) uv: vec2<f32>
+};
+
+struct VertexOutput {
+ @builtin(position) clip_position: vec4<f32>,
+ @location(0) uv: vec2<f32>
+};
+
+@vertex
+fn vs_main(
+ model: VertexInput,
+) -> VertexOutput {
+ var out: VertexOutput;
+
+ out.clip_position = vec4<f32>(model.position, 1.0);
+ out.uv = model.uv;
+
+ return out;
+}
+
+@group(0) @binding(0)
+var canvas_texture: texture_2d<f32>;
+@group(0) @binding(1)
+var canvas_sampler: sampler;
+
+@fragment
+fn fs_main(in: VertexOutput
+) -> @location(0) vec4<f32> {
+ return textureSample(canvas_texture, canvas_sampler, in.uv);
+}
diff --git a/assets/shaders/tacbody.wgsl b/assets/shaders/tacbody.wgsl
new file mode 100644
index 0000000..e8fb653
--- /dev/null
+++ b/assets/shaders/tacbody.wgsl
@@ -0,0 +1,79 @@
+struct VertexInput {
+ @location(0) position: vec3<f32>
+};
+
+struct InstanceInput {
+ @location(2) position: vec3<f32>,
+ @location(3) radius: f32
+};
+
+struct VertexOutput {
+ @builtin(position) clip_position: vec4<f32>,
+ @location(0) local_position: vec3<f32>
+};
+
+struct CameraUniform {
+ view: mat4x4<f32>,
+ proj: mat4x4<f32>,
+ scale: f32
+};
+
+@group(0) @binding(0)
+var<uniform> camera: CameraUniform;
+
+@vertex
+fn vs_main(
+ model: VertexInput,
+ instance: InstanceInput
+) -> VertexOutput {
+ var out: VertexOutput;
+
+ var view = camera.view;
+
+ //Billboard the circle
+ view[0][0] = 1.0;
+ view[0][1] = 0.0;
+ view[0][2] = 0.0;
+
+ view[1][0] = 0.0;
+ view[1][1] = 1.0;
+ view[1][2] = 0.0;
+
+ view[2][0] = 0.0;
+ view[2][1] = 0.0;
+ view[2][2] = 1.0;
+
+ let min_size = 0.025;
+
+ //Scale the world around the camera scale.
+ let model_pos = model.position;
+ let instance_pos = instance.position * camera.scale;
+
+ let view_proj = camera.proj * view;
+
+ let center_view_pos = view_proj * vec4<f32>(instance_pos, 1.0);
+ let vertex_view_pos = view_proj * vec4<f32>(instance_pos + (model_pos * camera.scale), 1.0);
+
+ let vertex_dist = length(vertex_view_pos - center_view_pos) / center_view_pos.w;
+ if vertex_dist < min_size {
+ out.clip_position = center_view_pos / center_view_pos.w;
+ out.clip_position += camera.proj * vec4<f32>(model_pos.xy * (min_size / 2), 0.0, 0.0);
+ }else{
+ out.clip_position = vertex_view_pos;
+ }
+
+ out.local_position = model.position;
+
+ return out;
+}
+
+@fragment
+fn fs_main(in: VertexOutput
+) -> @location(0) vec4<f32> {
+ let point_dist = length(in.local_position);
+ var alpha = 1.0;
+ if point_dist > 1.0 {
+ alpha = 0.0;
+ }
+ return vec4<f32>(1.0, 1.0, 1.0, alpha);
+}
diff --git a/assets/systems/sol.csv b/assets/systems/sol.csv
new file mode 100644
index 0000000..81fc14c
--- /dev/null
+++ b/assets/systems/sol.csv
@@ -0,0 +1,3 @@
+name,orbits,mass,radius,eccentricity,inclination,long_asc_node,long_periapsis,sgp,mean_long,semi_major_axis
+Sol,0,1.988475e30,695700.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0
+Earth,0,5.97217e24,6371.0,0.0167,0.1249,-0.1965,1.7966,3.986e14,1.7534,149598023
diff --git a/rust-toolchain.toml b/rust-toolchain.toml
new file mode 100644
index 0000000..292fe49
--- /dev/null
+++ b/rust-toolchain.toml
@@ -0,0 +1,2 @@
+[toolchain]
+channel = "stable"
diff --git a/shell.nix b/shell.nix
new file mode 100644
index 0000000..1ae9742
--- /dev/null
+++ b/shell.nix
@@ -0,0 +1,52 @@
+{ pkgs ? import <nixpkgs> {} }:
+let
+ overrides = (builtins.fromTOML (builtins.readFile ./rust-toolchain.toml));
+ libPath = with pkgs; lib.makeLibraryPath [
+ libGL
+ libxkbcommon
+ wayland
+ vulkan-loader
+ ];
+in {
+ devShell = with pkgs; mkShell {
+ nativeBuildInputs = [
+ pkg-config
+ ];
+ buildInputs = [
+ clang
+ llvmPackages.bintools
+ rustup
+ rust-analyzer
+ ];
+
+ RUSTC_VERSION = overrides.toolchain.channel;
+ LIBCLANG_PATH = lib.makeLibraryPath [ llvmPackages_latest.libclang.lib ];
+
+ shellHook = ''
+ export PATH=$PATH:''${CARGO_HOME:-~/.cargo}/bin
+ export PATH=$PATH:''${RUSTUP_HOME:-~/.rustup}/toolchains/$RUSTC_VERSION-x86_64-unknown-linux-gnu/bin/
+ '';
+
+ # Add precompiled library to rustc search path
+ RUSTFLAGS = (builtins.map (a: ''-L ${a}/lib'') [
+
+ ]);
+
+ # Add glibc, clang, glib, and other headers to bindgen search path
+ BINDGEN_EXTRA_CLANG_ARGS =
+ # Includes normal include path
+ (builtins.map (a: ''-I"${a}/include"'') [
+ # add dev libraries here (e.g. pkgs.libvmi.dev)
+ pkgs.glibc.dev
+ ])
+ # Includes with special directory paths
+ ++ [
+ ''-I"${pkgs.llvmPackages_latest.libclang.lib}/lib/clang/${pkgs.llvmPackages_latest.libclang.version}/include"''
+ ''-I"${pkgs.glib.dev}/include/glib-2.0"''
+ ''-I${pkgs.glib.out}/lib/glib-2.0/include/''
+ ];
+
+ # RUST_LOG="debug";
+ LD_LIBRARY_PATH=libPath;
+ };
+}
diff --git a/src/canvas.rs b/src/canvas.rs
new file mode 100644
index 0000000..dbcac7f
--- /dev/null
+++ b/src/canvas.rs
@@ -0,0 +1,176 @@
+use wgpu::RenderPipeline;
+
+use crate::wgpuctx::WgpuCtx;
+use crate::texture::Texture;
+use crate::vertex::Vertex;
+use crate::wgpuctx::pipeline::RenderPipelineBuilder;
+
+struct CanvasTexture
+{
+ pub texture: Texture,
+ buffer: wgpu::Buffer
+}
+
+pub struct Canvas
+{
+ pipeline: wgpu::RenderPipeline,
+ vertex_buffer: wgpu::Buffer,
+ texture: CanvasTexture,
+
+ position: winit::dpi::LogicalPosition<f32>,
+ size: winit::dpi::LogicalSize<f32>
+}
+
+impl Canvas
+{
+ fn create_canvas_texture(
+ wgpuctx: &WgpuCtx,
+ canvas_size: winit::dpi::LogicalSize<f32>,
+ window_size: winit::dpi::PhysicalSize<u32>)
+ -> CanvasTexture
+ {
+ let canvas_physical_size = winit::dpi::PhysicalSize::<u32>::new(
+ (window_size.width as f32 * canvas_size.width).floor() as u32,
+ (window_size.height as f32 * canvas_size.height).floor() as u32
+ );
+
+ CanvasTexture::new(wgpuctx, canvas_physical_size)
+ }
+
+ pub fn new(
+ wgpuctx: &WgpuCtx,
+ window_size: winit::dpi::PhysicalSize<u32>,
+ position: winit::dpi::LogicalPosition<f32>,
+ size: winit::dpi::LogicalSize<f32>
+ ) -> Result<Self, ()> {
+
+ let pos_x = position.x * 2.0 - 1.0;
+ let pos_y = position.y * 2.0 - 1.0;
+ let size_x = size.width * 2.0;
+ let size_y = size.height * 2.0;
+
+ let vertices: &[Vertex] = &[
+ Vertex { position: [ pos_x, pos_y, 0.0 ], uv: [ 0.0, 1.0 ] },
+ Vertex { position: [ pos_x+size_x, pos_y, 0.0 ], uv: [ 1.0, 1.0 ] },
+ Vertex { position: [ pos_x+size_x, pos_y+size_y, 0.0 ], uv: [ 1.0, 0.0 ] },
+ Vertex { position: [ pos_x+size_x, pos_y+size_y, 0.0 ], uv: [ 1.0, 0.0 ] },
+ Vertex { position: [ pos_x, pos_y+size_y, 0.0 ], uv: [ 0.0, 0.0 ] },
+ Vertex { position: [ pos_x, pos_y, 0.0 ], uv: [ 0.0, 1.0 ] },
+ ];
+
+ let vertex_buffer = wgpuctx.create_buffer_init(
+ &wgpu::util::BufferInitDescriptor {
+ label: Some("Vertex Buffer"),
+ contents: bytemuck::cast_slice(vertices),
+ usage: wgpu::BufferUsages::VERTEX
+ }
+ );
+
+ let shader = wgpuctx.create_shader(
+ wgpu::include_wgsl!(concat!(
+ env!("CARGO_MANIFEST_DIR"),
+ "/assets/shaders/canvas.wgsl")
+ ));
+
+ let canvas_texture = Canvas::create_canvas_texture(wgpuctx, size, window_size);
+
+ let render_pipeline = RenderPipelineBuilder::new(
+ &shader)
+ .add_bindgroup(&canvas_texture.texture.bindgrouplayout)
+ .add_vertex_layout(Vertex::descr())
+ .build(Some("Canvas render pipleine"), wgpuctx);
+
+ Ok(Self {
+ pipeline: render_pipeline,
+ vertex_buffer: vertex_buffer,
+ texture: canvas_texture,
+ position: position,
+ size: size
+ })
+ }
+
+ pub fn resize(
+ &mut self,
+ wgpuctx: &WgpuCtx,
+ width: u32,
+ height: u32)
+ {
+ let window_size = winit::dpi::PhysicalSize::new(width, height);
+ self.texture = Canvas::create_canvas_texture(wgpuctx, self.size, window_size);
+ }
+
+ pub fn present(
+ &mut self,
+ screen_pass: &mut wgpu::RenderPass
+ )
+ -> Result<(), wgpu::SurfaceError> {
+
+ screen_pass.set_pipeline(&self.pipeline);
+ self.texture.texture.use_on_pass(screen_pass, 0);
+ screen_pass.set_vertex_buffer(0, self.vertex_buffer.slice(..));
+ screen_pass.draw(0..6,0..1);
+ Ok(())
+ }
+
+ pub fn view(&self)
+ -> &wgpu::TextureView
+ {
+ self.texture.texture.view()
+ }
+} //impl Canvas
+
+impl CanvasTexture
+{
+ pub fn new(
+ wgpuctx: &WgpuCtx,
+ size: winit::dpi::PhysicalSize<u32>)
+ -> Self
+ {
+ let u32_size = std::mem::size_of::<u32>() as u32;
+
+ let buffer_size = (u32_size * size.width * size.height) as wgpu::BufferAddress;
+ let buffer_descr = wgpu::BufferDescriptor {
+ size: buffer_size,
+ usage: wgpu::BufferUsages::COPY_DST,
+ label: None,
+ mapped_at_creation: false
+ };
+
+ let texture = wgpuctx.new_render_texture(size);
+ let buffer = wgpuctx.create_buffer(&buffer_descr);
+
+ Self {
+ texture: texture,
+ buffer: buffer
+ }
+ }
+}
+
+impl WgpuCtx
+{
+ pub fn new_render_texture(
+ &self,
+ size: winit::dpi::PhysicalSize<u32>)
+ -> Texture
+ {
+ let descr = wgpu::TextureDescriptor {
+ size: wgpu::Extent3d {
+ width: size.width,
+ height: size.height,
+ depth_or_array_layers: 1
+ },
+ mip_level_count: 1,
+ sample_count: 1,
+ dimension: wgpu::TextureDimension::D2,
+ format: wgpu::TextureFormat::Bgra8UnormSrgb,
+ view_formats: &[
+ wgpu::TextureFormat::Bgra8UnormSrgb
+ ],
+ usage: wgpu::TextureUsages::TEXTURE_BINDING |
+ wgpu::TextureUsages::COPY_SRC |
+ wgpu::TextureUsages::RENDER_ATTACHMENT,
+ label: None
+ };
+ self.new_texture(descr)
+ }
+}
diff --git a/src/eguictx.rs b/src/eguictx.rs
new file mode 100644
index 0000000..572d352
--- /dev/null
+++ b/src/eguictx.rs
@@ -0,0 +1,143 @@
+use egui::{Context};
+use egui_wgpu::Renderer;
+use egui_winit::{EventResponse, State};
+use wgpu::{CommandEncoder, CommandEncoderDescriptor, Operations, RenderPassDescriptor, TextureView};
+use winit::{event::WindowEvent, window::{Theme, Window}};
+
+use crate::wgpuctx::WgpuCtx;
+
+
+pub struct EguiCtx {
+ state: State,
+ renderer: Renderer,
+ context: Context,
+ ready: bool
+}
+
+impl EguiCtx
+{
+ pub fn new(
+ window: &winit::window::Window,
+ wgpuctx: &WgpuCtx)
+ -> Self {
+ let ctx = egui::Context::default();
+
+ let viewport_id = ctx.viewport_id();
+ let state = egui_winit::State::new(
+ ctx.clone(),
+ viewport_id,
+ window,
+ Some(window.scale_factor() as _),
+ Some(Theme::Light),
+ None);
+
+ let renderer = Renderer::new(
+ wgpuctx.device(),
+ wgpuctx.surface_config().format,
+ egui_wgpu::RendererOptions {
+ depth_stencil_format: None,
+ msaa_samples: 1,
+ ..Default::default()
+ }
+ );
+
+ Self {
+ context: ctx,
+ state: state,
+ renderer: renderer,
+ ready: false
+ }
+ }
+
+ pub fn context(&self) -> &Context
+ { self.state.egui_ctx() }
+
+ pub fn window_event(
+ &mut self,
+ window: &Window,
+ event: &WindowEvent)
+ -> EventResponse
+ {
+ self.state.on_window_event(window, event)
+ }
+
+ pub fn prepare(
+ &mut self,
+ window: &Window)
+ {
+ let raw_input = self.state.take_egui_input(window);
+ self.context().begin_pass(raw_input);
+ self.ready = true;
+ }
+
+ pub fn present(
+ &mut self,
+ window: &Window,
+ wgpuctx: &WgpuCtx,
+ encoder: &mut CommandEncoder,
+ view: &TextureView)
+
+ {
+ if !self.ready {
+ return;
+ }
+ self.ready = false;
+
+ let full_output = self.context().end_pass();
+ self.state.handle_platform_output(window, full_output.platform_output);
+
+ let jobs = self.context().tessellate(
+ full_output.shapes,
+ full_output.pixels_per_point
+ );
+
+ let screen_descriptor = {
+ let view_size = view.texture().size();
+ let (width, height) = (view_size.width, view_size.height);
+ egui_wgpu::ScreenDescriptor {
+ size_in_pixels: [width, height],
+ pixels_per_point: full_output.pixels_per_point
+ }
+ };
+
+ for (id, image_delta) in full_output.textures_delta.set {
+ self.renderer.update_texture(wgpuctx.device(), wgpuctx.queue(), id, &image_delta);
+ }
+
+ self.renderer.update_buffers(
+ wgpuctx.device(),
+ wgpuctx.queue(),
+ encoder,
+ &jobs,
+ &screen_descriptor
+ );
+
+ {
+ let mut render_pass = encoder.begin_render_pass(&RenderPassDescriptor {
+ label: Some("EguiCtx render pass"),
+ color_attachments: &[Some(wgpu::RenderPassColorAttachment {
+ view: view,
+ resolve_target: None,
+ ops: Operations {
+ load: wgpu::LoadOp::Load,
+ store: wgpu::StoreOp::Store
+ },
+ depth_slice: None
+ })],
+ depth_stencil_attachment: None,
+ timestamp_writes: None,
+ occlusion_query_set: None
+ });
+
+ self.renderer.render(
+ &mut render_pass.forget_lifetime(),
+ &jobs,
+ &screen_descriptor
+ );
+ }
+
+ for id in &full_output.textures_delta.free {
+ self.renderer.free_texture(id);
+ }
+ }
+}
diff --git a/src/known_stars.rs b/src/known_stars.rs
new file mode 100644
index 0000000..cf78aa2
--- /dev/null
+++ b/src/known_stars.rs
@@ -0,0 +1,24 @@
+use phf::phf_map;
+use std::fmt;
+use std::error::Error;
+
+#[derive(Debug)]
+pub struct StarNotFoundError
+{
+ pub star: &'static str
+}
+
+impl fmt::Display for StarNotFoundError {
+ fn fmt(
+ &self,
+ f: &mut fmt::Formatter<'_>)
+ -> fmt::Result {
+ write!(f, "Star {:0} is not known!", self.star)
+ }
+}
+
+impl Error for StarNotFoundError {}
+
+pub static KNOWN_STARS: phf::Map<&'static str, &'static str> = phf_map! {
+ "sol" => include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/assets/systems/sol.csv"))
+};
diff --git a/src/main.rs b/src/main.rs
new file mode 100644
index 0000000..b6efdcd
--- /dev/null
+++ b/src/main.rs
@@ -0,0 +1,178 @@
+mod window;
+mod tacmap;
+mod wgpuctx;
+mod eguictx;
+mod vertex;
+mod texture;
+mod canvas;
+mod solar_system;
+mod known_stars;
+mod timeman;
+
+use std::cell::RefCell;
+use std::thread;
+use std::time::{Duration, Instant};
+
+use winit::event::{KeyEvent, WindowEvent};
+use winit::event_loop::{EventLoop, ActiveEventLoop, ControlFlow};
+use winit::error::{EventLoopError};
+use winit::application::{ApplicationHandler};
+use winit::keyboard::PhysicalKey;
+use winit::window::{Window, WindowId, WindowAttributes};
+
+use solar_system::SolarSystem;
+
+use crate::timeman::TimeMan;
+use crate::window::GameWindow;
+
+const TARGET_FPS: f32 = 100.0;
+const TARGET_DT: Duration = Duration::from_millis((1000.0 / TARGET_FPS) as u64);
+
+struct SystemicApp
+{
+ window: Option<GameWindow>,
+ wgpu_instance: wgpu::Instance,
+
+ last_render_time: Instant,
+ game_state: Option<RefCell<GameState>>
+}
+
+struct GameState
+{
+ timeman: TimeMan,
+ solar_systems: Vec<SolarSystem>
+}
+
+impl SystemicApp
+{
+ fn create_window(
+ &mut self,
+ event_loop: &ActiveEventLoop)
+ {
+ self.window = Some(GameWindow::new(
+ &self.wgpu_instance,
+ event_loop).unwrap());
+
+ self.game_state = Some(RefCell::new(GameState::new()));
+ }
+}
+
+impl GameState
+{
+ pub fn new()
+ -> Self {
+ let timeman = TimeMan::new(0);
+ let sol_system = match SolarSystem::new_from_known_star("sol")
+ {
+ Ok(system) => system,
+ Err(e) => panic!("Unable to create sol system : {}", e)
+ };
+
+ Self {
+ timeman: timeman,
+ solar_systems: vec![ sol_system ]
+ }
+ }
+
+ pub fn solar_systems(&self) -> &[SolarSystem]
+ { self.solar_systems.as_slice() }
+
+ pub fn timeman(&self) -> &TimeMan
+ { &self.timeman }
+
+ pub fn timeman_mut(&mut self) -> &mut TimeMan
+ { &mut self.timeman }
+}
+
+impl ApplicationHandler for SystemicApp
+{
+ fn resumed(
+ &mut self,
+ event_loop: &ActiveEventLoop
+ ) {
+ match self.window {
+ Some(_) => {}
+ None => self.create_window(event_loop)
+ }
+ }
+
+ fn window_event(
+ &mut self,
+ event_loop: &ActiveEventLoop,
+ window_id: WindowId,
+ event: winit::event::WindowEvent,
+ ) {
+ let window = match &mut self.window {
+ Some(w) => w,
+ None => return
+ };
+
+ let game_state = match &self.game_state {
+ Some(state) => state,
+ None => return
+ };
+
+ window.on_event(&event);
+
+ match event {
+ WindowEvent::CloseRequested => {
+ event_loop.exit();
+ }
+ WindowEvent::Resized(size) => {
+ window.resize(size.width, size.height);
+ }
+ WindowEvent::RedrawRequested => {
+ let now = Instant::now();
+ let delta_time = now - self.last_render_time;
+ self.last_render_time = now;
+
+ game_state.borrow_mut().timeman.update();
+ {
+ window.update(game_state, delta_time);
+ }
+ match window.render(game_state) {
+ Ok(_) => {}
+ Err(wgpu::SurfaceError::Outdated | wgpu::SurfaceError::Lost) => {
+ let size = window.size();
+ window.resize(size.width, size.height);
+ },
+ Err(e) => {
+ log::error!("Unable to render: {}", e);
+ }
+ }
+ if delta_time < TARGET_DT {
+ thread::sleep(TARGET_DT - delta_time);
+ }
+ }
+ WindowEvent::KeyboardInput {
+ event: KeyEvent {
+ physical_key: PhysicalKey::Code(key_code),
+ state: key_state,
+ ..
+ },
+ ..
+ } => {
+ window.keyboard_input(game_state, key_code, key_state);
+ }
+ _ => {}
+ }
+ }
+} // impl ApplicationHandler for SystemicApp
+
+fn main()
+ -> Result<(), EventLoopError>
+{
+ env_logger::init();
+ let event_loop = EventLoop::new().unwrap();
+
+ event_loop.set_control_flow(ControlFlow::Poll);
+
+ let mut app = SystemicApp {
+ window: None,
+ wgpu_instance: wgpu::Instance::default(),
+ last_render_time: Instant::now(),
+ game_state: None
+ };
+
+ event_loop.run_app(&mut app)
+}
diff --git a/src/solar_system.rs b/src/solar_system.rs
new file mode 100644
index 0000000..e8137f0
--- /dev/null
+++ b/src/solar_system.rs
@@ -0,0 +1,161 @@
+use serde::{Deserialize};
+use crate::{known_stars::{KNOWN_STARS, StarNotFoundError}, timeman::Second};
+use std::error::Error;
+
+const GRAVITATIONAL_CONSTANT: f64 = 6.67408e-11;
+
+pub type Kilograms = f64;
+pub type Kilometers = f64;
+pub type Percentage = f64;
+pub type Angle = f64;
+
+pub type BodyId = usize;
+pub type SystemId = usize;
+
+#[derive(Debug, Deserialize)]
+pub struct SerialOrbitalBody
+{
+ name: String,
+ orbits: BodyId,
+ mass: Kilograms,
+ radius: Kilometers,
+
+ eccentricity: Percentage,
+ inclination: Angle,
+ long_asc_node: Angle,
+ long_periapsis: Angle,
+ sgp: f64,
+ mean_long: Angle,
+
+ semi_major_axis: Kilometers,
+}
+
+pub struct OrbitalBody
+{
+ body: SerialOrbitalBody,
+ position: Option<(Second, cgmath::Point3<Kilometers>)>
+}
+
+pub struct SolarSystem
+{
+ name: String,
+ bodies: Vec<OrbitalBody>,
+}
+
+impl SolarSystem
+{
+ pub fn new_from_csv(
+ data: &'static str)
+ -> Result<Self, Box<dyn Error>> {
+ let data_reader = stringreader::StringReader::new(data);
+ let mut body_reader = csv::Reader::from_reader(data_reader);
+
+ let mut bodies = Vec::<OrbitalBody>::new();
+
+ for result in body_reader.deserialize() {
+ let record: SerialOrbitalBody = result?;
+
+ println!("New body: {:?}", record);
+
+ bodies.push(OrbitalBody { body: record, position: None });
+ }
+
+ Ok(Self {
+ name: bodies[0].name().clone(),
+ bodies: bodies,
+ })
+ }
+
+ pub fn new_from_known_star(
+ star: &'static str)
+ -> Result<Self, Box<dyn Error>> {
+ let star_csv = match KNOWN_STARS.get(star).copied() {
+ Some(csv) => csv,
+ None => return Err(Box::new(StarNotFoundError { star: star }))
+ };
+ SolarSystem::new_from_csv(star_csv)
+ }
+
+ pub fn name(&self) -> &String { &self.name }
+
+ pub fn bodies(&self)
+ -> &[OrbitalBody]
+ {
+ self.bodies.as_slice()
+ }
+}
+
+impl OrbitalBody
+{
+ pub fn name(&self) -> &String { &self.body.name }
+ pub fn radius(&self) -> f32 { self.body.radius as f32 }
+
+ pub fn position(&self, time: Second)
+ -> Option<cgmath::Point3<Kilometers>>
+ {
+ match self.position {
+ Some((cache_time, pos)) => {
+ if time == cache_time {
+ return Some(pos);
+ }
+ return None;
+ },
+ None => None
+ }
+ }
+
+ fn calculate_orbit(
+ &self,
+ time: i64)
+ -> cgmath::Point3<Kilometers> {
+ cgmath::Point3 { x: 0.0, y: 0.0, z: 0.0 }
+ /*let arg_periapsis = self.long_periapsis - self.long_asc_node;
+
+
+ let mean_angular_motion: f64 = (
+ self.sgp as f64 / (self.semi_major_axis as f64).powf(3.0)
+ ).sqrt();
+
+ let mean_anomaly = (self.mean_long - self.long_periapsis) + (mean_angular_motion * time as f64) as f32;
+
+ let mut eccentric_anomaly = mean_anomaly + self.eccentricity * mean_anomaly.sin();
+ for _ in 0..100 {
+ let new_eccentric = eccentric_anomaly +
+ (mean_anomaly - eccentric_anomaly + self.eccentricity * eccentric_anomaly.sin()) /
+ (1.0 - self.eccentricity * eccentric_anomaly.cos());
+ if (new_eccentric - eccentric_anomaly).abs() < 1e-6 {
+ eccentric_anomaly = new_eccentric;
+ break;
+ }
+ eccentric_anomaly = new_eccentric;
+ }
+
+ let beta = self.eccentricity / 1.0 + (1.0 - self.eccentricity * self.eccentricity).sqrt();
+ let true_anomaly = eccentric_anomaly + 2.0 *
+ ((beta * eccentric_anomaly.sin()) / (1.0 - beta * eccentric_anomaly.cos())).atan();
+
+ let radius: f64 = self.semi_major_axis * (1.0 - self.eccentricity * eccentric_anomaly.cos()) as f64;
+
+ let ohm_sin = self.long_asc_node.sin();
+ let ohm_cos = self.long_asc_node.cos();
+
+ let inc_sin = self.inclination.sin();
+ let inc_cos = self.inclination.cos();
+
+ let per_anom_sin = (arg_periapsis + true_anomaly).sin();
+ let per_anom_cos = (arg_periapsis + true_anomaly).cos();
+
+ let x: f32 = (radius as f32) * (
+ (ohm_cos * per_anom_cos) -
+ (ohm_sin * per_anom_sin * inc_cos));
+ let y: f32 = (radius as f32) * (
+ (ohm_sin * per_anom_cos) +
+ (ohm_cos * per_anom_sin * inc_cos));
+ let z: f32 = (radius as f32) * (inc_sin * per_anom_sin);
+
+ OrbitState {
+ position: cgmath::Vector3::new(x, y, z)
+ }*/
+ }
+}
+
diff --git a/src/tacmap.rs b/src/tacmap.rs
new file mode 100644
index 0000000..89d21aa
--- /dev/null
+++ b/src/tacmap.rs
@@ -0,0 +1,158 @@
+pub mod camera;
+pub mod render;
+
+mod tacmap {
+ pub use super::render;
+ pub use super::camera;
+}
+
+use std::cell::RefCell;
+use std::time::Duration;
+
+use winit::event::ElementState;
+use winit::keyboard::KeyCode;
+
+use crate::GameState;
+use crate::canvas::Canvas;
+use crate::solar_system::SolarSystem;
+use crate::solar_system::SystemId;
+use crate::wgpuctx::WgpuCtx;
+use render::*;
+use camera::*;
+use crate::window::ui::GameWindowUiState;
+
+pub struct TacticalMap
+{
+ canvas: Canvas,
+ camera: Camera,
+ pmatrix: Projection,
+
+ camera_controller: CameraController,
+ renderstate: Option<(SystemId, BodyRenderer)>
+}
+
+impl TacticalMap
+{
+ pub fn new(
+ wgpuctx: &WgpuCtx,
+ position: winit::dpi::LogicalPosition<f32>,
+ size: winit::dpi::LogicalSize<f32>)
+ -> Self {
+ let surface_size = winit::dpi::PhysicalSize::new(
+ wgpuctx.surface_config().width,
+ wgpuctx.surface_config().height
+ );
+
+ let canvas = Canvas::new(
+ wgpuctx,
+ surface_size,
+ position,
+ size).unwrap();
+
+ let camera = Camera::new(
+ wgpuctx,
+ cgmath::Point3::<f32>::new(0.0, 0.0, 1.0),
+ cgmath::Deg(-90.0),
+ cgmath::Rad(0.0));
+
+ let projection = Projection::new(
+ surface_size.width,
+ surface_size.height,
+ cgmath::Deg(60.0),
+ (0.1, 1000.0));
+
+ Self {
+ canvas: canvas,
+ camera: camera,
+ pmatrix: projection,
+ camera_controller: CameraController::new(),
+ renderstate: None,
+ }
+ }
+
+ pub fn resize(
+ &mut self,
+ wgpuctx: &WgpuCtx,
+ width: u32,
+ height: u32)
+ {
+ self.canvas.resize(wgpuctx, width, height);
+ self.pmatrix.resize(width, height);
+ }
+
+ pub fn update(
+ &mut self,
+ game_state: &RefCell<GameState>,
+ ui_state: &mut GameWindowUiState,
+ dt: Duration)
+ {
+ self.camera_controller.update(&mut self.camera, dt);
+ ui_state.camera_scale = self.camera.get_scale();
+ }
+
+ pub fn keyboard_input(
+ &mut self,
+ game_state: &RefCell<GameState>,
+ key_code: KeyCode,
+ key_state: ElementState)
+ {
+ self.camera_controller.keyboard_input(key_code, key_state);
+ }
+
+ pub fn draw(
+ &mut self,
+ wgpuctx: &WgpuCtx,
+ game_state: &RefCell<GameState>,
+ current_system: Option<SystemId>)
+ -> Result<(), wgpu::SurfaceError>
+ {
+ let game_state = game_state.borrow();
+
+ let current_system_id = match current_system {
+ Some(system) => system,
+ None => return Ok(())
+ };
+ let solar_systems = game_state.solar_systems();
+ let current_system = &solar_systems[current_system_id];
+
+ self.camera.update_buffer(wgpuctx, &self.pmatrix);
+
+ let tacrender = match &mut self.renderstate {
+ Some((id, render)) => {
+ if *id != current_system_id {
+ *id = current_system_id;
+ render.mark_to_rebuild();
+ }
+ render
+ },
+ None => {
+ let tmp = render::BodyRenderer::new(wgpuctx);
+ self.renderstate = Some((current_system_id, tmp));
+ &mut self.renderstate.as_mut().unwrap().1
+ }
+ };
+
+ //Update buffers for the current system and time.
+ tacrender.rebuild(wgpuctx, current_system);
+ match tacrender.update(
+ wgpuctx, current_system,
+ game_state.timeman().seconds())
+ {
+ Ok(()) => {},
+ Err(e) => println!("Tactical map render update error: {}", e)
+ }
+
+ tacrender.render(wgpuctx, &self.canvas, &self.camera);
+
+ Ok(())
+ }
+
+ pub fn present(
+ &mut self,
+ screen_pass: &mut wgpu::RenderPass)
+ -> Result<(), wgpu::SurfaceError>
+ {
+ self.canvas.present(screen_pass)?;
+ Ok(())
+ }
+} // impl SystemViewport
diff --git a/src/tacmap/camera.rs b/src/tacmap/camera.rs
new file mode 100644
index 0000000..1a2787b
--- /dev/null
+++ b/src/tacmap/camera.rs
@@ -0,0 +1,272 @@
+use std::time::Duration;
+
+use cgmath::{InnerSpace, Point3, Rad, Vector2, Vector3, Vector4, Zero, perspective};
+use winit::{event::ElementState, keyboard::KeyCode};
+
+use crate::{solar_system::BodyId, wgpuctx::WgpuCtx};
+
+pub const OPENGL_TO_WGPU_MATRIX: cgmath::Matrix4<f32> = cgmath::Matrix4::from_cols(
+ Vector4::new(1.0, 0.0, 0.0, 0.0),
+ Vector4::new(0.0, 1.0, 0.0, 0.0),
+ Vector4::new(0.0, 0.0, 0.5, 0.0),
+ Vector4::new(0.0, 0.0, 0.5, 1.0)
+);
+
+pub struct Camera
+{
+ position: Point3<f32>,
+ pitch: Rad<f32>,
+ yaw: Rad<f32>,
+ scale: f32,
+
+ target: Option<BodyId>,
+
+ buffer: wgpu::Buffer,
+ staging_buffer: wgpu::Buffer,
+ bindgroup: wgpu::BindGroup
+}
+
+pub struct CameraController
+{
+ position_pos_delta: Vector3<f32>,
+ position_neg_delta: Vector3<f32>,
+ rotation_pos_delta: Vector2<f32>,
+ rotation_neg_delta: Vector2<f32>,
+ scale_delta: Vector2<f32>,
+}
+
+#[repr(C)]
+#[derive(Debug, Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
+pub struct CameraUniform
+{
+ view: [[f32;4];4],
+ proj: [[f32;4];4],
+ scale: f32
+}
+
+pub struct Projection
+{
+ aspect: f32,
+ fovy: Rad<f32>,
+ clip: (f32, f32)
+}
+
+impl Camera
+{
+ pub fn new<
+ V: Into<Point3<f32>>,
+ Y: Into<Rad<f32>>,
+ P: Into<Rad<f32>>
+ >(
+ wgpuctx: &WgpuCtx,
+ position: V,
+ yaw: Y,
+ pitch: P)
+ -> Self {
+ use wgpu::BufferUsages;
+ let buffer = wgpuctx.create_buffer(
+ &wgpu::BufferDescriptor {
+ label: Some("Camera buffer"),
+ size: 144,
+ usage: BufferUsages::UNIFORM | BufferUsages::COPY_DST,
+ mapped_at_creation: false
+ }
+ );
+
+ let staging_buffer = wgpuctx.create_buffer(
+ &wgpu::wgt::BufferDescriptor {
+ label: Some("Camera staging buffer"),
+ size: 144,
+ usage: BufferUsages::COPY_SRC | BufferUsages::COPY_DST,
+ mapped_at_creation: false
+ }
+ );
+
+ let bind_group_layout = Camera::bindgroup_layout(wgpuctx);
+ let bind_group = wgpuctx.device().create_bind_group(
+ &wgpu::BindGroupDescriptor {
+ label: Some("Camera bind group"),
+ layout: &bind_group_layout,
+ entries: &[
+ wgpu::BindGroupEntry {
+ binding: 0,
+ resource: buffer.as_entire_binding()
+ }
+ ]
+ }
+ );
+ Self {
+ position: position.into(),
+ yaw: yaw.into(),
+ pitch: pitch.into(),
+ scale: 1.0,
+ target: None,
+ buffer: buffer,
+ staging_buffer: staging_buffer,
+ bindgroup: bind_group
+ }
+ }
+
+ pub fn get_scale(&self) -> f32
+ { self.scale }
+
+ pub fn bindgroup_layout(wgpuctx: &WgpuCtx)
+ -> wgpu::BindGroupLayout
+ {
+ wgpuctx.device().create_bind_group_layout(
+ &wgpu::BindGroupLayoutDescriptor {
+ label: Some("Camera bind group layout"),
+ entries: &[
+ wgpu::BindGroupLayoutEntry {
+ binding: 0,
+ visibility: wgpu::ShaderStages::VERTEX,
+ ty: wgpu::BindingType::Buffer {
+ ty: wgpu::BufferBindingType::Uniform,
+ has_dynamic_offset: false,
+ min_binding_size: None
+ },
+ count: None
+ }
+ ]
+ }
+ )
+ }
+
+ pub fn bindgroup(&self)
+ -> &wgpu::BindGroup
+ { &self.bindgroup }
+
+ pub fn stage_changes(
+ &self,
+ encoder: &mut wgpu::CommandEncoder)
+ {
+ let buffer_size = size_of::<CameraUniform>();
+ encoder.copy_buffer_to_buffer(&self.staging_buffer, 0, &self.buffer, 0, Some(buffer_size as u64));
+ }
+
+ pub fn update_buffer(
+ &self,
+ wgpuctx: &WgpuCtx,
+ projection: &Projection)
+ {
+ wgpuctx.queue().write_buffer(&self.staging_buffer, 0, bytemuck::cast_slice(&[self.uniform(projection)]));
+ }
+
+ pub fn view_matrix(
+ &self)
+ -> cgmath::Matrix4<f32> {
+
+ let (yaw_sin, yaw_cos) = self.yaw.0.sin_cos();
+ let (pitch_sin, pitch_cos) = self.pitch.0.sin_cos();
+
+ cgmath::Matrix4::look_to_rh(
+ self.position,
+ Vector3::new(
+ pitch_cos * yaw_cos,
+ pitch_sin,
+ pitch_cos * yaw_sin
+ ).normalize(),
+ Vector3::unit_y()
+ )
+ }
+
+ pub fn uniform(
+ &self,
+ projection: &Projection)
+ -> CameraUniform
+ {
+ CameraUniform {
+ view: (self.view_matrix()).into(),
+ proj: (OPENGL_TO_WGPU_MATRIX * projection.projection_matrix()).into(),
+ scale: self.scale
+ }
+ }
+} //impl Camera
+
+impl CameraController
+{
+ pub fn new() -> Self {
+ Self {
+ position_pos_delta: Vector3::new(0.0, 0.0, 0.0),
+ position_neg_delta: Vector3::new(0.0, 0.0, 0.0),
+ rotation_pos_delta: Vector2::new(0.0, 0.0),
+ rotation_neg_delta: Vector2::new(0.0, 0.0),
+ scale_delta: Vector2::new(0.0, 0.0),
+ }
+ }
+
+ pub fn keyboard_input(
+ &mut self,
+ key_code: KeyCode,
+ key_state: ElementState)
+ {
+ let press_q = if key_state == ElementState::Pressed { 1.0 } else { 0.0 };
+ match key_code {
+ KeyCode::KeyW => { self.position_pos_delta.z = press_q; },
+ KeyCode::KeyS => { self.position_neg_delta.z = press_q; },
+ KeyCode::KeyA => { self.position_neg_delta.x = press_q; },
+ KeyCode::KeyD => { self.position_pos_delta.x = press_q; },
+ KeyCode::Space => { self.position_pos_delta.y = press_q; },
+ KeyCode::ShiftLeft => { self.position_neg_delta.y = press_q; },
+
+ KeyCode::KeyQ => { self.scale_delta.x = press_q; },
+ KeyCode::KeyE => { self.scale_delta.y = press_q; },
+ _ => {}
+ }
+ }
+
+ pub fn update(
+ &mut self,
+ camera: &mut Camera,
+ dt: Duration)
+ {
+ let dt = dt.as_secs_f32();
+ let speed = 1.0;
+
+ camera.pitch.0 += (self.rotation_pos_delta.x - self.rotation_neg_delta.x) * speed * dt;
+ camera.yaw.0 += (self.rotation_pos_delta.y - self.rotation_neg_delta.y) * speed * dt;
+
+ let (yaw_sin, yaw_cos) = camera.yaw.0.sin_cos();
+ let forward = Vector3::new(yaw_cos, 0.0, yaw_sin).normalize();
+ let right = Vector3::new(-yaw_sin, 0.0, yaw_cos).normalize();
+ let up = Vector3::new(0.0, 1.0, 0.0);
+
+ camera.position += forward * (self.position_pos_delta.z - self.position_neg_delta.z) * speed * dt;
+ camera.position += right * (self.position_pos_delta.x - self.position_neg_delta.x) * speed * dt;
+ camera.position += up * (self.position_pos_delta.y - self.position_neg_delta.y) * speed * dt;
+
+ camera.scale *= 1.0 + ((self.scale_delta.y - self.scale_delta.x) * 0.1);
+ camera.scale = f32::max(1e-16, f32::min(1.0, camera.scale));
+ }
+}
+
+impl Projection {
+ pub fn new<
+ F: Into<Rad<f32>>
+ >(
+ width: u32,
+ height: u32,
+ fovy: F,
+ clip: (f32, f32))
+ -> Self {
+ Self {
+ aspect: width as f32 / height as f32,
+ fovy: fovy.into(),
+ clip: clip
+ }
+ }
+
+ pub fn resize(
+ &mut self,
+ width: u32,
+ height: u32)
+ {
+ self.aspect = width as f32 / height as f32;
+ }
+
+ pub fn projection_matrix(
+ &self)
+ -> cgmath::Matrix4<f32> {
+ perspective(self.fovy, self.aspect, self.clip.0, self.clip.1)
+ }
+}
diff --git a/src/tacmap/render.rs b/src/tacmap/render.rs
new file mode 100644
index 0000000..c22878e
--- /dev/null
+++ b/src/tacmap/render.rs
@@ -0,0 +1,233 @@
+use std::{fmt::Display, num::NonZero};
+use std::error::Error;
+
+use crate::canvas::Canvas;
+use crate::solar_system::Kilometers;
+use crate::tacmap::camera::Camera;
+use crate::wgpuctx::RenderPassBuilder;
+use crate::{solar_system::{SolarSystem, SystemId}, timeman::Second, vertex::{self, Vertex}, wgpuctx::{WgpuCtx, pipeline::RenderPipelineBuilder}};
+
+struct BodyInstance
+{
+ position: cgmath::Point3<Kilometers>,
+ radius: f32
+}
+
+#[repr(C)]
+#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
+struct BodyInstanceRaw
+{
+ position: [f32;3],
+ radius: f32
+}
+
+#[derive(Debug, Clone)]
+pub struct NeedsRebuildError;
+
+impl Display for NeedsRebuildError {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ write!(f, "RenderState needs to be rebuilt before updating!")
+ }
+}
+
+impl Error for NeedsRebuildError {}
+
+pub struct BodyRenderer
+{
+ needs_rebuild: bool,
+ last_time: Option<Second>,
+
+ pipeline: wgpu::RenderPipeline,
+
+ body_vertex_buffer: wgpu::Buffer,
+ body_instance_buffer: Option<(usize, wgpu::Buffer)>
+}
+
+impl BodyRenderer
+{
+ pub fn new(
+ wgpuctx: &WgpuCtx)
+ -> Self {
+ let quad_vertices = vertex::QUAD_VERTICES;
+
+ let body_vertex_buffer = wgpuctx.create_buffer_init(
+ &wgpu::util::BufferInitDescriptor {
+ label: None,
+ contents: bytemuck::cast_slice(quad_vertices),
+ usage: wgpu::BufferUsages::VERTEX
+ }
+ );
+
+ let shader = wgpuctx.create_shader(
+ wgpu::include_wgsl!(concat!(
+ env!("CARGO_MANIFEST_DIR"),
+ "/assets/shaders/tacbody.wgsl")
+ ));
+
+ let render_pipeline = RenderPipelineBuilder::new(&shader)
+ .add_bindgroup(&Camera::bindgroup_layout(wgpuctx))
+ .add_vertex_layout(Vertex::descr())
+ .add_vertex_layout(BodyInstanceRaw::descr())
+ .build(Some("Tactical map render pipeline"), wgpuctx);
+
+ Self {
+ needs_rebuild: true,
+ last_time: None,
+ pipeline: render_pipeline,
+ body_vertex_buffer,
+ body_instance_buffer: None
+ }
+ }
+
+ pub fn mark_to_rebuild(&mut self)
+ { self.needs_rebuild = true; }
+
+ pub fn rebuild(
+ &mut self,
+ wgpuctx: &WgpuCtx,
+ solar_system: &SolarSystem)
+ {
+ if self.body_instance_buffer.is_some() && !self.needs_rebuild {
+ return;
+ }
+
+ match self.body_instance_buffer.as_mut() {
+ Some(buffer) => {
+ buffer.1.destroy();
+ self.body_instance_buffer = None;
+ }
+ None => {}
+ }
+
+ let bodies = solar_system.bodies();
+ let buffer_len = bodies.len() * size_of::<BodyInstanceRaw>();
+
+ let buffer = wgpuctx.create_buffer(
+ &wgpu::BufferDescriptor {
+ label: Some("Tactical map bodies instance buffer"),
+ size: buffer_len as u64,
+ usage: wgpu::BufferUsages::COPY_DST |
+ wgpu::BufferUsages::VERTEX,
+ mapped_at_creation: false
+ }
+ );
+
+ self.last_time = None;
+ self.body_instance_buffer = Some((bodies.len(), buffer));
+ }
+
+ pub fn update(
+ &mut self,
+ wgpuctx: &WgpuCtx,
+ solar_system: &SolarSystem,
+ time: Second)
+ -> Result<(), Box<dyn Error>>
+ {
+ //If the last updated time is the same, we don't need to update
+ //the positions of all the bodies.
+ match self.last_time {
+ Some(last_time) => {
+ if last_time == time {
+ return Ok(());
+ }
+ }
+ None => {}
+ }
+
+ self.last_time = Some(time);
+ let (_, bodies_buffer) = match &self.body_instance_buffer {
+ Some(tuple) => tuple,
+ None => return Err(Box::new(NeedsRebuildError))
+ };
+
+ let bodies = solar_system.bodies();
+ let body_instances = bodies.iter().map(|body| {
+ let position = match body.position(time) {
+ Some(pos) => pos,
+ None => return BodyInstanceRaw {
+ position: [0.0, 0.0, 0.0],
+ radius: body.radius() }
+ };
+ BodyInstance {
+ position: position,
+ radius: body.radius()
+ }.raw()
+ }).collect::<Vec<_>>();
+
+ wgpuctx.queue().write_buffer(bodies_buffer, 0, bytemuck::cast_slice(&body_instances));
+ //Update the canvas texture.
+
+ Ok(())
+ }
+
+ pub fn render(
+ &self,
+ wgpuctx: &WgpuCtx,
+ canvas: &Canvas,
+ camera: &Camera)
+ {
+ let (num_bodies, bodies_buffer) = match &self.body_instance_buffer {
+ Some(tuple) => tuple,
+ None => return
+ };
+
+ let mut encoder = wgpuctx.create_default_encoder("Tactical map renderer encoder");
+ let view = canvas.view();
+
+ {
+ camera.stage_changes(&mut encoder);
+ }
+ {
+ let mut pass = RenderPassBuilder::new("Tactiacal map render pass", view)
+ .clear_color(wgpu::Color { r: 0.0, g: 0.0, b: 0.1, a: 1.0 })
+ .build(&mut encoder);
+
+ pass.set_pipeline(&self.pipeline);
+ pass.set_bind_group(0, camera.bindgroup(), &[]);
+
+ pass.set_vertex_buffer(0, self.body_vertex_buffer.slice(..));
+ pass.set_vertex_buffer(1, bodies_buffer.slice(..));
+
+ pass.draw(0..6, 0..num_bodies.clone() as _);
+ }
+
+ wgpuctx.submit_encoder(encoder);
+ }
+} // impl RenderState
+
+impl BodyInstance
+{
+ fn raw(&self) -> BodyInstanceRaw
+ {
+ BodyInstanceRaw {
+ position: [
+ self.position.x as f32,
+ self.position.y as f32,
+ self.position.z as f32 ],
+ radius: self.radius
+ }
+ }
+} // impl BodyInstance
+
+impl BodyInstanceRaw
+{
+ fn descr() -> wgpu::VertexBufferLayout<'static> {
+ use std::mem;
+ wgpu::VertexBufferLayout {
+ array_stride: mem::size_of::<BodyInstanceRaw>() as wgpu::BufferAddress,
+ step_mode: wgpu::VertexStepMode::Instance,
+ attributes: &[
+ wgpu::VertexAttribute {
+ offset: 0,
+ shader_location: 2,
+ format: wgpu::VertexFormat::Float32x3
+ },
+ wgpu::VertexAttribute {
+ offset: mem::size_of::<[f32;3]>() as wgpu::BufferAddress,
+ shader_location: 3,
+ format: wgpu::VertexFormat::Float32
+ }
+ ]
+ }
+ }
+}
diff --git a/src/texture.rs b/src/texture.rs
new file mode 100644
index 0000000..be07dd3
--- /dev/null
+++ b/src/texture.rs
@@ -0,0 +1,96 @@
+use crate::wgpuctx::WgpuCtx;
+
+pub struct Texture
+{
+ texture: wgpu::Texture,
+ view: wgpu::TextureView,
+ sampler: wgpu::Sampler,
+ pub bindgrouplayout: wgpu::BindGroupLayout,
+ bindgroup: wgpu::BindGroup,
+}
+
+impl Texture
+{
+ pub fn new(
+ device: &wgpu::Device,
+ descr: wgpu::TextureDescriptor)
+ -> Self
+ {
+ let texture = device.create_texture(&descr);
+ let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
+ let sampler = device.create_sampler(
+ &wgpu::SamplerDescriptor {
+ address_mode_u: wgpu::AddressMode::Repeat,
+ address_mode_v: wgpu::AddressMode::Repeat,
+ address_mode_w: wgpu::AddressMode::ClampToEdge,
+ mag_filter: wgpu::FilterMode::Linear,
+ min_filter: wgpu::FilterMode::Nearest,
+ mipmap_filter: wgpu::FilterMode::Nearest,
+ ..Default::default()
+ }
+ );
+
+ let bind_group_layout = device.create_bind_group_layout(
+ &wgpu::BindGroupLayoutDescriptor {
+ label: None,
+ entries: &[
+ wgpu::BindGroupLayoutEntry {
+ binding: 0,
+ visibility: wgpu::ShaderStages::FRAGMENT,
+ ty: wgpu::BindingType::Texture {
+ sample_type: wgpu::TextureSampleType::Float {
+ filterable: true
+ },
+ view_dimension: wgpu::TextureViewDimension::D2,
+ multisampled: false
+ },
+ count: None
+ },
+ wgpu::BindGroupLayoutEntry {
+ binding: 1,
+ visibility: wgpu::ShaderStages::FRAGMENT,
+ ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
+ count: None
+ }
+ ]
+ }
+ );
+
+ let bind_group = device.create_bind_group(
+ &wgpu::BindGroupDescriptor {
+ label: None,
+ layout: &bind_group_layout,
+ entries: &[
+ wgpu::BindGroupEntry {
+ binding: 0,
+ resource: wgpu::BindingResource::TextureView(&view)
+ },
+ wgpu::BindGroupEntry {
+ binding: 1,
+ resource: wgpu::BindingResource::Sampler(&sampler)
+ }
+ ]
+ }
+ );
+
+ Self {
+ texture: texture,
+ view: view,
+ sampler: sampler,
+ bindgrouplayout: bind_group_layout,
+ bindgroup: bind_group,
+ }
+ }
+
+ pub fn view(&self)
+ -> &wgpu::TextureView
+ { &self.view }
+
+ pub fn use_on_pass(
+ &self,
+ pass: &mut wgpu::RenderPass,
+ index: u32)
+ {
+ pass.set_bind_group(index, &self.bindgroup, &[]);
+ }
+} // impl Texture
diff --git a/src/timeman.rs b/src/timeman.rs
new file mode 100644
index 0000000..9d17999
--- /dev/null
+++ b/src/timeman.rs
@@ -0,0 +1,69 @@
+use std::cell::RefCell;
+use std::time::Duration;
+use std::{fmt::Display, string};
+use std::error::Error;
+
+use crate::GameState;
+use crate::window::ui::GameWindowUiState;
+
+pub type Second = u64;
+
+pub struct TimeMan
+{
+ time: Second,
+ auto_tick: Option<Second>
+}
+
+impl TimeMan
+{
+ pub fn new(time: Second)
+ -> Self {
+ Self {
+ time,
+ auto_tick: None
+ }
+ }
+
+ pub fn seconds(&self)
+ -> Second {
+ self.time
+ }
+
+ pub fn advance(
+ &mut self,
+ by: Second)
+ {
+ self.time += by;
+ }
+
+ pub fn update(
+ &mut self)
+ {
+ match self.auto_tick {
+ Some(advance) => { self.time += advance; },
+ None => {}
+ }
+ }
+
+ pub fn format_duration(time: Second) -> String
+ {
+ let seconds = time % 60;
+ let minutes = (time / 60) % 60;
+ let hours = (time / (60 * 60)) % 24;
+ let days = (time / (60 * 60 * 24)) % 365;
+ let years = time / (60 * 60 * 24 * 365);
+
+ if time == 0 {
+ return "0s".to_string();
+ }
+
+ format!("{}{}{}{}{}",
+ if seconds > 0 { format!("{}s", seconds) } else { format!("") },
+ if minutes > 0 { format!("{}m", minutes) } else { format!("") },
+ if hours > 0 { format!("{}h", hours) } else { format!("") },
+ if days > 0 { format!("{}d", days) } else { format!("") },
+ if years > 0 { format!("{}y", years) } else { format!("") })
+ }
+
+
+} //impl TimeMan
diff --git a/src/vertex.rs b/src/vertex.rs
new file mode 100644
index 0000000..aab7d02
--- /dev/null
+++ b/src/vertex.rs
@@ -0,0 +1,33 @@
+#[repr(C)]
+#[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)]
+pub struct Vertex
+{
+ pub position: [f32;3],
+ pub uv: [f32;2]
+}
+
+impl Vertex
+{
+ const ATTRIBS: [wgpu::VertexAttribute;2] =
+ wgpu::vertex_attr_array![0 => Float32x3, 1 => Float32x2];
+
+ pub fn descr()
+ -> wgpu::VertexBufferLayout<'static> {
+ use std::mem;
+
+ wgpu::VertexBufferLayout {
+ array_stride: mem::size_of::<Self>() as wgpu::BufferAddress,
+ step_mode: wgpu::VertexStepMode::Vertex,
+ attributes: &Self::ATTRIBS
+ }
+ }
+}
+
+pub const QUAD_VERTICES: &[Vertex] = &[
+ Vertex { position: [ -1.0, -1.0, 0.0 ], uv: [ 0.0, 1.0 ] },
+ Vertex { position: [ 1.0, -1.0, 0.0 ], uv: [ 1.0, 1.0 ] },
+ Vertex { position: [ 1.0, 1.0, 0.0 ], uv: [ 1.0, 0.0 ] },
+ Vertex { position: [ 1.0, 1.0, 0.0 ], uv: [ 1.0, 0.0 ] },
+ Vertex { position: [ -1.0, 1.0, 0.0 ], uv: [ 0.0, 0.0 ] },
+ Vertex { position: [ -1.0, -1.0, 0.0 ], uv: [ 0.0, 1.0 ] }
+];
diff --git a/src/wgpuctx/mod.rs b/src/wgpuctx/mod.rs
new file mode 100644
index 0000000..f8aa3ec
--- /dev/null
+++ b/src/wgpuctx/mod.rs
@@ -0,0 +1,245 @@
+use std::sync::Arc;
+use std::error::Error;
+use wgpu::util::DeviceExt;
+use winit::window::{Window};
+
+use crate::texture::Texture;
+
+pub mod pipeline;
+
+pub struct WgpuCtx
+{
+ surface: wgpu::Surface<'static>,
+ surface_conf: wgpu::SurfaceConfiguration,
+ surface_texture: Option<wgpu::SurfaceTexture>,
+ is_configured: bool,
+
+ adapter: wgpu::Adapter,
+ device: wgpu::Device,
+ queue: wgpu::Queue,
+}
+
+pub struct RenderPassBuilder<'encoder>
+{
+ label: &'static str,
+ view: &'encoder wgpu::TextureView,
+ clear_color: wgpu::Color
+}
+
+impl WgpuCtx
+{
+ pub async fn new(
+ instance: &wgpu::Instance,
+ window: Arc<Window>,
+ ) -> Self {
+ let surface = instance.create_surface(window.clone()).unwrap();
+
+ let adapter = instance
+ .request_adapter(&wgpu::RequestAdapterOptions {
+ compatible_surface: Some(&surface),
+ ..Default::default()
+ }).await.expect("Failed to find valid adapter");
+
+ let (device, queue) = adapter
+ .request_device(&wgpu::DeviceDescriptor {
+ label: None,
+ required_features: wgpu::Features::empty(),
+ required_limits: wgpu::Limits::downlevel_defaults(),
+ experimental_features: wgpu::ExperimentalFeatures::disabled(),
+ memory_hints: wgpu::MemoryHints::Performance,
+ trace: wgpu::Trace::Off
+ }).await.expect("Failed to find device and queue");
+
+ let size = window.inner_size();
+ let surface_config = surface.get_default_config(&adapter, size.width, size.height).unwrap();
+
+ Self {
+ surface: surface,
+ surface_conf: surface_config,
+ surface_texture: None,
+ is_configured: false,
+ adapter: adapter,
+ device: device,
+ queue: queue
+ }
+ }
+
+ pub fn resize(
+ &mut self,
+ width: u32,
+ height: u32)
+ {
+ self.surface_conf.width = width;
+ self.surface_conf.height = height;
+ self.surface.configure(&self.device, &self.surface_conf);
+ self.is_configured = true;
+ }
+
+ pub fn is_ready(
+ &self)
+ -> bool {
+ self.is_configured
+ }
+
+ pub fn surface_config(
+ &self)
+ -> wgpu::SurfaceConfiguration {
+ self.surface_conf.clone()
+ }
+
+ pub fn adapter(
+ &self)
+ -> &wgpu::Adapter {
+ &self.adapter
+ }
+
+ pub fn device(
+ &self)
+ -> &wgpu::Device {
+ &self.device
+ }
+
+ pub fn queue(
+ &self)
+ -> &wgpu::Queue {
+ &self.queue
+ }
+
+ pub fn prepare_surface(
+ &mut self,
+ view_descr: &wgpu::TextureViewDescriptor)
+ -> Result<wgpu::TextureView, wgpu::SurfaceError>
+ {
+ let texture = self.surface.get_current_texture()?;
+ let view = texture.texture.create_view(view_descr);
+
+ self.surface_texture = Some(texture);
+ Ok(view)
+ }
+
+ pub fn create_encoder(
+ &self,
+ descr: &wgpu::CommandEncoderDescriptor)
+ -> wgpu::CommandEncoder
+ {
+ self.device.create_command_encoder(descr)
+ }
+
+ pub fn create_default_encoder(
+ &self,
+ label: &'static str)
+ -> wgpu::CommandEncoder {
+ self.create_encoder(
+ &wgpu::CommandEncoderDescriptor {
+ label: Some(label)
+ }
+ )
+ }
+
+ pub fn submit_encoder(
+ &self,
+ encoder: wgpu::CommandEncoder)
+ {
+ self.queue.submit(std::iter::once(encoder.finish()));
+ }
+
+ pub fn present_surface(
+ &mut self)
+ {
+ let texture = self.surface_texture.take();
+ match texture {
+ Some(t) => t.present(),
+ None => {}
+ }
+ }
+
+ pub fn create_shader(
+ &self,
+ module_descr: wgpu::ShaderModuleDescriptor)
+ -> wgpu::ShaderModule {
+ self.device.create_shader_module(module_descr)
+ }
+
+ pub fn create_pipeline_layout(
+ &self,
+ layout_descr: &wgpu::PipelineLayoutDescriptor)
+ -> wgpu::PipelineLayout {
+ self.device.create_pipeline_layout(layout_descr)
+ }
+
+ pub fn create_render_pipeline(
+ &self,
+ pipeline_layout: &wgpu::RenderPipelineDescriptor)
+ -> wgpu::RenderPipeline {
+ self.device.create_render_pipeline(pipeline_layout)
+ }
+
+ pub fn create_buffer_init(
+ &self,
+ descr: &wgpu::util::BufferInitDescriptor
+ ) -> wgpu::Buffer {
+ self.device.create_buffer_init(descr)
+ }
+
+ pub fn create_buffer(
+ &self,
+ descr: &wgpu::BufferDescriptor)
+ -> wgpu::Buffer {
+ self.device.create_buffer(descr)
+ }
+
+ pub fn new_texture(
+ &self,
+ descr: wgpu::TextureDescriptor)
+ -> Texture
+ {
+ Texture::new(&self.device, descr)
+ }
+
+} //impl WgpuCtx
+
+impl<'encoder> RenderPassBuilder<'encoder>
+{
+ pub fn new(
+ label: &'static str,
+ view: &'encoder wgpu::TextureView)
+ -> Self {
+ Self {
+ label: label,
+ view: view,
+ clear_color: wgpu::Color { r: 0.0, g: 0.0, b: 0.0, a: 1.0 }
+ }
+ }
+
+ pub fn clear_color(
+ mut self,
+ color: wgpu::Color)
+ -> Self {
+ self.clear_color = color;
+ self
+ }
+
+ pub fn build(
+ self,
+ encoder: &'encoder mut wgpu::CommandEncoder)
+ -> wgpu::RenderPass<'encoder>
+ {
+ encoder.begin_render_pass(
+ &wgpu::RenderPassDescriptor {
+ label: Some(self.label),
+ color_attachments: &[Some(wgpu::RenderPassColorAttachment {
+ view: self.view,
+ resolve_target: None,
+ depth_slice: None,
+ ops: wgpu::Operations {
+ load: wgpu::LoadOp::Clear(self.clear_color),
+ store: wgpu::StoreOp::Store
+ }
+ })],
+ depth_stencil_attachment: None,
+ occlusion_query_set: None,
+ timestamp_writes: None,
+ }
+ )
+ }
+}
diff --git a/src/wgpuctx/pipeline.rs b/src/wgpuctx/pipeline.rs
new file mode 100644
index 0000000..7606203
--- /dev/null
+++ b/src/wgpuctx/pipeline.rs
@@ -0,0 +1,121 @@
+
+use crate::wgpuctx::WgpuCtx;
+
+pub struct RenderPipelineBuilder<'a>
+{
+ bind_groups: Vec<&'a wgpu::BindGroupLayout>,
+ shader: &'a wgpu::ShaderModule,
+
+ vertex_entry_point: Option<&'static str>,
+ fragment_entry_point: Option<&'static str>,
+
+ vertex_comp_options: Option<wgpu::PipelineCompilationOptions<'a>>,
+ fragment_comp_options: Option<wgpu::PipelineCompilationOptions<'a>>,
+
+ vertex_buffer_layouts: Vec<wgpu::VertexBufferLayout<'a>>
+}
+
+impl<'a> RenderPipelineBuilder<'a>
+{
+ pub fn new(
+ shader: &'a wgpu::ShaderModule)
+ -> Self {
+ Self {
+ bind_groups: Vec::new(),
+ shader: shader,
+
+ vertex_entry_point: Some("vs_main"),
+ fragment_entry_point: Some("fs_main"),
+
+ vertex_comp_options: None,
+ fragment_comp_options: None,
+
+ vertex_buffer_layouts: Vec::new()
+ }
+ }
+
+ pub fn add_bindgroup(
+ mut self,
+ bindgroup: &'a wgpu::BindGroupLayout)
+ -> Self {
+ self.bind_groups.push(bindgroup);
+ self
+ }
+
+ pub fn add_vertex_layout(
+ mut self,
+ layout: wgpu::VertexBufferLayout<'a>)
+ -> Self {
+ self.vertex_buffer_layouts.push(layout);
+ self
+ }
+
+ pub fn build(
+ self,
+ label: Option<&'static str>,
+ wgpuctx: &WgpuCtx)
+ -> wgpu::RenderPipeline
+ {
+ let layout_descr = wgpu::PipelineLayoutDescriptor {
+ label: label,
+ bind_group_layouts: self.bind_groups.as_slice(),
+ push_constant_ranges: &[]
+ };
+
+ let layout = wgpuctx.create_pipeline_layout(&layout_descr);
+
+ wgpuctx.create_render_pipeline(
+ &wgpu::RenderPipelineDescriptor {
+ label: label,
+ layout: Some(&layout),
+ vertex: wgpu::VertexState {
+ module: self.shader,
+ entry_point: self.vertex_entry_point,
+ compilation_options: match self.vertex_comp_options {
+ Some(option) => option,
+ None => wgpu::PipelineCompilationOptions::default()
+ },
+ buffers: self.vertex_buffer_layouts.as_slice()
+ },
+ fragment: Some(wgpu::FragmentState {
+ module: self.shader,
+ entry_point: self.fragment_entry_point,
+ compilation_options: match self.fragment_comp_options {
+ Some(option) => option,
+ None => wgpu::PipelineCompilationOptions::default()
+ },
+ targets: &[Some(wgpu::ColorTargetState {
+ format: wgpuctx.surface_config().format,
+ blend: Some(wgpu::BlendState{
+ color: wgpu::BlendComponent {
+ src_factor: wgpu::BlendFactor::SrcAlpha,
+ dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha,
+ operation: wgpu::BlendOperation::Add
+ },
+ alpha: wgpu::BlendComponent::OVER
+ }),
+ write_mask: wgpu::ColorWrites::ALL
+ })],
+ }),
+ primitive: wgpu::PrimitiveState {
+ topology: wgpu::PrimitiveTopology::TriangleList,
+ strip_index_format: None,
+ front_face: wgpu::FrontFace::Ccw,
+ cull_mode: Some(wgpu::Face::Back),
+ polygon_mode: wgpu::PolygonMode::Fill,
+ unclipped_depth: false,
+ conservative: false
+ },
+ depth_stencil: None,
+ multisample: wgpu::MultisampleState {
+ count: 1,
+ mask: !0,
+ alpha_to_coverage_enabled: false
+ },
+ multiview: None,
+ cache: None
+
+ }
+ )
+ }
+}
diff --git a/src/window.rs b/src/window.rs
new file mode 100644
index 0000000..0be88e7
--- /dev/null
+++ b/src/window.rs
@@ -0,0 +1,148 @@
+pub mod ui;
+
+mod window {
+ pub use super::ui;
+}
+
+use std::cell::RefCell;
+use std::sync::{Arc};
+use std::time::Duration;
+
+use winit::event::{ElementState, WindowEvent};
+use winit::keyboard::KeyCode;
+
+use crate::tacmap::TacticalMap;
+use crate::{GameState, SystemicApp};
+use crate::solar_system::{SolarSystem, SystemId};
+use crate::wgpuctx::{RenderPassBuilder, WgpuCtx};
+use crate::eguictx::EguiCtx;
+
+use ui::*;
+
+pub struct GameWindow
+{
+ window: Arc<winit::window::Window>,
+ wgpuctx: WgpuCtx,
+ eguictx: EguiCtx,
+
+ tactical_map: TacticalMap,
+
+ ui_state: GameWindowUiState
+}
+
+impl GameWindow
+{
+ pub fn new(
+ instance: &wgpu::Instance,
+ event_loop: &winit::event_loop::ActiveEventLoop)
+ -> Result<GameWindow, ()>
+ {
+ let window_attrs = winit::window::Window::default_attributes()
+ .with_title("Systemic 4X")
+ .with_inner_size(winit::dpi::LogicalSize::new(640, 480));
+
+ let window = Arc::new(event_loop.create_window(window_attrs).unwrap());
+
+ let wgpuctx = pollster::block_on(WgpuCtx::new(instance, window.clone()));
+ let eguictx = EguiCtx::new(&window, &wgpuctx);
+
+ let tacmap = TacticalMap::new(
+ &wgpuctx,
+ winit::dpi::LogicalPosition::new(0.0, 0.0),
+ winit::dpi::LogicalSize::new(1.0, 1.0));
+
+ Ok(Self {
+ window: window,
+ wgpuctx: wgpuctx,
+ eguictx: eguictx,
+ tactical_map: tacmap,
+
+ ui_state: Default::default()
+ })
+ }
+
+ pub fn update(
+ &mut self,
+ game_state: &RefCell<GameState>,
+ dt: Duration)
+ {
+ self.tactical_map.update(game_state, &mut self.ui_state, dt);
+ }
+
+ pub fn keyboard_input(
+ &mut self,
+ game_state: &RefCell<GameState>,
+ key_code: KeyCode,
+ key_state: ElementState)
+ {
+ self.tactical_map.keyboard_input(game_state, key_code, key_state);
+ }
+
+ pub fn render(
+ &mut self,
+ game_state: &RefCell<GameState>)
+ -> Result<(), wgpu::SurfaceError> {
+ if !self.wgpuctx.is_ready() {
+ return Ok(());
+ }
+
+ self.tactical_map.draw(
+ &self.wgpuctx,
+ game_state,
+ self.ui_state.current_system)?;
+
+ let mut encoder = self.wgpuctx.create_default_encoder("Systemic window command encoder");
+ let view = self.wgpuctx.prepare_surface(&wgpu::TextureViewDescriptor::default())?;
+ {
+ let mut pass = RenderPassBuilder::new("Systemic window render pass", &view)
+ .clear_color(wgpu::Color { r: 1.0, g: 1.0, b: 1.0, a: 1.0 })
+ .build(&mut encoder);
+
+ //Draw the tactical map canvas.
+ self.tactical_map.present(&mut pass)?;
+ }
+ {
+ self.eguictx.prepare(&self.window);
+ self.ui_state = GameWindowUiState::render(&self.ui_state, game_state, &self.eguictx);
+
+ self.eguictx.present(
+ &self.window,
+ &self.wgpuctx,
+ &mut encoder,
+ &view);
+ }
+
+ self.wgpuctx.submit_encoder(encoder);
+ self.wgpuctx.present_surface();
+ self.window.request_redraw();
+
+ Ok(())
+ }
+
+ pub fn on_event(
+ &mut self,
+ event: &WindowEvent)
+ {
+ if self.eguictx.window_event(&self.window, event).consumed {
+ return;
+ }
+ }
+
+ pub fn resize(
+ &mut self,
+ width: u32,
+ height: u32
+ ) {
+ if width > 0 && height > 0 {
+ self.wgpuctx.resize(width, height);
+ self.tactical_map.resize(&self.wgpuctx, width, height);
+ self.window.request_redraw();
+ }
+ }
+
+ pub fn size(
+ &self)
+ -> winit::dpi::PhysicalSize<u32> {
+ self.window.inner_size()
+ }
+}
diff --git a/src/window/ui.rs b/src/window/ui.rs
new file mode 100644
index 0000000..4d13740
--- /dev/null
+++ b/src/window/ui.rs
@@ -0,0 +1,96 @@
+use std::cell::RefCell;
+
+use cgmath::Vector3;
+
+use crate::{GameState, eguictx::EguiCtx, solar_system, timeman::{Second, TimeMan}};
+
+#[derive(Default, Clone)]
+pub struct GameWindowUiState
+{
+ pub current_system: Option<solar_system::SystemId>,
+ pub camera_scale: f32,
+ pub camera_target: Option<solar_system::BodyId>,
+ pub auto_time: Option<Second>,
+ pub do_auto_tick: bool
+}
+
+impl GameWindowUiState
+{
+ pub fn render(
+ old_state: &GameWindowUiState,
+ game_state: &RefCell<GameState>,
+ eguictx: &EguiCtx)
+ -> Self
+ {
+ let mut new_state = old_state.clone();
+
+ { GameWindowUiState::render_topbar(&mut new_state, game_state, eguictx); }
+
+ new_state
+ }
+
+ fn render_topbar(
+ state: &mut GameWindowUiState,
+ game_state: &RefCell<GameState>,
+ eguictx: &EguiCtx)
+ {
+ let mut game_state = game_state.borrow_mut();
+
+ egui::TopBottomPanel::top("topbar").show(
+ eguictx.context(),
+ |ui| {
+
+ ui.horizontal(|ui| {
+ ui.menu_button("File", |ui| {
+
+ });
+ });
+
+ ui.horizontal(|ui| {
+ let solar_systems = game_state.solar_systems();
+ let selected_label = match state.current_system {
+ Some(id) => solar_systems[id].name(),
+ None => ""
+ };
+
+ egui::ComboBox::from_label("Current System")
+ .selected_text(selected_label)
+ .show_ui(ui, |ui| {
+
+ for (i, system) in solar_systems.iter().enumerate() {
+ ui.selectable_value(
+ &mut state.current_system,
+ Some(i),
+ system.name()
+ );
+ }
+ });
+ });
+
+ ui.horizontal(|ui| {
+ ui.label(format!("Time: {}", TimeMan::format_duration(game_state.timeman().seconds())));
+
+ let button_seconds = [1, 5, 30, 60, 60*5, 60*30, 60*60, 60*60*24, 60*60*24*5, 60*60*24*30];
+ let selected_button = state.auto_time;
+
+ button_seconds.iter().for_each(|&seconds| {
+ ui.vertical(|ui| {
+ let auto_selected = match selected_button {
+ Some(o) => o == seconds,
+ None => false
+ };
+ let label = TimeMan::format_duration(seconds);
+
+ if ui.button(label.clone()).clicked() {
+ game_state.timeman_mut().advance(seconds);
+ }
+
+ if ui.add(egui::Button::new(label.clone()).selected(auto_selected)).clicked() {
+ state.auto_time = Some(seconds);
+ }
+ });
+ });
+ });
+ });
+ }
+}