diff --git a/.gitignore b/.gitignore index ea8c4bf..96ef6c0 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ /target +Cargo.lock diff --git a/Cargo.lock b/Cargo.lock deleted file mode 100644 index bb2a2ac..0000000 --- a/Cargo.lock +++ /dev/null @@ -1,1303 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 3 - -[[package]] -name = "ab_glyph_rasterizer" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9fe5e32de01730eb1f6b7f5b51c17e03e2325bf40a74f754f04f130043affff" - -[[package]] -name = "addr2line" -version = "0.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e61f2b7f93d2c7d2b08263acaa4a363b3e276806c68af6134c44f523bf1aacd" -dependencies = [ - "gimli", -] - -[[package]] -name = "adler" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" - -[[package]] -name = "andrew" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c4afb09dd642feec8408e33f92f3ffc4052946f6b20f32fb99c1f58cd4fa7cf" -dependencies = [ - "bitflags", - "rusttype", - "walkdir", - "xdg", - "xml-rs", -] - -[[package]] -name = "android_glue" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "000444226fcff248f2bc4c7625be32c63caccfecc2723a2b9f78a7487a49c407" - -[[package]] -name = "autocfg" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" - -[[package]] -name = "backtrace" -version = "0.3.61" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7a905d892734eea339e896738c14b9afce22b5318f64b951e70bf3844419b01" -dependencies = [ - "addr2line", - "cc", - "cfg-if 1.0.0", - "libc", - "miniz_oxide", - "object", - "rustc-demangle", -] - -[[package]] -name = "bitflags" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" - -[[package]] -name = "block" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a" - -[[package]] -name = "calloop" -version = "0.6.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b036167e76041694579972c28cf4877b4f92da222560ddb49008937b6a6727c" -dependencies = [ - "log", - "nix 0.18.0", -] - -[[package]] -name = "cc" -version = "1.0.69" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e70cc2f62c6ce1868963827bd677764c62d07c3d9a3e1fb1177ee1a9ab199eb2" - -[[package]] -name = "cfg-if" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" - -[[package]] -name = "cfg-if" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" - -[[package]] -name = "cgl" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ced0551234e87afee12411d535648dd89d2e7f34c78b753395567aff3d447ff" -dependencies = [ - "libc", -] - -[[package]] -name = "cocoa" -version = "0.24.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f63902e9223530efb4e26ccd0cf55ec30d592d3b42e21a28defc42a9586e832" -dependencies = [ - "bitflags", - "block", - "cocoa-foundation", - "core-foundation 0.9.1", - "core-graphics 0.22.2", - "foreign-types", - "libc", - "objc", -] - -[[package]] -name = "cocoa-foundation" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ade49b65d560ca58c403a479bb396592b155c0185eada742ee323d1d68d6318" -dependencies = [ - "bitflags", - "block", - "core-foundation 0.9.1", - "core-graphics-types", - "foreign-types", - "libc", - "objc", -] - -[[package]] -name = "core-foundation" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57d24c7a13c43e870e37c1556b74555437870a04514f7685f5b354e090567171" -dependencies = [ - "core-foundation-sys 0.7.0", - "libc", -] - -[[package]] -name = "core-foundation" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a89e2ae426ea83155dccf10c0fa6b1463ef6d5fcb44cee0b224a408fa640a62" -dependencies = [ - "core-foundation-sys 0.8.2", - "libc", -] - -[[package]] -name = "core-foundation-sys" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3a71ab494c0b5b860bdc8407ae08978052417070c2ced38573a9157ad75b8ac" - -[[package]] -name = "core-foundation-sys" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea221b5284a47e40033bf9b66f35f984ec0ea2931eb03505246cd27a963f981b" - -[[package]] -name = "core-graphics" -version = "0.19.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3889374e6ea6ab25dba90bb5d96202f61108058361f6dc72e8b03e6f8bbe923" -dependencies = [ - "bitflags", - "core-foundation 0.7.0", - "foreign-types", - "libc", -] - -[[package]] -name = "core-graphics" -version = "0.22.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "269f35f69b542b80e736a20a89a05215c0ce80c2c03c514abb2e318b78379d86" -dependencies = [ - "bitflags", - "core-foundation 0.9.1", - "core-graphics-types", - "foreign-types", - "libc", -] - -[[package]] -name = "core-graphics-types" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a68b68b3446082644c91ac778bf50cd4104bfb002b5a6a7c44cca5a2c70788b" -dependencies = [ - "bitflags", - "core-foundation 0.9.1", - "foreign-types", - "libc", -] - -[[package]] -name = "core-video-sys" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34ecad23610ad9757664d644e369246edde1803fcb43ed72876565098a5d3828" -dependencies = [ - "cfg-if 0.1.10", - "core-foundation-sys 0.7.0", - "core-graphics 0.19.2", - "libc", - "objc", -] - -[[package]] -name = "crossbeam" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ae5588f6b3c3cb05239e90bd110f257254aecd01e4635400391aeae07497845" -dependencies = [ - "cfg-if 1.0.0", - "crossbeam-channel", - "crossbeam-deque", - "crossbeam-epoch", - "crossbeam-queue", - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-channel" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06ed27e177f16d65f0f0c22a213e17c696ace5dd64b14258b52f9417ccb52db4" -dependencies = [ - "cfg-if 1.0.0", - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-deque" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6455c0ca19f0d2fbf751b908d5c55c1f5cbc65e03c4225427254b46890bdde1e" -dependencies = [ - "cfg-if 1.0.0", - "crossbeam-epoch", - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-epoch" -version = "0.9.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ec02e091aa634e2c3ada4a392989e7c3116673ef0ac5b72232439094d73b7fd" -dependencies = [ - "cfg-if 1.0.0", - "crossbeam-utils", - "lazy_static", - "memoffset", - "scopeguard", -] - -[[package]] -name = "crossbeam-queue" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b10ddc024425c88c2ad148c1b0fd53f4c6d38db9697c9f1588381212fa657c9" -dependencies = [ - "cfg-if 1.0.0", - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-utils" -version = "0.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d82cfc11ce7f2c3faef78d8a684447b40d503d9681acebed6cb728d45940c4db" -dependencies = [ - "cfg-if 1.0.0", - "lazy_static", -] - -[[package]] -name = "darling" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d706e75d87e35569db781a9b5e2416cff1236a47ed380831f959382ccd5f858" -dependencies = [ - "darling_core", - "darling_macro", -] - -[[package]] -name = "darling_core" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0c960ae2da4de88a91b2d920c2a7233b400bc33cb28453a2987822d8392519b" -dependencies = [ - "fnv", - "ident_case", - "proc-macro2", - "quote", - "strsim", - "syn", -] - -[[package]] -name = "darling_macro" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9b5a2f4ac4969822c62224815d069952656cadc7084fdca9751e6d959189b72" -dependencies = [ - "darling_core", - "quote", - "syn", -] - -[[package]] -name = "derivative" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "dispatch" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b" - -[[package]] -name = "dlib" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b11f15d1e3268f140f68d390637d5e76d849782d971ae7063e0da69fe9709a76" -dependencies = [ - "libloading 0.6.7", -] - -[[package]] -name = "dlib" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac1b7517328c04c2aa68422fc60a41b92208182142ed04a25879c26c8f878794" -dependencies = [ - "libloading 0.7.0", -] - -[[package]] -name = "downcast-rs" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ea835d29036a4087793836fa931b08837ad5e957da9e23886b29586fb9b6650" - -[[package]] -name = "either" -version = "1.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" - -[[package]] -name = "fnv" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" - -[[package]] -name = "foreign-types" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" -dependencies = [ - "foreign-types-shared", -] - -[[package]] -name = "foreign-types-shared" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" - -[[package]] -name = "gimli" -version = "0.25.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0a01e0497841a3b2db4f8afa483cce65f7e96a3498bd6c541734792aeac8fe7" - -[[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 = "glium" -version = "0.30.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6dfaf64eee4e23d1d5429945816ab083cb94b44e00c3c5f9f9aebfd09ec63df" -dependencies = [ - "backtrace", - "fnv", - "gl_generator", - "glutin", - "lazy_static", - "memoffset", - "smallvec", - "takeable-option", -] - -[[package]] -name = "glutin" -version = "0.27.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "762d6cd2e1b855d99668ebe591cc9058659d85ac39a9a2078000eb122ddba8f0" -dependencies = [ - "android_glue", - "cgl", - "cocoa", - "core-foundation 0.9.1", - "glutin_egl_sys", - "glutin_emscripten_sys", - "glutin_gles2_sys", - "glutin_glx_sys", - "glutin_wgl_sys", - "lazy_static", - "libloading 0.7.0", - "log", - "objc", - "osmesa-sys", - "parking_lot", - "wayland-client", - "wayland-egl", - "winapi", - "winit", -] - -[[package]] -name = "glutin_egl_sys" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2abb6aa55523480c4adc5a56bbaa249992e2dddb2fc63dc96e04a3355364c211" -dependencies = [ - "gl_generator", - "winapi", -] - -[[package]] -name = "glutin_emscripten_sys" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80de4146df76e8a6c32b03007bc764ff3249dcaeb4f675d68a06caf1bac363f1" - -[[package]] -name = "glutin_gles2_sys" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8094e708b730a7c8a1954f4f8a31880af00eb8a1c5b5bf85d28a0a3c6d69103" -dependencies = [ - "gl_generator", - "objc", -] - -[[package]] -name = "glutin_glx_sys" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e393c8fc02b807459410429150e9c4faffdb312d59b8c038566173c81991351" -dependencies = [ - "gl_generator", - "x11-dl", -] - -[[package]] -name = "glutin_wgl_sys" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3da5951a1569dbab865c6f2a863efafff193a93caf05538d193e9e3816d21696" -dependencies = [ - "gl_generator", -] - -[[package]] -name = "hermit-abi" -version = "0.1.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" -dependencies = [ - "libc", -] - -[[package]] -name = "ident_case" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" - -[[package]] -name = "instant" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bee0328b1209d157ef001c94dd85b4f8f64139adb0eac2659f4b08382b2f474d" -dependencies = [ - "cfg-if 1.0.0", -] - -[[package]] -name = "jni-sys" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" - -[[package]] -name = "khronos_api" -version = "3.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2db585e1d738fc771bf08a151420d3ed193d9d895a36df7f6f8a9456b911ddc" - -[[package]] -name = "lazy_static" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" - -[[package]] -name = "libc" -version = "0.2.100" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1fa8cddc8fbbee11227ef194b5317ed014b8acbf15139bd716a18ad3fe99ec5" - -[[package]] -name = "libloading" -version = "0.6.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "351a32417a12d5f7e82c368a66781e307834dae04c6ce0cd4456d52989229883" -dependencies = [ - "cfg-if 1.0.0", - "winapi", -] - -[[package]] -name = "libloading" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f84d96438c15fcd6c3f244c8fce01d1e2b9c6b5623e9c711dc9286d8fc92d6a" -dependencies = [ - "cfg-if 1.0.0", - "winapi", -] - -[[package]] -name = "lock_api" -version = "0.4.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0382880606dff6d15c9476c416d18690b72742aa7b605bb6dd6ec9030fbf07eb" -dependencies = [ - "scopeguard", -] - -[[package]] -name = "lodtree" -version = "0.1.4" -dependencies = [ - "glium", - "rayon", -] - -[[package]] -name = "log" -version = "0.4.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" -dependencies = [ - "cfg-if 1.0.0", -] - -[[package]] -name = "malloc_buf" -version = "0.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62bb907fe88d54d8d9ce32a3cceab4218ed2f6b7d35617cafe9adf84e43919cb" -dependencies = [ - "libc", -] - -[[package]] -name = "maybe-uninit" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00" - -[[package]] -name = "memchr" -version = "2.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a" - -[[package]] -name = "memmap2" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9b70ca2a6103ac8b665dc150b142ef0e4e89df640c9e6cf295d189c3caebe5a" -dependencies = [ - "libc", -] - -[[package]] -name = "memoffset" -version = "0.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59accc507f1338036a0477ef61afdae33cde60840f4dfe481319ce3ad116ddf9" -dependencies = [ - "autocfg", -] - -[[package]] -name = "minimal-lexical" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6595bb28ed34f43c3fe088e48f6cfb2e033cab45f25a5384d5fdf564fbc8c4b2" - -[[package]] -name = "miniz_oxide" -version = "0.4.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a92518e98c078586bc6c934028adcca4c92a53d6a958196de835170a01d84e4b" -dependencies = [ - "adler", - "autocfg", -] - -[[package]] -name = "mio" -version = "0.7.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c2bdb6314ec10835cd3293dd268473a835c02b7b352e788be788b3c6ca6bb16" -dependencies = [ - "libc", - "log", - "miow", - "ntapi", - "winapi", -] - -[[package]] -name = "mio-misc" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ddf05411bb159cdb5801bb10002afb66cb4572be656044315e363460ce69dc2" -dependencies = [ - "crossbeam", - "crossbeam-queue", - "log", - "mio", -] - -[[package]] -name = "miow" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9f1c5b025cda876f66ef43a113f91ebc9f4ccef34843000e0adf6ebbab84e21" -dependencies = [ - "winapi", -] - -[[package]] -name = "ndk" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8794322172319b972f528bf90c6b467be0079f1fa82780ffb431088e741a73ab" -dependencies = [ - "jni-sys", - "ndk-sys", - "num_enum", - "thiserror", -] - -[[package]] -name = "ndk-glue" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5caf0c24d51ac1c905c27d4eda4fa0635bbe0de596b8f79235e0b17a4d29385" -dependencies = [ - "lazy_static", - "libc", - "log", - "ndk", - "ndk-macro", - "ndk-sys", -] - -[[package]] -name = "ndk-macro" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05d1c6307dc424d0f65b9b06e94f88248e6305726b14729fd67a5e47b2dc481d" -dependencies = [ - "darling", - "proc-macro-crate 0.1.5", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "ndk-sys" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c44922cb3dbb1c70b5e5f443d63b64363a898564d739ba5198e3a9138442868d" - -[[package]] -name = "nix" -version = "0.18.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83450fe6a6142ddd95fb064b746083fc4ef1705fe81f64a64e1d4b39f54a1055" -dependencies = [ - "bitflags", - "cc", - "cfg-if 0.1.10", - "libc", -] - -[[package]] -name = "nix" -version = "0.20.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa9b4819da1bc61c0ea48b63b7bc8604064dd43013e7cc325df098d49cd7c18a" -dependencies = [ - "bitflags", - "cc", - "cfg-if 1.0.0", - "libc", -] - -[[package]] -name = "nom" -version = "7.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ffd9d26838a953b4af82cbeb9f1592c6798916983959be223a7124e992742c1" -dependencies = [ - "memchr", - "minimal-lexical", - "version_check", -] - -[[package]] -name = "ntapi" -version = "0.3.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f6bb902e437b6d86e03cce10a7e2af662292c5dfef23b65899ea3ac9354ad44" -dependencies = [ - "winapi", -] - -[[package]] -name = "num_cpus" -version = "1.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3" -dependencies = [ - "hermit-abi", - "libc", -] - -[[package]] -name = "num_enum" -version = "0.5.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f9bd055fb730c4f8f4f57d45d35cd6b3f0980535b056dc7ff119cee6a66ed6f" -dependencies = [ - "derivative", - "num_enum_derive", -] - -[[package]] -name = "num_enum_derive" -version = "0.5.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "486ea01961c4a818096de679a8b740b26d9033146ac5291b1c98557658f8cdd9" -dependencies = [ - "proc-macro-crate 1.0.0", - "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 = "object" -version = "0.26.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee2766204889d09937d00bfbb7fec56bb2a199e2ade963cab19185d8a6104c7c" -dependencies = [ - "memchr", -] - -[[package]] -name = "once_cell" -version = "1.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "692fcb63b64b1758029e0a96ee63e049ce8c5948587f2f7208df04625e5f6b56" - -[[package]] -name = "osmesa-sys" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88cfece6e95d2e717e0872a7f53a8684712ad13822a7979bc760b9c77ec0013b" -dependencies = [ - "shared_library", -] - -[[package]] -name = "owned_ttf_parser" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f923fb806c46266c02ab4a5b239735c144bdeda724a50ed058e5226f594cde3" -dependencies = [ - "ttf-parser", -] - -[[package]] -name = "parking_lot" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d7744ac029df22dca6284efe4e898991d28e3085c706c972bcd7da4a27a15eb" -dependencies = [ - "instant", - "lock_api", - "parking_lot_core", -] - -[[package]] -name = "parking_lot_core" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa7a782938e745763fe6907fc6ba86946d72f49fe7e21de074e08128a99fb018" -dependencies = [ - "cfg-if 1.0.0", - "instant", - "libc", - "redox_syscall", - "smallvec", - "winapi", -] - -[[package]] -name = "percent-encoding" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" - -[[package]] -name = "pkg-config" -version = "0.3.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3831453b3449ceb48b6d9c7ad7c96d5ea673e9b470a1dc578c2ce6521230884c" - -[[package]] -name = "proc-macro-crate" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d6ea3c4595b96363c13943497db34af4460fb474a95c43f4446ad341b8c9785" -dependencies = [ - "toml", -] - -[[package]] -name = "proc-macro-crate" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41fdbd1df62156fbc5945f4762632564d7d038153091c3fcf1067f6aef7cff92" -dependencies = [ - "thiserror", - "toml", -] - -[[package]] -name = "proc-macro2" -version = "1.0.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c7ed8b8c7b886ea3ed7dde405212185f423ab44682667c8c6dd14aa1d9f6612" -dependencies = [ - "unicode-xid", -] - -[[package]] -name = "quote" -version = "1.0.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7" -dependencies = [ - "proc-macro2", -] - -[[package]] -name = "raw-window-handle" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a441a7a6c80ad6473bd4b74ec1c9a4c951794285bf941c2126f607c72e48211" -dependencies = [ - "libc", -] - -[[package]] -name = "rayon" -version = "1.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c06aca804d41dbc8ba42dfd964f0d01334eceb64314b9ecf7c5fad5188a06d90" -dependencies = [ - "autocfg", - "crossbeam-deque", - "either", - "rayon-core", -] - -[[package]] -name = "rayon-core" -version = "1.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d78120e2c850279833f1dd3582f730c4ab53ed95aeaaaa862a2a5c71b1656d8e" -dependencies = [ - "crossbeam-channel", - "crossbeam-deque", - "crossbeam-utils", - "lazy_static", - "num_cpus", -] - -[[package]] -name = "redox_syscall" -version = "0.2.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8383f39639269cde97d255a32bdb68c047337295414940c68bdd30c2e13203ff" -dependencies = [ - "bitflags", -] - -[[package]] -name = "rustc-demangle" -version = "0.1.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dead70b0b5e03e9c814bcb6b01e03e68f7c57a80aa48c72ec92152ab3e818d49" - -[[package]] -name = "rusttype" -version = "0.9.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc7c727aded0be18c5b80c1640eae0ac8e396abf6fa8477d96cb37d18ee5ec59" -dependencies = [ - "ab_glyph_rasterizer", - "owned_ttf_parser", -] - -[[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.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea6a9290e3c9cf0f18145ef7ffa62d68ee0bf5fcd651017e586dc7fd5da448c2" - -[[package]] -name = "scopeguard" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" - -[[package]] -name = "serde" -version = "1.0.129" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1f72836d2aa753853178eda473a3b9d8e4eefdaf20523b919677e6de489f8f1" - -[[package]] -name = "shared_library" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a9e7e0f2bfae24d8a5b5a66c5b257a83c7412304311512a0c054cd5e619da11" -dependencies = [ - "lazy_static", - "libc", -] - -[[package]] -name = "smallvec" -version = "1.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe0f37c9e8f3c5a4a66ad655a93c74daac4ad00c441533bf5c6e7990bb42604e" - -[[package]] -name = "smithay-client-toolkit" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4750c76fd5d3ac95fa3ed80fe667d6a3d8590a960e5b575b98eea93339a80b80" -dependencies = [ - "andrew", - "bitflags", - "calloop", - "dlib 0.4.2", - "lazy_static", - "log", - "memmap2", - "nix 0.18.0", - "wayland-client", - "wayland-cursor", - "wayland-protocols", -] - -[[package]] -name = "strsim" -version = "0.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6446ced80d6c486436db5c078dde11a9f73d42b57fb273121e160b84f63d894c" - -[[package]] -name = "syn" -version = "1.0.75" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7f58f7e8eaa0009c5fec437aabf511bd9933e4b2d7407bd05273c01a8906ea7" -dependencies = [ - "proc-macro2", - "quote", - "unicode-xid", -] - -[[package]] -name = "takeable-option" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36ae8932fcfea38b7d3883ae2ab357b0d57a02caaa18ebb4f5ece08beaec4aa0" - -[[package]] -name = "thiserror" -version = "1.0.26" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93119e4feac1cbe6c798c34d3a53ea0026b0b1de6a120deef895137c0529bfe2" -dependencies = [ - "thiserror-impl", -] - -[[package]] -name = "thiserror-impl" -version = "1.0.26" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "060d69a0afe7796bf42e9e2ff91f5ee691fb15c53d38b4b62a9a53eb23164745" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "toml" -version = "0.5.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a31142970826733df8241ef35dc040ef98c679ab14d7c3e54d827099b3acecaa" -dependencies = [ - "serde", -] - -[[package]] -name = "ttf-parser" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e5d7cd7ab3e47dda6e56542f4bbf3824c15234958c6e1bd6aaa347e93499fdc" - -[[package]] -name = "unicode-xid" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" - -[[package]] -name = "version_check" -version = "0.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe" - -[[package]] -name = "walkdir" -version = "2.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "808cf2735cd4b6866113f648b791c6adc5714537bc222d9347bb203386ffda56" -dependencies = [ - "same-file", - "winapi", - "winapi-util", -] - -[[package]] -name = "wayland-client" -version = "0.28.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3ab332350e502f159382201394a78e3cc12d0f04db863429260164ea40e0355" -dependencies = [ - "bitflags", - "downcast-rs", - "libc", - "nix 0.20.0", - "scoped-tls", - "wayland-commons", - "wayland-scanner", - "wayland-sys", -] - -[[package]] -name = "wayland-commons" -version = "0.28.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a21817947c7011bbd0a27e11b17b337bfd022e8544b071a2641232047966fbda" -dependencies = [ - "nix 0.20.0", - "once_cell", - "smallvec", - "wayland-sys", -] - -[[package]] -name = "wayland-cursor" -version = "0.28.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be610084edd1586d45e7bdd275fe345c7c1873598caa464c4fb835dee70fa65a" -dependencies = [ - "nix 0.20.0", - "wayland-client", - "xcursor", -] - -[[package]] -name = "wayland-egl" -version = "0.28.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99ba1ab1e18756b23982d36f08856d521d7df45015f404a2d7c4f0b2d2f66956" -dependencies = [ - "wayland-client", - "wayland-sys", -] - -[[package]] -name = "wayland-protocols" -version = "0.28.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "286620ea4d803bacf61fa087a4242ee316693099ee5a140796aaba02b29f861f" -dependencies = [ - "bitflags", - "wayland-client", - "wayland-commons", - "wayland-scanner", -] - -[[package]] -name = "wayland-scanner" -version = "0.28.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce923eb2deb61de332d1f356ec7b6bf37094dc5573952e1c8936db03b54c03f1" -dependencies = [ - "proc-macro2", - "quote", - "xml-rs", -] - -[[package]] -name = "wayland-sys" -version = "0.28.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d841fca9aed7febf9bed2e9796c49bf58d4152ceda8ac949ebe00868d8f0feb8" -dependencies = [ - "dlib 0.5.0", - "lazy_static", - "pkg-config", -] - -[[package]] -name = "winapi" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" -dependencies = [ - "winapi-i686-pc-windows-gnu", - "winapi-x86_64-pc-windows-gnu", -] - -[[package]] -name = "winapi-i686-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" - -[[package]] -name = "winapi-util" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" -dependencies = [ - "winapi", -] - -[[package]] -name = "winapi-x86_64-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" - -[[package]] -name = "winit" -version = "0.25.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79610794594d5e86be473ef7763f604f2159cbac8c94debd00df8fb41e86c2f8" -dependencies = [ - "bitflags", - "cocoa", - "core-foundation 0.9.1", - "core-graphics 0.22.2", - "core-video-sys", - "dispatch", - "instant", - "lazy_static", - "libc", - "log", - "mio", - "mio-misc", - "ndk", - "ndk-glue", - "ndk-sys", - "objc", - "parking_lot", - "percent-encoding", - "raw-window-handle", - "scopeguard", - "smithay-client-toolkit", - "wayland-client", - "winapi", - "x11-dl", -] - -[[package]] -name = "x11-dl" -version = "2.18.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2bf981e3a5b3301209754218f962052d4d9ee97e478f4d26d4a6eced34c1fef8" -dependencies = [ - "lazy_static", - "libc", - "maybe-uninit", - "pkg-config", -] - -[[package]] -name = "xcursor" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "463705a63313cd4301184381c5e8042f0a7e9b4bb63653f216311d4ae74690b7" -dependencies = [ - "nom", -] - -[[package]] -name = "xdg" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d089681aa106a86fade1b0128fb5daf07d5867a509ab036d99988dec80429a57" - -[[package]] -name = "xml-rs" -version = "0.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2d7d3948613f75c98fd9328cfdcc45acc4d360655289d0a7d4ec931392200a3" diff --git a/Cargo.toml b/Cargo.toml index 150b377..5609b7d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,14 +1,15 @@ [package] name = "lodtree" description = "A simple crate to help create octrees and quadtrees for chunked level of detail" -version = "0.1.4" -edition = "2018" +version = "0.1.5" +edition = "2021" license = "MIT OR Apache-2.0" repository = "https://github.com/Dimev/lodtree" documentation = "https://docs.rs/lodtree" keywords = ["lod", "terrain", "octree", "quadtree", "tree"] categories = ["data-structures"] + # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] @@ -16,3 +17,10 @@ categories = ["data-structures"] [dev_dependencies] rayon = "1.5" glium = "0.30" +rand ={version="0.8.5", features=['small_rng']} +rand_derive = "0.5.0" +criterion = {version="0.3", features=['html_reports']} + +[[bench]] +name = "iterators" +harness = false diff --git a/benches/iterators.rs b/benches/iterators.rs new file mode 100644 index 0000000..751884a --- /dev/null +++ b/benches/iterators.rs @@ -0,0 +1,171 @@ +use lodtree::coords::OctVec; +use lodtree::Tree; +use rand::rngs::SmallRng; +use rand::{Rng, SeedableRng}; + +use criterion::{black_box, criterion_group, criterion_main, BenchmarkId, Criterion}; + +const N_LOOKUPS: usize = 40; +fn generate_area_bounds(rng: &mut SmallRng, depth:u8) -> (OctVec, OctVec) { + let cmax = 1 << depth; + + let min = OctVec::new( + rng.gen_range(0..cmax - 1), + rng.gen_range(0..cmax - 1), + rng.gen_range(0..cmax - 1), + depth, + ); + let max = OctVec::new( + rng.gen_range(min.x + 1..cmax), + rng.gen_range(min.y + 1..cmax), + rng.gen_range(min.z + 1..cmax), + depth, + ); + return (min, max); +} + +struct ChuChunk { + a_index: u8, + b_index: u8, + material_index: u16, +} + +impl Default for ChuChunk { + fn default() -> ChuChunk { + ChuChunk { + a_index: 1, + b_index: 2, + material_index: 3, + } + } +} + +fn create_and_fill_octree(num_chunks: u32, depth: u8) -> Tree { + let mut rng = SmallRng::seed_from_u64(42); + let mut tree: Tree = Tree::with_capacity(0, 0); + + let cmax = 1 << depth; + + for _c in 0..num_chunks { + let qv = OctVec::new( + rng.gen_range(0..cmax), + rng.gen_range(0..cmax), + rng.gen_range(0..cmax), + depth, + ); + + while tree.prepare_insert(&[qv], 0, &mut |_p| C::default()) { + // do the update + tree.do_update(); + // and clean + tree.complete_update(); + } + } + tree +} + +fn bench_lookups_in_octree(tree: &Tree, depth:u8) { + let mut rng = SmallRng::seed_from_u64(42); + for _ in 0..N_LOOKUPS { + let (min, max) = generate_area_bounds(&mut rng, depth); + for i in tree.iter_all_chunks_in_bounds_and_tree(min, max, min.depth) { + black_box(i); + } + } +} + +fn bench_mut_lookups_in_octree(tree: &mut Tree, depth:u8) { + let mut rng = SmallRng::seed_from_u64(42); + for _ in 0..N_LOOKUPS { + let (min, max) = generate_area_bounds(&mut rng, depth); + for i in tree.iter_all_chunks_in_bounds_and_tree_mut(min, max, min.depth) { + i.1.material_index += 1; + i.1.a_index += 1; + i.1.b_index += 1; + } + } +} + +pub fn bench_iteration(c: &mut Criterion) { + let mut group = c.benchmark_group("mutable iteration"); + let mut samples_num = 100; + + for depth in [4u8, 6, 8].iter() { + if *depth as i8 == 4 { + samples_num = 100; + } + if *depth as i8 == 6 { + samples_num = 40; + } + if *depth as i8 == 8 { + samples_num = 10; + } + group.significance_level(0.1).sample_size(samples_num); + + let num_chunks: u32 = 2u32.pow(*depth as u32).pow(3) / 10; + group.bench_with_input(BenchmarkId::from_parameter(depth), depth, |b, depth| { + let mut tree = create_and_fill_octree::(num_chunks, *depth); + b.iter(|| { + bench_mut_lookups_in_octree(&mut tree, *depth); + }); + black_box(tree); + }); + } + group.finish(); + + let mut group = c.benchmark_group("immutable iteration"); + let mut samples_num = 10; + + for depth in [4u8, 6, 8].iter() { + if *depth as i8 == 4 { + samples_num = 100; + } + if *depth as i8 == 6 { + samples_num = 40; + } + if *depth as i8 == 8 { + samples_num = 10; + } + group.significance_level(0.1).sample_size(samples_num); + let num_chunks: u32 = 2u32.pow(*depth as u32).pow(3) / 10; + group.bench_with_input(BenchmarkId::from_parameter(depth), depth, |b, depth| { + let tree = create_and_fill_octree::(num_chunks, *depth); + b.iter(|| { + bench_lookups_in_octree(&tree, *depth); + }); + }); + } + group.finish(); +} + +pub fn bench_creation(c: &mut Criterion) { + let mut group = c.benchmark_group("tree creation"); + + let mut samples_num = 10; + + for depth in [4u8, 6, 8].iter() { + if *depth as i8 == 4 { + samples_num = 100; + } + if *depth as i8 == 6 { + samples_num = 40; + } + if *depth as i8 == 8 { + samples_num = 10; + } + group.significance_level(0.1).sample_size(samples_num); + group.bench_with_input(BenchmarkId::from_parameter(depth), depth, |b, &depth| { + let volume = 2u32.pow(depth as u32).pow(3); + let num_chunks: u32 = volume / 10; + println!("Creating {num_chunks} voxels out of {volume} possible"); + b.iter(|| { + let t = create_and_fill_octree::(num_chunks, depth); + black_box(t); + }); + }); + } + group.finish(); +} + +criterion_group!(benches, bench_creation, bench_iteration); +criterion_main!(benches); diff --git a/doc/node_structure.md b/doc/node_structure.md new file mode 100644 index 0000000..ea2cd39 --- /dev/null +++ b/doc/node_structure.md @@ -0,0 +1,32 @@ +Each node has array of children, which are indices into the array of nodes. +Chlidren array can contain zeros, which indicate absence of appropriate child. + +Chunks array points towards chunks associated with a given child. The root node (nodes[0]) +always gets a chunk assigned when it is first created (as zero would be a valid index for nodes otherwise). +There is no way to delete the root node. + +For all other nodes chunks are optional. + +Benefits of this layout: + * nodes are automatically grouped, so less operations on nodes array are needed to traverse the same depth of tree. + * vast majority of chunks are optional, which means we can store sparse data more efficiently + * + +In this example we assume QuadVec addressing. Thus, children and chunks are both 4 elements long, +and their encoding matches the offsets defined in appropriate fn get_child(self, index: u32). + +Pos does not need to be stored in nodes array, we keep it here for clarity of example. + +``` rust +nodes:Vec=vec![ +{pos:(0,0,0),children:[0,1,2,0],chunks:[0,0,0,0]}, +{pos:(0,1,1),children:[0,0,0,0],chunks:[1,0,0,0]}, +{pos:(1,0,1),children:[0,0,0,0],chunks:[0,2,0,0]}, +]; + +chunks:Vec=vec![ +{node:0, pos:(0,0,0)},// this chunk belongs to root node, if that is present. no way to disable this +{node:1, pos:(0,3,2)}, +{node:2, pos:(3,1,2)}, + ]; +``` diff --git a/examples/glium.rs b/examples/glium.rs index c5d7673..727e59e 100644 --- a/examples/glium.rs +++ b/examples/glium.rs @@ -1,65 +1,25 @@ +use glium::glutin::event_loop::EventLoop; use glium::index::PrimitiveType; -use glium::{glutin, implement_vertex, program, uniform, Surface}; +use glium::{ + glutin, implement_vertex, program, uniform, Display, IndexBuffer, Program, Surface, + VertexBuffer, +}; use lodtree::coords::QuadVec; use lodtree::*; // the chunk struct for the tree +#[allow(dead_code)] struct Chunk { visible: bool, - cache_state: i32, // 0 is new, 1 is merged, 2 is cached, 3 is both + cache_state: i32, + // 0 is new, 1 is merged, 2 is cached, 3 is both selected: bool, in_bounds: bool, } -fn main() { - // start the glium event loop - let event_loop = glutin::event_loop::EventLoop::new(); - let wb = glutin::window::WindowBuilder::new().with_title("Quadtree demo"); - let cb = glutin::ContextBuilder::new().with_vsync(true); - let display = glium::Display::new(wb, cb, &event_loop).unwrap(); - - // make a vertex buffer - // we'll reuse it as we only need to draw one quad multiple times anyway - let vertex_buffer = { - #[derive(Copy, Clone)] - struct Vertex { - // only need a 2d position - position: [f32; 2], - } - - implement_vertex!(Vertex, position); - - glium::VertexBuffer::new( - &display, - &[ - Vertex { - position: [-1.0, -1.0], - }, - Vertex { - position: [-1.0, 1.0], - }, - Vertex { - position: [1.0, -1.0], - }, - Vertex { - position: [1.0, 1.0], - }, - ], - ) - .unwrap() - }; - - // and the index buffer to form the triangle - let index_buffer = glium::IndexBuffer::new( - &display, - PrimitiveType::TrianglesList, - &[0 as u16, 1, 2, 1, 2, 3], - ) - .unwrap(); - - // and get the shaders - let program = program!(&display, +fn make_shaders(display: &Display) -> Program { + let program = program!(display, 140 => { vertex: " #version 140 @@ -101,100 +61,153 @@ fn main() { } ) .unwrap(); + return program; +} - let draw = move |mouse_pos: (f64, f64), - tree: &mut Tree, - display: &glium::Display| { - // update the tree - // adding chunks to their respective position, and also set them visible when adding - if tree.prepare_update( - &[QuadVec::from_float_coords( - mouse_pos.0, - 1.0 - mouse_pos.1, - 6, - )], - 2, - |_position| Chunk { - visible: true, - cache_state: 0, - selected: false, - in_bounds: false, - }, - ) { - // position should already have been set, so we can just change the visibility - for chunk in tree.iter_chunks_to_activate_mut() { - chunk.visible = true; - chunk.cache_state |= 1; - } - - for chunk in tree.iter_chunks_to_deactivate_mut() { - chunk.visible = false; - } +#[derive(Copy, Clone)] +struct Vertex { + // only need a 2d position + position: [f32; 2], +} +implement_vertex!(Vertex, position); - // and make chunks that are cached visible - for chunk in tree.iter_chunks_to_remove_mut() { - chunk.cache_state = 2; - } +struct RenderContext { + display: Display, + vertex_buffer: VertexBuffer, + shaders: Program, + index_buffer: IndexBuffer, +} - // do the update - tree.do_update(); +impl RenderContext { + pub fn new(event_loop: &EventLoop<()>) -> Self { + let wb = glutin::window::WindowBuilder::new().with_title("Quadtree demo"); + let cb = glutin::ContextBuilder::new().with_vsync(true); + let display = Display::new(wb, cb, &event_loop).unwrap(); + // make a vertex buffer + // we'll reuse it as we only need to draw one quad multiple times anyway + let vertex_buffer = { + VertexBuffer::new( + &display, + &[ + Vertex { + position: [-1.0, -1.0], + }, + Vertex { + position: [-1.0, 1.0], + }, + Vertex { + position: [1.0, -1.0], + }, + Vertex { + position: [1.0, 1.0], + }, + ], + ) + .unwrap() + }; + // and the index buffer to form the triangle + let index_buffer = IndexBuffer::new( + &display, + PrimitiveType::TrianglesList, + &[0 as u16, 1, 2, 1, 2, 3], + ) + .unwrap(); + + let shaders = make_shaders(&display); + Self { + display, + vertex_buffer, + index_buffer, + shaders, + } + } +} - // and clean - tree.complete_update(); +fn draw(mouse_pos: (f32, f32), tree: &mut Tree, ctx: &RenderContext) { + //function for adding chunks to their respective position, and also set their properties + fn chunk_creator(_position: QuadVec) -> Chunk { + Chunk { + visible: true, + cache_state: 0, + selected: false, + in_bounds: false, } + } - // go over all chunks in the tree and set them to not be selected - for chunk in tree.iter_chunks_mut() { - chunk.selected = false; + let qv = QuadVec::from_float_coords(mouse_pos.0 as f64, (1.0 - mouse_pos.1) as f64, 6); + if tree.prepare_update(&[qv], 2, &mut chunk_creator) { + // position should already have been set, so we can just change the visibility + for chunk in tree.iter_chunks_to_activate_mut() { + chunk.visible = true; + chunk.cache_state |= 1; } - // and select the chunk at the mouse position - if let Some(chunk) = tree.get_chunk_from_position_mut(QuadVec::from_float_coords( - mouse_pos.0, - 1.0 - mouse_pos.1, - 6, - )) { - chunk.selected = true; + for chunk in tree.iter_chunks_to_deactivate_mut() { + chunk.visible = false; } - // and select a number of chunks in a region when the mouse buttons are selected - - // and, Redraw! - let mut target = display.draw(); - target.clear_color(0.6, 0.6, 0.6, 1.0); - - // go over all chunks, iterator version - for (chunk, position) in tree.iter_chunks_and_positions() { - if chunk.visible { - // draw it if it's visible - // here we get the chunk position and size - let uniforms = uniform! { - offset: [position.get_float_coords().0 as f32, position.get_float_coords().1 as f32], - scale: position.get_size() as f32, - state: chunk.cache_state, - selected: chunk.selected as i32, - }; - - // draw it with glium - target - .draw( - &vertex_buffer, - &index_buffer, - &program, - &uniforms, - &Default::default(), - ) - .unwrap(); - } + // and make chunks that are cached visible + for chunk in tree.iter_chunks_to_remove_mut() { + chunk.cache_state = 2; } - target.finish().unwrap(); - }; + // do the update + tree.do_update(); - // set up the tree - let mut tree = Tree::::new(64); + // and clean + tree.complete_update(); + } + + // go over all chunks in the tree and set them to not be selected + for chunk in tree.iter_chunks_mut() { + chunk.selected = false; + } - draw((0.5, 0.5), &mut tree, &display); + // and select the chunk at the mouse position + if let Some(chunk) = tree.get_chunk_from_position_mut(qv) { + chunk.selected = true; + } + + // and select a number of chunks in a region when the mouse buttons are selected + + // and, Redraw! + let mut target = ctx.display.draw(); + target.clear_color(0.6, 0.6, 0.6, 1.0); + + // go over all chunks, iterator version + for (chunk, position) in tree.iter_chunks_and_positions() { + if chunk.visible { + // draw it if it's visible + // here we get the chunk position and size + let uniforms = uniform! { + offset: [position.get_float_coords().0 as f32, position.get_float_coords().1 as f32], + scale: position.get_size() as f32, + state: chunk.cache_state, + selected: chunk.selected as i32, + }; + + // draw it with glium + target + .draw( + &ctx.vertex_buffer, + &ctx.index_buffer, + &ctx.shaders, + &uniforms, + &Default::default(), + ) + .unwrap(); + } + } + target.finish().unwrap(); +} + +fn main() { + // set up the tree + let mut tree = Tree::::new(32); + // start the glium event loop + let event_loop = glutin::event_loop::EventLoop::new(); + let context = RenderContext::new(&event_loop); + draw((0.5, 0.5), &mut tree, &context); // the mouse cursor position let mut mouse_pos = (0.5, 0.5); @@ -208,7 +221,7 @@ fn main() { glutin::event::Event::RedrawRequested(_) => { // and draw, if enough time elapses if last_redraw.elapsed().as_millis() > 16 { - draw(mouse_pos, &mut tree, &display); + draw(mouse_pos, &mut tree, &context); last_redraw = std::time::Instant::now(); } @@ -220,12 +233,12 @@ fn main() { glutin::event::WindowEvent::CursorMoved { position, .. } => { // get the mouse position mouse_pos = ( - position.x / display.get_framebuffer_dimensions().0 as f64, - position.y / display.get_framebuffer_dimensions().1 as f64, + position.x as f32 / context.display.get_framebuffer_dimensions().0 as f32, + position.y as f32 / context.display.get_framebuffer_dimensions().1 as f32, ); // request a redraw - display.gl_window().window().request_redraw(); + context.display.gl_window().window().request_redraw(); glutin::event_loop::ControlFlow::Wait } diff --git a/examples/rayon.rs b/examples/rayon.rs index 52f5f10..e80ff2a 100644 --- a/examples/rayon.rs +++ b/examples/rayon.rs @@ -44,7 +44,7 @@ fn main() { if tree.prepare_update( &[OctVec::new(4096, 4096, 4096, 32)], // target position in the tree 2, // the amount of detail - |position_in_tree| Chunk::new(position_in_tree), // and how we should make a new tree inside the function here. This should be done quickly + &mut |position_in_tree| Chunk::new(position_in_tree), // and how we should make a new tree inside the function here. This should be done quickly ) { let duration = start_time.elapsed().as_micros(); @@ -55,7 +55,7 @@ fn main() { // if there was an update, we need to first generate new chunks with expensive_init tree.get_chunks_to_add_slice_mut().par_iter_mut().for_each( - |ToAddContainer { position, chunk }| { + |ToAddContainer { position, chunk,.. }| { // and run expensive init chunk.expensive_init(*position); }, diff --git a/rust-toolchain.toml b/rust-toolchain.toml new file mode 100644 index 0000000..0542f3e --- /dev/null +++ b/rust-toolchain.toml @@ -0,0 +1,3 @@ +[toolchain] +#channel = "nightly" +channel = "stable" \ No newline at end of file diff --git a/src/coords.rs b/src/coords.rs index eb0d176..36c485c 100644 --- a/src/coords.rs +++ b/src/coords.rs @@ -1,10 +1,12 @@ //! Contains coordinate structs, QuadVec for quadtrees, and OctVec for octrees, as well as their LodVec implementation use crate::traits::LodVec; +use std::cmp::Ordering; /// A Lod Vector for use in a quadtree. /// It subdivides into 4 children of equal size. -#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Default, Debug, Hash)] +//#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Default, Debug, Hash)] +#[derive(Copy, Clone, PartialEq, Eq, Default, Debug, Hash)] pub struct QuadVec { /// x position in the quadtree. pub x: u64, @@ -17,6 +19,24 @@ pub struct QuadVec { pub depth: u8, } +impl PartialOrd for QuadVec { + fn partial_cmp(&self, other: &Self) -> Option { + if self.depth != other.depth { + return None; + } + if (self.x == other.x) && (self.y == other.y) { + return Some(Ordering::Equal); + } + + if (self.x < other.x) && (self.y < other.y) { + return Some(Ordering::Less); + } else if (self.x > other.x) && (self.y > other.y) { + return Some(Ordering::Greater); + } + None + } +} + impl QuadVec { /// creates a new vector from the raw x and y coords. /// # Args @@ -25,6 +45,9 @@ impl QuadVec { /// * `depth` the lod depth the coord is at. This is soft limited at roughly 60, and the tree might behave weird if it gets higher #[inline] pub fn new(x: u64, y: u64, depth: u8) -> Self { + debug_assert!(x < (1 << depth)); + debug_assert!(y < (1 << depth)); + debug_assert!(depth <= 60); Self { x, y, depth } } @@ -67,21 +90,8 @@ impl QuadVec { impl LodVec for QuadVec { #[inline] - fn num_children() -> usize { - 4 - } - - #[inline] - fn root() -> Self { - Self { - x: 0, - y: 0, - depth: 0, - } - } - - #[inline] - fn get_child(self, index: usize) -> Self { + fn get_child(self, index: u32) -> Self { + debug_assert!(index < 4); // the positions, doubled in scale let x = self.x << 1; let y = self.y << 1; @@ -98,8 +108,20 @@ impl LodVec for QuadVec { } } + const NUM_CHILDREN: u32 = 4; + + #[inline] + fn root() -> Self { + Self { + x: 0, + y: 0, + depth: 0, + } + } + #[inline] - fn can_subdivide(self, node: Self, detail: u64) -> bool { + fn can_subdivide(self, node: Self, detail: u32) -> bool { + let detail = detail as u64; // return early if the level of this chunk is too high if node.depth >= self.depth { return false; @@ -131,7 +153,7 @@ impl LodVec for QuadVec { local.0 >= min.0 && local.0 < max.0 && local.1 >= min.1 && local.1 < max.1 } - fn is_inside_bounds(self, min: Self, max: Self, max_depth: u64) -> bool { + fn is_inside_bounds(self, min: Self, max: Self, max_depth: u8) -> bool { // get the lowest lod level let level = self.depth.min(min.depth.min(max.depth)); @@ -139,7 +161,7 @@ impl LodVec for QuadVec { let self_difference = self.depth - level; let min_difference = min.depth - level; let max_difference = max.depth - level; - + // println!("diff {:?}, {:?}, {:?}", self_difference, min_difference,max_difference); // get the coords to that level let self_x = self.x >> self_difference; let self_y = self.y >> self_difference; @@ -149,13 +171,13 @@ impl LodVec for QuadVec { let max_x = max.x >> max_difference; let max_y = max.y >> max_difference; - + // dbg!(min_x, min_y, max_x, max_y); // then check if we are inside the AABB - self.depth as u64 <= max_depth + self.depth <= max_depth && self_x >= min_x - && self_x < max_x + && self_x <= max_x && self_y >= min_y - && self_y < max_y + && self_y <= max_y } #[inline] @@ -174,7 +196,7 @@ impl LodVec for QuadVec { /// A Lod Vector for use in an octree. /// It subdivides into 8 children of equal size. -#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Default, Debug, Hash)] +#[derive(Copy, Clone, PartialEq, Eq, Default, Debug, Hash)] pub struct OctVec { /// x position in the octree. pub x: u64, @@ -190,6 +212,23 @@ pub struct OctVec { pub depth: u8, } +impl PartialOrd for OctVec { + fn partial_cmp(&self, other: &Self) -> Option { + if self.depth != other.depth { + return None; + } + if (self.x == other.x) && (self.y == other.y) && (self.z == other.z) { + return Some(Ordering::Equal); + } + + if (self.x < other.x) && (self.y < other.y) && (self.z < other.z) { + return Some(Ordering::Less); + } else if (self.x > other.x) && (self.y > other.y) && (self.z > other.z) { + return Some(Ordering::Greater); + } + None + } +} impl OctVec { /// creates a new vector from the raw x and y coords. /// # Args @@ -199,9 +238,12 @@ impl OctVec { /// * `depth` the lod depth the coord is at. This is soft limited at roughly 60, and the tree might behave weird if it gets higher. #[inline] pub fn new(x: u64, y: u64, z: u64, depth: u8) -> Self { + debug_assert!(x < (1 << depth)); + debug_assert!(y < (1 << depth)); + debug_assert!(z < (1 << depth)); + debug_assert!(depth <= 60); Self { x, y, z, depth } } - /// creates a new vector from floating point coords. /// mapped so that (0, 0, 0) is the front bottom left corner and (1, 1, 1) is the back top right. /// # Args @@ -247,22 +289,8 @@ impl OctVec { impl LodVec for OctVec { #[inline] - fn num_children() -> usize { - 8 - } - - #[inline] - fn root() -> Self { - Self { - x: 0, - y: 0, - z: 0, - depth: 0, - } - } - - #[inline] - fn get_child(self, index: usize) -> Self { + fn get_child(self, index: u32) -> Self { + debug_assert!(index < 8); // the positions, doubled in scale let x = self.x << 1; let y = self.y << 1; @@ -282,8 +310,21 @@ impl LodVec for OctVec { } } + const NUM_CHILDREN: u32 = 8; + #[inline] - fn can_subdivide(self, node: Self, detail: u64) -> bool { + fn root() -> Self { + Self { + x: 0, + y: 0, + z: 0, + depth: 0, + } + } + + #[inline] + fn can_subdivide(self, node: Self, detail: u32) -> bool { + let detail = detail as u64; // return early if the level of this chunk is too high if node.depth >= self.depth { return false; @@ -324,7 +365,8 @@ impl LodVec for OctVec { && local.2 < max.2 } - fn is_inside_bounds(self, min: Self, max: Self, max_depth: u64) -> bool { + #[inline] + fn is_inside_bounds(self, min: Self, max: Self, max_depth: u8) -> bool { // get the lowest lod level let level = self.depth.min(min.depth.min(max.depth)); @@ -347,13 +389,13 @@ impl LodVec for OctVec { let max_z = max.z >> max_difference; // then check if we are inside the AABB - self.depth as u64 <= max_depth + self.depth <= max_depth && self_x >= min_x - && self_x < max_x + && self_x <= max_x && self_y >= min_y - && self_y < max_y + && self_y <= max_y && self_z >= min_z - && self_z < max_z + && self_z <= max_z } #[inline] diff --git a/src/iter.rs b/src/iter.rs index f0d8c96..e0ac6b1 100644 --- a/src/iter.rs +++ b/src/iter.rs @@ -1,5 +1,4 @@ //! Iterators over chunks - use crate::traits::*; use crate::tree::*; @@ -407,7 +406,7 @@ pub struct ChunksInBoundIter { stack: Vec, // and maximum depth to go to - max_depth: u64, + max_depth: u8, // and the min of the bound bound_min: L, @@ -424,7 +423,7 @@ impl Iterator for ChunksInBoundIter { let current = self.stack.pop()?; // go over all child nodes - for i in 0..L::num_children() { + for i in 0..L::NUM_CHILDREN { let position = current.get_child(i); // if they are in bounds, and the correct depth, add them to the stack @@ -445,7 +444,7 @@ pub struct ChunksInBoundAndMaybeTreeIter<'a, C: Sized, L: LodVec> { stack: Vec<(L, Option)>, // and maximum depth to go to - max_depth: u64, + max_depth: u8, // and the min of the bound bound_min: L, @@ -462,7 +461,7 @@ impl<'a, C: Sized, L: LodVec> Iterator for ChunksInBoundAndMaybeTreeIter<'a, C, let (current_position, current_node) = self.stack.pop()?; // go over all child nodes - for i in 0..L::num_children() { + for i in 0..L::NUM_CHILDREN { let position = current_position.get_child(i); // if they are in bounds, and the correct depth, add them to the stack @@ -472,8 +471,10 @@ impl<'a, C: Sized, L: LodVec> Iterator for ChunksInBoundAndMaybeTreeIter<'a, C, // and if it has children if let Some(children) = node.children { // children, so node - self.stack - .push((position, Some(self.tree.nodes[children.get() + i]))); + self.stack.push(( + position, + Some(self.tree.nodes[(children.get() + i) as usize]), + )); } else { // no node, so no chunk self.stack.push((position, None)); @@ -487,7 +488,7 @@ impl<'a, C: Sized, L: LodVec> Iterator for ChunksInBoundAndMaybeTreeIter<'a, C, // and return this item from the stack if let Some(node) = current_node { // there is a node, so get the chunk it has - let chunk = &self.tree.chunks[node.chunk].chunk; + let chunk = &self.tree.chunks[node.chunk as usize].chunk; // and return it Some((current_position, Some(chunk))) @@ -506,7 +507,7 @@ pub struct ChunksInBoundAndTreeIter<'a, C: Sized, L: LodVec> { stack: Vec<(L, TreeNode)>, // and maximum depth to go to - max_depth: u64, + max_depth: u8, // and the min of the bound bound_min: L, @@ -523,7 +524,7 @@ impl<'a, C: Sized, L: LodVec> Iterator for ChunksInBoundAndTreeIter<'a, C, L> { let (current_position, current_node) = self.stack.pop()?; // go over all child nodes - for i in 0..L::num_children() { + for i in 0..L::NUM_CHILDREN { let position = current_position.get_child(i); // if the node has children @@ -532,7 +533,7 @@ impl<'a, C: Sized, L: LodVec> Iterator for ChunksInBoundAndTreeIter<'a, C, L> { if position.is_inside_bounds(self.bound_min, self.bound_max, self.max_depth) { // and push to the stack self.stack - .push((position, self.tree.nodes[children.get() + i])); + .push((position, self.tree.nodes[(children.get() + i) as usize])); } } } @@ -540,7 +541,7 @@ impl<'a, C: Sized, L: LodVec> Iterator for ChunksInBoundAndTreeIter<'a, C, L> { // and return the position and node Some(( current_position, - &self.tree.chunks[current_node.chunk].chunk, + &self.tree.chunks[current_node.chunk as usize].chunk, )) } } @@ -553,7 +554,7 @@ pub struct ChunksInBoundAndMaybeTreeIterMut<'a, C: Sized, L: LodVec> { stack: Vec<(L, Option)>, // and maximum depth to go to - max_depth: u64, + max_depth: u8, // and the min of the bound bound_min: L, @@ -570,7 +571,7 @@ impl<'a, C: Sized, L: LodVec> Iterator for ChunksInBoundAndMaybeTreeIterMut<'a, let (current_position, current_node) = self.stack.pop()?; // go over all child nodes - for i in 0..L::num_children() { + for i in 0..L::NUM_CHILDREN { let position = current_position.get_child(i); // if they are in bounds, and the correct depth, add them to the stack @@ -580,8 +581,10 @@ impl<'a, C: Sized, L: LodVec> Iterator for ChunksInBoundAndMaybeTreeIterMut<'a, // and if it has children if let Some(children) = node.children { // children, so node - self.stack - .push((position, Some(self.tree.nodes[children.get() + i]))); + self.stack.push(( + position, + Some(self.tree.nodes[(children.get() + i) as usize]), + )); } else { // no node, so no chunk self.stack.push((position, None)); @@ -595,7 +598,7 @@ impl<'a, C: Sized, L: LodVec> Iterator for ChunksInBoundAndMaybeTreeIterMut<'a, // and return this item from the stack if let Some(node) = current_node { // there is a node, so get the chunk it has - let chunk = &mut self.tree.chunks[node.chunk].chunk as *mut C; + let chunk = &mut self.tree.chunks[node.chunk as usize].chunk as *mut C; // and return it // Safety: The iterator lives at least as long as the tree, and no changes can be made to the tree while it's borrowed by the iterator @@ -615,7 +618,7 @@ pub struct ChunksInBoundAndTreeIterMut<'a, C: Sized, L: LodVec> { stack: Vec<(L, TreeNode)>, // and maximum depth to go to - max_depth: u64, + max_depth: u8, // and the min of the bound bound_min: L, @@ -632,7 +635,7 @@ impl<'a, C: Sized, L: LodVec> Iterator for ChunksInBoundAndTreeIterMut<'a, C, L> let (current_position, current_node) = self.stack.pop()?; // go over all child nodes - for i in 0..L::num_children() { + for i in 0..L::NUM_CHILDREN { let position = current_position.get_child(i); // if the node has children @@ -641,7 +644,7 @@ impl<'a, C: Sized, L: LodVec> Iterator for ChunksInBoundAndTreeIterMut<'a, C, L> if position.is_inside_bounds(self.bound_min, self.bound_max, self.max_depth) { // and push to the stack self.stack - .push((position, self.tree.nodes[children.get() + i])); + .push((position, self.tree.nodes[(children.get() + i) as usize])); } } } @@ -649,7 +652,7 @@ impl<'a, C: Sized, L: LodVec> Iterator for ChunksInBoundAndTreeIterMut<'a, C, L> // and return the position and node // Safety: The iterator lives at least as long as the tree, and no changes can be made to the tree while it's borrowed by the iterator Some((current_position, unsafe { - (&mut self.tree.chunks[current_node.chunk].chunk as *mut C).as_mut()? + (&mut self.tree.chunks[current_node.chunk as usize].chunk as *mut C).as_mut()? })) } } @@ -669,8 +672,12 @@ where pub fn iter_all_chunks_in_bounds( bound_min: L, bound_max: L, - max_depth: u64, + max_depth: u8, ) -> ChunksInBoundIter { + debug_assert!( + bound_min < bound_max, + "Bounds must select a non-empty area/volume" + ); ChunksInBoundIter { stack: vec![L::root()], max_depth, @@ -685,7 +692,7 @@ where &'a self, bound_min: L, bound_max: L, - max_depth: u64, + max_depth: u8, ) -> ChunksInBoundAndMaybeTreeIter { ChunksInBoundAndMaybeTreeIter { stack: vec![(L::root(), self.nodes.first().copied())], @@ -703,7 +710,7 @@ where &'a self, bound_min: L, bound_max: L, - max_depth: u64, + max_depth: u8, ) -> ChunksInBoundAndTreeIter { // get the stack, empty if we can't get the first node let stack = if let Some(node) = self.nodes.first() { @@ -727,7 +734,7 @@ where &'a mut self, bound_min: L, bound_max: L, - max_depth: u64, + max_depth: u8, ) -> ChunksInBoundAndMaybeTreeIterMut { ChunksInBoundAndMaybeTreeIterMut { stack: vec![(L::root(), self.nodes.first().copied())], @@ -745,7 +752,7 @@ where &'a mut self, bound_min: L, bound_max: L, - max_depth: u64, + max_depth: u8, ) -> ChunksInBoundAndTreeIterMut { // get the stack, empty if we can't get the first node let stack = if let Some(node) = self.nodes.first() { @@ -753,7 +760,6 @@ where } else { vec![] }; - ChunksInBoundAndTreeIterMut { stack, tree: self, @@ -766,22 +772,183 @@ where #[cfg(test)] mod tests { - use super::*; use crate::coords::*; + use rand::rngs::SmallRng; + use rand::{Rng, SeedableRng}; + use std::cmp::Ordering; - // TODO: also test the other iters + const NUM_QUERIES: usize = 100; + + fn get_chunk_count_at_max_depth_quad(a: QuadVec, b: QuadVec) -> u64 { + assert_eq!(a.depth, b.depth); + ((b.x - a.x) + 1) * ((b.y - a.y) + 1) + } #[test] - fn test_bounds() { - struct C; - - for pos in Tree::::iter_all_chunks_in_bounds( - QuadVec::new(1, 1, 4), - QuadVec::new(8, 8, 4), - 4, - ) { - println!("{:?}", pos); + ///Tests generation of coordinates in bounds over QuadTree + fn test_bounds_quadtree() { + const D: u8 = 4; + + let mut rng = rand::thread_rng(); + + for _i in 1..NUM_QUERIES { + let cmax = 1 << D; + let min = QuadVec::new(rng.gen_range(0..cmax), rng.gen_range(0..cmax), D); + let max = QuadVec::new(rng.gen_range(0..cmax), rng.gen_range(0..cmax), D); + //println!("Generated min {:?}", min); + //println!("Generated max {:?}", max); + let cmp = min.partial_cmp(&max); + if cmp.is_none() { + // println!("Can not compare {min:?} and {max:?}"); + continue; + } + let (min, max) = match cmp.unwrap() { + Ordering::Greater => (max, min), + Ordering::Less => (min, max), + Ordering::Equal => { + continue; + } + }; + struct C; + let mut count = 0; + for pos in Tree::::iter_all_chunks_in_bounds(min, max, D) { + // println!("{:?}", pos); + + if pos.depth == 4 { + count += 1; + } + } + assert_eq!(count, get_chunk_count_at_max_depth_quad(min, max)); + } + } + + fn get_chunk_count_at_max_depth_oct(a: OctVec, b: OctVec) -> u64 { + assert_eq!(a.depth, b.depth); + ((b.x - a.x) + 1) * ((b.y - a.y) + 1) * ((b.z - a.z) + 1) + } + + ///Tests generation of coordinates in bounds over OctTree + #[test] + fn test_bounds_octree() { + const D: u8 = 4; + + let mut rng = rand::thread_rng(); + + for _i in 1..100 { + let cmax = 1 << D; + let min = OctVec::new( + rng.gen_range(0..cmax), + rng.gen_range(0..cmax), + rng.gen_range(0..cmax), + D, + ); + let max = OctVec::new( + rng.gen_range(0..cmax), + rng.gen_range(0..cmax), + rng.gen_range(0..cmax), + D, + ); + + println!("Generated min {:?}", min); + println!("Generated max {:?}", max); + let cmp = min.partial_cmp(&max); + if cmp.is_none() { + println!("Can not compare {min:?} and {max:?}"); + continue; + } + let (min, max) = match cmp.unwrap() { + Ordering::Greater => (max, min), + Ordering::Less => (min, max), + Ordering::Equal => { + continue; + } + }; + struct C; + let mut count = 0; + for pos in Tree::::iter_all_chunks_in_bounds(min, max, D) { + // println!("{:?}", pos); + + if pos.depth == 4 { + count += 1; + } + } + assert_eq!(count, get_chunk_count_at_max_depth_oct(min, max)); + } + } + + #[test] + fn test_readonly_iterator_over_chunks_oct() { + struct Chunk { + visible: bool, + } + const D: u8 = 4; + const R: u64 = 3; + + fn chunk_creator(position: OctVec) -> Chunk { + let r = (R * R) as i32 - 2; + + let visible = match position.depth { + D => { + (position.x as i32 - r).pow(2) + + (position.y as i32 - r).pow(2) + + (position.z as i32 - r).pow(2) + < r + } + _ => false, + }; + //println!("create {:?} {:?}", position, visible); + Chunk { visible } + } + + let mut tree = Tree::new(65); + let qv = OctVec::new(R, R, R, D); + while tree.prepare_update(&[qv], R as u32, &mut chunk_creator) { + // do the update + tree.do_update(); + // and clean + tree.complete_update(); + } + + //for i in &tree.chunks {} + + let mut rng = SmallRng::seed_from_u64(42); + + for _ite in 1..NUM_QUERIES { + let cmax = 1 << D; + let min = OctVec::new( + rng.gen_range(0..cmax), + rng.gen_range(0..cmax), + rng.gen_range(0..cmax), + D, + ); + let max = OctVec::new( + rng.gen_range(0..cmax), + rng.gen_range(0..cmax), + rng.gen_range(0..cmax), + D, + ); + let cmp = min.partial_cmp(&max); + if cmp.is_none() { + //println!("Can not compare {min:?} and {max:?}"); + continue; + } + let (min, max) = match cmp.unwrap() { + Ordering::Greater => (max, min), + Ordering::Less => (min, max), + Ordering::Equal => { + continue; + } + }; + let mut filled_voxels: u32 = 0; + //println!("{:?} {:?} {:?}", ite, min, max); + for (l, c) in tree.iter_all_chunks_in_bounds_and_tree(min, max, D) { + if c.visible { + println!(" Sphere chunk {:?}", l); + filled_voxels += 1; + } + } + println!(" filled {:?}", filled_voxels); } } } diff --git a/src/lib.rs b/src/lib.rs index fdba650..7bd4d95 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,4 @@ +//#![feature(generic_const_exprs)] //! # LodTree //! LodTree, a simple tree data structure for doing chunk-based level of detail. //! @@ -21,7 +22,7 @@ //! # use lodtree::*; //! # use lodtree::coords::OctVec; //! # struct Chunk {} -//! let mut tree = Tree::::new(); +//! let mut tree = Tree::::new(64); //! ``` //! //! If you want to update chunks due to the camera being moved, you can check if it's needed with prepare_update. @@ -45,9 +46,9 @@ //! # struct Chunk {} //! # let mut tree = Tree::::new(64); //! let needs_updating = tree.prepare_update( -//! &[OctVec::new(8, 8, 8, 8)], // the target positions to generate the lod around -//! 4, // amount of detail -//! |pos| Chunk {} // and the function to construct the chunk with +//! &[OctVec::new(8, 8, 8, 8)], // the target positions to generate the lod around +//! 4, // amount of detail +//! &mut |pos| Chunk {} // and the function to construct the chunk with //! // NOTE: this is only called for completely new chunks, not the ones loaded from the chunk cache! //! ); //! ``` @@ -62,14 +63,14 @@ //! # impl Chunk { //! # fn expensive_init(&mut self, pos: QuadVec) {} //! # } -//! # let mut tree = Tree::::new(); -//! tree.get_chunks_to_add_slice_mut() -//! .iter_mut() // or par_iter_mut() if you're using rayon -//! .for_each(|(position, chunk)| { +//! # let mut tree = Tree::::new(64); //! -//! // and run expensive init, probably does something with procedural generation -//! chunk.expensive_init(*position); -//! }); +//! tree.get_chunks_to_add_slice_mut() +//! .iter_mut() // or par_iter_mut() if you're using rayon +//! .for_each(|to_add| { +//! // and run expensive init, probably does something with procedural generation +//! to_add.chunk.expensive_init(to_add.position); +//! }); //! ``` //! //! Next, we'll also want to change the visibility of some chunks so they don't overlap with higher detail lods. @@ -80,14 +81,14 @@ //! # impl Chunk { //! # fn set_visible(&mut self, v: bool) {} //! # } -//! # let mut tree = Tree::::new(); +//! # let mut tree = Tree::::new(64); //! // and make all chunks visible or not //! for chunk in tree.iter_chunks_to_activate_mut() { -//! chunk.set_visible(true); +//! chunk.set_visible(true); //! } //! //! for chunk in tree.iter_chunks_to_deactivate_mut() { -//! chunk.set_visible(false); +//! chunk.set_visible(false); //! } //! ``` //! We'll probably also want to do some cleanup with chunks that are removed. @@ -99,9 +100,9 @@ //! # impl Chunk { //! # fn cleanup(&mut self) {} //! # } -//! # let mut tree = Tree::::new(); +//! # let mut tree = Tree::::new(64); //! for chunk in tree.iter_chunks_to_remove_mut() { -//! chunk.cleanup(); +//! chunk.cleanup(); //! } //! ``` //! And finally, actually update the tree with the new chunks. @@ -110,7 +111,7 @@ //! # use lodtree::*; //! # use lodtree::coords::QuadVec; //! # struct Chunk {} -//! # let mut tree = Tree::::new(); +//! # let mut tree = Tree::::new(64); //! tree.do_update(); //! ``` //! But we're not done yet! @@ -123,10 +124,11 @@ //! # impl Chunk { //! # fn true_cleanup(&mut self) {} //! # } -//! # let mut tree = Tree::::new(); -//! for (position, chunk) in tree.get_chunks_to_delete_slice_mut().iter_mut() { // there's also an iterator for just chunks here -//! chunk.true_cleanup(); -//! } +//! +//! let mut tree = Tree::::new(64); +//! for td in tree.get_chunks_to_delete_slice_mut().iter_mut() { // there's also an iterator for just chunks here +//! td.chunk.true_cleanup(); +//! } //! //! // and finally, complete the entire update //! tree.complete_update(); diff --git a/src/traits.rs b/src/traits.rs index f2a8987..777c8fa 100644 --- a/src/traits.rs +++ b/src/traits.rs @@ -3,12 +3,13 @@ /// trait for defining a Level of Detail vector. /// such a vector contains the current position in the octree (3d coords), as well as the lod level it's at, in integer coords. -pub trait LodVec: std::hash::Hash + Eq + Sized + Copy + Clone + Send + Sync + Default { +pub trait LodVec: + std::hash::Hash + Eq + Sized + Copy + Clone + Send + Sync + Default + PartialOrd + std::fmt::Debug +{ /// gets one of the child node position of this node, defined by it's index. - fn get_child(self, index: usize) -> Self; - - /// get the number of child nodes a node can have in the tree. - fn num_children() -> usize; + fn get_child(self, index: u32) -> Self; + /// the number of child nodes a node can have in the tree. + const NUM_CHILDREN: u32; /// returns the lod vector as if it's at the root of the tree. fn root() -> Self; @@ -58,14 +59,15 @@ pub trait LodVec: std::hash::Hash + Eq + Sized + Copy + Clone + Send + Sync + De /// } /// # } /// ``` - fn can_subdivide(self, node: Self, detail: u64) -> bool; + fn can_subdivide(self, node: Self, detail: u32) -> bool; /// check if this chunk is inside of a bounding box /// where min is the lowest corner of the box, and max is the highest corner /// The implementation for QuadVec is as follows: /// ```rust - /// # struct Chunk { x: u64, y: u64, depth: u8 } - /// # impl Chunk { + /// struct Chunk { x: u64, y: u64, depth: u8 } + /// impl Chunk { + /// fn is_inside_bounds(self, min: Self, max: Self, max_depth: u8) -> bool{ /// // get the lowest lod level /// let level = self.depth.min(min.depth.min(max.depth)); /// @@ -74,7 +76,7 @@ pub trait LodVec: std::hash::Hash + Eq + Sized + Copy + Clone + Send + Sync + De /// let min_difference = min.depth - level; /// let max_difference = max.depth - level; /// - //// // get the coords to that level + /// // get the coords to that level /// let self_x = self.x >> self_difference; /// let self_y = self.y >> self_difference; /// @@ -85,14 +87,15 @@ pub trait LodVec: std::hash::Hash + Eq + Sized + Copy + Clone + Send + Sync + De /// let max_y = max.y >> max_difference; /// /// // then check if we are inside the AABB - /// self.depth as u64 <= max_depth - /// && self_x >= min_x - /// && self_x < max_x - /// && self_y >= min_y - /// && self_y < max_y - /// # } + /// self.depth <= max_depth + /// && self_x >= min_x + /// && self_x < max_x + /// && self_y >= min_y + /// && self_y < max_y + /// } + /// } /// ``` - fn is_inside_bounds(self, min: Self, max: Self, max_depth: u64) -> bool; + fn is_inside_bounds(self, min: Self, max: Self, max_depth: u8) -> bool; /// Wether this node contains a child node fn contains_child_node(self, child: Self) -> bool; diff --git a/src/tree.rs b/src/tree.rs index c247bd4..548c99b 100644 --- a/src/tree.rs +++ b/src/tree.rs @@ -3,7 +3,8 @@ use crate::traits::*; use std::collections::{HashMap, VecDeque}; -use std::num::NonZeroUsize; +use std::fmt::Debug; +use std::num::NonZeroU32; // struct for keeping track of chunks // keeps track of the parent and child indices @@ -11,18 +12,33 @@ use std::num::NonZeroUsize; pub(crate) struct TreeNode { // children, these can't be the root (index 0), so we can use Some and Nonzero for slightly more compact memory // children are also contiguous, so we can assume that this to this + num children - 1 are all the children of this node - pub(crate) children: Option, + pub(crate) children: Option, // where the chunk for this node is stored - pub(crate) chunk: usize, + pub(crate) chunk: u32, //TODO change this to Option such that nodes with no chunks can be created } +//TODO: a better treenode structure +// Tree node that encompasses 4/8 children at once. Each child has two pointers: +// children[i] will point to the TreeNode which encompasses its children +// chunk[i] will point to the data chunk. +// both pointers may be "None", indicating either no children, or no data +//#[derive(Clone, Debug)] +// pub(crate) struct TreeNode2 where [(); L::NUM_CHILDREN as usize]: { +// // children, these can't be the root (index 0), so we can use Some and Nonzero for slightly more compact memory +// // children are also contiguous, so we can assume that this to this + num children - 1 are all the children of this node +// pub(crate) children: [Option; L::NUM_CHILDREN as usize], +// +// // where the chunk for this node is stored +// pub(crate) chunk: [Option; L::NUM_CHILDREN as usize], +// } + // utility struct for holding actual chunks and the node that owns them #[derive(Clone, Debug)] pub(crate) struct ChunkContainer { - pub(crate) chunk: C, - pub(crate) index: usize, - pub(crate) position: L, + pub(crate) chunk: C, // actual data inside the chunk + pub(crate) index: u32, // index of the node that holds this chunk + pub(crate) position: L, // where the chunk is (as this can not be recovered from node tree) } /// holds a chunk to add and it's position @@ -35,13 +51,15 @@ pub struct ToAddContainer { /// Position of the chunk to add pub position: L, + /// Index of the parent node + parent_node_index: u32, } // utility struct for holding chunks to remove #[derive(Clone, Debug)] struct ToRemoveContainer { - chunk: usize, // chunk index - parent: usize, // parent index + chunk: u32, // chunk index + parent: u32, // parent index } /// holds a chunk that's going to be deleted and it's position @@ -57,7 +75,7 @@ pub struct ToDeleteContainer { // utility struct for holding chunks in the queue #[derive(Clone, Debug)] struct QueueContainer { - node: usize, // chunk index + node: u32, // chunk index position: L, // and it's position } @@ -73,23 +91,19 @@ pub struct Tree { pub(crate) nodes: Vec, /// list of free nodes in the Tree, to allocate new nodes into - free_list: VecDeque, - - /// parent chunk indices of the chunks to be added. - /// tuple of the parent index and the position. - chunks_to_add_parent: Vec, + free_list: VecDeque, - /// actual chunk to add + /// actual chunks to add during next update chunks_to_add: Vec>, /// chunk indices to be removed, tuple of index, parent index chunks_to_remove: Vec, - /// indices of the chunks that need to be activated - chunks_to_activate: Vec, + /// indices of the chunks that need to be activated (i.e. the chunks that have just lost children) + chunks_to_activate: Vec, - /// indices of the chunks that need to be deactivated - chunks_to_deactivate: Vec, + /// indices of the chunks that need to be deactivated (i.e. chunks that have been subdivided in this iteration) + chunks_to_deactivate: Vec, /// internal queue for processing, that way we won't need to reallocate it processing_queue: Vec>, @@ -112,7 +126,8 @@ where C: Sized, L: LodVec, { - // helper function for later, gets a node index from a position + /// Gets an index in self.nodes vector from a position. + /// If position is not pointing to a node, None is returned. fn get_node_index_from_position(&self, position: L) -> Option { // the current node let mut current = *self.nodes.get(0)?; @@ -124,16 +139,15 @@ where loop { // if the current node is the one we are looking for, return if current_position == position { - return Some(current.chunk); + return Some(current.chunk as usize); } // if the current node does not have children, stop - // this works according to clippy + // this works according to clippy current.children?; - // if not, go over the node children - if let Some((index, found_position)) = (0..L::num_children()) + if let Some((index, found_position)) = (0..L::NUM_CHILDREN) .map(|i| (i, current_position.get_child(i))) .find(|(_, x)| x.contains_child_node(position)) { @@ -141,7 +155,7 @@ where current_position = found_position; // and the node is at the index of the child nodes + index - current = self.nodes[current.children.unwrap().get() + index]; + current = self.nodes[(current.children.unwrap().get() + index) as usize]; } else { // if no child got found that matched the item, return none return None; @@ -149,20 +163,41 @@ where } } - /// create a new, empty tree + /// Create a new, empty tree, with a cache of given size + /// Set cache to zero to disable it entirely (may speed up certain workloads by ~50%) pub fn new(cache_size: usize) -> Self { // make a new Tree // also allocate some room for nodes Self { - chunks_to_add_parent: Vec::with_capacity(512), - chunks_to_add: Vec::with_capacity(512), - chunks_to_remove: Vec::with_capacity(512), - chunks_to_activate: Vec::with_capacity(512), - chunks_to_deactivate: Vec::with_capacity(512), - chunks: Vec::with_capacity(512), - nodes: Vec::with_capacity(512), - free_list: VecDeque::with_capacity(512), - processing_queue: Vec::with_capacity(512), + chunks_to_add: Vec::new(), + chunks_to_remove: Vec::new(), + chunks_to_activate: Vec::new(), + chunks_to_deactivate: Vec::new(), + chunks: Vec::new(), + nodes: Vec::new(), + free_list: VecDeque::new(), + processing_queue: Vec::new(), + cache_size, + chunk_cache: HashMap::with_capacity(cache_size), + cache_queue: VecDeque::with_capacity(cache_size), + chunks_to_delete: Vec::with_capacity(cache_size), + } + } + + /// create a tree with preallocated memory for chunks and nodes + /// Set cache to zero to disable it entirely (may speed up certain workloads by ~50%) + pub fn with_capacity(capacity: usize, cache_size: usize) -> Self { + // make a new Tree + // also allocate some room for nodes + Self { + chunks_to_add: Vec::with_capacity(capacity), + chunks_to_remove: Vec::with_capacity(capacity), + chunks_to_activate: Vec::with_capacity(capacity), + chunks_to_deactivate: Vec::with_capacity(capacity), + chunks: Vec::with_capacity(capacity), + nodes: Vec::with_capacity(capacity), + free_list: VecDeque::with_capacity(capacity), + processing_queue: Vec::with_capacity(capacity), cache_size, chunk_cache: HashMap::with_capacity(cache_size), cache_queue: VecDeque::with_capacity(cache_size), @@ -230,13 +265,13 @@ where /// get a chunk pending activation #[inline] pub fn get_chunk_to_activate(&self, index: usize) -> &C { - &self.chunks[self.nodes[self.chunks_to_activate[index]].chunk].chunk + &self.chunks[self.nodes[self.chunks_to_activate[index] as usize].chunk as usize].chunk } /// get a mutable chunk pending activation #[inline] pub fn get_chunk_to_activate_mut(&mut self, index: usize) -> &mut C { - &mut self.chunks[self.nodes[self.chunks_to_activate[index]].chunk].chunk + &mut self.chunks[self.nodes[self.chunks_to_activate[index] as usize].chunk as usize].chunk } /// gets a mutable pointer to a chunk that is pending activation @@ -249,7 +284,7 @@ where /// get the position of a chunk pending activation #[inline] pub fn get_position_of_chunk_to_activate(&self, index: usize) -> L { - self.chunks[self.nodes[self.chunks_to_activate[index]].chunk].position + self.chunks[self.nodes[self.chunks_to_activate[index] as usize].chunk as usize].position } /// get the number of chunks pending deactivation @@ -261,13 +296,13 @@ where /// get a chunk pending deactivation #[inline] pub fn get_chunk_to_deactivate(&self, index: usize) -> &C { - &self.chunks[self.nodes[self.chunks_to_deactivate[index]].chunk].chunk + &self.chunks[self.nodes[self.chunks_to_deactivate[index] as usize].chunk as usize].chunk } /// get a mutable chunk pending deactivation #[inline] pub fn get_chunk_to_deactivate_mut(&mut self, index: usize) -> &mut C { - &mut self.chunks[self.nodes[self.chunks_to_deactivate[index]].chunk].chunk + &mut self.chunks[self.nodes[self.chunks_to_deactivate[index] as usize].chunk as usize].chunk } /// gets a mutable pointer to a chunk that is pending deactivation @@ -280,7 +315,7 @@ where /// get the position of a chunk pending deactivation #[inline] pub fn get_position_of_chunk_to_deactivate(&self, index: usize) -> L { - self.chunks[self.nodes[self.chunks_to_deactivate[index]].chunk].position + self.chunks[self.nodes[self.chunks_to_deactivate[index] as usize].chunk as usize].position } /// get the number of chunks pending removal @@ -292,13 +327,14 @@ where /// get a chunk pending removal #[inline] pub fn get_chunk_to_remove(&self, index: usize) -> &C { - &self.chunks[self.nodes[self.chunks_to_remove[index].chunk].chunk].chunk + &self.chunks[self.nodes[self.chunks_to_remove[index].chunk as usize].chunk as usize].chunk } /// get a mutable chunk pending removal #[inline] pub fn get_chunk_to_remove_mut(&mut self, index: usize) -> &mut C { - &mut self.chunks[self.nodes[self.chunks_to_remove[index].chunk].chunk].chunk + &mut self.chunks[self.nodes[self.chunks_to_remove[index].chunk as usize].chunk as usize] + .chunk } /// gets a mutable pointer to a chunk that is pending removal @@ -311,7 +347,7 @@ where /// get the position of a chunk pending removal #[inline] pub fn get_position_of_chunk_to_remove(&self, index: usize) -> L { - self.chunks[self.nodes[self.chunks_to_remove[index].chunk].chunk].position + self.chunks[self.nodes[self.chunks_to_remove[index].chunk as usize].chunk as usize].position } /// get the number of chunks to be added @@ -400,6 +436,108 @@ where &mut self.chunks_to_delete[..] } + /// Adds chunks at and around specified locations. + /// This operation will also add chunks at other locations around the target to fullfill the + /// datastructure constraints (such that no partially filled nodes exist). + pub fn prepare_insert( + &mut self, + targets: &[L], + detail: u32, + chunk_creator: &mut dyn FnMut(L) -> C, + ) -> bool { + //FIXME: this function currently will dry-run once for every update to make sure + // there is nothing left to update. This is a waste of CPU time, especially for many targets + + // first, clear the previous arrays + self.chunks_to_add.clear(); + self.chunks_to_remove.clear(); + self.chunks_to_activate.clear(); + self.chunks_to_deactivate.clear(); + + // if we don't have a root, make one pending for creation + if self.nodes.is_empty() { + // chunk to add + let chunk_to_add = self.get_chunk_from_cache(L::root(), chunk_creator); + + // we need to add the root as pending + self.chunks_to_add.push(ToAddContainer { + position: L::root(), + chunk: chunk_to_add, + parent_node_index:0, + }); + + // and an update is needed + return true; + } + + // clear the processing queue from any previous updates + self.processing_queue.clear(); + + // add the root node (always at 0, if there is no root we would have returned earlier) to the processing queue + self.processing_queue.push(QueueContainer { + position: L::root(), + node: 0, + }); + + // then, traverse the tree, as long as something is inside the queue + while let Some(QueueContainer { + position: current_position, + node: current_node_index, + }) = self.processing_queue.pop() + { + // fetch the current node + let current_node = self.nodes[current_node_index as usize]; + //dbg!(current_node_index, current_node); + // if we can subdivide, and the current node does not have children, subdivide the current node + if current_node.children.is_none() { + //println!("adding children"); + // add children to be added + for i in 0..L::NUM_CHILDREN { + // chunk to add + let chunk_to_add = + self.get_chunk_from_cache(current_position.get_child(i), chunk_creator); + + // add the new chunk to be added + self.chunks_to_add.push(ToAddContainer { + position: current_position.get_child(i), + chunk: chunk_to_add, + parent_node_index:current_node_index, + }); + + } + + // and add ourselves for deactivation + self.chunks_to_deactivate.push(current_node_index); + } else if let Some(index) = current_node.children { + //println!("has children at {index:?}"); + // queue child nodes for processing + for i in 0..L::NUM_CHILDREN { + // wether we can subdivide + let child_pos = current_position.get_child(i); + //dbg!(child_pos); + for t in targets { + if *t == child_pos { + //println!("Found match for target {t:?}"); + self.chunks[(index.get() + i) as usize].chunk = + chunk_creator(child_pos); + continue; + } + if t.can_subdivide(child_pos, detail) { + self.processing_queue.push(QueueContainer { + position: child_pos, + node: index.get() + i, + }); + break; + } + } + } + } + } + + // and return whether an update needs to be done + !self.chunks_to_add.is_empty() + } + // how it works: // each node contains a pointer to it's chunk data and first child // start from the root node, which is at 0 @@ -407,25 +545,29 @@ where // if so, queue children for removal, and self for activation (child indices, chunk pointer) // if we can subdivide, and have no children, queue children for addition, and self for removal (child positions, chunk pointer) // if none of the above and have children, queue children for processing - // processing queue is only the node positon and node index + // processing queue is only the node position and node index // when removing nodes, do so in groups of num children, and use the free list // clear the free list once we only have one chunk (the root) active // swap remove chunks, and update the node that references them (nodes won't move due to free list) - /// prepares the tree for an update. - /// this fills the internal lists of what chunks need to be added or removed. + /// prepares the tree for an update, an update is an operation that + /// adds chunks around specified locations (targets) while also erasing all other chunks. + /// this fills the internal lists of what chunks need to be added or removed as appropriate. /// # Params /// * `targets` The target positions to generate the lod around (QuadVec and OctVec define the center position and max lod in depth for this) /// * `detail` The detail for these targets (QuadVec and OctVec define this as amount of chunks around this point) /// * `chunk_creator` function to create a new chunk from a given position - /// returns wether any update is needed. + /// returns whether any update is needed. pub fn prepare_update( &mut self, targets: &[L], - detail: u64, - chunk_creator: fn(L) -> C, + detail: u32, + chunk_creator: &mut dyn FnMut(L) -> C, ) -> bool { + //FIXME: this function currently will dry-run once for every update to make sure + // there is nothing left to update. This is a waste of CPU time, especially for many targets + // first, clear the previous arrays self.chunks_to_add.clear(); self.chunks_to_remove.clear(); @@ -441,10 +583,9 @@ where self.chunks_to_add.push(ToAddContainer { position: L::root(), chunk: chunk_to_add, + parent_node_index:0, }); - // and the parent - self.chunks_to_add_parent.push(0); // and an update is needed return true; @@ -466,7 +607,7 @@ where }) = self.processing_queue.pop() { // fetch the current node - let current_node = self.nodes[current_node_index]; + let current_node = self.nodes[current_node_index as usize]; // wether we can subdivide let can_subdivide = targets @@ -476,7 +617,7 @@ where // if we can subdivide, and the current node does not have children, subdivide the current node if can_subdivide && current_node.children.is_none() { // add children to be added - for i in 0..L::num_children() { + for i in 0..L::NUM_CHILDREN { // chunk to add let chunk_to_add = self.get_chunk_from_cache(current_position.get_child(i), chunk_creator); @@ -485,10 +626,9 @@ where self.chunks_to_add.push(ToAddContainer { position: current_position.get_child(i), chunk: chunk_to_add, + parent_node_index:current_node_index, }); - // and add the parent - self.chunks_to_add_parent.push(current_node_index); } // and add ourselves for deactivation @@ -496,14 +636,14 @@ where } else if let Some(index) = current_node.children { // otherwise, if we cant subdivide and have children, remove our children if !can_subdivide - && !(0..L::num_children()) + && !(0..L::NUM_CHILDREN) .into_iter() - .any(|i| self.nodes[i + index.get()].children.is_some()) + .any(|i| self.nodes[(i + index.get()) as usize].children.is_some()) { // first, queue ourselves for activation self.chunks_to_activate.push(current_node_index); - for i in 0..L::num_children() { + for i in 0..L::NUM_CHILDREN { // no need to do this in reverse, that way the last node removed will be added to the free list, which is also the first thing used by the adding logic self.chunks_to_remove.push(ToRemoveContainer { chunk: index.get() + i, @@ -512,7 +652,7 @@ where } } else { // queue child nodes for processing if we didn't subdivide or clean up our children - for i in 0..L::num_children() { + for i in 0..L::NUM_CHILDREN { self.processing_queue.push(QueueContainer { position: current_position.get_child(i), node: index.get() + i, @@ -535,10 +675,7 @@ where // first, get the iterator for chunks that will be added // this becomes useful later - let mut chunks_to_add_iter = self - .chunks_to_add_parent - .drain(..) - .zip(self.chunks_to_add.drain(..)); + let mut chunks_to_add_iter = self.chunks_to_add.drain(..); // then, remove old chunks, or cache them // we'll drain the vector, as we don't need it anymore afterward @@ -549,22 +686,22 @@ where // but we do need to cache these { // remove the node from the tree - self.nodes[parent_index].children = None; + self.nodes[parent_index as usize].children = None; self.free_list.push_back(index); // and remove the chunk - let chunk_index = self.nodes[index].chunk; + let chunk_index = self.nodes[index as usize].chunk; // but not so fast, because if we can overwrite it with a new chunk, do so // that way we can avoid a copy later on, which might be expensive - if let Some((parent_index, ToAddContainer { position, chunk })) = + if let Some(ToAddContainer { position, chunk, parent_node_index:parent_index}) = chunks_to_add_iter.next() { // add the node let new_node_index = match self.free_list.pop_front() { Some(x) => { // reuse a free node - self.nodes[x] = TreeNode { + self.nodes[x as usize] = TreeNode { children: None, chunk: chunk_index, }; @@ -577,7 +714,7 @@ where position, }; - std::mem::swap(&mut old_chunk, &mut self.chunks[chunk_index]); + std::mem::swap(&mut old_chunk, &mut self.chunks[chunk_index as usize]); // old chunk shouldn't be mutable anymore let old_chunk = old_chunk; @@ -600,21 +737,21 @@ where break; } } + if self.cache_size > 0 { + // then assign this chunk into the cache + if let Some(cached_chunk) = + self.chunk_cache.insert(old_chunk.position, old_chunk.chunk) + { + // there might have been another cached chunk + self.chunks_to_delete.push(ToDeleteContainer { + position: old_chunk.position, + chunk: cached_chunk, + }); + } - // then assign this chunk into the cache - if let Some(cached_chunk) = - self.chunk_cache.insert(old_chunk.position, old_chunk.chunk) - { - // there might have been another cached chunk - self.chunks_to_delete.push(ToDeleteContainer { - position: old_chunk.position, - chunk: cached_chunk, - }); + // and make sure it's tracked + self.cache_queue.push_back(old_chunk.position); } - - // and make sure it's tracked - self.cache_queue.push_back(old_chunk.position); - x } // This can't be reached due to us *always* adding a chunk to the free list before popping it @@ -625,15 +762,15 @@ where // because the last node we come by in with ordered iteration is on num_children - 1, we need to set it as such]. // node 0 is the root, so the last child it has will be on num_children. // then subtracting num_children - 1 from that gives us node 1, which is the first child of the root. - if new_node_index >= L::num_children() { + if new_node_index >= L::NUM_CHILDREN { // because we loop in order, and our nodes are contiguous, the first node of the children got added on index i - (num children - 1) // so we need to adjust for that - self.nodes[parent_index].children = - NonZeroUsize::new(new_node_index - (L::num_children() - 1)); + self.nodes[parent_index as usize].children = + NonZeroU32::new(new_node_index - (L::NUM_CHILDREN - 1)); } } else { // otherwise we do need to do a regular swap remove - let old_chunk = self.chunks.swap_remove(chunk_index); + let old_chunk = self.chunks.swap_remove(chunk_index as usize); // now, we can try to add this chunk into the cache // first, remove any extra nodes if they are in the cache @@ -652,39 +789,39 @@ where break; } } - - // then assign this chunk into the cache - if let Some(cached_chunk) = - self.chunk_cache.insert(old_chunk.position, old_chunk.chunk) - { - // there might have been another cached chunk - self.chunks_to_delete.push(ToDeleteContainer { - position: old_chunk.position, - chunk: cached_chunk, - }); + if self.cache_size > 0 { + //then assign this chunk into the cache + if let Some(cached_chunk) = + self.chunk_cache.insert(old_chunk.position, old_chunk.chunk) + { + // there might have been another cached chunk + self.chunks_to_delete.push(ToDeleteContainer { + position: old_chunk.position, + chunk: cached_chunk, + }); + } + // and make sure it's tracked + self.cache_queue.push_back(old_chunk.position); } - - // and make sure it's tracked - self.cache_queue.push_back(old_chunk.position); } // and properly set the chunk pointer of the node of the chunk we just moved, if any // if we removed the last chunk, no need to update anything - if chunk_index < self.chunks.len() { - self.nodes[self.chunks[chunk_index].index].chunk = chunk_index; + if chunk_index < self.chunks.len() as u32 { + self.nodes[self.chunks[chunk_index as usize].index as usize].chunk = chunk_index; } } // add new chunks // we'll drain the vector here as well, as we won't need it anymore afterward - for (parent_index, ToAddContainer { position, chunk }) in chunks_to_add_iter { + for ToAddContainer { position, chunk, parent_node_index:parent_index } in chunks_to_add_iter { // add the node let new_node_index = match self.free_list.pop_front() { Some(x) => { // reuse a free node - self.nodes[x] = TreeNode { + self.nodes[x as usize] = TreeNode { children: None, - chunk: self.chunks.len(), + chunk: self.chunks.len() as u32, }; self.chunks.push(ChunkContainer { index: x, @@ -697,14 +834,14 @@ where // otherwise, use a new index self.nodes.push(TreeNode { children: None, - chunk: self.chunks.len(), + chunk: self.chunks.len() as u32, }); self.chunks.push(ChunkContainer { - index: self.nodes.len() - 1, + index: self.nodes.len() as u32 - 1, chunk, position, }); - self.nodes.len() - 1 + (self.nodes.len() - 1) as u32 } }; @@ -712,11 +849,11 @@ where // because the last node we come by in with ordered iteration is on num_children - 1, we need to set it as such]. // node 0 is the root, so the last child it has will be on num_children. // then subtracting num_children - 1 from that gives us node 1, which is the first child of the root. - if new_node_index >= L::num_children() { + if new_node_index >= L::NUM_CHILDREN { // because we loop in order, and our nodes are contiguous, the first node of the children got added on index i - (num children - 1) // so we need to adjust for that - self.nodes[parent_index].children = - NonZeroUsize::new(new_node_index - (L::num_children() - 1)); + self.nodes[parent_index as usize].children = + NonZeroU32::new(new_node_index - (L::NUM_CHILDREN - 1)); } } @@ -790,12 +927,13 @@ where // gets a chunk from the cache, otehrwise generates one from the given function #[inline] - fn get_chunk_from_cache(&mut self, position: L, chunk_creator: fn(L) -> C) -> C { - if let Some(chunk) = self.chunk_cache.remove(&position) { - chunk - } else { - chunk_creator(position) + fn get_chunk_from_cache(&mut self, position: L, chunk_creator: &mut dyn FnMut(L) -> C) -> C { + if self.cache_size > 0 { + if let Some(chunk) = self.chunk_cache.remove(&position) { + return chunk; + } } + chunk_creator(position) } } @@ -819,53 +957,112 @@ mod tests { struct TestChunk; #[test] - fn new_tree() { + fn update_tree() { // make a tree let mut tree = Tree::::new(64); - // as long as we need to update, do so - while tree.prepare_update(&[QuadVec::new(128, 128, 32)], 8, |_| TestChunk {}) { - // and actually update - tree.do_update(); - } - - // and move the target - while tree.prepare_update(&[QuadVec::new(16, 8, 16)], 8, |_| TestChunk {}) { - // and actually update - tree.do_update(); - } - - // get the resulting chunk from a search - let found_chunk = tree.get_chunk_from_position(QuadVec::new(16, 8, 16)); + for tgt in [QuadVec::new(1, 1, 2), QuadVec::new(2, 3, 2)] { + dbg!(tgt); + while tree.prepare_update(&[tgt], 0, &mut |_| TestChunk {}) { + for c in tree.iter_chunks_to_activate_positions() { + println!("* {c:?}"); + } + for c in tree.iter_chunks_to_deactivate_positions() { + println!("o {c:?}"); + } - // and find the resulting chunk - println!("{:?}", found_chunk.is_some()); + for c in tree.iter_chunks_to_remove_positions() { + println!("- {c:?}"); + } - // and make the tree have no items - while tree.prepare_update(&[], 8, |_| TestChunk {}) { - // and actually update - tree.do_update(); + for c in tree.iter_chunks_to_add_positions() { + println!("+ {c:?}"); + } + println!("updating..."); + // and actually update + tree.do_update(); + } } - - // and do the same for an octree - let mut tree = Tree::::new(64); - + } + #[test] + fn insert_into_tree() { + // make a tree + let mut tree = Tree::::new(64); // as long as we need to update, do so - while tree.prepare_update(&[OctVec::new(128, 128, 128, 32)], 8, |_| TestChunk {}) { - // and actually update - tree.do_update(); - } + for tgt in [ + QuadVec::new(1, 1, 1), + QuadVec::new(0, 1, 1), + QuadVec::new(2, 3, 2), + QuadVec::new(2, 2, 2), + ] { + println!("====NEXT TARGET ====="); + dbg!(tgt); + while tree.prepare_insert(&[tgt], 0, &mut |_| TestChunk {}) { + for c in tree.iter_chunks_to_activate_positions() { + println!("* {c:?}"); + } + for c in tree.iter_chunks_to_deactivate_positions() { + println!("o {c:?}"); + } - // and move the target - while tree.prepare_update(&[OctVec::new(16, 8, 32, 16)], 8, |_| TestChunk {}) { - // and actually update - tree.do_update(); - } + for c in tree.iter_chunks_to_remove_positions() { + println!("- {c:?}"); + } - // and make the tree have no items - while tree.prepare_update(&[], 8, |_| TestChunk {}) { - // and actually update - tree.do_update(); + for c in tree.iter_chunks_to_add_positions() { + println!("+ {c:?}"); + } + println!("updating..."); + // and actually update + tree.do_update(); + } } } + + #[test] + pub fn things() { + // + // // and move the target + // while tree.prepare_update(&[QuadVec::new(16, 8, 16)], 8, |_| TestChunk {}) { + // // and actually update + // tree.do_update(); + // } + // + // // get the resulting chunk from a search + // let found_chunk = tree.get_chunk_from_position(QuadVec::new(16, 8, 16)); + // + // // and find the resulting chunk + // println!("{:?}", found_chunk.is_some()); + // + // // and make the tree have no items + // while tree.prepare_update(&[], 8, |_| TestChunk {}) { + // // and actually update + // tree.do_update(); + // } + // + // // and do the same for an octree + // let mut tree = Tree::::new(64); + // + // // as long as we need to update, do so + // while tree.prepare_update(&[OctVec::new(128, 128, 128, 32)], 8, |_| TestChunk {}) { + // // and actually update + // tree.do_update(); + // } + // + // // and move the target + // while tree.prepare_update(&[OctVec::new(16, 8, 32, 16)], 8, |_| TestChunk {}) { + // // and actually update + // tree.do_update(); + // } + // + // // and make the tree have no items + // while tree.prepare_update(&[], 8, |_| TestChunk {}) { + // // and actually update + // tree.do_update(); + // } + } + #[test] + pub fn alignment() { + assert_eq!(std::mem::size_of::(), 8); + } }