[
  {
    "path": ".gitattributes",
    "content": "*.tif filter=lfs diff=lfs merge=lfs -text\n"
  },
  {
    "path": ".gitignore",
    "content": ".idea\nCargo.lock\ntarget\nassets/**/data/*\nassets/**/config.tc\nassets/terrains/spherical/source/height/200m.tif"
  },
  {
    "path": "Cargo.toml",
    "content": "[package]\nname = \"bevy_terrain\"\ndescription = \"Terrain Rendering for the Bevy Engine.\"\nversion = \"0.1.0-dev\"\nlicense = \"MIT OR Apache-2.0\"\nedition = \"2021\"\ncategories = [\"game-engines\", \"rendering\", \"graphics\"]\nkeywords = [\"gamedev\", \"graphics\", \"bevy\", \"terrain\"]\nexclude = [\"assets/*\"]\nreadme = \"README.md\"\nauthors = [\"Kurt Kühnert <kurt@kuehnert.dev>\"]\nrepository = \"https://github.com/kurtkuehnert/bevy_terrain\"\n\n[features]\nhigh_precision = [\"dep:big_space\"]\n\n[dependencies]\nbevy = \"0.14.0\" #{ git=\"https://github.com/bevyengine/bevy/\", branch=\"main\" }\nndarray = \"0.15\"\nitertools = \"0.12\"\nimage = \"0.25\"\ntiff = \"0.9\"\nlru = \"0.12\"\nbitflags = \"2.4\"\nbytemuck = \"1.14\"\nanyhow = \"1.0\"\nbincode = \"2.0.0-rc.3\"\nasync-channel = \"2.1\"\nbig_space = { version = \"0.7\", optional = true }\n\n[[example]]\nname = \"preprocess_planar\"\npath = \"examples/preprocess_planar.rs\"\nrequired-features = [\"bevy/embedded_watcher\"]\n\n[package.metadata.example.preprocess_planar]\nname = \"Preprocess Planar\"\ndescription = \"Preprocesses the terrain data for the planar examples.\"\n\n[[example]]\nname = \"minimal\"\npath = \"examples/minimal.rs\"\nrequired-features = [\"bevy/embedded_watcher\"]\n\n[package.metadata.example.minial]\nname = \"Minimal\"\ndescription = \"Renders a basic flat terrain with only the base attachment.\"\n\n[[example]]\nname = \"planar\"\npath = \"examples/planar.rs\"\nrequired-features = [\"high_precision\", \"bevy/embedded_watcher\"]\n\n[package.metadata.example.planar]\nname = \"Planar Advanced\"\ndescription = \"Renders a flat terrain with the base attachment and an albedo texture, using a custom shader.\"\n\n[[example]]\nname = \"preprocess_spherical\"\npath = \"examples/preprocess_spherical.rs\"\nrequired-features = [\"bevy/embedded_watcher\"]\n\n[package.metadata.example.preprocess_spherical]\nname = \"Preprocess Spherical\"\ndescription = \"Preprocesses the terrain data for the spherical examples.\"\n\n[[example]]\nname = \"spherical\"\npath = \"examples/spherical.rs\"\nrequired-features = [\"high_precision\", \"bevy/embedded_watcher\"]\n\n[package.metadata.example.spherical]\nname = \"Spherical\"\ndescription = \"Renders a spherical terrain using a custom shader.\"\n\n"
  },
  {
    "path": "LICENSE-APACHE",
    "content": "                                 Apache License\n                           Version 2.0, January 2004\n                        http://www.apache.org/licenses/\n\n   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n   1. Definitions.\n\n      \"License\" shall mean the terms and conditions for use, reproduction,\n      and distribution as defined by Sections 1 through 9 of this document.\n\n      \"Licensor\" shall mean the copyright owner or entity authorized by\n      the copyright owner that is granting the License.\n\n      \"Legal Entity\" shall mean the union of the acting entity and all\n      other entities that control, are controlled by, or are under common\n      control with that entity. For the purposes of this definition,\n      \"control\" means (i) the power, direct or indirect, to cause the\n      direction or management of such entity, whether by contract or\n      otherwise, or (ii) ownership of fifty percent (50%) or more of the\n      outstanding shares, or (iii) beneficial ownership of such entity.\n\n      \"You\" (or \"Your\") shall mean an individual or Legal Entity\n      exercising permissions granted by this License.\n\n      \"Source\" form shall mean the preferred form for making modifications,\n      including but not limited to software source code, documentation\n      source, and configuration files.\n\n      \"Object\" form shall mean any form resulting from mechanical\n      transformation or translation of a Source form, including but\n      not limited to compiled object code, generated documentation,\n      and conversions to other media types.\n\n      \"Work\" shall mean the work of authorship, whether in Source or\n      Object form, made available under the License, as indicated by a\n      copyright notice that is included in or attached to the work\n      (an example is provided in the Appendix below).\n\n      \"Derivative Works\" shall mean any work, whether in Source or Object\n      form, that is based on (or derived from) the Work and for which the\n      editorial revisions, annotations, elaborations, or other modifications\n      represent, as a whole, an original work of authorship. For the purposes\n      of this License, Derivative Works shall not include works that remain\n      separable from, or merely link (or bind by name) to the interfaces of,\n      the Work and Derivative Works thereof.\n\n      \"Contribution\" shall mean any work of authorship, including\n      the original version of the Work and any modifications or additions\n      to that Work or Derivative Works thereof, that is intentionally\n      submitted to Licensor for inclusion in the Work by the copyright owner\n      or by an individual or Legal Entity authorized to submit on behalf of\n      the copyright owner. For the purposes of this definition, \"submitted\"\n      means any form of electronic, verbal, or written communication sent\n      to the Licensor or its representatives, including but not limited to\n      communication on electronic mailing lists, source code control systems,\n      and issue tracking systems that are managed by, or on behalf of, the\n      Licensor for the purpose of discussing and improving the Work, but\n      excluding communication that is conspicuously marked or otherwise\n      designated in writing by the copyright owner as \"Not a Contribution.\"\n\n      \"Contributor\" shall mean Licensor and any individual or Legal Entity\n      on behalf of whom a Contribution has been received by Licensor and\n      subsequently incorporated within the Work.\n\n   2. Grant of Copyright License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      copyright license to reproduce, prepare Derivative Works of,\n      publicly display, publicly perform, sublicense, and distribute the\n      Work and such Derivative Works in Source or Object form.\n\n   3. Grant of Patent License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      (except as stated in this section) patent license to make, have made,\n      use, offer to sell, sell, import, and otherwise transfer the Work,\n      where such license applies only to those patent claims licensable\n      by such Contributor that are necessarily infringed by their\n      Contribution(s) alone or by combination of their Contribution(s)\n      with the Work to which such Contribution(s) was submitted. If You\n      institute patent litigation against any entity (including a\n      cross-claim or counterclaim in a lawsuit) alleging that the Work\n      or a Contribution incorporated within the Work constitutes direct\n      or contributory patent infringement, then any patent licenses\n      granted to You under this License for that Work shall terminate\n      as of the date such litigation is filed.\n\n   4. Redistribution. You may reproduce and distribute copies of the\n      Work or Derivative Works thereof in any medium, with or without\n      modifications, and in Source or Object form, provided that You\n      meet the following conditions:\n\n      (a) You must give any other recipients of the Work or\n          Derivative Works a copy of this License; and\n\n      (b) You must cause any modified files to carry prominent notices\n          stating that You changed the files; and\n\n      (c) You must retain, in the Source form of any Derivative Works\n          that You distribute, all copyright, patent, trademark, and\n          attribution notices from the Source form of the Work,\n          excluding those notices that do not pertain to any part of\n          the Derivative Works; and\n\n      (d) If the Work includes a \"NOTICE\" text file as part of its\n          distribution, then any Derivative Works that You distribute must\n          include a readable copy of the attribution notices contained\n          within such NOTICE file, excluding those notices that do not\n          pertain to any part of the Derivative Works, in at least one\n          of the following places: within a NOTICE text file distributed\n          as part of the Derivative Works; within the Source form or\n          documentation, if provided along with the Derivative Works; or,\n          within a display generated by the Derivative Works, if and\n          wherever such third-party notices normally appear. The contents\n          of the NOTICE file are for informational purposes only and\n          do not modify the License. You may add Your own attribution\n          notices within Derivative Works that You distribute, alongside\n          or as an addendum to the NOTICE text from the Work, provided\n          that such additional attribution notices cannot be construed\n          as modifying the License.\n\n      You may add Your own copyright statement to Your modifications and\n      may provide additional or different license terms and conditions\n      for use, reproduction, or distribution of Your modifications, or\n      for any such Derivative Works as a whole, provided Your use,\n      reproduction, and distribution of the Work otherwise complies with\n      the conditions stated in this License.\n\n   5. Submission of Contributions. Unless You explicitly state otherwise,\n      any Contribution intentionally submitted for inclusion in the Work\n      by You to the Licensor shall be under the terms and conditions of\n      this License, without any additional terms or conditions.\n      Notwithstanding the above, nothing herein shall supersede or modify\n      the terms of any separate license agreement you may have executed\n      with Licensor regarding such Contributions.\n\n   6. Trademarks. This License does not grant permission to use the trade\n      names, trademarks, service marks, or product names of the Licensor,\n      except as required for reasonable and customary use in describing the\n      origin of the Work and reproducing the content of the NOTICE file.\n\n   7. Disclaimer of Warranty. Unless required by applicable law or\n      agreed to in writing, Licensor provides the Work (and each\n      Contributor provides its Contributions) on an \"AS IS\" BASIS,\n      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n      implied, including, without limitation, any warranties or conditions\n      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n      PARTICULAR PURPOSE. You are solely responsible for determining the\n      appropriateness of using or redistributing the Work and assume any\n      risks associated with Your exercise of permissions under this License.\n\n   8. Limitation of Liability. In no event and under no legal theory,\n      whether in tort (including negligence), contract, or otherwise,\n      unless required by applicable law (such as deliberate and grossly\n      negligent acts) or agreed to in writing, shall any Contributor be\n      liable to You for damages, including any direct, indirect, special,\n      incidental, or consequential damages of any character arising as a\n      result of this License or out of the use or inability to use the\n      Work (including but not limited to damages for loss of goodwill,\n      work stoppage, computer failure or malfunction, or any and all\n      other commercial damages or losses), even if such Contributor\n      has been advised of the possibility of such damages.\n\n   9. Accepting Warranty or Additional Liability. While redistributing\n      the Work or Derivative Works thereof, You may choose to offer,\n      and charge a fee for, acceptance of support, warranty, indemnity,\n      or other liability obligations and/or rights consistent with this\n      License. However, in accepting such obligations, You may act only\n      on Your own behalf and on Your sole responsibility, not on behalf\n      of any other Contributor, and only if You agree to indemnify,\n      defend, and hold each Contributor harmless for any liability\n      incurred by, or claims asserted against, such Contributor by reason\n      of your accepting any such warranty or additional liability.\n\n   END OF TERMS AND CONDITIONS\n"
  },
  {
    "path": "LICENSE-MIT",
    "content": "MIT License\n\nCopyright (c) 2023 Kurt Kühnert\nCopyright (c) 2023 Argeo\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE."
  },
  {
    "path": "README.md",
    "content": "# Bevy Terrain\n\n![GitHub](https://img.shields.io/github/license/Ku95/bevy_terrain)\n![Crates.io](https://img.shields.io/crates/v/bevy_terrain)\n![docs.rs](https://img.shields.io/docsrs/bevy_terrain)\n![Discord](https://img.shields.io/discord/999221999517843456?label=discord)\n\nBevy Terrain is a plugin for rendering terrains with the Bevy game engine.\n\n![](https://user-images.githubusercontent.com/51823519/202845032-0537e929-b13c-410b-8072-4c5b5df9830d.png)\n(Data Source: Federal Office of Topography, [©swisstopo](https://www.swisstopo.admin.ch/en/home.html))\n\n**Warning:** This plugin is still in early development, so expect the API to change and possibly break you existing\ncode.\n\nBevy terrain was developed as part of my [bachelor thesis](https://github.com/kurtkuehnert/terrain_renderer) on the\ntopic of large-scale terrain rendering.\nNow that this project is finished I am planning on adding more features related to game development and rendering\nvirtual worlds.\nIf you would like to help me build an extensive open-source terrain rendering library for the Bevy game engine, feel\nfree to contribute to the project.\nAlso, join the Bevy Terrain [Discord server](https://discord.gg/7mtZWEpA82) for help, feedback, or to discuss feature\nideas.\n\n## Examples\n\nCurrently, there are two examples.\n\nThe basic one showcases the different debug views of the terrain. See controls down below.\n\nThe advanced one showcases how to use the Bevy material system for texturing,\nas well as how to add additional terrain attachments.\nUse the `A` Key to toggle between the custom material and the albedo attachment.\n\nBefore running the examples you have to preprocess the terrain data this may take a while.\nOnce the data is preprocessed you can disable it by commenting out the preprocess line.\n\n## Documentation\n\nThe `docs` folder contains a\nhigh-level [implementation overview](https://github.com/kurtkuehnert/bevy_terrain/blob/main/docs/implementation.md),\nas well as, the [development status](https://github.com/kurtkuehnert/bevy_terrain/blob/main/docs/development.md),\nenumerating the features that I am planning on implementing next, of the project.\nIf you would like to contribute to the project this is a good place to start. Simply pick an issue/feature and discuss\nthe details with me on Discord or GitHub.\nI would also recommend you to take a look at\nmy [thesis](https://github.com/kurtkuehnert/terrain_renderer/blob/main/Thesis.pdf).\nThere I present the basics of terrain rendering (chapter 2), common approaches (chapter 3) and a detailed explanation of\nmethod used by `bevy_terrain` (chapter 4).\n\n## Debug Controls\n\nThese are the debug controls of the plugin.\nUse them to fly over the terrain, experiment with the quality settings and enter the different debug views.\n\n- `T` - toggle camera movement\n- move the mouse to look around\n- press the arrow keys to move the camera horizontally\n- use `PageUp` and `PageDown` to move the camera vertically\n- use `Home` and `End` to increase/decrease the camera's movement speed\n\n- `W` - toggle wireframe view\n- `P` - toggle tile view\n- `L` - toggle lod view\n- `U` - toggle uv view\n- `C` - toggle tile view\n- `D` - toggle mesh morph\n- `A` - toggle albedo\n- `B` - toggle base color black / white\n- `S` - toggle lighting\n- `G` - toggle filtering bilinear / trilinear + anisotropic\n- `F` - freeze frustum culling\n- `H` - decrease tile scale\n- `J` - increase tile scale\n- `N` - decrease grid size\n- `E` - increase grid size\n- `I` - decrease view distance\n- `O` - increase view distance\n\n<!---\n## Supported Bevy Versions\n\n| `bevy_terrain` | `bevy` |\n|----------------|--------|\n| 0.1.0          | 0.9    |\n--->\n\n## Attribution\n\nThe planar terrain dataset is generated using the free version of the Gaia Terrain Generator.\nThe spherical terrain example dataset is a reprojected version of the GEBCO_2023 Grid dataset.\n\nGEBCO Compilation Group (2023) GEBCO 2023 Grid (doi:10.5285/f98b053b-0cbc-6c23-e053-6c86abc0af7b)\n\n## License\n\nBevy Terrain source code (this excludes the datasets in the assets directory) is dual-licensed under either\n\n* MIT License (LICENSE-MIT or http://opensource.org/licenses/MIT)\n* Apache License, Version 2.0 (LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0)\n\nat your option.\n"
  },
  {
    "path": "assets/shaders/planar.wgsl",
    "content": "#import bevy_terrain::types::AtlasTile\n#import bevy_terrain::attachments::{sample_attachment0 as sample_height, sample_normal, sample_attachment1 as sample_albedo}\n#import bevy_terrain::fragment::{FragmentInput, FragmentOutput, fragment_info, fragment_output, fragment_debug}\n#import bevy_terrain::functions::lookup_tile\n#import bevy_pbr::pbr_types::{PbrInput, pbr_input_new}\n\n@group(3) @binding(0)\nvar gradient: texture_1d<f32>;\n@group(3) @binding(1)\nvar gradient_sampler: sampler;\n\nfn sample_color(tile: AtlasTile) -> vec4<f32> {\n#ifdef ALBEDO\n    return sample_albedo(tile);\n#else\n    let height = sample_height(tile).x;\n\n    return textureSampleLevel(gradient, gradient_sampler, pow(height, 0.9), 0.0);\n#endif\n}\n\n@fragment\nfn fragment(input: FragmentInput) -> FragmentOutput {\n    var info = fragment_info(input);\n\n    let tile   = lookup_tile(info.coordinate, info.blend, 0u);\n    var color  = sample_color(tile);\n    var normal = sample_normal(tile, info.world_normal);\n\n    if (info.blend.ratio > 0.0) {\n        let tile2 = lookup_tile(info.coordinate, info.blend, 1u);\n        color     = mix(color,  sample_color(tile2),                     info.blend.ratio);\n        normal    = mix(normal, sample_normal(tile2, info.world_normal), info.blend.ratio);\n    }\n\n    var output: FragmentOutput;\n    fragment_output(&info, &output, color, normal);\n    fragment_debug(&info, &output, tile, normal);\n    return output;\n}\n"
  },
  {
    "path": "assets/shaders/spherical.wgsl",
    "content": "#import bevy_terrain::types::{AtlasTile}\n#import bevy_terrain::bindings::config\n#import bevy_terrain::attachments::{sample_height, sample_normal}\n#import bevy_terrain::fragment::{FragmentInput, FragmentOutput, fragment_info, fragment_output, fragment_debug}\n#import bevy_terrain::functions::lookup_tile\n#import bevy_pbr::pbr_types::{PbrInput, pbr_input_new}\n#import bevy_pbr::pbr_functions::{calculate_view, apply_pbr_lighting}\n\n\n@group(3) @binding(0)\nvar gradient: texture_1d<f32>;\n@group(3) @binding(1)\nvar gradient_sampler: sampler;\n\nfn sample_color(tile: AtlasTile) -> vec4<f32> {\n    let height = sample_height(tile);\n\n    var color: vec4<f32>;\n\n    if (height < 0.0) {\n        color = textureSampleLevel(gradient, gradient_sampler, mix(0.0, 0.075, pow(height / config.min_height, 0.25)), 0.0);\n    }\n    else {\n        color = textureSampleLevel(gradient, gradient_sampler, mix(0.09, 1.0, pow(height / config.max_height * 2.0, 1.0)), 0.0);\n    }\n\n    return color;\n}\n\n@fragment\nfn fragment(input: FragmentInput) -> FragmentOutput {\n    var info = fragment_info(input);\n\n    let tile   = lookup_tile(info.coordinate, info.blend, 0u);\n    var color  = sample_color(tile);\n    var normal = sample_normal(tile, info.world_normal);\n\n    if (info.blend.ratio > 0.0) {\n        let tile2 = lookup_tile(info.coordinate, info.blend, 1u);\n        color     = mix(color,  sample_color(tile2),                     info.blend.ratio);\n        normal    = mix(normal, sample_normal(tile2, info.world_normal), info.blend.ratio);\n    }\n\n    var output: FragmentOutput;\n    fragment_output(&info, &output, color, normal);\n    fragment_debug(&info, &output, tile, normal);\n    return output;\n}\n"
  },
  {
    "path": "docs/development.md",
    "content": "# Development Status Bevy Terrain\n\nThis document assesses the current status of the `bevy_terrain` plugin.\nI built this plugin as part of my bachelor thesis, which focused on rendering large-scale terrains.\nThe thesis and its project can be found [here](https://github.com/kurtkuehnert/terrain_renderer).\n\nFor that, I set out to solve two key problems of terrain rendering. \nFor one, I developed the Uniform Distance-Dependent Level of Detail (UDLOD) algorithm to represent the terrain geometry, and for another, \nI came up with the Chunked Clipmap data structure used to represent the terrain data. \nBoth are implemented as part of bevy terrain and work quite well for rendering large-scale terrains.\n\nNow that I have finished my thesis I would like to continue working on this project and extend its capabilities. \nThe topic of terrain rendering is vast, and thus I can not work on all the stuff at once.\nIn the following, I will list a couple of features that I would like to integrate into this crate in the future. \nI will probably not have the time to implement all of them by myself, so if you are interested please get in touch, and let us work on them together. \nAdditionally, there are still plenty of improvements, bug fixes, and optimizations to be completed on the already existing implementation.\n\n## Features\n\n- Procedural Texturing\n- Shadow Rendering\n- Real-Time Editing\n- Collision\n- Path-Finding\n- Spherical Terrain\n\n### Procedural Texturing\n\nProbably the biggest missing puzzle piece of this plugin is support for procedural texturing using splat maps or something similar. \nCurrently, texturing has to be implemented manually in the terrain shader (see the advanced example for reference). \nI would like to support this use case in a more integrated manner in the future. Unfortunately, \nI am not familiar with the terrain texturing systems of other engines (e.g. Unity, Unreal, Godot) \nor have any experience texturing and building my own terrains. \nI would greatly appreciate it if anyone can share some requirements for this area of terrain rendering. \nAlso, a prototype of a custom texturing system would be a great resource to develop further ideas.\n\n### Shadow Rendering\n\nAnother important capability that is currently missing is the support for large-scale shadow rendering. \nThis would be probably implemented using cascading shadow maps or a similar method.\nCurrently, Bevy itself does not implement a system we could use for this yet. \nRegardless, I think reusing Bevy’s implementation would be the best choice in the future.\n\n### Real-Time Editing\n\nOne of the most interesting problems that need to be solved before `bevy_terrain` can be used for any serious project is the editing of the terrain data in real time. \nThis is not only important for sculpting the terrain of your game, but also for texturing, vegetation placement, etc.\nThis is going to be my next focus area and I would like to discuss designs and additional requirements with anyone interested.\n\n### Collision\n\nSame as for shadow rendering, Bevy does not have a built-in physics engine yet. For now, the de-facto standard is the rapier physics engine. \nIntegrating the collision of the terrain with rapier would enable many types of games and is a commonly requested feature.\n\n### Path-Finding\n\nSimilar to collision, path-finding is essential for most games. I have not investigated this field at all yet, but I am always interested in your ideas.\n\n### Spherical Terrain\n\nI think that with a little design work the current two-dimensional terrain rendering method could be extended to the spherical terrain.\nHowever, I am unsure how much of the existing code could be extended and reused. Maybe planet rendering would require its entirely separate crate.\n"
  },
  {
    "path": "docs/implementation.md",
    "content": "# Implementation Overview Bevy Terrain\n\nThis document serves as a general overview of the implementation of the `bevy_terrain` plugin.\n\nCurrently, this crate provides two fundamental capabilities.\nFor one, the UDLOD algorithm approximates the terrain geometry, and for another, the Chunked Clipmap stores the terrain\ndata in a convenient and randomly accessible data structure.\n\nBoth are described in detail in\nmy [bachelor thesis]([https://github.com/kurtkuehnert/terrain_renderer/blob/main/Thesis.pdf](https://github.com/kurtkuehnert/terrain_renderer/blob/main/Thesis.pdf)).\nTo understand the implementation of this crate and the reasons behind some design decisions, I recommend that you read\nat least the entire chapter 4.\nIf you are unfamiliar with terrain rendering in general, taking a look at chapter 2 will prove beneficial as well.\n\nIn the following, I will now explain how both of these systems are currently implemented, what limitations they possess,\nand how they should work in the future.\nFurthermore, I have listed a couple of todos outlining the essence of these issues.\nIf any of them sound interesting to you, and you would like to work on them, please get in touch with me, so we can\ndiscuss solutions.\nThese are certainly not all problems of the current implementation, so if you notice anything else, please let me know,\nso I can add it here.\n\n## Terrain Geometry\n\n### Ideal\n\nIdeally, we would like to represent the terrain geometry without any error according to our source data. Unfortunately,\nrendering each data point as a vertex is not scalable, nor efficient.\n\n### Reality\n\nThat is why we need a sophisticated level of detail (LOD) algorithm that minimizes the error introduced by its\napproximation of the geometry.\n\n### Solution\n\nOne such solution is the Uniform Distance-Dependent Level of Detail (UDLOD) algorithm that I have developed as part of\nmy thesis (for a detailed explanation read section 4.4).\nIt divides the terrain into numerous small tiles in parallel on the GPU. They are then rendered using a single indirect\ndraw call and morphed together (in the vertex shader) to form a continuous surface with an approximately uniform\ntessellation in screen space.\n\n### Issues\n\nFor any LOD algorithm, an appropriate crack-avoiding and morphing strategy are important to eliminate and reduce visual\ndiscrepancies as much as possible.\nUDLOD uses a slightly modified version of the CDLOD morphing scheme.\n\nThe UDLOD algorithm can be used to tessellate procedural ground details like rocks or cobblestones as well.\nTherefore, simply increase the tile_tree depth using the `additional_refinement` parameter.\n\nEven though the tessellation produced by UDLOD is somewhat uniform with respect to the distance, it does not take\nfactors like the terrain's roughness and the viewing angle into account.\nGenerally, the current UDLOD algorithm tiers to cover the worst-case terrain roughness like many other algorithms (\nGeoMipmap, GeoClipmap, PGM, CDLOD, FarCry5).\nI believe that we can still develop more efficient LOD algorithms that scale favorably for large-scale terrains in the\nfuture.\n\nThe culling is currently pretty bare-bones.\nWe could probably implement most of the techniques researched by the Far Cry 5 terrain renderer as well.\n\nCurrently, the prepass is pretty inefficient, because the shader occupancy is very low (the prepass is still plenty\nfast, but could be improved).\nI think that this could be resolved by using the atomic operations more cleverly and reducing the shader dispatches in\ngeneral.\nIn the past, I have tried doing all the work in a single pass.\nUnfortunately, that did not work, but maybe someone can figure out a better solution.\n\nThe frustum culling uses a 2D min-max height data attachment to approximate the bounding volumes of each tile correctly.\nThis is currently stored with the same resolution as the source height data, but only a fraction of this resolution is\nactually required.\n\n### Todo\n\n- [x]  come up with a smooth morphing strategy that solves the geometry crack problem as well\n- [x]  implement bounding box frustum culling\n- [x]  further refine the geometry for procedural details (rocks, cobblestone)\n- [ ]  explore different LOD algorithms (maybe apply the clipmap idea to CBTs?)\n- [ ]  try incorporating a screen space error metric, local terrain roughness, or the viewing angle\n- [ ]  implement more advanced culling solutions (occlusion, backface)\n- [ ]  try reducing the compute shader dispatches in the prepass phase\n- [ ]  store min-max height data, required by frustum culling, at a way lower resolution\n- [ ]  experiment with hardware tessellation or mesh shaders\n\n## Terrain Data\n\n### Ideal\n\nIdeally, we would like to store any desired information at any desired resolution across the terrain’s surface.\nFor example, a terrain could require a heightmap with a resolution of 0.5m, an albedo map with a resolution of 0.2m, and\na vegetation map (for placing trees and bushes) with a resolution of 1m.\nEach of these three different kinds of terrain data are called terrain attachments.\nThis terrain data should be available in any system and shader of our application. Additionally, we would like to access\nthe data at any position and sample a value with distant-dependent accuracy.\nFinally, some use cases require the ability to sample some attachments like the albedo or splat data trilinearly and\nanisotropically to mitigate aliasing artifacts.\n\n### Reality\n\nBecause we are using height-map-based terrain these attachments should be stored as large two-dimensional textures.\nHowever, due to the size of most landscapes, using a single texture would quickly use up all of our video memory.\nThat is why we need to partition and adjust the loaded data according to our view.\nAdditionally, it is important that we can share this terrain data efficiently between multiple views for use cases like\nsplit-screen or shadow rendering.\n\n### Solution\n\nTo solve this, I have developed the chunked clipmap data structure (if you are unfamiliar with the concept, I encourage\nyou to read section 4.5 of my thesis).\nIt divides the entire terrain data into one large tile_tree, covering the entire terrain.\nThis requires that all terrain data has to be preprocessed into small square textures: the tiles of the tile_tree.\nEach tile possesses one texture per attachment. To allow for different resolutions of the attachments (e.g. the\nheight data should be twice as accurate as our splat map), the size of these textures has to be different as well.\nFollowing the same example, this would mean that the height textures would have a size of 100x100 and the splat textures\na size of 50x50 pixels.\n\n### Issues\n\nBecause of our compound representation of the terrain data, consisting of many small textures, some problems arise\nduring texture filtering.\nThe biggest issue is that adjacent tiles do not line up perfectly due to missing texture information at the border.\nThis causes noticeable texture seams between adjacent tiles.\nTo remedy this issue we have to duplicate the border data between adjacent tiles.\nThis complicates our preprocessing but results in a completely seamless terrain data representation.\n\nFor trilinear filtering, we additionally require mipmap information.\nCurrently, bevy does not support mipmap generation.\nThat is why I have implemented a simple mipmap creation function, which is executed after the tile textures have\nbeen loaded. Unfortunately, my simple approach only works on textures with a side length equal to a power of two (e.g.\n256x256, 512x512).\nThis needlessly limits the resolutions of our terrain data.\nIn the future, I would like to generate the mipmaps for any texture size.\n\nAs mentioned above the terrain data has to be loaded depending on our current view position.\nCurrently, I load all tiles inside the `load_distance` around the viewer.\nThere is no prioritization or load balancing. I would like to explore different loading strategies (e.g. distance only,\nview frustum based, etc.) to enable use cases like streaming data from a web server.\nFor that, the strategy would have to minimize the loading requests while maximizing the visual quality.\nWhen streaming from disk this wasn't a problem yet.\n\nAdditionally, the plugin panics if the tile atlas is out of indices (i.e. the maximum amount of tiles is\nloaded).\nThis is unacceptable in production use.\nHere we would have to come up with a strategy of prioritizing which tiles to keep and which ones to discard in\norder to accommodate more important ones.\n\nThe tile loading code itself is currently pretty inefficient.\nDue to the nature of the bevy image abstraction, all textures are duplicated multiple times.\nHopefully in the near future, once the asset processing has been reworked, it will be easier to express loading parts of\nan array texture directly.\n\nTo divide the terrain into the numerous tile textures I use a 3-step preprocessing algorithm.\nThis is implemented pretty inefficiently.\nIf you are interested in optimizing data transformation code, this should be the task for you :D.\n\nTo save space the terrain data is compressed using common image formats, when it is stored on the hard-drive.\nTo unfortunately the encoding of PNGs is quite slow.\nThat is why I came up with the [DTM image format](https://github.com/kurtkuehnert/dtm).\nIt uses a sequential compression technique similar to the QOI format.\nDTM works quite well for the shallow terrain I used for testing, but is not ideal for the steep and hilly terrains used\nin most games.\nThere are probably significant gains to be had in this area.\n\nAnother huge challenge regarding the terrain data is its modification in real-time.\nWorkflows like sculpting, texturing, etc. do require the ability to update the terrain data in a visual manner.\nThis topic is vast and will require extensive investigation before we can settle on a final design.\nIf you have experience/ideas please let me know.\n\n### Todo\n\n- [x]  duplicate border information to eliminate texture seams\n- [x]  generate mipmaps to enable trilinear filtering\n- [ ]  Incorporate better mipmap generation for any texture size.\n- [ ]  different loading strategies\n- [ ]  handle tile atlas out of indices\n- [ ]  improve loading to tile atlas (i.e. loading layers of an array texture), remove excessive\n  duplication/copying\n- [ ]  improve the preprocessing with caching, GPU acceleration, etc.\n- [ ]  explore the usage of more efficient image formats\n- [ ]  investigate real-time modification\n"
  },
  {
    "path": "examples/minimal.rs",
    "content": "use bevy::math::DVec3;\nuse bevy::prelude::*;\nuse bevy_terrain::prelude::*;\n\nconst PATH: &str = \"terrains/planar\";\nconst TERRAIN_SIZE: f64 = 1000.0;\nconst HEIGHT: f32 = 250.0;\nconst TEXTURE_SIZE: u32 = 512;\nconst LOD_COUNT: u32 = 4;\n\nfn main() {\n    App::new()\n        .add_plugins((\n            DefaultPlugins,\n            TerrainPlugin,\n            TerrainMaterialPlugin::<DebugTerrainMaterial>::default(),\n            TerrainDebugPlugin,\n        ))\n        .add_systems(Startup, setup)\n        .run();\n}\n\nfn setup(\n    mut commands: Commands,\n    mut materials: ResMut<Assets<DebugTerrainMaterial>>,\n    mut tile_trees: ResMut<TerrainViewComponents<TileTree>>,\n    mut meshes: ResMut<Assets<Mesh>>,\n) {\n    // Configure all the important properties of the terrain, as well as its attachments.\n    let config = TerrainConfig {\n        lod_count: LOD_COUNT,\n        model: TerrainModel::planar(DVec3::new(0.0, -100.0, 0.0), TERRAIN_SIZE, 0.0, HEIGHT),\n        path: PATH.to_string(),\n        ..default()\n    }\n    .add_attachment(AttachmentConfig {\n        name: \"height\".to_string(),\n        texture_size: TEXTURE_SIZE,\n        border_size: 2,\n        mip_level_count: 4,\n        format: AttachmentFormat::R16,\n    });\n\n    // Configure the quality settings of the terrain view. Adapt the settings to your liking.\n    let view_config = TerrainViewConfig::default();\n\n    let tile_atlas = TileAtlas::new(&config);\n    let tile_tree = TileTree::new(&tile_atlas, &view_config);\n\n    let terrain = commands\n        .spawn((\n            TerrainBundle::new(tile_atlas),\n            materials.add(DebugTerrainMaterial::default()),\n        ))\n        .id();\n\n    let view = commands.spawn(DebugCameraBundle::default()).id();\n\n    tile_trees.insert((terrain, view), tile_tree);\n\n    commands.spawn(PbrBundle {\n        mesh: meshes.add(Cuboid::from_length(10.0)),\n        transform: Transform::from_translation(Vec3::new(\n            TERRAIN_SIZE as f32 / 2.0,\n            100.0,\n            TERRAIN_SIZE as f32 / 2.0,\n        )),\n        ..default()\n    });\n}\n"
  },
  {
    "path": "examples/planar.rs",
    "content": "use bevy::math::DVec3;\nuse bevy::{prelude::*, reflect::TypePath, render::render_resource::*};\nuse bevy_terrain::prelude::*;\n\nconst PATH: &str = \"terrains/planar\";\nconst TERRAIN_SIZE: f64 = 2000.0;\nconst HEIGHT: f32 = 500.0;\nconst TEXTURE_SIZE: u32 = 512;\nconst LOD_COUNT: u32 = 8;\n\n#[derive(Asset, AsBindGroup, TypePath, Clone)]\npub struct TerrainMaterial {\n    #[texture(0, dimension = \"1d\")]\n    #[sampler(1)]\n    gradient: Handle<Image>,\n}\n\nimpl Material for TerrainMaterial {\n    fn fragment_shader() -> ShaderRef {\n        \"shaders/planar.wgsl\".into()\n    }\n}\n\nfn main() {\n    App::new()\n        .add_plugins((\n            DefaultPlugins.build().disable::<TransformPlugin>(),\n            TerrainPlugin,\n            TerrainDebugPlugin, // enable debug settings and controls\n            TerrainMaterialPlugin::<TerrainMaterial>::default(),\n        ))\n        .add_systems(Startup, setup)\n        .run();\n}\n\nfn setup(\n    mut commands: Commands,\n    mut images: ResMut<LoadingImages>,\n    mut materials: ResMut<Assets<TerrainMaterial>>,\n    mut tile_trees: ResMut<TerrainViewComponents<TileTree>>,\n    asset_server: Res<AssetServer>,\n) {\n    let gradient = asset_server.load(\"textures/gradient2.png\");\n    images.load_image(\n        &gradient,\n        TextureDimension::D1,\n        TextureFormat::Rgba8UnormSrgb,\n    );\n\n    // Configure all the important properties of the terrain, as well as its attachments.\n    let config = TerrainConfig {\n        lod_count: LOD_COUNT,\n        model: TerrainModel::planar(DVec3::new(0.0, -100.0, 0.0), TERRAIN_SIZE, 0.0, HEIGHT),\n        path: PATH.to_string(),\n        ..default()\n    }\n    .add_attachment(AttachmentConfig {\n        name: \"height\".to_string(),\n        texture_size: TEXTURE_SIZE,\n        border_size: 2,\n        mip_level_count: 4,\n        format: AttachmentFormat::R16,\n    })\n    .add_attachment(AttachmentConfig {\n        name: \"albedo\".to_string(),\n        texture_size: TEXTURE_SIZE,\n        border_size: 2,\n        mip_level_count: 4,\n        format: AttachmentFormat::Rgba8,\n    });\n\n    // Configure the quality settings of the terrain view. Adapt the settings to your liking.\n    let view_config = TerrainViewConfig::default();\n\n    let tile_atlas = TileAtlas::new(&config);\n    let tile_tree = TileTree::new(&tile_atlas, &view_config);\n\n    commands.spawn_big_space(ReferenceFrame::default(), |root| {\n        let frame = root.frame().clone();\n\n        let terrain = root\n            .spawn_spatial((\n                TerrainBundle::new(tile_atlas, &frame),\n                materials.add(TerrainMaterial { gradient }),\n            ))\n            .id();\n\n        let view = root.spawn_spatial(DebugCameraBundle::default()).id();\n\n        tile_trees.insert((terrain, view), tile_tree);\n    });\n}\n"
  },
  {
    "path": "examples/preprocess_planar.rs",
    "content": "use bevy::prelude::*;\nuse bevy_terrain::prelude::*;\n\nconst PATH: &str = \"terrains/planar\";\nconst TEXTURE_SIZE: u32 = 512;\nconst LOD_COUNT: u32 = 4;\n\nfn main() {\n    App::new()\n        .add_plugins((DefaultPlugins, TerrainPlugin, TerrainPreprocessPlugin))\n        .add_systems(Startup, setup)\n        .run();\n}\n\nfn setup(mut commands: Commands, asset_server: Res<AssetServer>) {\n    let config = TerrainConfig {\n        lod_count: LOD_COUNT,\n        path: PATH.to_string(),\n        ..default()\n    }\n    .add_attachment(AttachmentConfig {\n        name: \"height\".to_string(),\n        texture_size: TEXTURE_SIZE,\n        border_size: 2,\n        format: AttachmentFormat::R16,\n        ..default()\n    })\n    .add_attachment(AttachmentConfig {\n        name: \"albedo\".to_string(),\n        texture_size: TEXTURE_SIZE,\n        border_size: 2,\n        format: AttachmentFormat::Rgba8,\n        ..default()\n    });\n\n    let mut tile_atlas = TileAtlas::new(&config);\n\n    let preprocessor = Preprocessor::new()\n        .clear_attachment(0, &mut tile_atlas)\n        .clear_attachment(1, &mut tile_atlas)\n        .preprocess_tile(\n            PreprocessDataset {\n                attachment_index: 0,\n                path: format!(\"{PATH}/source/height.png\"),\n                lod_range: 0..LOD_COUNT,\n                ..default()\n            },\n            &asset_server,\n            &mut tile_atlas,\n        )\n        .preprocess_tile(\n            PreprocessDataset {\n                attachment_index: 1,\n                path: format!(\"{PATH}/source/albedo.png\"),\n                lod_range: 0..LOD_COUNT,\n                ..default()\n            },\n            &asset_server,\n            &mut tile_atlas,\n        );\n\n    commands.spawn((tile_atlas, preprocessor));\n}\n"
  },
  {
    "path": "examples/preprocess_spherical.rs",
    "content": "use bevy::prelude::*;\nuse bevy_terrain::prelude::*;\n\nconst PATH: &str = \"terrains/spherical\";\nconst TEXTURE_SIZE: u32 = 512;\nconst LOD_COUNT: u32 = 5;\n\nfn main() {\n    App::new()\n        .add_plugins((\n            DefaultPlugins.build().disable::<TransformPlugin>(),\n            TerrainPlugin,\n            TerrainPreprocessPlugin,\n        ))\n        .add_systems(Startup, setup)\n        .run();\n}\n\nfn setup(mut commands: Commands, asset_server: Res<AssetServer>) {\n    let config = TerrainConfig {\n        lod_count: LOD_COUNT,\n        path: PATH.to_string(),\n        atlas_size: 2048,\n        ..default()\n    }\n    .add_attachment(AttachmentConfig {\n        name: \"height\".to_string(),\n        texture_size: TEXTURE_SIZE,\n        border_size: 2,\n        format: AttachmentFormat::R16,\n        ..default()\n    });\n\n    let mut tile_atlas = TileAtlas::new(&config);\n\n    let preprocessor = Preprocessor::new()\n        .clear_attachment(0, &mut tile_atlas)\n        .preprocess_spherical(\n            SphericalDataset {\n                attachment_index: 0,\n                paths: (0..6)\n                    .map(|side| format!(\"{PATH}/source/height/face{side}.tif\"))\n                    .collect(),\n                lod_range: 0..LOD_COUNT,\n            },\n            &asset_server,\n            &mut tile_atlas,\n        );\n\n    commands.spawn((tile_atlas, preprocessor));\n}\n"
  },
  {
    "path": "examples/spherical.rs",
    "content": "use bevy::{math::DVec3, prelude::*, reflect::TypePath, render::render_resource::*};\nuse bevy_terrain::prelude::*;\n\nconst PATH: &str = \"terrains/spherical\";\nconst RADIUS: f64 = 6371000.0;\nconst MAJOR_AXES: f64 = 6378137.0;\nconst MINOR_AXES: f64 = 6356752.314245;\nconst MIN_HEIGHT: f32 = -12000.0;\nconst MAX_HEIGHT: f32 = 9000.0;\nconst TEXTURE_SIZE: u32 = 512;\nconst LOD_COUNT: u32 = 16;\n\n#[derive(Asset, AsBindGroup, TypePath, Clone)]\npub struct TerrainMaterial {\n    #[texture(0, dimension = \"1d\")]\n    #[sampler(1)]\n    gradient: Handle<Image>,\n}\n\nimpl Material for TerrainMaterial {\n    fn fragment_shader() -> ShaderRef {\n        \"shaders/spherical.wgsl\".into()\n    }\n}\n\nfn main() {\n    App::new()\n        .add_plugins((\n            DefaultPlugins.build().disable::<TransformPlugin>(),\n            TerrainPlugin,\n            TerrainMaterialPlugin::<TerrainMaterial>::default(),\n            TerrainDebugPlugin, // enable debug settings and controls\n        ))\n        // .insert_resource(ClearColor(Color::WHITE))\n        .add_systems(Startup, setup)\n        .run();\n}\n\nfn setup(\n    mut commands: Commands,\n    mut images: ResMut<LoadingImages>,\n    mut meshes: ResMut<Assets<Mesh>>,\n    mut materials: ResMut<Assets<TerrainMaterial>>,\n    mut tile_trees: ResMut<TerrainViewComponents<TileTree>>,\n    asset_server: Res<AssetServer>,\n) {\n    let gradient = asset_server.load(\"textures/gradient.png\");\n    images.load_image(\n        &gradient,\n        TextureDimension::D1,\n        TextureFormat::Rgba8UnormSrgb,\n    );\n\n    // Configure all the important properties of the terrain, as well as its attachments.\n    let config = TerrainConfig {\n        lod_count: LOD_COUNT,\n        model: TerrainModel::ellipsoid(DVec3::ZERO, MAJOR_AXES, MINOR_AXES, MIN_HEIGHT, MAX_HEIGHT),\n        // model: TerrainModel::ellipsoid(\n        //     DVec3::ZERO,\n        //     6378137.0,\n        //     6378137.0 * 0.5,\n        //     MIN_HEIGHT,\n        //     MAX_HEIGHT,\n        // ),\n        // model: TerrainModel::sphere(DVec3::ZERO, RADIUS),\n        path: PATH.to_string(),\n        ..default()\n    }\n    .add_attachment(AttachmentConfig {\n        name: \"height\".to_string(),\n        texture_size: TEXTURE_SIZE,\n        border_size: 2,\n        mip_level_count: 4,\n        format: AttachmentFormat::R16,\n    });\n\n    // Configure the quality settings of the terrain view. Adapt the settings to your liking.\n    let view_config = TerrainViewConfig::default();\n\n    let tile_atlas = TileAtlas::new(&config);\n    let tile_tree = TileTree::new(&tile_atlas, &view_config);\n\n    commands.spawn_big_space(ReferenceFrame::default(), |root| {\n        let frame = root.frame().clone();\n\n        let terrain = root\n            .spawn_spatial((\n                TerrainBundle::new(tile_atlas, &frame),\n                materials.add(TerrainMaterial {\n                    gradient: gradient.clone(),\n                }),\n            ))\n            .id();\n\n        let view = root\n            .spawn_spatial(DebugCameraBundle::new(\n                -DVec3::X * RADIUS * 3.0,\n                RADIUS,\n                &frame,\n            ))\n            .id();\n\n        tile_trees.insert((terrain, view), tile_tree);\n\n        let sun_position = DVec3::new(-1.0, 1.0, -1.0) * RADIUS * 10.0;\n        let (sun_cell, sun_translation) = frame.translation_to_grid(sun_position);\n\n        root.spawn_spatial((\n            PbrBundle {\n                mesh: meshes.add(Sphere::new(RADIUS as f32 * 2.0).mesh().build()),\n                transform: Transform::from_translation(sun_translation),\n                ..default()\n            },\n            sun_cell,\n        ));\n\n        root.spawn_spatial(PbrBundle {\n            mesh: meshes.add(Cuboid::from_length(RADIUS as f32 * 0.1)),\n            ..default()\n        });\n    });\n}\n"
  },
  {
    "path": "src/big_space.rs",
    "content": "pub use big_space::{BigSpaceCommands, FloatingOrigin};\n\npub type GridPrecision = i32;\n\npub type BigSpacePlugin = big_space::BigSpacePlugin<GridPrecision>;\npub type ReferenceFrame = big_space::reference_frame::ReferenceFrame<GridPrecision>;\npub type ReferenceFrames<'w, 's> =\n    big_space::reference_frame::local_origin::ReferenceFrames<'w, 's, GridPrecision>;\npub type GridCell = big_space::GridCell<GridPrecision>;\npub type GridTransform = big_space::world_query::GridTransform<GridPrecision>;\npub type GridTransformReadOnly = big_space::world_query::GridTransformReadOnly<GridPrecision>;\npub type GridTransformOwned = big_space::world_query::GridTransformOwned<GridPrecision>;\npub type GridTransformItem<'w> = big_space::world_query::GridTransformItem<'w, GridPrecision>;\n"
  },
  {
    "path": "src/debug/camera.rs",
    "content": "#[cfg(feature = \"high_precision\")]\nuse crate::big_space::{\n    FloatingOrigin, GridCell, GridTransform, GridTransformItem, ReferenceFrame, ReferenceFrames,\n};\n\nuse bevy::{input::mouse::MouseMotion, math::DVec3, prelude::*};\n\n#[derive(Bundle)]\npub struct DebugCameraBundle {\n    pub camera: Camera3dBundle,\n    pub controller: DebugCameraController,\n    #[cfg(feature = \"high_precision\")]\n    pub cell: GridCell,\n    #[cfg(feature = \"high_precision\")]\n    pub origin: FloatingOrigin,\n}\n\nimpl Default for DebugCameraBundle {\n    fn default() -> Self {\n        Self {\n            camera: default(),\n            controller: default(),\n            #[cfg(feature = \"high_precision\")]\n            cell: default(),\n            #[cfg(feature = \"high_precision\")]\n            origin: FloatingOrigin,\n        }\n    }\n}\n\nimpl DebugCameraBundle {\n    #[cfg(feature = \"high_precision\")]\n    pub fn new(position: DVec3, speed: f64, frame: &ReferenceFrame) -> Self {\n        let (cell, translation) = frame.translation_to_grid(position);\n\n        Self {\n            camera: Camera3dBundle {\n                transform: Transform::from_translation(translation).looking_to(Vec3::X, Vec3::Y),\n                projection: PerspectiveProjection {\n                    near: 0.000001,\n                    ..default()\n                }\n                .into(),\n                ..default()\n            },\n            cell,\n            controller: DebugCameraController {\n                translation_speed: speed,\n                ..default()\n            },\n            ..default()\n        }\n    }\n\n    #[cfg(not(feature = \"high_precision\"))]\n    pub fn new(position: Vec3, speed: f64) -> Self {\n        Self {\n            camera: Camera3dBundle {\n                transform: Transform::from_translation(position).looking_to(Vec3::X, Vec3::Y),\n                projection: PerspectiveProjection {\n                    near: 0.000001,\n                    ..default()\n                }\n                .into(),\n                ..default()\n            },\n            controller: DebugCameraController {\n                translation_speed: speed,\n                ..default()\n            },\n            ..default()\n        }\n    }\n}\n\n#[derive(Clone, Debug, Reflect, Component)]\npub struct DebugCameraController {\n    pub enabled: bool,\n    /// Smoothness of translation, from `0.0` to `1.0`.\n    pub translational_smoothness: f64,\n    /// Smoothness of rotation, from `0.0` to `1.0`.\n    pub rotational_smoothness: f32,\n    pub translation_speed: f64,\n    pub rotation_speed: f32,\n    pub acceleration_speed: f64,\n    pub translation_velocity: DVec3,\n    pub rotation_velocity: Vec2,\n}\n\nimpl Default for DebugCameraController {\n    fn default() -> Self {\n        Self {\n            enabled: false,\n            translational_smoothness: 0.9,\n            rotational_smoothness: 0.8,\n            translation_speed: 10e1,\n            rotation_speed: 1e-1,\n            acceleration_speed: 4.0,\n            translation_velocity: Default::default(),\n            rotation_velocity: Default::default(),\n        }\n    }\n}\n\npub fn camera_controller(\n    #[cfg(feature = \"high_precision\")] frames: ReferenceFrames,\n    time: Res<Time>,\n    keyboard: Res<ButtonInput<KeyCode>>,\n    mut mouse_move: EventReader<MouseMotion>,\n    #[cfg(feature = \"high_precision\")] mut camera: Query<(\n        Entity,\n        GridTransform,\n        &mut DebugCameraController,\n    )>,\n    #[cfg(not(feature = \"high_precision\"))] mut camera: Query<(\n        &mut Transform,\n        &mut DebugCameraController,\n    )>,\n) {\n    #[cfg(feature = \"high_precision\")]\n    let (\n        camera,\n        GridTransformItem {\n            mut transform,\n            mut cell,\n        },\n        mut controller,\n    ) = camera.single_mut();\n    #[cfg(feature = \"high_precision\")]\n    let frame = frames.parent_frame(camera).unwrap();\n\n    #[cfg(not(feature = \"high_precision\"))]\n    let (mut transform, mut controller) = camera.single_mut();\n\n    keyboard\n        .just_pressed(KeyCode::KeyT)\n        .then(|| controller.enabled = !controller.enabled);\n\n    if !controller.enabled {\n        return;\n    }\n\n    let mut translation_direction = DVec3::ZERO; // x: left/right, y: up/down, z: forward/backward\n    let rotation_direction = mouse_move.read().map(|m| -m.delta).sum::<Vec2>(); // x: yaw, y: pitch, z: roll\n    let mut acceleration = 0.0;\n\n    keyboard\n        .pressed(KeyCode::ArrowLeft)\n        .then(|| translation_direction.x -= 1.0);\n    keyboard\n        .pressed(KeyCode::ArrowRight)\n        .then(|| translation_direction.x += 1.0);\n    keyboard\n        .pressed(KeyCode::PageUp)\n        .then(|| translation_direction.y += 1.0);\n    keyboard\n        .pressed(KeyCode::PageDown)\n        .then(|| translation_direction.y -= 1.0);\n    keyboard\n        .pressed(KeyCode::ArrowUp)\n        .then(|| translation_direction.z -= 1.0);\n    keyboard\n        .pressed(KeyCode::ArrowDown)\n        .then(|| translation_direction.z += 1.0);\n    keyboard.pressed(KeyCode::Home).then(|| acceleration -= 1.0);\n    keyboard.pressed(KeyCode::End).then(|| acceleration += 1.0);\n\n    translation_direction = transform.rotation.as_dquat() * translation_direction;\n\n    let dt = time.delta_seconds_f64();\n    let lerp_translation = 1.0 - controller.translational_smoothness.clamp(0.0, 0.999);\n    let lerp_rotation = 1.0 - controller.rotational_smoothness.clamp(0.0, 0.999);\n\n    let translation_velocity_target = translation_direction * controller.translation_speed * dt;\n    let rotation_velocity_target = rotation_direction * controller.rotation_speed * dt as f32;\n\n    controller.translation_velocity = controller\n        .translation_velocity\n        .lerp(translation_velocity_target, lerp_translation);\n    controller.rotation_velocity = controller\n        .rotation_velocity\n        .lerp(rotation_velocity_target, lerp_rotation);\n    controller.translation_speed *= 1.0 + acceleration * controller.acceleration_speed * dt;\n\n    let (yaw, pitch, _) = transform.rotation.to_euler(EulerRot::YXZ);\n    let new_yaw = (yaw + controller.rotation_velocity.x) % std::f32::consts::TAU;\n    let new_pitch = (pitch + controller.rotation_velocity.y)\n        .clamp(-std::f32::consts::FRAC_PI_2, std::f32::consts::FRAC_PI_2);\n\n    #[cfg(feature = \"high_precision\")]\n    {\n        let (cell_delta, translation_delta) =\n            frame.translation_to_grid(controller.translation_velocity);\n\n        *cell += cell_delta;\n        transform.translation += translation_delta;\n    }\n    #[cfg(not(feature = \"high_precision\"))]\n    {\n        transform.translation += controller.translation_velocity.as_vec3();\n    }\n\n    transform.rotation = Quat::from_euler(EulerRot::YXZ, new_yaw, new_pitch, 0.0);\n}\n"
  },
  {
    "path": "src/debug/mod.rs",
    "content": "//! Contains a debug resource and systems controlling it to visualize different internal\n//! data of the plugin.\nuse crate::{\n    debug::camera::camera_controller, terrain_data::tile_tree::TileTree,\n    terrain_view::TerrainViewComponents,\n};\nuse bevy::{\n    asset::LoadState,\n    prelude::*,\n    render::{render_resource::*, Extract, RenderApp},\n    transform::TransformSystem,\n    window::PrimaryWindow,\n};\n\npub mod camera;\n\n#[derive(Asset, AsBindGroup, TypePath, Clone, Default)]\npub struct DebugTerrainMaterial {}\n\nimpl Material for DebugTerrainMaterial {}\n\n/// Adds a terrain debug config, a debug camera and debug control systems.\npub struct TerrainDebugPlugin;\n\nimpl Plugin for TerrainDebugPlugin {\n    fn build(&self, app: &mut App) {\n        app.init_resource::<DebugTerrain>()\n            .init_resource::<LoadingImages>()\n            .add_systems(Startup, (debug_lighting, debug_window))\n            .add_systems(\n                Update,\n                (toggle_debug, update_view_parameter, finish_loading_images),\n            )\n            .add_systems(\n                PostUpdate,\n                camera_controller.before(TransformSystem::TransformPropagate),\n            );\n\n        app.sub_app_mut(RenderApp)\n            .init_resource::<DebugTerrain>()\n            .add_systems(ExtractSchedule, extract_debug);\n    }\n}\n\n#[derive(Clone, Resource)]\npub struct DebugTerrain {\n    pub wireframe: bool,\n    pub show_data_lod: bool,\n    pub show_geometry_lod: bool,\n    pub show_tile_tree: bool,\n    pub show_pixels: bool,\n    pub show_uv: bool,\n    pub show_normals: bool,\n    pub morph: bool,\n    pub blend: bool,\n    pub tile_tree_lod: bool,\n    pub lighting: bool,\n    pub sample_grad: bool,\n    pub high_precision: bool,\n    pub freeze: bool,\n    pub test1: bool,\n    pub test2: bool,\n    pub test3: bool,\n}\n\nimpl Default for DebugTerrain {\n    fn default() -> Self {\n        Self {\n            wireframe: false,\n            show_data_lod: false,\n            show_geometry_lod: false,\n            show_tile_tree: false,\n            show_pixels: false,\n            show_uv: false,\n            show_normals: false,\n            morph: true,\n            blend: true,\n            tile_tree_lod: false,\n            lighting: true,\n            sample_grad: true,\n            high_precision: true,\n            freeze: false,\n            test1: false,\n            test2: false,\n            test3: false,\n        }\n    }\n}\n\npub fn extract_debug(mut debug: ResMut<DebugTerrain>, extracted_debug: Extract<Res<DebugTerrain>>) {\n    *debug = extracted_debug.clone();\n}\n\npub fn toggle_debug(input: Res<ButtonInput<KeyCode>>, mut debug: ResMut<DebugTerrain>) {\n    if input.just_pressed(KeyCode::KeyW) {\n        debug.wireframe = !debug.wireframe;\n        println!(\n            \"Toggled the wireframe view {}.\",\n            if debug.wireframe { \"on\" } else { \"off\" }\n        )\n    }\n    if input.just_pressed(KeyCode::KeyL) {\n        debug.show_data_lod = !debug.show_data_lod;\n        println!(\n            \"Toggled the terrain data LOD view {}.\",\n            if debug.show_data_lod { \"on\" } else { \"off\" }\n        )\n    }\n    if input.just_pressed(KeyCode::KeyY) {\n        debug.show_geometry_lod = !debug.show_geometry_lod;\n        println!(\n            \"Toggled the terrain geometry LOD view {}.\",\n            if debug.show_geometry_lod { \"on\" } else { \"off\" }\n        )\n    }\n    if input.just_pressed(KeyCode::KeyQ) {\n        debug.show_tile_tree = !debug.show_tile_tree;\n        println!(\n            \"Toggled the tile tree LOD view {}.\",\n            if debug.show_tile_tree { \"on\" } else { \"off\" }\n        )\n    }\n    if input.just_pressed(KeyCode::KeyP) {\n        debug.show_pixels = !debug.show_pixels;\n        println!(\n            \"Toggled the pixel view {}.\",\n            if debug.show_pixels { \"on\" } else { \"off\" }\n        )\n    }\n    if input.just_pressed(KeyCode::KeyU) {\n        debug.show_uv = !debug.show_uv;\n        println!(\n            \"Toggled the uv view {}.\",\n            if debug.show_uv { \"on\" } else { \"off\" }\n        )\n    }\n    if input.just_pressed(KeyCode::KeyB) {\n        debug.show_normals = !debug.show_normals;\n        println!(\n            \"Toggled the normals view {}.\",\n            if debug.show_normals { \"on\" } else { \"off\" }\n        )\n    }\n    if input.just_pressed(KeyCode::KeyM) {\n        debug.morph = !debug.morph;\n        println!(\n            \"Toggled morphing {}.\",\n            if debug.morph { \"on\" } else { \"off\" }\n        )\n    }\n    if input.just_pressed(KeyCode::KeyK) {\n        debug.blend = !debug.blend;\n        println!(\n            \"Toggled blending {}.\",\n            if debug.blend { \"on\" } else { \"off\" }\n        )\n    }\n    if input.just_pressed(KeyCode::KeyZ) {\n        debug.tile_tree_lod = !debug.tile_tree_lod;\n        println!(\n            \"Toggled tile tree lod {}.\",\n            if debug.tile_tree_lod { \"on\" } else { \"off\" }\n        )\n    }\n    if input.just_pressed(KeyCode::KeyS) {\n        debug.lighting = !debug.lighting;\n        println!(\n            \"Toggled the lighting {}.\",\n            if debug.lighting { \"on\" } else { \"off\" }\n        )\n    }\n    if input.just_pressed(KeyCode::KeyG) {\n        debug.sample_grad = !debug.sample_grad;\n        println!(\n            \"Toggled the texture sampling using gradients {}.\",\n            if debug.sample_grad { \"on\" } else { \"off\" }\n        )\n    }\n    if input.just_pressed(KeyCode::KeyH) {\n        debug.high_precision = !debug.high_precision;\n        println!(\n            \"Toggled high precision coordinates {}.\",\n            if debug.high_precision { \"on\" } else { \"off\" }\n        )\n    }\n    if input.just_pressed(KeyCode::KeyF) {\n        debug.freeze = !debug.freeze;\n        println!(\n            \"{} the view frustum.\",\n            if debug.freeze { \"Froze\" } else { \"Unfroze\" }\n        )\n    }\n    if input.just_pressed(KeyCode::Digit1) {\n        debug.test1 = !debug.test1;\n        println!(\n            \"Toggled the debug flag 1 {}.\",\n            if debug.test1 { \"on\" } else { \"off\" }\n        )\n    }\n    if input.just_pressed(KeyCode::Digit2) {\n        debug.test2 = !debug.test2;\n        println!(\n            \"Toggled the debug flag 2 {}.\",\n            if debug.test2 { \"on\" } else { \"off\" }\n        )\n    }\n    if input.just_pressed(KeyCode::Digit3) {\n        debug.test3 = !debug.test3;\n        println!(\n            \"Toggled the debug flag 3 {}.\",\n            if debug.test3 { \"on\" } else { \"off\" }\n        )\n    }\n}\n\npub fn update_view_parameter(\n    input: Res<ButtonInput<KeyCode>>,\n    mut tile_trees: ResMut<TerrainViewComponents<TileTree>>,\n) {\n    for tile_tree in &mut tile_trees.values_mut() {\n        if input.just_pressed(KeyCode::KeyN) {\n            tile_tree.blend_distance -= 0.25;\n            println!(\n                \"Decreased the blend distance to {}.\",\n                tile_tree.blend_distance\n            );\n        }\n        if input.just_pressed(KeyCode::KeyE) {\n            tile_tree.blend_distance += 0.25;\n            println!(\n                \"Increased the blend distance to {}.\",\n                tile_tree.blend_distance\n            );\n        }\n\n        if input.just_pressed(KeyCode::KeyI) {\n            tile_tree.morph_distance -= 0.25;\n            println!(\n                \"Decreased the morph distance to {}.\",\n                tile_tree.morph_distance\n            );\n        }\n        if input.just_pressed(KeyCode::KeyO) {\n            tile_tree.morph_distance += 0.25;\n            println!(\n                \"Increased the morph distance to {}.\",\n                tile_tree.morph_distance\n            );\n        }\n\n        if input.just_pressed(KeyCode::KeyX) && tile_tree.grid_size > 2 {\n            tile_tree.grid_size -= 2;\n            println!(\"Decreased the grid size to {}.\", tile_tree.grid_size);\n        }\n        if input.just_pressed(KeyCode::KeyJ) {\n            tile_tree.grid_size += 2;\n            println!(\"Increased the grid size to {}.\", tile_tree.grid_size);\n        }\n    }\n}\n\npub(crate) fn debug_lighting(mut commands: Commands) {\n    commands.spawn(DirectionalLightBundle {\n        directional_light: DirectionalLight {\n            illuminance: 5000.0,\n            ..default()\n        },\n        transform: Transform::from_xyz(-1.0, 1.0, -1.0).looking_at(Vec3::ZERO, Vec3::Y),\n        ..default()\n    });\n    commands.insert_resource(AmbientLight {\n        brightness: 100.0,\n        ..default()\n    });\n}\n\npub fn debug_window(mut window: Query<&mut Window, With<PrimaryWindow>>) {\n    let mut window = window.single_mut();\n    window.cursor.visible = false;\n}\n\n#[derive(Resource, Default)]\npub struct LoadingImages(Vec<(AssetId<Image>, TextureDimension, TextureFormat)>);\n\nimpl LoadingImages {\n    pub fn load_image(\n        &mut self,\n        handle: &Handle<Image>,\n        dimension: TextureDimension,\n        format: TextureFormat,\n    ) -> &mut Self {\n        self.0.push((handle.id(), dimension, format));\n        self\n    }\n}\n\nfn finish_loading_images(\n    asset_server: Res<AssetServer>,\n    mut loading_images: ResMut<LoadingImages>,\n    mut images: ResMut<Assets<Image>>,\n) {\n    loading_images.0.retain(|&(id, dimension, format)| {\n        if asset_server.load_state(id) == LoadState::Loaded {\n            let image = images.get_mut(id).unwrap();\n            image.texture_descriptor.dimension = dimension;\n            image.texture_descriptor.format = format;\n\n            false\n        } else {\n            true\n        }\n    });\n}\n"
  },
  {
    "path": "src/formats/mod.rs",
    "content": "pub mod tiff;\n\nuse crate::math::TileCoordinate;\nuse anyhow::Result;\nuse bincode::{config, Decode, Encode};\nuse std::{fs, path::Path};\n\n#[derive(Encode, Decode, Debug)]\npub struct TC {\n    pub tiles: Vec<TileCoordinate>,\n}\n\nimpl TC {\n    pub fn decode_alloc(encoded: &[u8]) -> Result<Self> {\n        let config = config::standard();\n        let decoded = bincode::decode_from_slice(encoded, config)?;\n        Ok(decoded.0)\n    }\n\n    pub fn encode_alloc(&self) -> Result<Vec<u8>> {\n        let config = config::standard();\n        let encoded = bincode::encode_to_vec(self, config)?;\n        Ok(encoded)\n    }\n\n    pub fn load_file<P: AsRef<Path>>(path: P) -> Result<Self> {\n        let encoded = fs::read(path)?;\n        Self::decode_alloc(&encoded)\n    }\n\n    pub fn save_file<P: AsRef<Path>>(&self, path: P) -> Result<()> {\n        let encoded = self.encode_alloc()?;\n        fs::write(path, encoded)?;\n        Ok(())\n    }\n}\n"
  },
  {
    "path": "src/formats/tiff.rs",
    "content": "use bevy::{\n    asset::{io::Reader, AssetLoader, AsyncReadExt, LoadContext},\n    prelude::*,\n    render::{\n        render_asset::RenderAssetUsages,\n        render_resource::{Extent3d, TextureDimension, TextureFormat},\n        texture::TextureError,\n    },\n};\nuse bytemuck::cast_slice;\nuse std::io::Cursor;\nuse tiff::decoder::{Decoder, DecodingResult};\n\n#[derive(Default)]\npub struct TiffLoader;\nimpl AssetLoader for TiffLoader {\n    type Asset = Image;\n    type Settings = ();\n    type Error = TextureError;\n    async fn load<'a>(\n        &'a self,\n        reader: &'a mut Reader<'_>,\n        _settings: &'a Self::Settings,\n        _load_context: &'a mut LoadContext<'_>,\n    ) -> Result<Image, Self::Error> {\n        let mut bytes = Vec::new();\n        reader.read_to_end(&mut bytes).await.unwrap();\n\n        let mut decoder = Decoder::new(Cursor::new(bytes)).unwrap();\n\n        let (width, height) = decoder.dimensions().unwrap();\n\n        let data = match decoder.read_image().unwrap() {\n            DecodingResult::U8(data) => cast_slice(&data).to_vec(),\n            DecodingResult::U16(data) => cast_slice(&data).to_vec(),\n            DecodingResult::U32(data) => cast_slice(&data).to_vec(),\n            DecodingResult::U64(data) => cast_slice(&data).to_vec(),\n            DecodingResult::F32(data) => cast_slice(&data).to_vec(),\n            DecodingResult::F64(data) => cast_slice(&data).to_vec(),\n            DecodingResult::I8(data) => cast_slice(&data).to_vec(),\n            DecodingResult::I16(data) => cast_slice(&data).to_vec(),\n            DecodingResult::I32(data) => cast_slice(&data).to_vec(),\n            DecodingResult::I64(data) => cast_slice(&data).to_vec(),\n        };\n\n        Ok(Image::new(\n            Extent3d {\n                width,\n                height,\n                depth_or_array_layers: 1,\n            },\n            TextureDimension::D2,\n            data,\n            TextureFormat::R16Unorm,\n            RenderAssetUsages::default(),\n        ))\n    }\n\n    fn extensions(&self) -> &[&str] {\n        &[\"tif\", \"tiff\"]\n    }\n}\n"
  },
  {
    "path": "src/lib.rs",
    "content": "//! This crate provides the ability to render beautiful height-field terrains of any size.\n//! This is achieved in extensible and modular manner, so that the terrain data\n//! can be accessed from nearly anywhere (systems, shaders) [^note].\n//!\n//! # Background\n//! There are three critical questions that each terrain renderer has to solve:\n//!\n//! ## How to store, manage and access the terrain data?\n//! Each terrain has different types of textures associated with it.\n//! For example a simple one might only need height and albedo information.\n//! Because terrains can be quite large the space required for all of these so called\n//! attachments, can/should not be stored in RAM and VRAM all at once.\n//! Thus they have to be streamed in and out depending on the positions of the\n//! viewers (cameras, lights, etc.).\n//! Therefore the terrain is subdivided into a giant tile_tree, whose tiles store their\n//! section of these attachments.\n//! This crate uses the chunked clipmap data structure, which consist of two pieces working together.\n//! The wrapping [`TileTree`](prelude::TileTree) views together with\n//! the [`TileAtlas`](prelude::TileAtlas) (the data structure\n//! that stores all of the currently loaded data) can be used to efficiently retrieve\n//! the best currently available data at any position for terrains of any size.\n//! See the [`terrain_data`] module for more information.\n//!\n//! ## How to best approximate the terrain geometry?\n//! Even a small terrain with a height map of 1000x1000 pixels would require 1 million vertices\n//! to be rendered each frame per view, with an naive approach without an lod strategy.\n//! To better distribute the vertices over the screen there exist many different algorithms.\n//! This crate comes with its own default terrain geometry algorithm, called the\n//! Uniform Distance-Dependent Level of Detail (UDLOD), which was developed with performance and\n//! quality scalability in mind.\n//! See the [`render`] module for more information.\n//! You can also implement a different algorithm yourself and only use the terrain\n//! data structures to solve the first question.\n//!\n//! ## How to shade the terrain?\n//! The third and most important challenge of terrain rendering is the shading. This is a very\n//! project specific problem and thus there does not exist a one-size-fits-all solution.\n//! You can define your own terrain [Material](bevy::prelude::Material) and shader with all the\n//! detail textures tailored to your application.\n//! In the future this plugin will provide modular shader functions to make techniques like splat\n//! mapping, triplane mapping, etc. easier.\n//! Additionally a virtual texturing solution might be integrated to achieve better performance.\n//!\n//! [^note]: Some of these claims are not yet fully implemented.\n\n#[cfg(feature = \"high_precision\")]\npub mod big_space;\npub mod debug;\npub mod formats;\npub mod math;\npub mod plugin;\npub mod preprocess;\npub mod render;\npub mod shaders;\npub mod terrain;\npub mod terrain_data;\npub mod terrain_view;\npub mod util;\n\npub mod prelude {\n    //! `use bevy_terrain::prelude::*;` to import common components, bundles, and plugins.\n    // #[doc(hidden)]\n\n    #[cfg(feature = \"high_precision\")]\n    pub use crate::big_space::{BigSpaceCommands, ReferenceFrame};\n\n    pub use crate::{\n        debug::{\n            camera::{DebugCameraBundle, DebugCameraController},\n            DebugTerrainMaterial, LoadingImages, TerrainDebugPlugin,\n        },\n        math::TerrainModel,\n        plugin::TerrainPlugin,\n        preprocess::{\n            preprocessor::Preprocessor,\n            preprocessor::{PreprocessDataset, SphericalDataset},\n            TerrainPreprocessPlugin,\n        },\n        render::terrain_material::TerrainMaterialPlugin,\n        terrain::{TerrainBundle, TerrainConfig},\n        terrain_data::{\n            tile_atlas::TileAtlas, tile_tree::TileTree, AttachmentConfig, AttachmentFormat,\n        },\n        terrain_view::{TerrainViewComponents, TerrainViewConfig},\n    };\n}\n"
  },
  {
    "path": "src/math/coordinate.rs",
    "content": "use crate::math::{TerrainModel, C_SQR};\nuse bevy::{\n    math::{DVec2, DVec3, IVec2},\n    render::render_resource::ShaderType,\n};\nuse bincode::{Decode, Encode};\nuse std::fmt;\n\nconst NEIGHBOURING_SIDES: [[u32; 5]; 6] = [\n    [0, 4, 2, 1, 5],\n    [1, 0, 2, 3, 5],\n    [2, 0, 4, 3, 1],\n    [3, 2, 4, 5, 1],\n    [4, 2, 0, 5, 3],\n    [5, 4, 0, 1, 3],\n];\n\n#[derive(Clone, Copy)]\nenum SideInfo {\n    Fixed0,\n    Fixed1,\n    PositiveS,\n    PositiveT,\n}\n\nimpl SideInfo {\n    const EVEN_LIST: [[SideInfo; 2]; 6] = [\n        [SideInfo::PositiveS, SideInfo::PositiveT],\n        [SideInfo::Fixed0, SideInfo::PositiveT],\n        [SideInfo::Fixed0, SideInfo::PositiveS],\n        [SideInfo::PositiveT, SideInfo::PositiveS],\n        [SideInfo::PositiveT, SideInfo::Fixed0],\n        [SideInfo::PositiveS, SideInfo::Fixed0],\n    ];\n    const ODD_LIST: [[SideInfo; 2]; 6] = [\n        [SideInfo::PositiveS, SideInfo::PositiveT],\n        [SideInfo::PositiveS, SideInfo::Fixed1],\n        [SideInfo::PositiveT, SideInfo::Fixed1],\n        [SideInfo::PositiveT, SideInfo::PositiveS],\n        [SideInfo::Fixed1, SideInfo::PositiveS],\n        [SideInfo::Fixed1, SideInfo::PositiveT],\n    ];\n\n    fn project_to_side(side: u32, other_side: u32) -> [SideInfo; 2] {\n        let index = ((6 + other_side - side) % 6) as usize;\n\n        if side % 2 == 0 {\n            SideInfo::EVEN_LIST[index]\n        } else {\n            SideInfo::ODD_LIST[index]\n        }\n    }\n}\n\n/// Describes a location on the unit cube sphere.\n/// The side index refers to one of the six cube faces and the uv coordinate describes the location within this side.\n#[derive(Copy, Clone, Debug, Default)]\npub struct Coordinate {\n    pub side: u32,\n    pub uv: DVec2,\n}\n\nimpl Coordinate {\n    pub fn new(side: u32, uv: DVec2) -> Self {\n        Self { side, uv }\n    }\n\n    /// Calculates the coordinate for for the local position on the unit cube sphere.\n    pub(crate) fn from_world_position(world_position: DVec3, model: &TerrainModel) -> Self {\n        let local_position = model.position_world_to_local(world_position);\n\n        let (side, uv) = if model.is_spherical() {\n            let normal = local_position;\n            let abs_normal = normal.abs();\n\n            let (side, uv) = if abs_normal.x > abs_normal.y && abs_normal.x > abs_normal.z {\n                if normal.x < 0.0 {\n                    (0, DVec2::new(-normal.z / normal.x, normal.y / normal.x))\n                } else {\n                    (3, DVec2::new(-normal.y / normal.x, normal.z / normal.x))\n                }\n            } else if abs_normal.z > abs_normal.y {\n                if normal.z > 0.0 {\n                    (1, DVec2::new(normal.x / normal.z, -normal.y / normal.z))\n                } else {\n                    (4, DVec2::new(normal.y / normal.z, -normal.x / normal.z))\n                }\n            } else {\n                if normal.y > 0.0 {\n                    (2, DVec2::new(normal.x / normal.y, normal.z / normal.y))\n                } else {\n                    (5, DVec2::new(-normal.z / normal.y, -normal.x / normal.y))\n                }\n            };\n\n            let w = uv * ((1.0 + C_SQR) / (1.0 + C_SQR * uv * uv)).powf(0.5);\n            let uv = 0.5 * w + 0.5;\n\n            (side, uv)\n        } else {\n            let uv = DVec2::new(local_position.x + 0.5, local_position.z + 0.5)\n                .clamp(DVec2::ZERO, DVec2::ONE);\n\n            (0, uv)\n        };\n\n        Self { side, uv }\n    }\n\n    pub(crate) fn world_position(self, model: &TerrainModel, height: f32) -> DVec3 {\n        let local_position = if model.is_spherical() {\n            let w = (self.uv - 0.5) / 0.5;\n            let uv = w / (1.0 + C_SQR - C_SQR * w * w).powf(0.5);\n\n            match self.side {\n                0 => DVec3::new(-1.0, -uv.y, uv.x),\n                1 => DVec3::new(uv.x, -uv.y, 1.0),\n                2 => DVec3::new(uv.x, 1.0, uv.y),\n                3 => DVec3::new(1.0, -uv.x, uv.y),\n                4 => DVec3::new(uv.y, -uv.x, -1.0),\n                5 => DVec3::new(uv.y, -1.0, uv.x),\n                _ => unreachable!(),\n            }\n            .normalize()\n        } else {\n            DVec3::new(self.uv.x - 0.5, 0.0, self.uv.y - 0.5)\n        };\n\n        model.position_local_to_world(local_position, height as f64)\n    }\n\n    /// Projects the coordinate onto one of the six cube faces.\n    /// Thereby it chooses the closest location on this face to the original coordinate.\n    pub(crate) fn project_to_side(self, side: u32, model: &TerrainModel) -> Self {\n        if model.is_spherical() {\n            let info = SideInfo::project_to_side(self.side, side);\n\n            let uv = info\n                .map(|info| match info {\n                    SideInfo::Fixed0 => 0.0,\n                    SideInfo::Fixed1 => 1.0,\n                    SideInfo::PositiveS => self.uv.x,\n                    SideInfo::PositiveT => self.uv.y,\n                })\n                .into();\n\n            Self { side, uv }\n        } else {\n            self\n        }\n    }\n}\n\n/// The global coordinate and identifier of a tile.\n#[derive(Copy, Clone, Default, Debug, Hash, Eq, PartialEq, ShaderType, Encode, Decode)]\npub struct TileCoordinate {\n    /// The side of the cube sphere the tile is located on.\n    pub side: u32,\n    /// The lod of the tile, where 0 is the highest level of detail with the smallest size\n    /// and highest resolution\n    pub lod: u32,\n    /// The x position of the tile in tile sizes.\n    pub x: u32,\n    /// The y position of the tile in tile sizes.\n    pub y: u32,\n}\n\nimpl TileCoordinate {\n    pub const INVALID: TileCoordinate = TileCoordinate {\n        side: u32::MAX,\n        lod: u32::MAX,\n        x: u32::MAX,\n        y: u32::MAX,\n    };\n\n    pub fn new(side: u32, lod: u32, x: u32, y: u32) -> Self {\n        Self { side, lod, x, y }\n    }\n\n    pub fn count(lod: u32) -> u32 {\n        1 << lod\n    }\n\n    pub fn path(self, path: &str, extension: &str) -> String {\n        format!(\"{path}/{self}.{extension}\")\n    }\n\n    pub fn parent(self) -> Self {\n        Self {\n            side: self.side,\n            lod: self.lod.wrapping_sub(1),\n            x: self.x >> 1,\n            y: self.y >> 1,\n        }\n    }\n\n    pub fn children(self) -> impl Iterator<Item = Self> {\n        (0..4).map(move |index| {\n            TileCoordinate::new(\n                self.side,\n                self.lod + 1,\n                (self.x << 1) + index % 2,\n                (self.y << 1) + index / 2,\n            )\n        })\n    }\n\n    pub fn neighbours(self, spherical: bool) -> impl Iterator<Item = Self> {\n        const OFFSETS: [IVec2; 8] = [\n            IVec2::new(0, -1),\n            IVec2::new(1, 0),\n            IVec2::new(0, 1),\n            IVec2::new(-1, 0),\n            IVec2::new(-1, -1),\n            IVec2::new(1, -1),\n            IVec2::new(1, 1),\n            IVec2::new(-1, 1),\n        ];\n\n        OFFSETS.iter().map(move |&offset| {\n            let neighbour_position = IVec2::new(self.x as i32, self.y as i32) + offset;\n\n            self.neighbour_coordinate(neighbour_position, spherical)\n        })\n    }\n\n    fn neighbour_coordinate(self, neighbour_position: IVec2, spherical: bool) -> Self {\n        let tile_count = Self::count(self.lod) as i32;\n\n        if spherical {\n            let edge_index = match neighbour_position {\n                IVec2 { x, y }\n                    if x < 0 && y < 0\n                        || x < 0 && y >= tile_count\n                        || x >= tile_count && y < 0\n                        || x >= tile_count && y >= tile_count =>\n                {\n                    return Self::INVALID;\n                }\n                IVec2 { x, .. } if x < 0 => 1,\n                IVec2 { y, .. } if y < 0 => 2,\n                IVec2 { x, .. } if x >= tile_count => 3,\n                IVec2 { y, .. } if y >= tile_count => 4,\n                _ => 0,\n            };\n\n            let neighbour_position = neighbour_position\n                .clamp(IVec2::ZERO, IVec2::splat(tile_count - 1))\n                .as_uvec2();\n\n            let neighbour_side = NEIGHBOURING_SIDES[self.side as usize][edge_index];\n\n            let info = SideInfo::project_to_side(self.side, neighbour_side);\n\n            let [x, y] = info.map(|info| match info {\n                SideInfo::Fixed0 => 0,\n                SideInfo::Fixed1 => tile_count as u32 - 1,\n                SideInfo::PositiveS => neighbour_position.x,\n                SideInfo::PositiveT => neighbour_position.y,\n            });\n\n            Self::new(neighbour_side, self.lod, x, y)\n        } else {\n            if neighbour_position.x < 0\n                || neighbour_position.y < 0\n                || neighbour_position.x >= tile_count\n                || neighbour_position.y >= tile_count\n            {\n                Self::INVALID\n            } else {\n                Self::new(\n                    self.side,\n                    self.lod,\n                    neighbour_position.x as u32,\n                    neighbour_position.y as u32,\n                )\n            }\n        }\n    }\n}\n\nimpl fmt::Display for TileCoordinate {\n    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {\n        write!(f, \"{}_{}_{}_{}\", self.side, self.lod, self.x, self.y)\n    }\n}\n"
  },
  {
    "path": "src/math/ellipsoid.rs",
    "content": "use bevy::math::{DVec2, DVec3, Vec3Swizzles};\nuse std::cmp::Ordering;\n\n// Adapted from https://www.geometrictools.com/Documentation/DistancePointEllipseEllipsoid.pdf\n// Original licensed under Creative Commons Attribution 4.0 International License\n// http://creativecommons.org/licenses/by/4.0/\n\n// After 1074 iterations, s0 == s1 == s\n// This should probably be relaxed to only limit the error s1-s0 to a constant e.\nconst MAX_ITERATIONS: usize = 1074;\n\npub fn project_point_ellipsoid(e: DVec3, y: DVec3) -> DVec3 {\n    let sign = y.signum();\n    let y = y.xzy().abs();\n\n    let x = if y.z > 0.0 {\n        if y.y > 0.0 {\n            if y.x > 0.0 {\n                let z = y / e;\n                let g = z.length_squared() - 1.0;\n\n                if g != 0.0 {\n                    let r = DVec3::new((e.x * e.x) / (e.z * e.z), (e.y * e.y) / (e.z * e.z), 1.0);\n\n                    r * y / (get_root_3d(r, z, g) + r)\n                } else {\n                    y\n                }\n            } else {\n                project_point_ellipse(e.yz(), y.yz()).extend(0.0).zxy()\n            }\n        } else {\n            if y.x > 0.0 {\n                project_point_ellipse(e.xz(), y.xz()).extend(0.0).xzy()\n            } else {\n                DVec3::new(0.0, 0.0, e.z)\n            }\n        }\n    } else {\n        let denom0 = e.x * e.x - e.z * e.z;\n        let denom1 = e.y * e.y - e.z * e.z;\n        let numer0 = e.x * y.x;\n        let numer1 = e.y * y.y;\n\n        let mut x = None;\n\n        if numer0 < denom0 && numer1 < denom1 {\n            let xde0 = numer0 / denom0;\n            let xde1 = numer1 / denom1;\n            let xde0sqr = xde0 * xde0;\n            let xde1sqr = xde1 * xde1;\n            let discr = 1.0 - xde0sqr - xde1sqr;\n\n            if discr > 0.0 {\n                x = Some(e * DVec3::new(xde0, xde1, discr.sqrt()));\n            }\n        }\n\n        x.unwrap_or_else(|| project_point_ellipse(e.xy(), y.xy()).extend(0.0))\n    };\n\n    sign * x.xzy()\n}\n\nfn project_point_ellipse(e: DVec2, y: DVec2) -> DVec2 {\n    if y.y > 0.0 {\n        if y.x > 0.0 {\n            let z = y / e;\n            let g = z.length_squared() - 1.0;\n\n            if g != 0.0 {\n                let r = DVec2::new((e.x * e.x) / (e.y * e.y), 1.0);\n                r * y / (get_root_2d(r, z, g) + r)\n            } else {\n                y\n            }\n        } else {\n            DVec2::new(0.0, e.y)\n        }\n    } else {\n        let numer0 = e.x * y.x;\n        let denom0 = e.x * e.x - e.y * e.y;\n        if numer0 < denom0 {\n            let xde0 = numer0 / denom0;\n            DVec2::new(e.x * xde0, e.y * (1.0 - xde0 * xde0).sqrt())\n        } else {\n            DVec2::new(e.x, 0.0)\n        }\n    }\n}\n\nfn get_root_3d(r: DVec3, z: DVec3, g: f64) -> f64 {\n    let n = r * z;\n\n    let mut s0 = z.z - 1.0;\n    let mut s1 = if g < 0.0 { 0.0 } else { n.length() - 1.0 };\n    let mut s = 0.0;\n\n    for _ in 0..MAX_ITERATIONS {\n        s = (s0 + s1) / 2.0;\n        if s == s0 || s == s1 {\n            break;\n        }\n\n        let ratio = n / (s + r);\n        let g = ratio.length_squared() - 1.0;\n\n        match g.total_cmp(&0.0) {\n            Ordering::Less => s1 = s,\n            Ordering::Equal => break,\n            Ordering::Greater => s0 = s,\n        }\n    }\n\n    s\n}\n\nfn get_root_2d(r: DVec2, z: DVec2, g: f64) -> f64 {\n    let n = r * z;\n\n    let mut s0 = z.y - 1.0;\n    let mut s1 = if g < 0.0 { 0.0 } else { n.length() - 1.0 };\n    let mut s = 0.0;\n\n    for _ in 0..MAX_ITERATIONS {\n        s = (s0 + s1) / 2.0;\n        if s == s0 || s == s1 {\n            break;\n        }\n\n        let ratio = n / (s + r);\n        let g = ratio.length_squared() - 1.0;\n\n        match g.total_cmp(&0.0) {\n            Ordering::Less => s1 = s,\n            Ordering::Equal => break,\n            Ordering::Greater => s0 = s,\n        }\n    }\n\n    s\n}\n"
  },
  {
    "path": "src/math/mod.rs",
    "content": "mod coordinate;\nmod ellipsoid;\nmod terrain_model;\n\npub use crate::math::{\n    coordinate::{Coordinate, TileCoordinate},\n    terrain_model::{\n        generate_terrain_model_approximation, TerrainModel, TerrainModelApproximation,\n    },\n};\n\n/// The square of the parameter c of the algebraic sigmoid function, used to convert between uv and st coordinates.\nconst C_SQR: f64 = 0.87 * 0.87;\n"
  },
  {
    "path": "src/math/terrain_model.rs",
    "content": "use crate::{\n    math::{coordinate::Coordinate, ellipsoid::project_point_ellipsoid, TileCoordinate, C_SQR},\n    terrain_data::tile_atlas::TileAtlas,\n    terrain_data::tile_tree::TileTree,\n    terrain_view::TerrainViewComponents,\n};\nuse bevy::{\n    math::{DMat3, DMat4, DQuat, DVec2, DVec3, IVec2},\n    prelude::*,\n    render::render_resource::ShaderType,\n};\n\n/// One matrix per side, which shuffles the a, b, and c component to their corresponding position.\nconst SIDE_MATRICES: [DMat3; 6] = [\n    DMat3::from_cols_array(&[-1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, -1.0, 0.0]),\n    DMat3::from_cols_array(&[0.0, 0.0, 1.0, 1.0, 0.0, 0.0, 0.0, -1.0, 0.0]),\n    DMat3::from_cols_array(&[0.0, 1.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0]),\n    DMat3::from_cols_array(&[1.0, 0.0, 0.0, 0.0, -1.0, 0.0, 0.0, 0.0, 1.0]),\n    DMat3::from_cols_array(&[0.0, 0.0, -1.0, 0.0, -1.0, 0.0, 1.0, 0.0, 0.0]),\n    DMat3::from_cols_array(&[0.0, -1.0, 0.0, 0.0, 0.0, 1.0, 1.0, 0.0, 0.0]),\n];\n\n#[derive(Clone)]\npub enum TerrainKind {\n    PLANAR {\n        side_length: f64,\n    },\n    SPHERICAL {\n        radius: f64,\n    },\n    ELLIPSOIDAL {\n        ellipsoid_from_world: DMat4,\n        major_axis: f64,\n        minor_axis: f64,\n    },\n}\n\n// Todo: keep in sync with terrain transform, make this authoritative?\n\n#[derive(Clone)]\npub struct TerrainModel {\n    pub(crate) kind: TerrainKind,\n    pub(crate) min_height: f32,\n    pub(crate) max_height: f32,\n    translation: DVec3,\n    scale: DVec3,\n    rotation: DQuat,\n    world_from_local: DMat4,\n    local_from_world: DMat4,\n}\n\nimpl TerrainModel {\n    pub(crate) fn is_spherical(&self) -> bool {\n        match self.kind {\n            TerrainKind::PLANAR { .. } => false,\n            TerrainKind::SPHERICAL { .. } => true,\n            TerrainKind::ELLIPSOIDAL { .. } => true,\n        }\n    }\n\n    fn from_scale_rotation_translation(\n        scale: DVec3,\n        rotation: DQuat,\n        translation: DVec3,\n        min_height: f32,\n        max_height: f32,\n        kind: TerrainKind,\n    ) -> Self {\n        let world_from_local = DMat4::from_scale_rotation_translation(scale, rotation, translation);\n        let local_from_world = world_from_local.inverse();\n\n        Self {\n            kind,\n            min_height,\n            max_height,\n            translation,\n            scale,\n            rotation,\n            world_from_local,\n            local_from_world,\n        }\n    }\n\n    pub fn planar(position: DVec3, side_length: f64, min_height: f32, max_height: f32) -> Self {\n        Self::from_scale_rotation_translation(\n            DVec3::splat(side_length),\n            DQuat::IDENTITY,\n            position,\n            min_height,\n            max_height,\n            TerrainKind::PLANAR { side_length },\n        )\n    }\n\n    pub fn sphere(position: DVec3, radius: f64, min_height: f32, max_height: f32) -> Self {\n        Self::from_scale_rotation_translation(\n            DVec3::splat(radius),\n            DQuat::IDENTITY,\n            position,\n            min_height,\n            max_height,\n            TerrainKind::SPHERICAL { radius },\n        )\n    }\n\n    pub fn ellipsoid(\n        position: DVec3,\n        major_axis: f64,\n        minor_axis: f64,\n        min_height: f32,\n        max_height: f32,\n    ) -> Self {\n        let rotation = DQuat::IDENTITY; // ::from_rotation_x(45.0_f64.to_radians());\n        let ellipsoid_from_world = DMat4::from_rotation_translation(rotation, position).inverse();\n\n        Self::from_scale_rotation_translation(\n            DVec3::new(major_axis, minor_axis, major_axis),\n            rotation,\n            position,\n            min_height,\n            max_height,\n            TerrainKind::ELLIPSOIDAL {\n                ellipsoid_from_world,\n                major_axis,\n                minor_axis,\n            },\n        )\n    }\n\n    pub(crate) fn position_local_to_world(&self, local_position: DVec3, height: f64) -> DVec3 {\n        let world_position = self.world_from_local.transform_point3(local_position);\n        let world_normal = self\n            .world_from_local\n            .transform_vector3(if self.is_spherical() {\n                local_position\n            } else {\n                DVec3::Y\n            })\n            .normalize();\n\n        world_position + height * world_normal\n    }\n\n    pub(crate) fn position_world_to_local(&self, world_position: DVec3) -> DVec3 {\n        match self.kind {\n            TerrainKind::PLANAR { .. } => {\n                DVec3::new(1.0, 0.0, 1.0) * self.local_from_world.transform_point3(world_position)\n            }\n\n            TerrainKind::SPHERICAL { .. } => self\n                .local_from_world\n                .transform_point3(world_position)\n                .normalize(),\n            TerrainKind::ELLIPSOIDAL {\n                ellipsoid_from_world,\n                major_axis,\n                minor_axis,\n            } => {\n                let ellipsoid_position = ellipsoid_from_world.transform_point3(world_position);\n                let surface_position = project_point_ellipsoid(\n                    DVec3::new(major_axis, major_axis, minor_axis),\n                    ellipsoid_position,\n                );\n                self.local_from_world\n                    .transform_point3(surface_position)\n                    .normalize()\n            }\n        }\n    }\n\n    pub(crate) fn surface_position(&self, world_position: DVec3, height: f64) -> DVec3 {\n        self.position_local_to_world(self.position_world_to_local(world_position), height)\n    }\n\n    pub(crate) fn side_count(&self) -> u32 {\n        if self.is_spherical() {\n            6\n        } else {\n            1\n        }\n    }\n\n    pub(crate) fn scale(&self) -> f64 {\n        match self.kind {\n            TerrainKind::PLANAR { side_length } => side_length / 2.0,\n            TerrainKind::SPHERICAL { radius } => radius,\n            TerrainKind::ELLIPSOIDAL {\n                major_axis,\n                minor_axis,\n                ..\n            } => (major_axis + minor_axis) / 2.0,\n        }\n    }\n\n    #[cfg(not(feature = \"high_precision\"))]\n    pub(crate) fn transform(&self) -> Transform {\n        Transform {\n            translation: self.translation.as_vec3(),\n            scale: self.scale.as_vec3(),\n            rotation: self.rotation.as_quat(),\n        }\n    }\n\n    #[cfg(feature = \"high_precision\")]\n    pub(crate) fn grid_transform(\n        &self,\n        frame: &crate::big_space::ReferenceFrame,\n    ) -> crate::big_space::GridTransformOwned {\n        let (cell, translation) = frame.translation_to_grid(self.translation);\n\n        crate::big_space::GridTransformOwned {\n            transform: Transform {\n                translation,\n                scale: self.scale.as_vec3(),\n                rotation: self.rotation.as_quat(),\n            },\n            cell,\n        }\n    }\n}\n\n/// Parameters of the view used to compute the position of a location on the sphere's surface relative to the view.\n/// This can be calculated directly using f64 operations, or approximated using a Taylor series and f32 operations.\n///\n/// The idea behind the approximation, is to map from st coordinates relative to the view, to world positions relative to the view.\n/// Therefore, we identify a origin tile with sufficiently high lod (origin LOD), that serves as a reference, to which we can compute our relative coordinate using partly integer math.\n#[derive(Copy, Clone, Debug, Default, ShaderType)]\npub(crate) struct SideParameter {\n    /// The tile index of the origin tile projected to this side.\n    pub(crate) origin_xy: IVec2,\n    /// The offset between the view st coordinate and the origin st coordinate.\n    /// This can be used to translate from st coordinates relative to the origin tile to st coordinates relative to the view coordinate in the shader.\n    pub(crate) origin_uv: Vec2,\n    /// The constant coefficient of the series.\n    /// Describes the offset between the location vertically under view and the view position.\n    pub(crate) c: Vec3,\n    /// The linear coefficient of the series with respect to s.\n    pub(crate) c_s: Vec3,\n    /// The linear coefficient of the series with respect to t.\n    pub(crate) c_t: Vec3,\n    /// The quadratic coefficient of the series with respect to s and s.\n    /// This value is pre-multiplied with 0.5.\n    pub(crate) c_ss: Vec3,\n    /// The quadratic coefficient of the series with respect to s and t.\n    pub(crate) c_st: Vec3,\n    /// The quadratic coefficient of the series with respect to t and t.\n    /// This value is pre-multiplied with 0.5.\n    pub(crate) c_tt: Vec3,\n}\n\n#[derive(Clone, Debug, Default, ShaderType)]\npub struct TerrainModelApproximation {\n    /// The reference tile, which is used to accurately determine the relative st coordinate in the shader.\n    /// The tile under the view (with the origin lod) is the origin for the Taylor series.\n    pub(crate) origin_lod: u32,\n    pub(crate) approximate_height: f32,\n    /// The parameters of the six cube sphere faces.\n    pub(crate) sides: [SideParameter; 6],\n}\n\nimpl TerrainModelApproximation {\n    /// Computes the view parameters based on the it's world position.\n    pub(crate) fn compute(\n        tile_tree: &TileTree,\n        tile_atlas: &TileAtlas,\n    ) -> TerrainModelApproximation {\n        let origin_count = TileCoordinate::count(tile_tree.origin_lod) as f64;\n\n        // Coordinate of the location vertically below the view.\n        let view_coordinate =\n            Coordinate::from_world_position(tile_tree.view_world_position, &tile_atlas.model);\n\n        // We want to approximate the position relative to the view using a second order Taylor series.\n        // For that, we have to calculate the Taylor coefficients for each cube side separately.\n        // As the basis, we use the view coordinate projected to the specific side.\n        // Then we calculate the relative position vector and derivatives at the view coordinate.\n\n        // u(s)=(2s-1)/sqrt(1-4cs(s-1))\n        // v(t)=(2t-1)/sqrt(1-4ct(t-1))\n        // l(s,t)=sqrt(1+u(s)^2+v(t)^2)\n        // a(s,t)=1/l(s,t)\n        // b(s,t)=u(s)/l(s,t)\n        // c(s,t)=v(t)/l(s,t)\n\n        let mut sides = [SideParameter::default(); 6];\n\n        for (side, &sm) in SIDE_MATRICES.iter().enumerate() {\n            let view_coordinate = view_coordinate.project_to_side(side as u32, &tile_atlas.model);\n            let view_xy = (view_coordinate.uv * origin_count).as_ivec2();\n            let view_uv = (view_coordinate.uv * origin_count).fract().as_vec2();\n\n            let DVec2 { x: s, y: t } = view_coordinate.uv;\n\n            let u_denom = (1.0 - 4.0 * C_SQR * s * (s - 1.0)).sqrt();\n            let u = (2.0 * s - 1.0) / u_denom;\n            let u_ds = 2.0 * (C_SQR + 1.0) / u_denom.powi(3);\n            let u_dss = 12.0 * C_SQR * (C_SQR + 1.0) * (2.0 * s - 1.0) / u_denom.powi(5);\n\n            let v_denom = (1.0 - 4.0 * C_SQR * t * (t - 1.0)).sqrt();\n            let v = (2.0 * t - 1.0) / v_denom;\n            let v_dt = 2.0 * (C_SQR + 1.0) / v_denom.powi(3);\n            let v_dtt = 12.0 * C_SQR * (C_SQR + 1.0) * (2.0 * t - 1.0) / v_denom.powi(5);\n\n            let l = (1.0 + u * u + v * v).sqrt();\n            let l_ds = u * u_ds / l;\n            let l_dt = v * v_dt / l;\n            let l_dss = (u * u_dss * l * l + (v * v + 1.0) * u_ds * u_ds) / l.powi(3);\n            let l_dst = -(u * v * u_ds * v_dt) / l.powi(3);\n            let l_dtt = (v * v_dtt * l * l + (u * u + 1.0) * v_dt * v_dt) / l.powi(3);\n\n            let a = 1.0;\n            let a_ds = -l_ds;\n            let a_dt = -l_dt;\n            let a_dss = 2.0 * l_ds * l_ds - l * l_dss;\n            let a_dst = 2.0 * l_ds * l_dt - l * l_dst;\n            let a_dtt = 2.0 * l_dt * l_dt - l * l_dtt;\n\n            let b = u;\n            let b_ds = -u * l_ds + l * u_ds;\n            let b_dt = -u * l_dt;\n            let b_dss = 2.0 * u * l_ds * l_ds - l * (2.0 * u_ds * l_ds + u * l_dss) + u_dss * l * l;\n            let b_dst = 2.0 * u * l_ds * l_dt - l * (u_ds * l_dt + u * l_dst);\n            let b_dtt = 2.0 * u * l_dt * l_dt - l * u * l_dtt;\n\n            let c = v;\n            let c_ds = -v * l_ds;\n            let c_dt = -v * l_dt + l * v_dt;\n            let c_dss = 2.0 * v * l_ds * l_ds - l * v * l_dss;\n            let c_dst = 2.0 * v * l_ds * l_dt - l * (v_dt * l_ds + v * l_dst);\n            let c_dtt = 2.0 * v * l_dt * l_dt - l * (2.0 * v_dt * l_dt + v * l_dtt) + v_dtt * l * l;\n\n            // The model matrix is used to transform the local position and directions into the corresponding world position and directions.\n            // p is transformed as a point, takes the model position into account\n            // the other coefficients are transformed as vectors, discards the translation\n            let m = tile_atlas.model.world_from_local;\n            let p = m.transform_point3(sm * DVec3::new(a, b, c) / l);\n            let p_ds = m.transform_vector3(sm * DVec3::new(a_ds, b_ds, c_ds) / l.powi(2));\n            let p_dt = m.transform_vector3(sm * DVec3::new(a_dt, b_dt, c_dt) / l.powi(2));\n            let p_dss = m.transform_vector3(sm * DVec3::new(a_dss, b_dss, c_dss) / l.powi(3));\n            let p_dst = m.transform_vector3(sm * DVec3::new(a_dst, b_dst, c_dst) / l.powi(3));\n            let p_dtt = m.transform_vector3(sm * DVec3::new(a_dtt, b_dtt, c_dtt) / l.powi(3));\n\n            sides[side] = SideParameter {\n                origin_xy: view_xy,\n                origin_uv: view_uv,\n                c: (p - tile_tree.view_world_position).as_vec3(),\n                c_s: p_ds.as_vec3(),\n                c_t: p_dt.as_vec3(),\n                c_ss: (p_dss / 2.0).as_vec3(),\n                c_st: p_dst.as_vec3(),\n                c_tt: (p_dtt / 2.0).as_vec3(),\n            };\n        }\n\n        TerrainModelApproximation {\n            origin_lod: tile_tree.origin_lod,\n            approximate_height: tile_tree.approximate_height,\n            sides,\n        }\n    }\n}\n\npub fn generate_terrain_model_approximation(\n    tile_trees: Res<TerrainViewComponents<TileTree>>,\n    tile_atlases: Query<&TileAtlas>,\n    mut terrain_model_approximations: ResMut<TerrainViewComponents<TerrainModelApproximation>>,\n) {\n    for (&(terrain, view), tile_tree) in tile_trees.iter() {\n        let tile_atlas = tile_atlases.get(terrain).unwrap();\n\n        terrain_model_approximations.insert(\n            (terrain, view),\n            TerrainModelApproximation::compute(tile_tree, tile_atlas),\n        );\n    }\n}\n"
  },
  {
    "path": "src/plugin.rs",
    "content": "use crate::{\n    math::{generate_terrain_model_approximation, TerrainModelApproximation},\n    render::{\n        culling_bind_group::CullingBindGroup,\n        terrain_bind_group::TerrainData,\n        terrain_view_bind_group::TerrainViewData,\n        tiling_prepass::{\n            queue_tiling_prepass, TilingPrepassItem, TilingPrepassLabel, TilingPrepassNode,\n            TilingPrepassPipelines,\n        },\n    },\n    shaders::{load_terrain_shaders, InternalShaders},\n    terrain::TerrainComponents,\n    terrain_data::{\n        gpu_tile_atlas::GpuTileAtlas, gpu_tile_tree::GpuTileTree, tile_atlas::TileAtlas,\n        tile_tree::TileTree,\n    },\n    terrain_view::TerrainViewComponents,\n};\nuse bevy::{\n    prelude::*,\n    render::{\n        graph::CameraDriverLabel,\n        render_graph::RenderGraph,\n        render_resource::*,\n        view::{check_visibility, VisibilitySystems},\n        Render, RenderApp, RenderSet,\n    },\n};\n\n/// The plugin for the terrain renderer.\npub struct TerrainPlugin;\n\nimpl Plugin for TerrainPlugin {\n    fn build(&self, app: &mut App) {\n        #[cfg(feature = \"high_precision\")]\n        app.add_plugins(crate::big_space::BigSpacePlugin::default());\n\n        app.init_resource::<InternalShaders>()\n            .init_resource::<TerrainViewComponents<TileTree>>()\n            .init_resource::<TerrainViewComponents<TerrainModelApproximation>>()\n            .add_systems(\n                PostUpdate,\n                check_visibility::<With<TileAtlas>>.in_set(VisibilitySystems::CheckVisibility),\n            )\n            .add_systems(\n                Last,\n                (\n                    TileTree::compute_requests,\n                    TileAtlas::update,\n                    TileTree::adjust_to_tile_atlas,\n                    TileTree::approximate_height,\n                    generate_terrain_model_approximation,\n                )\n                    .chain(),\n            );\n\n        app.sub_app_mut(RenderApp)\n            .init_resource::<TerrainComponents<GpuTileAtlas>>()\n            .init_resource::<TerrainComponents<TerrainData>>()\n            .init_resource::<TerrainViewComponents<GpuTileTree>>()\n            .init_resource::<TerrainViewComponents<TerrainViewData>>()\n            .init_resource::<TerrainViewComponents<CullingBindGroup>>()\n            .init_resource::<TerrainViewComponents<TilingPrepassItem>>()\n            .add_systems(\n                ExtractSchedule,\n                (\n                    GpuTileAtlas::initialize,\n                    GpuTileAtlas::extract.after(GpuTileAtlas::initialize),\n                    GpuTileTree::initialize,\n                    GpuTileTree::extract.after(GpuTileTree::initialize),\n                    TerrainData::initialize.after(GpuTileAtlas::initialize),\n                    TerrainData::extract.after(TerrainData::initialize),\n                    TerrainViewData::initialize.after(GpuTileTree::initialize),\n                    TerrainViewData::extract.after(TerrainViewData::initialize),\n                ),\n            )\n            .add_systems(\n                Render,\n                (\n                    (\n                        GpuTileTree::prepare,\n                        GpuTileAtlas::prepare,\n                        TerrainData::prepare,\n                        TerrainViewData::prepare,\n                        CullingBindGroup::prepare,\n                    )\n                        .in_set(RenderSet::Prepare),\n                    queue_tiling_prepass.in_set(RenderSet::Queue),\n                    GpuTileAtlas::cleanup\n                        .before(World::clear_entities)\n                        .in_set(RenderSet::Cleanup),\n                ),\n            );\n    }\n\n    fn finish(&self, app: &mut App) {\n        load_terrain_shaders(app);\n\n        let render_app = app\n            .sub_app_mut(RenderApp)\n            .init_resource::<TilingPrepassPipelines>()\n            .init_resource::<SpecializedComputePipelines<TilingPrepassPipelines>>();\n\n        let mut render_graph = render_app.world_mut().resource_mut::<RenderGraph>();\n        render_graph.add_node(TilingPrepassLabel, TilingPrepassNode);\n        render_graph.add_node_edge(TilingPrepassLabel, CameraDriverLabel);\n    }\n}\n"
  },
  {
    "path": "src/preprocess/gpu_preprocessor.rs",
    "content": "use crate::{\n    preprocess::{\n        preprocessor::{PreprocessTask, PreprocessTaskType, Preprocessor},\n        TerrainPreprocessItem,\n    },\n    terrain::TerrainComponents,\n    terrain_data::{\n        gpu_tile_atlas::GpuTileAtlas,\n        tile_atlas::{AtlasTile, TileAtlas},\n    },\n    util::StaticBuffer,\n};\nuse bevy::{\n    prelude::*,\n    render::{\n        render_asset::RenderAssets,\n        render_resource::{binding_types::*, *},\n        renderer::RenderDevice,\n        texture::GpuImage,\n        Extract,\n    },\n};\nuse std::collections::VecDeque;\n\npub(crate) struct ProcessingTask {\n    pub(crate) task: PreprocessTask,\n    pub(crate) bind_group: Option<BindGroup>,\n}\n\n#[derive(Clone, Debug, ShaderType)]\npub(crate) struct SplitData {\n    tile: AtlasTile,\n    top_left: Vec2,\n    bottom_right: Vec2,\n    tile_index: u32,\n}\n\n#[derive(Clone, Debug, ShaderType)]\nstruct StitchData {\n    tile: AtlasTile,\n    neighbour_tiles: [AtlasTile; 8],\n    tile_index: u32,\n}\n\n#[derive(Clone, Debug, ShaderType)]\nstruct DownsampleData {\n    tile: AtlasTile,\n    child_tiles: [AtlasTile; 4],\n    tile_index: u32,\n}\n\npub(crate) fn create_split_layout(device: &RenderDevice) -> BindGroupLayout {\n    device.create_bind_group_layout(\n        None,\n        &BindGroupLayoutEntries::sequential(\n            ShaderStages::COMPUTE,\n            (\n                uniform_buffer::<SplitData>(false), // split_tile_data\n                texture_2d(TextureSampleType::Float { filterable: true }), // tile\n                sampler(SamplerBindingType::Filtering), // tile_sampler\n            ),\n        ),\n    )\n}\n\npub(crate) fn create_stitch_layout(device: &RenderDevice) -> BindGroupLayout {\n    device.create_bind_group_layout(\n        None,\n        &BindGroupLayoutEntries::single(ShaderStages::COMPUTE, uniform_buffer::<StitchData>(false)),\n    )\n}\n\npub(crate) fn create_downsample_layout(device: &RenderDevice) -> BindGroupLayout {\n    device.create_bind_group_layout(\n        None,\n        &BindGroupLayoutEntries::single(\n            ShaderStages::COMPUTE,\n            uniform_buffer::<DownsampleData>(false),\n        ),\n    )\n}\n\npub(crate) struct GpuPreprocessor {\n    pub(crate) ready_tasks: VecDeque<PreprocessTask>,\n    pub(crate) processing_tasks: Vec<ProcessingTask>,\n}\n\nimpl GpuPreprocessor {\n    pub(crate) fn new() -> Self {\n        Self {\n            ready_tasks: default(),\n            processing_tasks: vec![],\n        }\n    }\n\n    pub(crate) fn initialize(\n        mut gpu_preprocessors: ResMut<TerrainComponents<GpuPreprocessor>>,\n        terrains: Extract<Query<Entity, Added<TileAtlas>>>,\n    ) {\n        for terrain in terrains.iter() {\n            gpu_preprocessors.insert(terrain, GpuPreprocessor::new());\n        }\n    }\n\n    pub(crate) fn extract(\n        mut gpu_preprocessors: ResMut<TerrainComponents<GpuPreprocessor>>,\n        preprocessors: Extract<Query<(Entity, &Preprocessor)>>,\n    ) {\n        for (terrain, preprocessor) in preprocessors.iter() {\n            let gpu_preprocessor = gpu_preprocessors.get_mut(&terrain).unwrap();\n\n            // Todo: mem take using &mut world?\n            gpu_preprocessor\n                .ready_tasks\n                .extend(preprocessor.ready_tasks.clone().into_iter());\n        }\n    }\n\n    #[allow(clippy::too_many_arguments)]\n    pub(crate) fn prepare(\n        device: Res<RenderDevice>,\n        images: Res<RenderAssets<GpuImage>>,\n        preprocess_items: Res<TerrainComponents<TerrainPreprocessItem>>,\n        mut gpu_preprocessors: ResMut<TerrainComponents<GpuPreprocessor>>,\n        mut gpu_tile_atlases: ResMut<TerrainComponents<GpuTileAtlas>>,\n        pipeline_cache: Res<PipelineCache>,\n    ) {\n        for (&terrain, item) in preprocess_items.iter() {\n            if !item.is_loaded(&pipeline_cache) {\n                continue;\n            }\n\n            let gpu_preprocessor = gpu_preprocessors.get_mut(&terrain).unwrap();\n            let gpu_tile_atlas = gpu_tile_atlases.get_mut(&terrain).unwrap();\n\n            gpu_preprocessor.processing_tasks.clear();\n\n            while !gpu_preprocessor.ready_tasks.is_empty() {\n                let task = gpu_preprocessor.ready_tasks.back().unwrap();\n                let attachment =\n                    &mut gpu_tile_atlas.attachments[task.tile.attachment_index as usize];\n\n                if let Some(section_index) = attachment.reserve_write_slot(task.tile) {\n                    let task = gpu_preprocessor.ready_tasks.pop_back().unwrap();\n\n                    let bind_group = match &task.task_type {\n                        PreprocessTaskType::Split {\n                            tile_data,\n                            top_left,\n                            bottom_right,\n                        } => {\n                            let tile_data = images.get(tile_data).unwrap();\n\n                            let split_buffer = StaticBuffer::create(\n                                format!(\"{}_split_buffer\", attachment.name).as_str(),\n                                &device,\n                                &SplitData {\n                                    tile: task.tile.into(),\n                                    top_left: *top_left,\n                                    bottom_right: *bottom_right,\n                                    tile_index: section_index,\n                                },\n                                BufferUsages::UNIFORM,\n                            );\n\n                            Some(device.create_bind_group(\n                                format!(\"{}_split_bind_group\", attachment.name).as_str(),\n                                &create_split_layout(&device),\n                                &BindGroupEntries::sequential((\n                                    &split_buffer,\n                                    &tile_data.texture_view,\n                                    &tile_data.sampler,\n                                )),\n                            ))\n                        }\n                        PreprocessTaskType::Stitch { neighbour_tiles } => {\n                            let stitch_buffer = StaticBuffer::create(\n                                format!(\"{}_stitch_buffer\", attachment.name).as_str(),\n                                &device,\n                                &StitchData {\n                                    tile: task.tile.into(),\n                                    neighbour_tiles: *neighbour_tiles,\n                                    tile_index: section_index,\n                                },\n                                BufferUsages::UNIFORM,\n                            );\n\n                            Some(device.create_bind_group(\n                                format!(\"{}_stitch_bind_group\", attachment.name).as_str(),\n                                &create_stitch_layout(&device),\n                                &BindGroupEntries::single(&stitch_buffer),\n                            ))\n                        }\n                        PreprocessTaskType::Downsample { child_tiles } => {\n                            let downsample_buffer = StaticBuffer::create(\n                                format!(\"{}_downsample_buffer\", attachment.name).as_str(),\n                                &device,\n                                &DownsampleData {\n                                    tile: task.tile.into(),\n                                    child_tiles: *child_tiles,\n                                    tile_index: section_index,\n                                },\n                                BufferUsages::UNIFORM,\n                            );\n\n                            Some(device.create_bind_group(\n                                format!(\"{}_downsample_bind_group\", attachment.name).as_str(),\n                                &create_downsample_layout(&device),\n                                &BindGroupEntries::single(&downsample_buffer),\n                            ))\n                        }\n                        _ => break,\n                    };\n\n                    gpu_preprocessor\n                        .processing_tasks\n                        .push(ProcessingTask { task, bind_group });\n                } else {\n                    break;\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "src/preprocess/mod.rs",
    "content": "use crate::{\n    formats::tiff::TiffLoader,\n    preprocess::{\n        gpu_preprocessor::{\n            create_downsample_layout, create_split_layout, create_stitch_layout, GpuPreprocessor,\n        },\n        preprocessor::{preprocessor_load_tile, select_ready_tasks, PreprocessTaskType},\n    },\n    shaders::{load_preprocess_shaders, DOWNSAMPLE_SHADER, SPLIT_SHADER, STITCH_SHADER},\n    terrain::TerrainComponents,\n    terrain_data::gpu_tile_atlas::{create_attachment_layout, GpuTileAtlas},\n};\nuse bevy::{\n    prelude::*,\n    render::{\n        graph::CameraDriverLabel,\n        render_graph::{self, RenderGraph, RenderLabel},\n        render_resource::*,\n        renderer::{RenderContext, RenderDevice},\n        Render, RenderApp, RenderSet,\n    },\n};\n\npub mod gpu_preprocessor;\npub mod preprocessor;\n\n#[derive(Debug, Hash, PartialEq, Eq, Clone, RenderLabel)]\npub struct TerrainPreprocessLabel;\n\nbitflags::bitflags! {\n    #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]\n    #[repr(transparent)]\n    pub struct TerrainPreprocessPipelineKey: u32 {\n        const NONE       = 1 << 0;\n        const SPLIT      = 1 << 1;\n        const STITCH     = 1 << 2;\n        const DOWNSAMPLE = 1 << 3;\n    }\n}\n\npub(crate) struct TerrainPreprocessItem {\n    split_pipeline: CachedComputePipelineId,\n    stitch_pipeline: CachedComputePipelineId,\n    downsample_pipeline: CachedComputePipelineId,\n}\n\nimpl TerrainPreprocessItem {\n    fn pipelines<'a>(\n        &'a self,\n        pipeline_cache: &'a PipelineCache,\n    ) -> Option<(&ComputePipeline, &ComputePipeline, &ComputePipeline)> {\n        Some((\n            pipeline_cache.get_compute_pipeline(self.split_pipeline)?,\n            pipeline_cache.get_compute_pipeline(self.stitch_pipeline)?,\n            pipeline_cache.get_compute_pipeline(self.downsample_pipeline)?,\n        ))\n    }\n\n    pub(crate) fn is_loaded(&self, pipeline_cache: &PipelineCache) -> bool {\n        self.pipelines(pipeline_cache).is_some()\n    }\n}\n\n#[derive(Resource)]\npub struct TerrainPreprocessPipelines {\n    attachment_layout: BindGroupLayout,\n    split_layout: BindGroupLayout,\n    stitch_layout: BindGroupLayout,\n    downsample_layout: BindGroupLayout,\n    split_shader: Handle<Shader>,\n    stitch_shader: Handle<Shader>,\n    downsample_shader: Handle<Shader>,\n}\n\nimpl FromWorld for TerrainPreprocessPipelines {\n    fn from_world(world: &mut World) -> Self {\n        let device = world.resource::<RenderDevice>();\n        let asset_server = world.resource::<AssetServer>();\n\n        let attachment_layout = create_attachment_layout(device);\n        let split_layout = create_split_layout(device);\n        let stitch_layout = create_stitch_layout(device);\n        let downsample_layout = create_downsample_layout(device);\n\n        let split_shader = asset_server.load(SPLIT_SHADER);\n        let stitch_shader = asset_server.load(STITCH_SHADER);\n        let downsample_shader = asset_server.load(DOWNSAMPLE_SHADER);\n\n        Self {\n            attachment_layout,\n            split_layout,\n            stitch_layout,\n            downsample_layout,\n            split_shader,\n            stitch_shader,\n            downsample_shader,\n        }\n    }\n}\n\nimpl SpecializedComputePipeline for TerrainPreprocessPipelines {\n    type Key = TerrainPreprocessPipelineKey;\n\n    fn specialize(&self, key: Self::Key) -> ComputePipelineDescriptor {\n        let mut layout = default();\n        let mut shader = default();\n        let mut entry_point = default();\n\n        let shader_defs = vec![];\n\n        if key.contains(TerrainPreprocessPipelineKey::SPLIT) {\n            layout = vec![self.attachment_layout.clone(), self.split_layout.clone()];\n            shader = self.split_shader.clone();\n            entry_point = \"split\".into();\n        }\n        if key.contains(TerrainPreprocessPipelineKey::STITCH) {\n            layout = vec![self.attachment_layout.clone(), self.stitch_layout.clone()];\n            shader = self.stitch_shader.clone();\n            entry_point = \"stitch\".into();\n        }\n        if key.contains(TerrainPreprocessPipelineKey::DOWNSAMPLE) {\n            layout = vec![\n                self.attachment_layout.clone(),\n                self.downsample_layout.clone(),\n            ];\n            shader = self.downsample_shader.clone();\n            entry_point = \"downsample\".into();\n        }\n\n        ComputePipelineDescriptor {\n            label: Some(\"terrain_preprocess_pipeline\".into()),\n            layout,\n            push_constant_ranges: default(),\n            shader,\n            shader_defs,\n            entry_point,\n        }\n    }\n}\n\npub struct TerrainPreprocessNode;\n\nimpl render_graph::Node for TerrainPreprocessNode {\n    fn run<'w>(\n        &self,\n        _graph: &mut render_graph::RenderGraphContext,\n        context: &mut RenderContext<'w>,\n        world: &'w World,\n    ) -> Result<(), render_graph::NodeRunError> {\n        let preprocess_items = world.resource::<TerrainComponents<TerrainPreprocessItem>>();\n        let pipeline_cache = world.resource::<PipelineCache>();\n        let preprocess_data = world.resource::<TerrainComponents<GpuPreprocessor>>();\n        let gpu_tile_atlases = world.resource::<TerrainComponents<GpuTileAtlas>>();\n\n        context.add_command_buffer_generation_task(move |device| {\n            let mut command_encoder =\n                device.create_command_encoder(&CommandEncoderDescriptor::default());\n\n            for (&terrain, preprocess_item) in preprocess_items.iter() {\n                let Some((split_pipeline, stitch_pipeline, downsample_pipeline)) =\n                    preprocess_item.pipelines(pipeline_cache)\n                else {\n                    continue;\n                };\n\n                let preprocess_data = preprocess_data.get(&terrain).unwrap();\n                let gpu_tile_atlas = gpu_tile_atlases.get(&terrain).unwrap();\n\n                for attachment in &gpu_tile_atlas.attachments {\n                    attachment.copy_tiles_to_write_section(&mut command_encoder);\n                }\n\n                if !preprocess_data.processing_tasks.is_empty() {\n                    let mut compute_pass =\n                        command_encoder.begin_compute_pass(&ComputePassDescriptor::default());\n\n                    for task in &preprocess_data.processing_tasks {\n                        let attachment =\n                            &gpu_tile_atlas.attachments[task.task.tile.attachment_index as usize];\n\n                        let pipeline = match task.task.task_type {\n                            PreprocessTaskType::Split { .. } => split_pipeline,\n                            PreprocessTaskType::Stitch { .. } => stitch_pipeline,\n                            PreprocessTaskType::Downsample { .. } => downsample_pipeline,\n                            _ => continue,\n                        };\n\n                        compute_pass.set_pipeline(pipeline);\n                        compute_pass.set_bind_group(0, &attachment.bind_group, &[]);\n                        compute_pass.set_bind_group(1, task.bind_group.as_ref().unwrap(), &[]);\n                        compute_pass.dispatch_workgroups(\n                            attachment.buffer_info.workgroup_count.x,\n                            attachment.buffer_info.workgroup_count.y,\n                            attachment.buffer_info.workgroup_count.z,\n                        );\n                    }\n                }\n\n                for attachment in &gpu_tile_atlas.attachments {\n                    attachment.copy_tiles_from_write_section(&mut command_encoder);\n\n                    attachment.download_tiles(&mut command_encoder);\n\n                    // if !attachment.atlas_write_slots.is_empty() {\n                    //     println!(\n                    //         \"Ran preprocessing pipeline with {} tiles.\",\n                    //         attachment.atlas_write_slots.len()\n                    //     )\n                    // }\n                }\n            }\n\n            command_encoder.finish()\n        });\n\n        Ok(())\n    }\n}\n\npub(crate) fn queue_terrain_preprocess(\n    pipeline_cache: Res<PipelineCache>,\n    preprocess_pipelines: ResMut<TerrainPreprocessPipelines>,\n    mut pipelines: ResMut<SpecializedComputePipelines<TerrainPreprocessPipelines>>,\n    mut preprocess_items: ResMut<TerrainComponents<TerrainPreprocessItem>>,\n    gpu_tile_atlas: Res<TerrainComponents<GpuTileAtlas>>,\n) {\n    for &terrain in gpu_tile_atlas.keys() {\n        let split_pipeline = pipelines.specialize(\n            &pipeline_cache,\n            &preprocess_pipelines,\n            TerrainPreprocessPipelineKey::SPLIT,\n        );\n        let stitch_pipeline = pipelines.specialize(\n            &pipeline_cache,\n            &preprocess_pipelines,\n            TerrainPreprocessPipelineKey::STITCH,\n        );\n        let downsample_pipeline = pipelines.specialize(\n            &pipeline_cache,\n            &preprocess_pipelines,\n            TerrainPreprocessPipelineKey::DOWNSAMPLE,\n        );\n\n        preprocess_items.insert(\n            terrain,\n            TerrainPreprocessItem {\n                split_pipeline,\n                stitch_pipeline,\n                downsample_pipeline,\n            },\n        );\n    }\n}\n\npub struct TerrainPreprocessPlugin;\n\nimpl Plugin for TerrainPreprocessPlugin {\n    fn build(&self, app: &mut App) {\n        app.init_asset_loader::<TiffLoader>()\n            .add_systems(Update, (select_ready_tasks, preprocessor_load_tile));\n\n        app.sub_app_mut(RenderApp)\n            .init_resource::<TerrainComponents<GpuPreprocessor>>()\n            .init_resource::<TerrainComponents<TerrainPreprocessItem>>()\n            .add_systems(\n                ExtractSchedule,\n                (\n                    GpuPreprocessor::initialize,\n                    GpuPreprocessor::extract.after(GpuPreprocessor::initialize),\n                ),\n            )\n            .add_systems(\n                Render,\n                (\n                    queue_terrain_preprocess.in_set(RenderSet::Queue),\n                    GpuPreprocessor::prepare\n                        .in_set(RenderSet::PrepareAssets)\n                        .before(GpuTileAtlas::prepare),\n                ),\n            );\n    }\n\n    fn finish(&self, app: &mut App) {\n        load_preprocess_shaders(app);\n\n        let render_app = app\n            .sub_app_mut(RenderApp)\n            .init_resource::<SpecializedComputePipelines<TerrainPreprocessPipelines>>()\n            .init_resource::<TerrainPreprocessPipelines>();\n\n        let mut render_graph = render_app.world_mut().resource_mut::<RenderGraph>();\n        render_graph.add_node(TerrainPreprocessLabel, TerrainPreprocessNode);\n        render_graph.add_node_edge(TerrainPreprocessLabel, CameraDriverLabel);\n    }\n}\n"
  },
  {
    "path": "src/preprocess/preprocessor.rs",
    "content": "use crate::{\n    math::TileCoordinate,\n    terrain_data::{\n        tile_atlas::{AtlasTile, AtlasTileAttachment, TileAtlas},\n        AttachmentFormat,\n    },\n    util::CollectArray,\n};\nuse bevy::{prelude::*, render::texture::ImageSampler};\nuse itertools::{iproduct, Itertools};\nuse std::{\n    collections::VecDeque,\n    fs,\n    ops::{DerefMut, Range},\n    time::Instant,\n};\n\npub fn reset_directory(directory: &str) {\n    let _ = fs::remove_file(format!(\"{directory}/../../config.tc\"));\n    let _ = fs::remove_dir_all(directory);\n    fs::create_dir_all(directory).unwrap();\n}\n\npub(crate) struct LoadingTile {\n    id: AssetId<Image>,\n    format: AttachmentFormat,\n}\n\npub struct SphericalDataset {\n    pub attachment_index: u32,\n    pub paths: Vec<String>,\n    pub lod_range: Range<u32>,\n}\n\npub struct PreprocessDataset {\n    pub attachment_index: u32,\n    pub path: String,\n    pub side: u32,\n    pub top_left: Vec2,\n    pub bottom_right: Vec2,\n    pub lod_range: Range<u32>,\n}\n\nimpl Default for PreprocessDataset {\n    fn default() -> Self {\n        Self {\n            attachment_index: 0,\n            path: \"\".to_string(),\n            side: 0,\n            top_left: Vec2::splat(0.0),\n            bottom_right: Vec2::splat(1.0),\n            lod_range: 0..1,\n        }\n    }\n}\n\nimpl PreprocessDataset {\n    fn overlapping_tiles(&self, lod: u32) -> impl Iterator<Item = TileCoordinate> + '_ {\n        let tile_count = TileCoordinate::count(lod);\n\n        let lower = (self.top_left * tile_count as f32).as_uvec2();\n        let upper = (self.bottom_right * tile_count as f32).ceil().as_uvec2();\n\n        iproduct!(lower.x..upper.x, lower.y..upper.y)\n            .map(move |(x, y)| TileCoordinate::new(self.side, lod, x, y))\n    }\n}\n\n#[derive(Clone)]\npub(crate) enum PreprocessTaskType {\n    Split {\n        tile_data: Handle<Image>,\n        top_left: Vec2,\n        bottom_right: Vec2,\n    },\n    Stitch {\n        neighbour_tiles: [AtlasTile; 8],\n    },\n    Downsample {\n        child_tiles: [AtlasTile; 4],\n    },\n    Save,\n    Barrier,\n}\n\n// Todo: store tile_coordinate, task_type, tile_dependencies and tile dependencies\n// loop over all tasks, take n, allocate/load tile and its dependencies, process task\n#[derive(Clone)]\npub(crate) struct PreprocessTask {\n    pub(crate) tile: AtlasTileAttachment,\n    pub(crate) task_type: PreprocessTaskType,\n}\n\nimpl PreprocessTask {\n    fn is_ready(&self, asset_server: &AssetServer, tile_atlas: &TileAtlas) -> bool {\n        match &self.task_type {\n            PreprocessTaskType::Split { tile_data, .. } => {\n                asset_server.is_loaded_with_dependencies(tile_data)\n            }\n            PreprocessTaskType::Stitch { .. } => true,\n            PreprocessTaskType::Downsample { .. } => true,\n            PreprocessTaskType::Barrier => {\n                tile_atlas.state.download_slots == tile_atlas.state.max_download_slots\n            }\n            PreprocessTaskType::Save => true,\n        }\n    }\n\n    #[allow(dead_code)]\n    fn debug(&self) {\n        match &self.task_type {\n            PreprocessTaskType::Split { .. } => {\n                println!(\"Splitting tile: {}\", self.tile.coordinate)\n            }\n            PreprocessTaskType::Stitch { .. } => {\n                println!(\"Stitching tile: {}\", self.tile.coordinate)\n            }\n            PreprocessTaskType::Downsample { .. } => {\n                println!(\"Downsampling tile: {}\", self.tile.coordinate)\n            }\n            PreprocessTaskType::Save => {\n                println!(\"Started saving tile: {}\", self.tile.coordinate)\n            }\n            PreprocessTaskType::Barrier => println!(\"Barrier\"),\n        }\n    }\n\n    fn barrier() -> Self {\n        Self {\n            tile: default(),\n            task_type: PreprocessTaskType::Barrier,\n        }\n    }\n\n    fn save(\n        tile_coordinate: TileCoordinate,\n        tile_atlas: &mut TileAtlas,\n        dataset: &PreprocessDataset,\n    ) -> Self {\n        let tile = tile_atlas\n            .get_or_allocate_tile(tile_coordinate)\n            .attachment(dataset.attachment_index);\n\n        Self {\n            tile,\n            task_type: PreprocessTaskType::Save,\n        }\n    }\n\n    fn split(\n        tile_coordinate: TileCoordinate,\n        tile_atlas: &mut TileAtlas,\n        dataset: &PreprocessDataset,\n        tile_data: Handle<Image>,\n    ) -> Self {\n        let tile = tile_atlas\n            .get_or_allocate_tile(tile_coordinate)\n            .attachment(dataset.attachment_index);\n\n        Self {\n            tile,\n            task_type: PreprocessTaskType::Split {\n                tile_data,\n                top_left: dataset.top_left,\n                bottom_right: dataset.bottom_right,\n            },\n        }\n    }\n\n    fn stitch(\n        tile_coordinate: TileCoordinate,\n        tile_atlas: &mut TileAtlas,\n        dataset: &PreprocessDataset,\n    ) -> Self {\n        let tile = tile_atlas\n            .get_or_allocate_tile(tile_coordinate)\n            .attachment(dataset.attachment_index);\n\n        let neighbour_tiles = tile\n            .coordinate\n            .neighbours(tile_atlas.model.is_spherical())\n            .map(|coordinate| tile_atlas.get_tile(coordinate))\n            .collect_array();\n\n        Self {\n            tile,\n            task_type: PreprocessTaskType::Stitch { neighbour_tiles },\n        }\n    }\n\n    fn downsample(\n        tile_coordinate: TileCoordinate,\n        tile_atlas: &mut TileAtlas,\n        dataset: &PreprocessDataset,\n    ) -> Self {\n        let tile = tile_atlas\n            .get_or_allocate_tile(tile_coordinate)\n            .attachment(dataset.attachment_index);\n\n        let child_tiles = tile\n            .coordinate\n            .children()\n            .map(|coordinate| tile_atlas.get_tile(coordinate))\n            .collect_array();\n\n        Self {\n            tile,\n            task_type: PreprocessTaskType::Downsample { child_tiles },\n        }\n    }\n}\n\n#[derive(Component)]\npub struct Preprocessor {\n    pub(crate) loading_tiles: Vec<LoadingTile>,\n    pub(crate) task_queue: VecDeque<PreprocessTask>,\n    pub(crate) ready_tasks: Vec<PreprocessTask>,\n\n    pub(crate) start_time: Option<Instant>,\n    loaded: bool,\n}\n\nimpl Preprocessor {\n    pub fn new() -> Self {\n        Self {\n            loading_tiles: default(),\n            task_queue: default(),\n            ready_tasks: default(),\n            start_time: None,\n            loaded: false,\n        }\n    }\n\n    fn split_and_downsample(\n        &mut self,\n        dataset: &PreprocessDataset,\n        asset_server: &AssetServer,\n        tile_atlas: &mut TileAtlas,\n    ) {\n        let tile_handle = asset_server.load(&dataset.path);\n\n        self.loading_tiles.push(LoadingTile {\n            id: tile_handle.id(),\n            format: tile_atlas.attachments[dataset.attachment_index as usize].format,\n        });\n\n        let mut lods = dataset.lod_range.clone().rev();\n\n        for tile_coordinate in dataset.overlapping_tiles(lods.next().unwrap()) {\n            self.task_queue.push_back(PreprocessTask::split(\n                tile_coordinate,\n                tile_atlas,\n                dataset,\n                tile_handle.clone(),\n            ));\n        }\n\n        for lod in lods {\n            self.task_queue.push_back(PreprocessTask::barrier());\n\n            for tile_coordinate in dataset.overlapping_tiles(lod) {\n                self.task_queue.push_back(PreprocessTask::downsample(\n                    tile_coordinate,\n                    tile_atlas,\n                    dataset,\n                ));\n            }\n        }\n    }\n\n    fn stitch_and_save_layer(\n        &mut self,\n        dataset: &PreprocessDataset,\n        tile_atlas: &mut TileAtlas,\n        lod: u32,\n    ) {\n        for tile_coordinate in dataset.overlapping_tiles(lod) {\n            self.task_queue\n                .push_back(PreprocessTask::stitch(tile_coordinate, tile_atlas, dataset));\n        }\n\n        self.task_queue.push_back(PreprocessTask::barrier());\n\n        for tile_coordinate in dataset.overlapping_tiles(lod) {\n            self.task_queue\n                .push_back(PreprocessTask::save(tile_coordinate, tile_atlas, dataset));\n        }\n    }\n\n    pub fn clear_attachment(self, attachment_index: u32, tile_atlas: &mut TileAtlas) -> Self {\n        let attachment = &mut tile_atlas.attachments[attachment_index as usize];\n        tile_atlas.state.existing_tiles.clear();\n        reset_directory(&attachment.path);\n\n        self\n    }\n\n    pub fn preprocess_tile(\n        mut self,\n        dataset: PreprocessDataset,\n        asset_server: &AssetServer,\n        tile_atlas: &mut TileAtlas,\n    ) -> Self {\n        self.split_and_downsample(&dataset, asset_server, tile_atlas);\n        self.task_queue.push_back(PreprocessTask::barrier());\n\n        for lod in dataset.lod_range.clone() {\n            self.stitch_and_save_layer(&dataset, tile_atlas, lod);\n        }\n\n        self\n    }\n\n    pub fn preprocess_spherical(\n        mut self,\n        dataset: SphericalDataset,\n        asset_server: &AssetServer,\n        tile_atlas: &mut TileAtlas,\n    ) -> Self {\n        let side_datasets = (0..6)\n            .map(|side| PreprocessDataset {\n                attachment_index: dataset.attachment_index,\n                path: dataset.paths[side as usize].clone(),\n                side,\n                lod_range: dataset.lod_range.clone(),\n                ..default()\n            })\n            .collect_vec();\n\n        for dataset in &side_datasets {\n            self.split_and_downsample(dataset, asset_server, tile_atlas);\n        }\n\n        self.task_queue.push_back(PreprocessTask::barrier());\n\n        for lod in dataset.lod_range {\n            for dataset in &side_datasets {\n                self.stitch_and_save_layer(dataset, tile_atlas, lod);\n            }\n        }\n\n        self\n    }\n}\n\npub(crate) fn select_ready_tasks(\n    asset_server: Res<AssetServer>,\n    mut terrains: Query<(&mut Preprocessor, &mut TileAtlas)>,\n) {\n    for (mut preprocessor, mut tile_atlas) in terrains.iter_mut() {\n        let Preprocessor {\n            task_queue,\n            ready_tasks,\n            start_time,\n            ..\n        } = preprocessor.deref_mut();\n\n        if let Some(time) = start_time {\n            if task_queue.is_empty()\n                && tile_atlas.state.download_slots == tile_atlas.state.max_download_slots\n                && tile_atlas.state.save_slots == tile_atlas.state.max_save_slots\n            {\n                println!(\"Preprocessing took {:?}\", time.elapsed());\n\n                tile_atlas.save_tile_config();\n                // tile_atlas.state.existing_tiles.iter().for_each(|tile| {\n                //     println!(\"{tile}\");\n                // });\n\n                *start_time = None;\n            }\n        } else {\n            break;\n        }\n\n        ready_tasks.clear();\n\n        loop {\n            if (tile_atlas.state.download_slots > 0)\n                && task_queue\n                    .front()\n                    .map_or(false, |task| task.is_ready(&asset_server, &tile_atlas))\n            {\n                let task = task_queue.pop_front().unwrap();\n\n                // task.debug();\n\n                if matches!(task.task_type, PreprocessTaskType::Save) {\n                    tile_atlas.save(task.tile);\n                } else {\n                    ready_tasks.push(task);\n                    tile_atlas.state.download_slots -= 1;\n                }\n            } else {\n                break;\n            }\n        }\n    }\n}\n\npub(crate) fn preprocessor_load_tile(\n    mut preprocessors: Query<&mut Preprocessor>,\n    mut images: ResMut<Assets<Image>>,\n) {\n    for mut preprocessor in preprocessors.iter_mut() {\n        preprocessor.loading_tiles.retain_mut(|tile| {\n            if let Some(image) = images.get_mut(tile.id) {\n                image.texture_descriptor.format = tile.format.processing_format();\n                image.sampler = ImageSampler::linear();\n                false\n            } else {\n                true\n            }\n        });\n\n        if !preprocessor.loaded && preprocessor.loading_tiles.is_empty() {\n            println!(\"finished loading all tiles\");\n            preprocessor.loaded = true;\n            preprocessor.start_time = Some(Instant::now());\n        }\n    }\n}\n"
  },
  {
    "path": "src/render/culling_bind_group.rs",
    "content": "use crate::{\n    terrain_data::gpu_tile_tree::GpuTileTree, terrain_view::TerrainViewComponents,\n    util::StaticBuffer,\n};\nuse bevy::{\n    prelude::*,\n    render::{\n        render_resource::{binding_types::*, *},\n        renderer::RenderDevice,\n        view::ExtractedView,\n    },\n};\nuse std::ops::Deref;\n\npub(crate) fn create_culling_layout(device: &RenderDevice) -> BindGroupLayout {\n    device.create_bind_group_layout(\n        None,\n        &BindGroupLayoutEntries::single(\n            ShaderStages::COMPUTE,\n            uniform_buffer::<CullingUniform>(false), // culling data\n        ),\n    )\n}\n\npub fn planes(view_projection: &Mat4) -> [Vec4; 5] {\n    let row3 = view_projection.row(3);\n    let mut planes = [default(); 5];\n    for (i, plane) in planes.iter_mut().enumerate() {\n        let row = view_projection.row(i / 2);\n        *plane = if (i & 1) == 0 && i != 4 {\n            row3 + row\n        } else {\n            row3 - row\n        };\n    }\n\n    planes\n}\n\n#[derive(Default, ShaderType)]\npub struct CullingUniform {\n    world_position: Vec3,\n    view_proj: Mat4,\n    planes: [Vec4; 5],\n}\n\nimpl From<&ExtractedView> for CullingUniform {\n    fn from(view: &ExtractedView) -> Self {\n        Self {\n            world_position: view.world_from_view.translation(),\n            view_proj: view.world_from_view.compute_matrix().inverse(),\n            planes: default(),\n        }\n    }\n}\n\n#[derive(Component)]\npub struct CullingBindGroup(BindGroup);\n\nimpl Deref for CullingBindGroup {\n    type Target = BindGroup;\n\n    #[inline]\n    fn deref(&self) -> &Self::Target {\n        &self.0\n    }\n}\n\nimpl CullingBindGroup {\n    fn new(device: &RenderDevice, culling_uniform: CullingUniform) -> Self {\n        let culling_buffer = StaticBuffer::<CullingUniform>::create(\n            None,\n            device,\n            &culling_uniform,\n            BufferUsages::UNIFORM,\n        );\n\n        let bind_group = device.create_bind_group(\n            None,\n            &create_culling_layout(device),\n            &BindGroupEntries::single(&culling_buffer),\n        );\n\n        Self(bind_group)\n    }\n\n    pub(crate) fn prepare(\n        device: Res<RenderDevice>,\n        gpu_tile_trees: Res<TerrainViewComponents<GpuTileTree>>,\n        extracted_views: Query<&ExtractedView>,\n        mut culling_bind_groups: ResMut<TerrainViewComponents<CullingBindGroup>>,\n    ) {\n        for &(terrain, view) in gpu_tile_trees.keys() {\n            let extracted_view = extracted_views.get(view).unwrap();\n\n            culling_bind_groups.insert(\n                (terrain, view),\n                CullingBindGroup::new(&device, extracted_view.into()),\n            );\n        }\n    }\n}\n"
  },
  {
    "path": "src/render/mod.rs",
    "content": "//! This module contains the implementation of the Uniform Distance-Dependent Level of Detail (UDLOD).\n//!\n//! This algorithm is responsible for approximating the terrain geometry.\n//! Therefore tiny mesh tiles are refined in a tile_tree-like manner in a compute shader prepass for\n//! each view. Then they are drawn using a single draw indirect call and morphed together to form\n//! one continuous surface.\n\npub mod culling_bind_group;\npub mod terrain_bind_group;\npub mod terrain_material;\npub mod terrain_view_bind_group;\npub mod tiling_prepass;\n"
  },
  {
    "path": "src/render/terrain_bind_group.rs",
    "content": "use crate::{\n    prelude::TileAtlas, terrain::TerrainComponents, terrain_data::gpu_tile_atlas::GpuTileAtlas,\n    util::StaticBuffer,\n};\nuse bevy::{\n    ecs::{\n        query::ROQueryItem,\n        system::{lifetimeless::SRes, SystemParamItem},\n    },\n    pbr::{MeshTransforms, MeshUniform, PreviousGlobalTransform},\n    prelude::*,\n    render::{\n        render_phase::{PhaseItem, RenderCommand, RenderCommandResult, TrackedRenderPass},\n        render_resource::{binding_types::*, *},\n        renderer::{RenderDevice, RenderQueue},\n        texture::FallbackImage,\n        Extract,\n    },\n};\nuse itertools::Itertools;\nuse std::iter;\n\npub(crate) fn create_terrain_layout(device: &RenderDevice) -> BindGroupLayout {\n    device.create_bind_group_layout(\n        None,\n        &BindGroupLayoutEntries::sequential(\n            ShaderStages::all(),\n            (\n                storage_buffer_read_only::<MeshUniform>(false), // mesh\n                uniform_buffer::<TerrainConfigUniform>(false),  // terrain config\n                uniform_buffer::<AttachmentUniform>(false),\n                sampler(SamplerBindingType::Filtering), // atlas sampler\n                texture_2d_array(TextureSampleType::Float { filterable: true }), // attachment 1\n                texture_2d_array(TextureSampleType::Float { filterable: true }), // attachment 2\n                texture_2d_array(TextureSampleType::Float { filterable: true }), // attachment 3\n                texture_2d_array(TextureSampleType::Float { filterable: true }), // attachment 4\n                texture_2d_array(TextureSampleType::Float { filterable: true }), // attachment 5\n                texture_2d_array(TextureSampleType::Float { filterable: true }), // attachment 6\n                texture_2d_array(TextureSampleType::Float { filterable: true }), // attachment 7\n                texture_2d_array(TextureSampleType::Float { filterable: true }), // attachment 8\n            ),\n        ),\n    )\n}\n\n#[derive(Default, ShaderType)]\nstruct AttachmentConfig {\n    size: f32,\n    scale: f32,\n    offset: f32,\n    _padding: u32,\n}\n\n#[derive(Default, ShaderType)]\nstruct AttachmentUniform {\n    data: [AttachmentConfig; 8],\n}\n\nimpl AttachmentUniform {\n    fn new(tile_atlas: &GpuTileAtlas) -> Self {\n        let mut uniform = Self::default();\n\n        for (config, attachment) in iter::zip(&mut uniform.data, &tile_atlas.attachments) {\n            config.size = attachment.buffer_info.center_size as f32;\n            config.scale = attachment.buffer_info.center_size as f32\n                / attachment.buffer_info.texture_size as f32;\n            config.offset = attachment.buffer_info.border_size as f32\n                / attachment.buffer_info.texture_size as f32;\n        }\n\n        uniform\n    }\n}\n\n/// The terrain config data that is available in shaders.\n#[derive(Default, ShaderType)]\nstruct TerrainConfigUniform {\n    lod_count: u32,\n    min_height: f32,\n    max_height: f32,\n    scale: f32,\n}\n\nimpl TerrainConfigUniform {\n    fn from_tile_atlas(tile_atlas: &TileAtlas) -> Self {\n        Self {\n            lod_count: tile_atlas.lod_count,\n            min_height: tile_atlas.model.min_height,\n            max_height: tile_atlas.model.max_height,\n            scale: tile_atlas.model.scale() as f32,\n        }\n    }\n}\n\npub struct TerrainData {\n    mesh_buffer: StaticBuffer<MeshUniform>,\n    pub(crate) terrain_bind_group: BindGroup,\n}\n\nimpl TerrainData {\n    fn new(\n        device: &RenderDevice,\n        fallback_image: &FallbackImage,\n        tile_atlas: &TileAtlas,\n        gpu_tile_atlas: &GpuTileAtlas,\n    ) -> Self {\n        let mesh_buffer = StaticBuffer::empty_sized(\n            None,\n            device,\n            MeshUniform::SHADER_SIZE.get(),\n            BufferUsages::STORAGE | BufferUsages::COPY_DST,\n        );\n        let terrain_config_buffer = StaticBuffer::create(\n            None,\n            device,\n            &TerrainConfigUniform::from_tile_atlas(tile_atlas),\n            BufferUsages::UNIFORM,\n        );\n\n        let atlas_sampler = device.create_sampler(&SamplerDescriptor {\n            mag_filter: FilterMode::Linear,\n            min_filter: FilterMode::Linear,\n            mipmap_filter: FilterMode::Linear,\n            anisotropy_clamp: 16, // Todo: make this customisable\n            ..default()\n        });\n\n        let attachments = (0..8)\n            .map(|i| {\n                gpu_tile_atlas\n                    .attachments\n                    .get(i)\n                    .map_or(fallback_image.d2_array.texture_view.clone(), |attachment| {\n                        attachment.atlas_texture.create_view(&default())\n                    })\n            })\n            .collect_vec();\n\n        let attachment_uniform = AttachmentUniform::new(gpu_tile_atlas);\n        let attachment_buffer =\n            StaticBuffer::create(None, device, &attachment_uniform, BufferUsages::UNIFORM);\n\n        let terrain_bind_group = device.create_bind_group(\n            \"terrain_bind_group\",\n            &create_terrain_layout(device),\n            &BindGroupEntries::sequential((\n                &mesh_buffer,\n                &terrain_config_buffer,\n                &attachment_buffer,\n                &atlas_sampler,\n                &attachments[0],\n                &attachments[1],\n                &attachments[2],\n                &attachments[3],\n                &attachments[4],\n                &attachments[5],\n                &attachments[6],\n                &attachments[7],\n            )),\n        );\n\n        Self {\n            mesh_buffer,\n            terrain_bind_group,\n        }\n    }\n\n    pub(crate) fn initialize(\n        device: Res<RenderDevice>,\n        fallback_image: Res<FallbackImage>,\n        mut terrain_data: ResMut<TerrainComponents<TerrainData>>,\n        gpu_tile_atlases: Res<TerrainComponents<GpuTileAtlas>>,\n        tile_atlases: Extract<Query<(Entity, &TileAtlas), Added<TileAtlas>>>,\n    ) {\n        for (terrain, tile_atlas) in &tile_atlases {\n            let gpu_tile_atlas = gpu_tile_atlases.get(&terrain).unwrap();\n\n            terrain_data.insert(\n                terrain,\n                TerrainData::new(&device, &fallback_image, tile_atlas.into(), gpu_tile_atlas),\n            );\n        }\n    }\n\n    pub(crate) fn extract(\n        mut terrain_data: ResMut<TerrainComponents<TerrainData>>,\n        terrains: Extract<\n            Query<(Entity, &GlobalTransform, Option<&PreviousGlobalTransform>), With<TileAtlas>>,\n        >,\n    ) {\n        for (terrain, transform, previous_transform) in terrains.iter() {\n            let mesh_transforms = MeshTransforms {\n                world_from_local: (&transform.affine()).into(),\n                flags: 0,\n                previous_world_from_local: (&previous_transform\n                    .map(|t| t.0)\n                    .unwrap_or(transform.affine()))\n                    .into(),\n            };\n            let mesh_uniform = MeshUniform::new(&mesh_transforms, None);\n\n            let terrain_data = terrain_data.get_mut(&terrain).unwrap();\n            terrain_data.mesh_buffer.set_value(mesh_uniform);\n        }\n    }\n\n    pub(crate) fn prepare(\n        queue: Res<RenderQueue>,\n        mut terrain_data: ResMut<TerrainComponents<TerrainData>>,\n    ) {\n        for terrain_data in &mut terrain_data.values_mut() {\n            terrain_data.mesh_buffer.update(&queue);\n        }\n    }\n}\n\npub struct SetTerrainBindGroup<const I: usize>;\n\nimpl<const I: usize, P: PhaseItem> RenderCommand<P> for SetTerrainBindGroup<I> {\n    type Param = SRes<TerrainComponents<TerrainData>>;\n    type ViewQuery = ();\n    type ItemQuery = ();\n\n    #[inline]\n    fn render<'w>(\n        item: &P,\n        _: ROQueryItem<'w, Self::ViewQuery>,\n        _: Option<ROQueryItem<'w, Self::ItemQuery>>,\n        terrain_data: SystemParamItem<'w, '_, Self::Param>,\n        pass: &mut TrackedRenderPass<'w>,\n    ) -> RenderCommandResult {\n        let data = terrain_data.into_inner().get(&item.entity()).unwrap();\n\n        pass.set_bind_group(I, &data.terrain_bind_group, &[]);\n        RenderCommandResult::Success\n    }\n}\n"
  },
  {
    "path": "src/render/terrain_material.rs",
    "content": "use crate::{\n    debug::DebugTerrain,\n    render::{\n        terrain_bind_group::{create_terrain_layout, SetTerrainBindGroup},\n        terrain_view_bind_group::{\n            create_terrain_view_layout, DrawTerrainCommand, SetTerrainViewBindGroup,\n        },\n    },\n    shaders::{DEFAULT_FRAGMENT_SHADER, DEFAULT_VERTEX_SHADER},\n    terrain::TerrainComponents,\n    terrain_data::gpu_tile_atlas::GpuTileAtlas,\n};\nuse bevy::{\n    core_pipeline::core_3d::{Opaque3d, Opaque3dBinKey},\n    pbr::{\n        MaterialPipeline, MeshPipeline, MeshPipelineViewLayoutKey, PreparedMaterial,\n        RenderMaterialInstances, SetMaterialBindGroup, SetMeshViewBindGroup,\n    },\n    prelude::*,\n    render::{\n        extract_instances::ExtractInstancesPlugin,\n        render_asset::{prepare_assets, RenderAssetPlugin, RenderAssets},\n        render_phase::{\n            AddRenderCommand, BinnedRenderPhaseType, DrawFunctions, SetItemPipeline,\n            ViewBinnedRenderPhases,\n        },\n        render_resource::*,\n        renderer::RenderDevice,\n        texture::{BevyDefault, GpuImage},\n        Render, RenderApp, RenderSet,\n    },\n};\nuse std::{hash::Hash, marker::PhantomData};\n\npub struct TerrainPipelineKey<M: Material> {\n    pub flags: TerrainPipelineFlags,\n    pub bind_group_data: M::Data,\n}\n\nimpl<M: Material> Eq for TerrainPipelineKey<M> where M::Data: PartialEq {}\n\nimpl<M: Material> PartialEq for TerrainPipelineKey<M>\nwhere\n    M::Data: PartialEq,\n{\n    fn eq(&self, other: &Self) -> bool {\n        self.flags == other.flags && self.bind_group_data == other.bind_group_data\n    }\n}\n\nimpl<M: Material> Clone for TerrainPipelineKey<M>\nwhere\n    M::Data: Clone,\n{\n    fn clone(&self) -> Self {\n        Self {\n            flags: self.flags,\n            bind_group_data: self.bind_group_data.clone(),\n        }\n    }\n}\n\nimpl<M: Material> Hash for TerrainPipelineKey<M>\nwhere\n    M::Data: Hash,\n{\n    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {\n        self.flags.hash(state);\n        self.bind_group_data.hash(state);\n    }\n}\n\nbitflags::bitflags! {\n    #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]\n    #[repr(transparent)]\n    pub struct TerrainPipelineFlags: u32 {\n        const NONE               = 0;\n        const SPHERICAL          = 1 <<  0;\n        const WIREFRAME          = 1 <<  1;\n        const SHOW_DATA_LOD      = 1 <<  2;\n        const SHOW_GEOMETRY_LOD  = 1 <<  3;\n        const SHOW_TILE_TREE     = 1 <<  4;\n        const SHOW_PIXELS        = 1 <<  5;\n        const SHOW_UV            = 1 <<  6;\n        const SHOW_NORMALS       = 1 <<  7;\n        const MORPH              = 1 <<  8;\n        const BLEND              = 1 <<  9;\n        const TILE_TREE_LOD      = 1 << 10;\n        const LIGHTING           = 1 << 11;\n        const SAMPLE_GRAD        = 1 << 12;\n        const HIGH_PRECISION     = 1 << 13;\n        const TEST1              = 1 << 14;\n        const TEST2              = 1 << 15;\n        const TEST3              = 1 << 16;\n        const MSAA_RESERVED_BITS = TerrainPipelineFlags::MSAA_MASK_BITS << TerrainPipelineFlags::MSAA_SHIFT_BITS;\n    }\n}\n\nimpl TerrainPipelineFlags {\n    const MSAA_MASK_BITS: u32 = 0b111111;\n    const MSAA_SHIFT_BITS: u32 = 32 - 6;\n\n    pub fn from_msaa_samples(msaa_samples: u32) -> Self {\n        let msaa_bits = ((msaa_samples - 1) & Self::MSAA_MASK_BITS) << Self::MSAA_SHIFT_BITS;\n        TerrainPipelineFlags::from_bits(msaa_bits).unwrap()\n    }\n\n    pub fn from_debug(debug: &DebugTerrain) -> Self {\n        let mut key = TerrainPipelineFlags::NONE;\n\n        if debug.wireframe {\n            key |= TerrainPipelineFlags::WIREFRAME;\n        }\n        if debug.show_data_lod {\n            key |= TerrainPipelineFlags::SHOW_DATA_LOD;\n        }\n        if debug.show_geometry_lod {\n            key |= TerrainPipelineFlags::SHOW_GEOMETRY_LOD;\n        }\n        if debug.show_tile_tree {\n            key |= TerrainPipelineFlags::SHOW_TILE_TREE;\n        }\n        if debug.show_pixels {\n            key |= TerrainPipelineFlags::SHOW_PIXELS;\n        }\n        if debug.show_uv {\n            key |= TerrainPipelineFlags::SHOW_UV;\n        }\n        if debug.show_normals {\n            key |= TerrainPipelineFlags::SHOW_NORMALS;\n        }\n        if debug.morph {\n            key |= TerrainPipelineFlags::MORPH;\n        }\n        if debug.blend {\n            key |= TerrainPipelineFlags::BLEND;\n        }\n        if debug.tile_tree_lod {\n            key |= TerrainPipelineFlags::TILE_TREE_LOD;\n        }\n        if debug.lighting {\n            key |= TerrainPipelineFlags::LIGHTING;\n        }\n        if debug.sample_grad {\n            key |= TerrainPipelineFlags::SAMPLE_GRAD;\n        }\n        if debug.high_precision {\n            key |= TerrainPipelineFlags::HIGH_PRECISION;\n        }\n        if debug.test1 {\n            key |= TerrainPipelineFlags::TEST1;\n        }\n        if debug.test2 {\n            key |= TerrainPipelineFlags::TEST2;\n        }\n        if debug.test3 {\n            key |= TerrainPipelineFlags::TEST3;\n        }\n\n        key\n    }\n\n    pub fn msaa_samples(&self) -> u32 {\n        ((self.bits() >> Self::MSAA_SHIFT_BITS) & Self::MSAA_MASK_BITS) + 1\n    }\n\n    pub fn polygon_mode(&self) -> PolygonMode {\n        match self.contains(TerrainPipelineFlags::WIREFRAME) {\n            true => PolygonMode::Line,\n            false => PolygonMode::Fill,\n        }\n    }\n\n    pub fn shader_defs(&self) -> Vec<ShaderDefVal> {\n        let mut shader_defs = Vec::new();\n\n        if self.contains(TerrainPipelineFlags::SPHERICAL) {\n            shader_defs.push(\"SPHERICAL\".into());\n        }\n        if self.contains(TerrainPipelineFlags::SHOW_DATA_LOD) {\n            shader_defs.push(\"SHOW_DATA_LOD\".into());\n        }\n        if self.contains(TerrainPipelineFlags::SHOW_GEOMETRY_LOD) {\n            shader_defs.push(\"SHOW_GEOMETRY_LOD\".into());\n        }\n        if self.contains(TerrainPipelineFlags::SHOW_TILE_TREE) {\n            shader_defs.push(\"SHOW_TILE_TREE\".into());\n        }\n        if self.contains(TerrainPipelineFlags::SHOW_PIXELS) {\n            shader_defs.push(\"SHOW_PIXELS\".into())\n        }\n        if self.contains(TerrainPipelineFlags::SHOW_UV) {\n            shader_defs.push(\"SHOW_UV\".into());\n        }\n        if self.contains(TerrainPipelineFlags::SHOW_NORMALS) {\n            shader_defs.push(\"SHOW_NORMALS\".into())\n        }\n        if self.contains(TerrainPipelineFlags::MORPH) {\n            shader_defs.push(\"MORPH\".into());\n        }\n        if self.contains(TerrainPipelineFlags::BLEND) {\n            shader_defs.push(\"BLEND\".into());\n        }\n        if self.contains(TerrainPipelineFlags::TILE_TREE_LOD) {\n            shader_defs.push(\"TILE_TREE_LOD\".into());\n        }\n        if self.contains(TerrainPipelineFlags::LIGHTING) {\n            shader_defs.push(\"LIGHTING\".into());\n        }\n        if self.contains(TerrainPipelineFlags::SAMPLE_GRAD) {\n            shader_defs.push(\"SAMPLE_GRAD\".into());\n        }\n        if self.contains(TerrainPipelineFlags::HIGH_PRECISION) {\n            shader_defs.push(\"HIGH_PRECISION\".into());\n        }\n        if self.contains(TerrainPipelineFlags::TEST1) {\n            shader_defs.push(\"TEST1\".into());\n        }\n        if self.contains(TerrainPipelineFlags::TEST2) {\n            shader_defs.push(\"TEST2\".into());\n        }\n        if self.contains(TerrainPipelineFlags::TEST3) {\n            shader_defs.push(\"TEST3\".into());\n        }\n\n        shader_defs\n    }\n}\n\n/// The pipeline used to render the terrain entities.\n#[derive(Resource)]\npub struct TerrainRenderPipeline<M: Material> {\n    pub(crate) view_layout: BindGroupLayout,\n    pub(crate) view_layout_multisampled: BindGroupLayout,\n    pub(crate) terrain_layout: BindGroupLayout,\n    pub(crate) terrain_view_layout: BindGroupLayout,\n    pub(crate) material_layout: BindGroupLayout,\n    pub vertex_shader: Handle<Shader>,\n    pub fragment_shader: Handle<Shader>,\n    marker: PhantomData<M>,\n}\n\nimpl<M: Material> FromWorld for TerrainRenderPipeline<M> {\n    fn from_world(world: &mut World) -> Self {\n        let device = world.resource::<RenderDevice>();\n        let asset_server = world.resource::<AssetServer>();\n        let mesh_pipeline = world.resource::<MeshPipeline>();\n\n        let view_layout = mesh_pipeline\n            .get_view_layout(MeshPipelineViewLayoutKey::empty())\n            .clone();\n        let view_layout_multisampled = mesh_pipeline\n            .get_view_layout(MeshPipelineViewLayoutKey::MULTISAMPLED)\n            .clone();\n        let terrain_layout = create_terrain_layout(device);\n        let terrain_view_layout = create_terrain_view_layout(device);\n        let material_layout = M::bind_group_layout(device);\n\n        let vertex_shader = match M::vertex_shader() {\n            ShaderRef::Default => asset_server.load(DEFAULT_VERTEX_SHADER),\n            ShaderRef::Handle(handle) => handle,\n            ShaderRef::Path(path) => asset_server.load(path),\n        };\n\n        let fragment_shader = match M::fragment_shader() {\n            ShaderRef::Default => asset_server.load(DEFAULT_FRAGMENT_SHADER),\n            ShaderRef::Handle(handle) => handle,\n            ShaderRef::Path(path) => asset_server.load(path),\n        };\n\n        Self {\n            view_layout,\n            view_layout_multisampled,\n            terrain_layout,\n            terrain_view_layout,\n            material_layout,\n            vertex_shader,\n            fragment_shader,\n            marker: PhantomData,\n        }\n    }\n}\n\nimpl<M: Material> SpecializedRenderPipeline for TerrainRenderPipeline<M>\nwhere\n    M::Data: PartialEq + Eq + Hash + Clone,\n{\n    type Key = TerrainPipelineKey<M>;\n\n    fn specialize(&self, key: Self::Key) -> RenderPipelineDescriptor {\n        let mut shader_defs = key.flags.shader_defs();\n\n        let mut bind_group_layout = match key.flags.msaa_samples() {\n            1 => vec![self.view_layout.clone()],\n            _ => {\n                shader_defs.push(\"MULTISAMPLED\".into());\n                vec![self.view_layout_multisampled.clone()]\n            }\n        };\n\n        bind_group_layout.push(self.terrain_layout.clone());\n        bind_group_layout.push(self.terrain_view_layout.clone());\n        bind_group_layout.push(self.material_layout.clone());\n\n        let vertex_shader_defs = shader_defs.clone();\n        let mut fragment_shader_defs = shader_defs.clone();\n        fragment_shader_defs.push(\"FRAGMENT\".into());\n\n        RenderPipelineDescriptor {\n            label: None,\n            layout: bind_group_layout,\n            push_constant_ranges: default(),\n            vertex: VertexState {\n                shader: self.vertex_shader.clone(),\n                entry_point: \"vertex\".into(),\n                shader_defs: vertex_shader_defs,\n                buffers: Vec::new(),\n            },\n            primitive: PrimitiveState {\n                front_face: FrontFace::Ccw,\n                cull_mode: Some(Face::Back),\n                unclipped_depth: false,\n                polygon_mode: key.flags.polygon_mode(),\n                conservative: false,\n                topology: PrimitiveTopology::TriangleStrip,\n                strip_index_format: None,\n            },\n            fragment: Some(FragmentState {\n                shader: self.fragment_shader.clone(),\n                shader_defs: fragment_shader_defs,\n                entry_point: \"fragment\".into(),\n                targets: vec![Some(ColorTargetState {\n                    format: TextureFormat::bevy_default(),\n                    blend: Some(BlendState::REPLACE),\n                    write_mask: ColorWrites::ALL,\n                })],\n            }),\n            depth_stencil: Some(DepthStencilState {\n                format: TextureFormat::Depth32Float,\n                depth_write_enabled: true,\n                depth_compare: CompareFunction::Greater,\n                stencil: StencilState {\n                    front: StencilFaceState::IGNORE,\n                    back: StencilFaceState::IGNORE,\n                    read_mask: 0,\n                    write_mask: 0,\n                },\n                bias: DepthBiasState {\n                    constant: 0,\n                    slope_scale: 0.0,\n                    clamp: 0.0,\n                },\n            }),\n            multisample: MultisampleState {\n                count: key.flags.msaa_samples(),\n                mask: !0,\n                alpha_to_coverage_enabled: false,\n            },\n        }\n    }\n}\n\n/// The draw function of the terrain. It sets the pipeline and the bind groups and then issues the\n/// draw call.\npub(crate) type DrawTerrain<M> = (\n    SetItemPipeline,\n    SetMeshViewBindGroup<0>,\n    SetTerrainBindGroup<1>,\n    SetTerrainViewBindGroup<2>,\n    SetMaterialBindGroup<M, 3>,\n    DrawTerrainCommand,\n);\n\n/// Queses all terrain entities for rendering via the terrain pipeline.\n#[allow(clippy::too_many_arguments)]\npub(crate) fn queue_terrain<M: Material>(\n    draw_functions: Res<DrawFunctions<Opaque3d>>,\n    msaa: Res<Msaa>,\n    debug: Option<Res<DebugTerrain>>,\n    render_materials: Res<RenderAssets<PreparedMaterial<M>>>,\n    pipeline_cache: Res<PipelineCache>,\n    terrain_pipeline: Res<TerrainRenderPipeline<M>>,\n    mut pipelines: ResMut<SpecializedRenderPipelines<TerrainRenderPipeline<M>>>,\n    mut opaque_render_phases: ResMut<ViewBinnedRenderPhases<Opaque3d>>,\n    gpu_tile_atlases: Res<TerrainComponents<GpuTileAtlas>>,\n    render_material_instances: Res<RenderMaterialInstances<M>>,\n) where\n    M::Data: PartialEq + Eq + Hash + Clone,\n{\n    for phase in opaque_render_phases.values_mut() {\n        let draw_function = draw_functions.read().get_id::<DrawTerrain<M>>().unwrap();\n\n        for (&terrain, &material_id) in render_material_instances.iter() {\n            let gpu_tile_atlas = gpu_tile_atlases.get(&terrain).unwrap();\n            if let Some(material) = render_materials.get(material_id) {\n                let mut flags = TerrainPipelineFlags::from_msaa_samples(msaa.samples());\n\n                if gpu_tile_atlas.is_spherical {\n                    flags |= TerrainPipelineFlags::SPHERICAL;\n                }\n\n                if let Some(debug) = &debug {\n                    flags |= TerrainPipelineFlags::from_debug(debug);\n                } else {\n                    flags |= TerrainPipelineFlags::LIGHTING\n                        | TerrainPipelineFlags::MORPH\n                        | TerrainPipelineFlags::BLEND\n                        | TerrainPipelineFlags::SAMPLE_GRAD;\n                }\n\n                let key = TerrainPipelineKey {\n                    flags,\n                    bind_group_data: material.key.clone(),\n                };\n\n                let pipeline = pipelines.specialize(&pipeline_cache, &terrain_pipeline, key);\n\n                phase.add(\n                    Opaque3dBinKey {\n                        pipeline,\n                        draw_function,\n                        asset_id: material_id.untyped(),\n                        material_bind_group_id: None,\n                        lightmap_image: None,\n                    },\n                    terrain,\n                    BinnedRenderPhaseType::NonMesh,\n                );\n            }\n        }\n    }\n}\n\n/// This plugin adds a custom material for a terrain.\n///\n/// It can be used to render the terrain using a custom vertex and fragment shader.\npub struct TerrainMaterialPlugin<M: Material>(PhantomData<M>);\n\nimpl<M: Material> Default for TerrainMaterialPlugin<M> {\n    fn default() -> Self {\n        Self(Default::default())\n    }\n}\n\nimpl<M: Material> Plugin for TerrainMaterialPlugin<M>\nwhere\n    M::Data: PartialEq + Eq + Hash + Clone,\n{\n    fn build(&self, app: &mut App) {\n        app.init_asset::<M>().add_plugins((\n            ExtractInstancesPlugin::<AssetId<M>>::extract_visible(),\n            RenderAssetPlugin::<PreparedMaterial<M>, GpuImage>::default(),\n        ));\n\n        app.sub_app_mut(RenderApp)\n            .add_render_command::<Opaque3d, DrawTerrain<M>>()\n            .add_systems(\n                Render,\n                queue_terrain::<M>\n                    .in_set(RenderSet::QueueMeshes)\n                    .after(prepare_assets::<PreparedMaterial<M>>),\n            );\n    }\n\n    fn finish(&self, app: &mut App) {\n        app.sub_app_mut(RenderApp)\n            .init_resource::<TerrainRenderPipeline<M>>()\n            .init_resource::<SpecializedRenderPipelines<TerrainRenderPipeline<M>>>()\n            .init_resource::<MaterialPipeline<M>>(); // prepare assets depends on this to access the material layout\n    }\n}\n"
  },
  {
    "path": "src/render/terrain_view_bind_group.rs",
    "content": "use crate::{\n    math::{TerrainModelApproximation, TileCoordinate},\n    terrain_data::{gpu_tile_tree::GpuTileTree, tile_tree::TileTree},\n    terrain_view::TerrainViewComponents,\n    util::StaticBuffer,\n};\nuse bevy::{\n    ecs::{\n        query::ROQueryItem,\n        system::{lifetimeless::SRes, SystemParamItem},\n    },\n    prelude::*,\n    render::{\n        render_phase::{PhaseItem, RenderCommand, RenderCommandResult, TrackedRenderPass},\n        render_resource::{binding_types::*, *},\n        renderer::{RenderDevice, RenderQueue},\n        Extract,\n    },\n};\n\npub(crate) fn create_prepare_indirect_layout(device: &RenderDevice) -> BindGroupLayout {\n    device.create_bind_group_layout(\n        None,\n        &BindGroupLayoutEntries::single(\n            ShaderStages::COMPUTE,\n            storage_buffer::<Indirect>(false), // indirect buffer\n        ),\n    )\n}\n\npub(crate) fn create_refine_tiles_layout(device: &RenderDevice) -> BindGroupLayout {\n    device.create_bind_group_layout(\n        None,\n        &BindGroupLayoutEntries::sequential(\n            ShaderStages::COMPUTE,\n            (\n                uniform_buffer::<TerrainViewConfigUniform>(false), // terrain view config\n                uniform_buffer::<TerrainModelApproximation>(false), // model view approximation\n                storage_buffer_read_only_sized(false, None),       // tile_tree\n                storage_buffer_read_only_sized(false, None),       // origins\n                storage_buffer_sized(false, None),                 // final tiles\n                storage_buffer_sized(false, None),                 // temporary tiles\n                storage_buffer::<Parameters>(false),               // parameters\n            ),\n        ),\n    )\n}\n\npub(crate) fn create_terrain_view_layout(device: &RenderDevice) -> BindGroupLayout {\n    device.create_bind_group_layout(\n        None,\n        &BindGroupLayoutEntries::sequential(\n            ShaderStages::VERTEX_FRAGMENT,\n            (\n                uniform_buffer::<TerrainViewConfigUniform>(false), // terrain view config\n                uniform_buffer::<TerrainModelApproximation>(false), // model view approximation\n                storage_buffer_read_only_sized(false, None),       // tile_tree\n                storage_buffer_read_only_sized(false, None),       // origins\n                storage_buffer_read_only_sized(false, None),       // tiles\n            ),\n        ),\n    )\n}\n\n#[derive(Default, ShaderType)]\npub(crate) struct Indirect {\n    x_or_vertex_count: u32,\n    y_or_instance_count: u32,\n    z_or_base_vertex: u32,\n    base_instance: u32,\n}\n\n#[derive(Default, ShaderType)]\nstruct Parameters {\n    tile_count: u32,\n    counter: i32,\n    child_index: i32,\n    final_index: i32,\n}\n\n#[derive(Default, ShaderType)]\nstruct TerrainViewConfigUniform {\n    tree_size: u32,\n    geometry_tile_count: u32,\n    refinement_count: u32,\n    grid_size: f32,\n    vertices_per_row: u32,\n    vertices_per_tile: u32,\n    morph_distance: f32,\n    blend_distance: f32,\n    load_distance: f32,\n    subdivision_distance: f32,\n    morph_range: f32,\n    blend_range: f32,\n    precision_threshold_distance: f32,\n}\n\nimpl TerrainViewConfigUniform {\n    fn from_tile_tree(tile_tree: &TileTree) -> Self {\n        TerrainViewConfigUniform {\n            tree_size: tile_tree.tree_size,\n            geometry_tile_count: tile_tree.geometry_tile_count,\n            refinement_count: tile_tree.refinement_count,\n            grid_size: tile_tree.grid_size as f32,\n            vertices_per_row: 2 * (tile_tree.grid_size + 2),\n            vertices_per_tile: 2 * tile_tree.grid_size * (tile_tree.grid_size + 2),\n            morph_distance: tile_tree.morph_distance as f32,\n            blend_distance: tile_tree.blend_distance as f32,\n            load_distance: tile_tree.load_distance as f32,\n            subdivision_distance: tile_tree.subdivision_distance as f32,\n            precision_threshold_distance: tile_tree.precision_threshold_distance as f32,\n            morph_range: tile_tree.morph_range,\n            blend_range: tile_tree.blend_range,\n        }\n    }\n}\n\npub struct TerrainViewData {\n    view_config_buffer: StaticBuffer<TerrainViewConfigUniform>,\n    terrain_model_approximation_buffer: StaticBuffer<TerrainModelApproximation>,\n    pub(super) indirect_buffer: StaticBuffer<Indirect>,\n    pub(super) prepare_indirect_bind_group: BindGroup,\n    pub(super) refine_tiles_bind_group: BindGroup,\n    pub(super) terrain_view_bind_group: BindGroup,\n}\n\nimpl TerrainViewData {\n    fn new(device: &RenderDevice, tile_tree: &TileTree, gpu_tile_tree: &GpuTileTree) -> Self {\n        // Todo: figure out a better way of limiting the tile buffer size\n        let tile_buffer_size =\n            TileCoordinate::min_size().get() * tile_tree.geometry_tile_count as BufferAddress;\n\n        let view_config_buffer =\n            StaticBuffer::empty(None, device, BufferUsages::UNIFORM | BufferUsages::COPY_DST);\n        let indirect_buffer =\n            StaticBuffer::empty(None, device, BufferUsages::STORAGE | BufferUsages::INDIRECT);\n        let parameter_buffer =\n            StaticBuffer::<Parameters>::empty(None, device, BufferUsages::STORAGE);\n        let temporary_tile_buffer =\n            StaticBuffer::<()>::empty_sized(None, device, tile_buffer_size, BufferUsages::STORAGE);\n        let final_tile_buffer =\n            StaticBuffer::<()>::empty_sized(None, device, tile_buffer_size, BufferUsages::STORAGE);\n        let terrain_model_approximation_buffer = StaticBuffer::<TerrainModelApproximation>::empty(\n            None,\n            device,\n            BufferUsages::UNIFORM | BufferUsages::COPY_DST,\n        );\n\n        let prepare_indirect_bind_group = device.create_bind_group(\n            \"prepare_indirect_bind_group\",\n            &create_prepare_indirect_layout(device),\n            &BindGroupEntries::single(&indirect_buffer),\n        );\n        let refine_tiles_bind_group = device.create_bind_group(\n            \"refine_tiles_bind_group\",\n            &create_refine_tiles_layout(device),\n            &BindGroupEntries::sequential((\n                &view_config_buffer,\n                &terrain_model_approximation_buffer,\n                &gpu_tile_tree.tile_tree_buffer,\n                &gpu_tile_tree.origins_buffer,\n                &final_tile_buffer,\n                &temporary_tile_buffer,\n                &parameter_buffer,\n            )),\n        );\n        let terrain_view_bind_group = device.create_bind_group(\n            \"terrain_view_bind_group\",\n            &create_terrain_view_layout(device),\n            &BindGroupEntries::sequential((\n                &view_config_buffer,\n                &terrain_model_approximation_buffer,\n                &gpu_tile_tree.tile_tree_buffer,\n                &gpu_tile_tree.origins_buffer,\n                &final_tile_buffer,\n            )),\n        );\n\n        Self {\n            view_config_buffer,\n            terrain_model_approximation_buffer,\n            indirect_buffer,\n            prepare_indirect_bind_group,\n            refine_tiles_bind_group,\n            terrain_view_bind_group,\n        }\n    }\n\n    pub(super) fn refinement_count(&self) -> u32 {\n        self.view_config_buffer.value().refinement_count\n    }\n\n    pub(crate) fn initialize(\n        device: Res<RenderDevice>,\n        mut terrain_view_data: ResMut<TerrainViewComponents<TerrainViewData>>,\n        gpu_tile_trees: Res<TerrainViewComponents<GpuTileTree>>,\n        tile_trees: Extract<Res<TerrainViewComponents<TileTree>>>,\n    ) {\n        for (&(terrain, view), tile_tree) in tile_trees.iter() {\n            if terrain_view_data.contains_key(&(terrain, view)) {\n                return;\n            }\n\n            let gpu_tile_tree = gpu_tile_trees.get(&(terrain, view)).unwrap();\n\n            terrain_view_data.insert(\n                (terrain, view),\n                TerrainViewData::new(&device, tile_tree, gpu_tile_tree),\n            );\n        }\n    }\n\n    pub(crate) fn extract(\n        mut terrain_view_data: ResMut<TerrainViewComponents<TerrainViewData>>,\n        tile_trees: Extract<Res<TerrainViewComponents<TileTree>>>,\n        terrain_model_approximations: Extract<\n            Res<TerrainViewComponents<TerrainModelApproximation>>,\n        >,\n    ) {\n        for (&(terrain, view), tile_tree) in tile_trees.iter() {\n            let terrain_view_data = terrain_view_data.get_mut(&(terrain, view)).unwrap();\n\n            terrain_view_data\n                .view_config_buffer\n                .set_value(TerrainViewConfigUniform::from_tile_tree(tile_tree));\n\n            terrain_view_data\n                .terrain_model_approximation_buffer\n                .set_value(\n                    terrain_model_approximations\n                        .get(&(terrain, view))\n                        .unwrap()\n                        .clone(),\n                );\n        }\n    }\n\n    pub(crate) fn prepare(\n        queue: Res<RenderQueue>,\n        mut terrain_view_data: ResMut<TerrainViewComponents<TerrainViewData>>,\n    ) {\n        for data in &mut terrain_view_data.values_mut() {\n            data.view_config_buffer.update(&queue);\n            data.terrain_model_approximation_buffer.update(&queue);\n        }\n    }\n}\n\npub struct SetTerrainViewBindGroup<const I: usize>;\n\nimpl<const I: usize, P: PhaseItem> RenderCommand<P> for SetTerrainViewBindGroup<I> {\n    type Param = SRes<TerrainViewComponents<TerrainViewData>>;\n    type ViewQuery = Entity;\n    type ItemQuery = ();\n\n    #[inline]\n    fn render<'w>(\n        item: &P,\n        view: ROQueryItem<'w, Self::ViewQuery>,\n        _: Option<ROQueryItem<'w, Self::ItemQuery>>,\n        terrain_view_data: SystemParamItem<'w, '_, Self::Param>,\n        pass: &mut TrackedRenderPass<'w>,\n    ) -> RenderCommandResult {\n        let data = terrain_view_data\n            .into_inner()\n            .get(&(item.entity(), view))\n            .unwrap();\n\n        pass.set_bind_group(I, &data.terrain_view_bind_group, &[]);\n        RenderCommandResult::Success\n    }\n}\n\npub(crate) struct DrawTerrainCommand;\n\nimpl<P: PhaseItem> RenderCommand<P> for DrawTerrainCommand {\n    type Param = SRes<TerrainViewComponents<TerrainViewData>>;\n    type ViewQuery = Entity;\n    type ItemQuery = ();\n\n    #[inline]\n    fn render<'w>(\n        item: &P,\n        view: ROQueryItem<'w, Self::ViewQuery>,\n        _: Option<ROQueryItem<'w, Self::ItemQuery>>,\n        terrain_view_data: SystemParamItem<'w, '_, Self::Param>,\n        pass: &mut TrackedRenderPass<'w>,\n    ) -> RenderCommandResult {\n        let data = terrain_view_data\n            .into_inner()\n            .get(&(item.entity(), view))\n            .unwrap();\n\n        pass.draw_indirect(&data.indirect_buffer, 0);\n\n        RenderCommandResult::Success\n    }\n}\n"
  },
  {
    "path": "src/render/tiling_prepass.rs",
    "content": "use crate::terrain_data::gpu_tile_tree::GpuTileTree;\nuse crate::{\n    debug::DebugTerrain,\n    render::{\n        culling_bind_group::{create_culling_layout, CullingBindGroup},\n        terrain_bind_group::{create_terrain_layout, TerrainData},\n        terrain_view_bind_group::{\n            create_prepare_indirect_layout, create_refine_tiles_layout, TerrainViewData,\n        },\n    },\n    shaders::{PREPARE_PREPASS_SHADER, REFINE_TILES_SHADER},\n    terrain::TerrainComponents,\n    terrain_data::gpu_tile_atlas::GpuTileAtlas,\n    terrain_view::TerrainViewComponents,\n};\nuse bevy::{\n    prelude::*,\n    render::{\n        render_graph::{self, RenderLabel},\n        render_resource::*,\n        renderer::{RenderContext, RenderDevice},\n    },\n};\n\n#[derive(Debug, Hash, PartialEq, Eq, Clone, RenderLabel)]\npub struct TilingPrepassLabel;\n\nbitflags::bitflags! {\n    #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]\n    #[repr(transparent)]\n    pub struct TilingPrepassPipelineKey: u32 {\n        const NONE           = 0;\n        const REFINE_TILES   = 1 << 0;\n        const PREPARE_ROOT   = 1 << 1;\n        const PREPARE_NEXT   = 1 << 2;\n        const PREPARE_RENDER = 1 << 3;\n        const SPHERICAL      = 1 << 4;\n        const TEST1          = 1 << 5;\n        const TEST2          = 1 << 6;\n        const TEST3          = 1 << 7;\n    }\n}\n\nimpl TilingPrepassPipelineKey {\n    pub fn from_debug(debug: &DebugTerrain) -> Self {\n        let mut key = TilingPrepassPipelineKey::NONE;\n\n        if debug.test1 {\n            key |= TilingPrepassPipelineKey::TEST1;\n        }\n        if debug.test2 {\n            key |= TilingPrepassPipelineKey::TEST2;\n        }\n        if debug.test3 {\n            key |= TilingPrepassPipelineKey::TEST3;\n        }\n\n        key\n    }\n\n    pub fn shader_defs(&self) -> Vec<ShaderDefVal> {\n        let mut shader_defs = Vec::new();\n\n        if self.contains(TilingPrepassPipelineKey::SPHERICAL) {\n            shader_defs.push(\"SPHERICAL\".into());\n        }\n        if self.contains(TilingPrepassPipelineKey::TEST1) {\n            shader_defs.push(\"TEST1\".into());\n        }\n        if self.contains(TilingPrepassPipelineKey::TEST2) {\n            shader_defs.push(\"TEST2\".into());\n        }\n        if self.contains(TilingPrepassPipelineKey::TEST3) {\n            shader_defs.push(\"TEST3\".into());\n        }\n\n        shader_defs\n    }\n}\n\npub(crate) struct TilingPrepassItem {\n    refine_tiles_pipeline: CachedComputePipelineId,\n    prepare_root_pipeline: CachedComputePipelineId,\n    prepare_next_pipeline: CachedComputePipelineId,\n    prepare_render_pipeline: CachedComputePipelineId,\n}\n\nimpl TilingPrepassItem {\n    fn pipelines<'a>(\n        &'a self,\n        pipeline_cache: &'a PipelineCache,\n    ) -> Option<(\n        &ComputePipeline,\n        &ComputePipeline,\n        &ComputePipeline,\n        &ComputePipeline,\n    )> {\n        Some((\n            pipeline_cache.get_compute_pipeline(self.refine_tiles_pipeline)?,\n            pipeline_cache.get_compute_pipeline(self.prepare_root_pipeline)?,\n            pipeline_cache.get_compute_pipeline(self.prepare_next_pipeline)?,\n            pipeline_cache.get_compute_pipeline(self.prepare_render_pipeline)?,\n        ))\n    }\n}\n\n#[derive(Resource)]\npub struct TilingPrepassPipelines {\n    pub(crate) prepare_indirect_layout: BindGroupLayout,\n    pub(crate) refine_tiles_layout: BindGroupLayout,\n    culling_data_layout: BindGroupLayout,\n    terrain_layout: BindGroupLayout,\n    prepare_prepass_shader: Handle<Shader>,\n    refine_tiles_shader: Handle<Shader>,\n}\n\nimpl FromWorld for TilingPrepassPipelines {\n    fn from_world(world: &mut World) -> Self {\n        let device = world.resource::<RenderDevice>();\n        let asset_server = world.resource::<AssetServer>();\n\n        let prepare_indirect_layout = create_prepare_indirect_layout(device);\n        let refine_tiles_layout = create_refine_tiles_layout(device);\n        let culling_data_layout = create_culling_layout(device);\n        let terrain_layout = create_terrain_layout(device);\n\n        let prepare_prepass_shader = asset_server.load(PREPARE_PREPASS_SHADER);\n        let refine_tiles_shader = asset_server.load(REFINE_TILES_SHADER);\n\n        TilingPrepassPipelines {\n            prepare_indirect_layout,\n            refine_tiles_layout,\n            culling_data_layout,\n            terrain_layout,\n            prepare_prepass_shader,\n            refine_tiles_shader,\n        }\n    }\n}\n\nimpl SpecializedComputePipeline for TilingPrepassPipelines {\n    type Key = TilingPrepassPipelineKey;\n\n    fn specialize(&self, key: Self::Key) -> ComputePipelineDescriptor {\n        let mut layout = default();\n        let mut shader = default();\n        let mut entry_point = default();\n\n        let shader_defs = key.shader_defs();\n\n        if key.contains(TilingPrepassPipelineKey::REFINE_TILES) {\n            layout = vec![\n                self.culling_data_layout.clone(),\n                self.terrain_layout.clone(),\n                self.refine_tiles_layout.clone(),\n            ];\n            shader = self.refine_tiles_shader.clone();\n            entry_point = \"refine_tiles\".into();\n        }\n        if key.contains(TilingPrepassPipelineKey::PREPARE_ROOT) {\n            layout = vec![\n                self.culling_data_layout.clone(),\n                self.terrain_layout.clone(),\n                self.refine_tiles_layout.clone(),\n                self.prepare_indirect_layout.clone(),\n            ];\n            shader = self.prepare_prepass_shader.clone();\n            entry_point = \"prepare_root\".into();\n        }\n        if key.contains(TilingPrepassPipelineKey::PREPARE_NEXT) {\n            layout = vec![\n                self.culling_data_layout.clone(),\n                self.terrain_layout.clone(),\n                self.refine_tiles_layout.clone(),\n                self.prepare_indirect_layout.clone(),\n            ];\n            shader = self.prepare_prepass_shader.clone();\n            entry_point = \"prepare_next\".into();\n        }\n        if key.contains(TilingPrepassPipelineKey::PREPARE_RENDER) {\n            layout = vec![\n                self.culling_data_layout.clone(),\n                self.terrain_layout.clone(),\n                self.refine_tiles_layout.clone(),\n                self.prepare_indirect_layout.clone(),\n            ];\n            shader = self.prepare_prepass_shader.clone();\n            entry_point = \"prepare_render\".into();\n        }\n\n        ComputePipelineDescriptor {\n            label: Some(\"tiling_prepass_pipeline\".into()),\n            layout,\n            push_constant_ranges: default(),\n            shader,\n            shader_defs,\n            entry_point,\n        }\n    }\n}\n\npub struct TilingPrepassNode;\n\nimpl render_graph::Node for TilingPrepassNode {\n    fn run<'w>(\n        &self,\n        _graph: &mut render_graph::RenderGraphContext,\n        context: &mut RenderContext<'w>,\n        world: &'w World,\n    ) -> Result<(), render_graph::NodeRunError> {\n        let prepass_items = world.resource::<TerrainViewComponents<TilingPrepassItem>>();\n        let pipeline_cache = world.resource::<PipelineCache>();\n        let terrain_data = world.resource::<TerrainComponents<TerrainData>>();\n        let terrain_view_data = world.resource::<TerrainViewComponents<TerrainViewData>>();\n        let culling_bind_groups = world.resource::<TerrainViewComponents<CullingBindGroup>>();\n        let debug = world.get_resource::<DebugTerrain>();\n\n        if debug.map(|debug| debug.freeze).unwrap_or(false) {\n            return Ok(());\n        }\n\n        context.add_command_buffer_generation_task(move |device| {\n            let mut command_encoder =\n                device.create_command_encoder(&CommandEncoderDescriptor::default());\n            let mut compute_pass =\n                command_encoder.begin_compute_pass(&ComputePassDescriptor::default());\n\n            for (&(terrain, view), prepass_item) in prepass_items.iter() {\n                let Some((\n                    refine_tiles_pipeline,\n                    prepare_root_pipeline,\n                    prepare_next_pipeline,\n                    prepare_render_pipeline,\n                )) = prepass_item.pipelines(pipeline_cache)\n                else {\n                    continue;\n                };\n\n                let culling_bind_group = culling_bind_groups.get(&(terrain, view)).unwrap();\n                let terrain_data = terrain_data.get(&terrain).unwrap();\n                let view_data = terrain_view_data.get(&(terrain, view)).unwrap();\n\n                compute_pass.set_bind_group(0, culling_bind_group, &[]);\n                compute_pass.set_bind_group(1, &terrain_data.terrain_bind_group, &[]);\n                compute_pass.set_bind_group(2, &view_data.refine_tiles_bind_group, &[]);\n                compute_pass.set_bind_group(3, &view_data.prepare_indirect_bind_group, &[]);\n\n                compute_pass.set_pipeline(prepare_root_pipeline);\n                compute_pass.dispatch_workgroups(1, 1, 1);\n\n                for _ in 0..view_data.refinement_count() {\n                    compute_pass.set_pipeline(refine_tiles_pipeline);\n                    compute_pass.dispatch_workgroups_indirect(&view_data.indirect_buffer, 0);\n\n                    compute_pass.set_pipeline(prepare_next_pipeline);\n                    compute_pass.dispatch_workgroups(1, 1, 1);\n                }\n\n                compute_pass.set_pipeline(refine_tiles_pipeline);\n                compute_pass.dispatch_workgroups_indirect(&view_data.indirect_buffer, 0);\n\n                compute_pass.set_pipeline(prepare_render_pipeline);\n                compute_pass.dispatch_workgroups(1, 1, 1);\n            }\n\n            drop(compute_pass);\n            command_encoder.finish()\n        });\n\n        Ok(())\n    }\n}\n\npub(crate) fn queue_tiling_prepass(\n    debug: Option<Res<DebugTerrain>>,\n    pipeline_cache: Res<PipelineCache>,\n    prepass_pipelines: ResMut<TilingPrepassPipelines>,\n    mut pipelines: ResMut<SpecializedComputePipelines<TilingPrepassPipelines>>,\n    mut prepass_items: ResMut<TerrainViewComponents<TilingPrepassItem>>,\n    gpu_tile_trees: Res<TerrainViewComponents<GpuTileTree>>,\n    gpu_tile_atlases: Res<TerrainComponents<GpuTileAtlas>>,\n) {\n    for &(terrain, view) in gpu_tile_trees.keys() {\n        let gpu_tile_atlas = gpu_tile_atlases.get(&terrain).unwrap();\n\n        let mut key = TilingPrepassPipelineKey::NONE;\n\n        if gpu_tile_atlas.is_spherical {\n            key |= TilingPrepassPipelineKey::SPHERICAL;\n        }\n\n        if let Some(debug) = &debug {\n            key |= TilingPrepassPipelineKey::from_debug(debug);\n        }\n\n        let refine_tiles_pipeline = pipelines.specialize(\n            &pipeline_cache,\n            &prepass_pipelines,\n            key | TilingPrepassPipelineKey::REFINE_TILES,\n        );\n        let prepare_root_pipeline = pipelines.specialize(\n            &pipeline_cache,\n            &prepass_pipelines,\n            key | TilingPrepassPipelineKey::PREPARE_ROOT,\n        );\n        let prepare_next_pipeline = pipelines.specialize(\n            &pipeline_cache,\n            &prepass_pipelines,\n            key | TilingPrepassPipelineKey::PREPARE_NEXT,\n        );\n        let prepare_render_pipeline = pipelines.specialize(\n            &pipeline_cache,\n            &prepass_pipelines,\n            key | TilingPrepassPipelineKey::PREPARE_RENDER,\n        );\n\n        prepass_items.insert(\n            (terrain, view),\n            TilingPrepassItem {\n                refine_tiles_pipeline,\n                prepare_root_pipeline,\n                prepare_next_pipeline,\n                prepare_render_pipeline,\n            },\n        );\n    }\n}\n"
  },
  {
    "path": "src/shaders/attachments.wgsl",
    "content": "#define_import_path bevy_terrain::attachments\n\n#import bevy_terrain::types::AtlasTile\n#import bevy_terrain::bindings::{config, atlas_sampler, attachments, attachment0_atlas, attachment1_atlas, attachment2_atlas}\n#import bevy_terrain::functions::tile_count\n\nfn attachment_uv(uv: vec2<f32>, attachment_index: u32) -> vec2<f32> {\n    let attachment = attachments[attachment_index];\n    return uv * attachment.scale + attachment.offset;\n}\n\nfn sample_attachment0(tile: AtlasTile) -> vec4<f32> {\n    let uv = attachment_uv(tile.coordinate.uv, 0u);\n\n#ifdef FRAGMENT\n#ifdef SAMPLE_GRAD\n    return textureSampleGrad(attachment0_atlas, atlas_sampler, uv, tile.index, tile.coordinate.uv_dx, tile.coordinate.uv_dy);\n#else\n    return textureSampleLevel(attachment0_atlas, atlas_sampler, uv, tile.index, 0.0);\n#endif\n#else\n    return textureSampleLevel(attachment0_atlas, atlas_sampler, uv, tile.index, 0.0);\n#endif\n}\n\nfn sample_attachment1(tile: AtlasTile) -> vec4<f32> {\n    let uv = attachment_uv(tile.coordinate.uv, 1u);\n\n#ifdef FRAGMENT\n#ifdef SAMPLE_GRAD\n    return textureSampleGrad(attachment1_atlas, atlas_sampler, uv, tile.index, tile.coordinate.uv_dx, tile.coordinate.uv_dy);\n#else\n    return textureSampleLevel(attachment1_atlas, atlas_sampler, uv, tile.index, 0.0);\n#endif\n#else\n    return textureSampleLevel(attachment1_atlas, atlas_sampler, uv, tile.index, 0.0);\n#endif\n}\n\nfn sample_attachment1_gather0(tile: AtlasTile) -> vec4<f32> {\n    let uv = attachment_uv(tile.coordinate.uv, 1u);\n    return textureGather(0, attachment1_atlas, atlas_sampler, uv, tile.index);\n}\n\nfn sample_height(tile: AtlasTile) -> f32 {\n    let height = sample_attachment0(tile).x;\n\n    return mix(config.min_height, config.max_height, height);\n}\n\nfn sample_normal(tile: AtlasTile, vertex_normal: vec3<f32>) -> vec3<f32> {\n    let uv = attachment_uv(tile.coordinate.uv, 0u);\n\n#ifdef SPHERICAL\n    var FACE_UP = array(\n        vec3( 0.0, 1.0,  0.0),\n        vec3( 0.0, 1.0,  0.0),\n        vec3( 0.0, 0.0, -1.0),\n        vec3( 0.0, 0.0, -1.0),\n        vec3(-1.0, 0.0,  0.0),\n        vec3(-1.0, 0.0,  0.0),\n    );\n\n    let face_up = FACE_UP[tile.coordinate.side];\n\n    let normal    = normalize(vertex_normal);\n    let tangent   = cross(face_up, normal);\n    let bitangent = cross(normal, tangent);\n    let TBN       = mat3x3(tangent, bitangent, normal);\n\n    let side_length = 3.14159265359 / 4.0 * config.scale;\n#else\n    let TBN = mat3x3(1.0, 0.0, 0.0,\n                     0.0, 0.0, 1.0,\n                     0.0, 1.0, 0.0);\n\n    let side_length = config.scale;\n#endif\n\n    // Todo: this is only an approximation of the S2 distance (pixels are not spaced evenly and they are not perpendicular)\n    let pixels_per_side = attachments[0u].size * tile_count(tile.coordinate.lod);\n    let distance_between_samples = side_length / pixels_per_side;\n    let offset = 0.5 / attachments[0u].size;\n\n#ifdef FRAGMENT\n#ifdef SAMPLE_GRAD\n    let left  = mix(config.min_height, config.max_height, textureSampleGrad(attachment0_atlas, atlas_sampler, uv + vec2<f32>(-offset,     0.0), tile.index, tile.coordinate.uv_dx, tile.coordinate.uv_dy).x);\n    let up    = mix(config.min_height, config.max_height, textureSampleGrad(attachment0_atlas, atlas_sampler, uv + vec2<f32>(    0.0, -offset), tile.index, tile.coordinate.uv_dx, tile.coordinate.uv_dy).x);\n    let right = mix(config.min_height, config.max_height, textureSampleGrad(attachment0_atlas, atlas_sampler, uv + vec2<f32>( offset,     0.0), tile.index, tile.coordinate.uv_dx, tile.coordinate.uv_dy).x);\n    let down  = mix(config.min_height, config.max_height, textureSampleGrad(attachment0_atlas, atlas_sampler, uv + vec2<f32>(    0.0,  offset), tile.index, tile.coordinate.uv_dx, tile.coordinate.uv_dy).x);\n#else\n    let left  = mix(config.min_height, config.max_height, textureSampleLevel(attachment0_atlas, atlas_sampler, uv + vec2<f32>(-offset,     0.0), tile.index, 0.0).x);\n    let up    = mix(config.min_height, config.max_height, textureSampleLevel(attachment0_atlas, atlas_sampler, uv + vec2<f32>(    0.0, -offset), tile.index, 0.0).x);\n    let right = mix(config.min_height, config.max_height, textureSampleLevel(attachment0_atlas, atlas_sampler, uv + vec2<f32>( offset,     0.0), tile.index, 0.0).x);\n    let down  = mix(config.min_height, config.max_height, textureSampleLevel(attachment0_atlas, atlas_sampler, uv + vec2<f32>(    0.0,  offset), tile.index, 0.0).x);\n#endif\n#else\n    let left  = mix(config.min_height, config.max_height, textureSampleLevel(attachment0_atlas, atlas_sampler, uv + vec2<f32>(-offset,     0.0), tile.index, 0.0).x);\n    let up    = mix(config.min_height, config.max_height, textureSampleLevel(attachment0_atlas, atlas_sampler, uv + vec2<f32>(    0.0, -offset), tile.index, 0.0).x);\n    let right = mix(config.min_height, config.max_height, textureSampleLevel(attachment0_atlas, atlas_sampler, uv + vec2<f32>( offset,     0.0), tile.index, 0.0).x);\n    let down  = mix(config.min_height, config.max_height, textureSampleLevel(attachment0_atlas, atlas_sampler, uv + vec2<f32>(    0.0,  offset), tile.index, 0.0).x);\n#endif\n\n    let surface_normal = normalize(vec3<f32>(left - right, down - up, distance_between_samples));\n\n    return normalize(TBN * surface_normal);\n}\n\nfn sample_color(tile: AtlasTile) -> vec4<f32> {\n    let height = sample_attachment0(tile).x;\n\n    return vec4<f32>(height * 0.5);\n}\n"
  },
  {
    "path": "src/shaders/bindings.wgsl",
    "content": "#define_import_path bevy_terrain::bindings\n\n#import bevy_terrain::types::{TerrainViewConfig, TerrainConfig, TileTreeEntry, TileCoordinate, AttachmentConfig, TerrainModelApproximation, CullingData, IndirectBuffer, Parameters}\n#import bevy_pbr::mesh_types::Mesh\n\n// terrain bindings\n@group(1) @binding(0)\nvar<storage> mesh: array<Mesh>;\n@group(1) @binding(1)\nvar<uniform> config: TerrainConfig;\n@group(1) @binding(2)\nvar<uniform> attachments: array<AttachmentConfig, 8u>;\n@group(1) @binding(3)\nvar atlas_sampler: sampler;\n@group(1) @binding(4)\nvar attachment0_atlas: texture_2d_array<f32>;\n@group(1) @binding(5)\nvar attachment1_atlas: texture_2d_array<f32>;\n@group(1) @binding(6)\nvar attachment2_atlas: texture_2d_array<f32>;\n@group(1) @binding(7)\nvar attachment3_atlas: texture_2d_array<f32>;\n@group(1) @binding(8)\nvar attachment4_atlas: texture_2d_array<f32>;\n@group(1) @binding(9)\nvar attachment5_atlas: texture_2d_array<f32>;\n@group(1) @binding(10)\nvar attachment6_atlas: texture_2d_array<f32>;\n@group(1) @binding(11)\nvar attachment7_atlas: texture_2d_array<f32>;\n\n// terrain view bindings\n@group(2) @binding(0)\nvar<uniform> view_config: TerrainViewConfig;\n@group(2) @binding(1)\nvar<uniform> terrain_model_approximation: TerrainModelApproximation;\n@group(2) @binding(2)\nvar<storage> tile_tree: array<TileTreeEntry>;\n@group(2) @binding(3)\nvar<storage> origins: array<vec2<u32>>;\n@group(2) @binding(4)\nvar<storage> geometry_tiles: array<TileCoordinate>;\n\n// refine geometry_tiles bindings\n@group(2) @binding(4)\nvar<storage, read_write> final_tiles: array<TileCoordinate>;\n@group(2) @binding(5)\nvar<storage, read_write> temporary_tiles: array<TileCoordinate>;\n@group(2) @binding(6)\nvar<storage, read_write> parameters: Parameters;\n\n@group(3) @binding(0)\nvar<storage, read_write> indirect_buffer: IndirectBuffer;\n\n// culling bindings\n@group(0) @binding(0)\nvar<uniform> culling_view: CullingData;"
  },
  {
    "path": "src/shaders/debug.wgsl",
    "content": "#define_import_path bevy_terrain::debug\n\n#import bevy_terrain::types::{Coordinate, AtlasTile, Blend}\n#import bevy_terrain::bindings::{config, tile_tree, view_config, geometry_tiles, attachments, origins, terrain_model_approximation}\n#import bevy_terrain::functions::{inverse_mix, compute_coordinate, lookup_best, approximate_view_distance, compute_blend, tree_lod, inside_square, tile_coordinate, coordinate_from_local_position, compute_subdivision_coordinate}\n#import bevy_pbr::mesh_view_bindings::view\n\nfn index_color(index: u32) -> vec4<f32> {\n    var COLOR_ARRAY = array(\n        vec4(1.0, 0.0, 0.0, 1.0),\n        vec4(0.0, 1.0, 0.0, 1.0),\n        vec4(0.0, 0.0, 1.0, 1.0),\n        vec4(1.0, 1.0, 0.0, 1.0),\n        vec4(1.0, 0.0, 1.0, 1.0),\n        vec4(0.0, 1.0, 1.0, 1.0),\n    );\n\n    return mix(COLOR_ARRAY[index % 6u], vec4<f32>(0.6), 0.2);\n}\n\nfn tile_tree_outlines(uv: vec2<f32>) -> f32 {\n    let thickness = 0.015;\n\n    return 1.0 - inside_square(uv, vec2<f32>(thickness), 1.0 - 2.0 * thickness);\n}\n\nfn checker_color(coordinate: Coordinate, ratio: f32) -> vec4<f32> {\n    var color        = index_color(coordinate.lod);\n    var parent_color = index_color(coordinate.lod - 1);\n    color            = select(color,        mix(color,        vec4(0.0), 0.5), (coordinate.xy.x + coordinate.xy.y) % 2u == 0u);\n    parent_color     = select(parent_color, mix(parent_color, vec4(0.0), 0.5), ((coordinate.xy.x >> 1) + (coordinate.xy.y >> 1)) % 2u == 0u);\n\n    return mix(color, parent_color, ratio);\n}\n\nfn show_data_lod(blend: Blend, tile: AtlasTile) -> vec4<f32> {\n#ifdef TILE_TREE_LOD\n    let ratio = 0.0;\n#else\n    let ratio = select(0.0, blend.ratio, blend.lod == tile.coordinate.lod);\n#endif\n\n    var color = checker_color(tile.coordinate, ratio);\n\n    if (ratio > 0.95 && blend.lod == tile.coordinate.lod) {\n        color = mix(color, vec4<f32>(0.0), 0.8);\n    }\n\n#ifdef SPHERICAL\n    color = mix(color, index_color(tile.coordinate.side), 0.3);\n#endif\n\n    return color;\n}\n\nfn show_geometry_lod(coordinate: Coordinate) -> vec4<f32> {\n    let view_distance  = approximate_view_distance(coordinate, view.world_position);\n    let target_lod     = log2(2.0 * view_config.morph_distance / view_distance);\n\n#ifdef MORPH\n    let ratio = select(inverse_mix(f32(coordinate.lod) + view_config.morph_range, f32(coordinate.lod), target_lod), 0.0, coordinate.lod == 0);\n#else\n    let ratio = 0.0;\n#endif\n\n    var color = checker_color(coordinate, ratio);\n\n    if (distance(coordinate.uv, compute_subdivision_coordinate(coordinate).uv) < 0.1) {\n        color = mix(index_color(coordinate.lod + 1), vec4(0.0), 0.7);\n    }\n\n    if (fract(target_lod) < 0.01 && target_lod >= 1.0) {\n        color = mix(color, vec4<f32>(0.0), 0.8);\n    }\n\n#ifdef SPHERICAL\n    color = mix(color, index_color(coordinate.side), 0.3);\n#endif\n\n    if (max(0.0, target_lod) < f32(coordinate.lod) - 1.0 + view_config.morph_range) {\n        // The view_distance and morph range are not sufficient.\n        // The same tile overlapps two morph zones.\n        // -> increase morph distance\n        color = vec4<f32>(1.0, 0.0, 0.0, 1.0);\n    }\n    if (floor(target_lod) > f32(coordinate.lod)) {\n        // The view_distance and morph range are not sufficient.\n        // The tile does have an insuffient LOD.\n        // -> increase morph tolerance\n        color = vec4<f32>(0.0, 1.0, 0.0, 1.0);\n    }\n\n    return color;\n}\nfn show_tile_tree(coordinate: Coordinate) -> vec4<f32> {\n    let view_distance  = approximate_view_distance(coordinate, view.world_position);\n    let target_lod     = log2(view_config.load_distance / view_distance);\n\n    let best_lookup = lookup_best(coordinate);\n\n    var color = checker_color(best_lookup.tile.coordinate, 0.0);\n    color     = mix(color, vec4<f32>(0.1), tile_tree_outlines(best_lookup.tile_tree_uv));\n\n    if (fract(target_lod) < 0.01 && target_lod >= 1.0) {\n        color = mix(index_color(u32(target_lod)), vec4<f32>(0.0), 0.8);\n    }\n\n    return color;\n}\n\nfn show_pixels(tile: AtlasTile) -> vec4<f32> {\n    let pixel_size = 4.0;\n    let pixel_coordinate = tile.coordinate.uv * f32(attachments[0].size) / pixel_size;\n\n    let is_even = (u32(pixel_coordinate.x) + u32(pixel_coordinate.y)) % 2u == 0u;\n\n    if (is_even) { return vec4<f32>(0.5, 0.5, 0.5, 1.0); }\n    else {         return vec4<f32>(0.1, 0.1, 0.1, 1.0); }\n}\n"
  },
  {
    "path": "src/shaders/functions.wgsl",
    "content": "#define_import_path bevy_terrain::functions\n\n#import bevy_terrain::bindings::{mesh, config, origins, view_config, geometry_tiles, tile_tree, terrain_model_approximation}\n#import bevy_terrain::types::{TileCoordinate, TileTree, TileTreeEntry, AtlasTile, Blend, BestLookup, Coordinate, Morph}\n#import bevy_pbr::mesh_view_bindings::view\n#import bevy_render::maths::{affine3_to_square, mat2x4_f32_to_mat3x3_unpack}\n\nconst F0 = 0u;\nconst F1 = 1u;\nconst PS = 2u;\nconst PT = 3u;\nconst C_SQR = 0.87 * 0.87;\n\nfn normal_local_to_world(local_position: vec3<f32>) -> vec3<f32> {\n#ifdef SPHERICAL\n    let local_normal = local_position;\n#else\n    let local_normal = vec3<f32>(0.0, 1.0, 0.0);\n#endif\n\n    let world_from_local = mat2x4_f32_to_mat3x3_unpack(mesh[0].local_from_world_transpose_a,\n                                                       mesh[0].local_from_world_transpose_b);\n    return normalize(world_from_local * local_normal);\n}\n\nfn position_local_to_world(local_position: vec3<f32>) -> vec3<f32> {\n    let world_from_local = affine3_to_square(mesh[0].world_from_local);\n    return (world_from_local * vec4<f32>(local_position, 1.0)).xyz;\n}\n\nfn inverse_mix(a: f32, b: f32, value: f32) -> f32 {\n    return saturate((value - a) / (b - a));\n}\n\nfn compute_morph(coordinate: Coordinate, view_distance: f32) -> Coordinate {\n#ifdef MORPH\n    // Morphing more than one layer at once is not possible, since the approximate view distance for vertices that\n    // should be placed on the same position will be slightly different, so the target lod and thus the ratio will be\n    // slightly off as well, which results in a pop.\n    let even_uv = vec2<f32>(vec2<u32>(coordinate.uv * view_config.grid_size) & vec2<u32>(~1u)) / view_config.grid_size;\n\n    let target_lod  = log2(2.0 * view_config.morph_distance / view_distance);\n    let ratio       = select(inverse_mix(f32(coordinate.lod) + view_config.morph_range, f32(coordinate.lod), target_lod), 0.0, coordinate.lod == 0);\n\n    return Coordinate(coordinate.side, coordinate.lod, coordinate.xy, mix(coordinate.uv, even_uv, ratio));\n#else\n    return coordinate;\n#endif\n}\n\nfn compute_blend(view_distance: f32) -> Blend {\n    let target_lod = min(log2(view_config.blend_distance / view_distance), f32(config.lod_count) - 0.00001);\n    let lod        = u32(target_lod);\n\n#ifdef BLEND\n    let ratio = select(inverse_mix(f32(lod) + view_config.blend_range, f32(lod), target_lod), 0.0, lod == 0u);\n\n    return Blend(lod, ratio);\n#else\n    return Blend(lod, 0.0);\n#endif\n}\n\nfn compute_tile_uv(vertex_index: u32) -> vec2<f32>{\n    // use first and last indices of the rows twice, to form degenerate triangles\n    let grid_index   = vertex_index % view_config.vertices_per_tile;\n    let row_index    = clamp(grid_index % view_config.vertices_per_row, 1u, view_config.vertices_per_row - 2u) - 1u;\n    let column_index = grid_index / view_config.vertices_per_row;\n\n    return vec2<f32>(f32(column_index + (row_index & 1u)), f32(row_index >> 1u)) / view_config.grid_size;\n}\n\nfn compute_local_position(coordinate: Coordinate) -> vec3<f32> {\n    var uv = (vec2<f32>(coordinate.xy) + coordinate.uv) / tile_count(coordinate.lod);\n\n#ifdef SPHERICAL\n    uv = (uv - 0.5) / 0.5;\n    uv = uv / sqrt(1.0 + C_SQR - C_SQR * uv * uv);\n\n    var local_position: vec3<f32>;\n\n    switch (coordinate.side) {\n        case 0u:      { local_position = vec3( -1.0, -uv.y,  uv.x); }\n        case 1u:      { local_position = vec3( uv.x, -uv.y,   1.0); }\n        case 2u:      { local_position = vec3( uv.x,   1.0,  uv.y); }\n        case 3u:      { local_position = vec3(  1.0, -uv.x,  uv.y); }\n        case 4u:      { local_position = vec3( uv.y, -uv.x,  -1.0); }\n        case 5u:      { local_position = vec3( uv.y,  -1.0,  uv.x); }\n        case default: {}\n    }\n\n    return normalize(local_position);\n#else\n    return vec3<f32>(uv.x - 0.5, 0.0, uv.y - 0.5);\n#endif\n}\n\nfn compute_relative_position(coord: Coordinate) -> vec3<f32> {\n    var coordinate = coord;\n    coordinate_change_lod(&coordinate, terrain_model_approximation.origin_lod);\n\n    let params = terrain_model_approximation.sides[coordinate.side];\n    let relative_st = (vec2<f32>(vec2<i32>(coordinate.xy) - params.view_xy) + coordinate.uv - params.view_uv) / tile_count(terrain_model_approximation.origin_lod);\n\n    let s = relative_st.x;\n    let t = relative_st.y;\n    let c = params.c;\n    let c_s = params.c_s;\n    let c_t = params.c_t;\n    let c_ss = params.c_ss;\n    let c_st = params.c_st;\n    let c_tt = params.c_tt;\n\n    return c + c_s * s + c_t * t + c_ss * s * s + c_st * s * t + c_tt * t * t;\n}\n\nfn approximate_view_distance(coordinate: Coordinate, view_world_position: vec3<f32>) -> f32 {\n    let local_position = compute_local_position(coordinate);\n    var world_position = position_local_to_world(local_position);\n    let world_normal   = normal_local_to_world(local_position);\n    var view_distance  = distance(world_position + terrain_model_approximation.approximate_height * world_normal, view_world_position);\n\n#ifdef HIGH_PRECISION\n    if (view_distance < view_config.precision_threshold_distance) {\n        let relative_position = compute_relative_position(coordinate);\n        view_distance         = length(relative_position + terrain_model_approximation.approximate_height * world_normal);\n    }\n#endif\n\n    return view_distance;\n}\n\nfn compute_subdivision_coordinate(coordinate: Coordinate) -> Coordinate {\n    let params  = terrain_model_approximation.sides[coordinate.side];\n\n#ifdef FRAGMENT\n    var view_coordinate = Coordinate(coordinate.side, terrain_model_approximation.origin_lod, vec2<u32>(params.view_xy), params.view_uv, vec2<f32>(0.0), vec2<f32>(0.0));\n#else\n    var view_coordinate = Coordinate(coordinate.side, terrain_model_approximation.origin_lod, vec2<u32>(params.view_xy), params.view_uv);\n#endif\n\n    coordinate_change_lod(&view_coordinate, coordinate.lod);\n    var offset = vec2<i32>(view_coordinate.xy) - vec2<i32>(coordinate.xy);\n    var uv = view_coordinate.uv;\n\n    if      (offset.x < 0) { uv.x = 0.0; }\n    else if (offset.x > 0) { uv.x = 1.0; }\n    if      (offset.y < 0) { uv.y = 0.0; }\n    else if (offset.y > 0) { uv.y = 1.0; }\n\n    var subdivision_coordinate = coordinate;\n    subdivision_coordinate.uv = uv;\n    return subdivision_coordinate;\n}\n\nfn tile_count(lod: u32) -> f32 { return f32(1u << lod); }\n\nfn inside_square(position: vec2<f32>, origin: vec2<f32>, size: f32) -> f32 {\n    let inside = step(origin, position) * step(position, origin + size);\n\n    return inside.x * inside.y;\n}\n\nfn coordinate_change_lod(coordinate: ptr<function, Coordinate>, new_lod: u32) {\n    let lod_difference = i32(new_lod) - i32((*coordinate).lod);\n\n    if (lod_difference == 0) { return; }\n\n    let delta_count = 1u << u32(abs(lod_difference));\n    let delta_size  = pow(2.0, f32(lod_difference));\n\n    (*coordinate).lod = new_lod;\n\n    if (lod_difference > 0) {\n        let scaled_uv    = (*coordinate).uv * delta_size;\n        (*coordinate).xy = (*coordinate).xy * delta_count + vec2<u32>(scaled_uv);\n        (*coordinate).uv = scaled_uv % 1.0;\n    } else {\n        let xy = (*coordinate).xy;\n        (*coordinate).xy = xy / delta_count;\n        (*coordinate).uv = (vec2<f32>(xy % delta_count) + (*coordinate).uv) * delta_size;\n    }\n\n#ifdef FRAGMENT\n    (*coordinate).uv_dx *= delta_size;\n    (*coordinate).uv_dy *= delta_size;\n#endif\n}\n\nfn compute_tile_tree_uv(coordinate: Coordinate) -> vec2<f32> {\n    let origin_xy = vec2<i32>(origins[coordinate.side * config.lod_count + coordinate.lod]);\n    let tree_size = min(f32(view_config.tree_size), tile_count(coordinate.lod));\n\n    return (vec2<f32>(vec2<i32>(coordinate.xy) - origin_xy) + coordinate.uv) / tree_size;\n}\n\n\nfn lookup_tile_tree_entry(coordinate: Coordinate) -> TileTreeEntry {\n    let tree_xy    = vec2<u32>(coordinate.xy) % view_config.tree_size;\n    let tree_index = ((coordinate.side * config.lod_count +\n                       coordinate.lod) * view_config.tree_size +\n                       tree_xy.x)      * view_config.tree_size +\n                       tree_xy.y;\n\n    return tile_tree[tree_index];\n}\n\n// Todo: implement this more efficiently\nfn lookup_best(lookup_coordinate: Coordinate) -> BestLookup {\n    var coordinate: Coordinate; var tile_tree_uv: vec2<f32>;\n\n    var new_coordinate   = lookup_coordinate;\n    coordinate_change_lod(&new_coordinate , 0u);\n    var new_tile_tree_uv = new_coordinate.uv;\n\n    while (new_coordinate.lod < config.lod_count && !any(new_tile_tree_uv <= vec2<f32>(0.0)) && !any(new_tile_tree_uv >= vec2<f32>(1.0))) {\n        coordinate  = new_coordinate;\n        tile_tree_uv = new_tile_tree_uv;\n\n        new_coordinate = lookup_coordinate;\n        coordinate_change_lod(&new_coordinate, coordinate.lod + 1u);\n        new_tile_tree_uv = compute_tile_tree_uv(new_coordinate);\n    }\n\n    let tile_tree_entry = lookup_tile_tree_entry(coordinate);\n\n    coordinate_change_lod(&coordinate, tile_tree_entry.atlas_lod);\n\n    return BestLookup(AtlasTile(tile_tree_entry.atlas_index, coordinate), tile_tree_uv);\n}\n\nfn lookup_tile(lookup_coordinate: Coordinate, blend: Blend, lod_offset: u32) -> AtlasTile {\n#ifdef TILE_TREE_LOD\n    return lookup_best(lookup_coordinate).tile;\n#else\n    var coordinate = lookup_coordinate;\n\n    coordinate_change_lod(&coordinate, blend.lod - lod_offset);\n\n    let tile_tree_entry = lookup_tile_tree_entry(coordinate);\n\n    coordinate_change_lod(&coordinate, tile_tree_entry.atlas_lod);\n\n    return AtlasTile(tile_tree_entry.atlas_index, coordinate);\n#endif\n}\n"
  },
  {
    "path": "src/shaders/mod.rs",
    "content": "use bevy::{asset::embedded_asset, prelude::*};\nuse itertools::Itertools;\n\npub const DEFAULT_VERTEX_SHADER: &str = \"embedded://bevy_terrain/shaders/render/vertex.wgsl\";\npub const DEFAULT_FRAGMENT_SHADER: &str = \"embedded://bevy_terrain/shaders/render/fragment.wgsl\";\npub const PREPARE_PREPASS_SHADER: &str =\n    \"embedded://bevy_terrain/shaders/tiling_prepass/prepare_prepass.wgsl\";\npub const REFINE_TILES_SHADER: &str =\n    \"embedded://bevy_terrain/shaders/tiling_prepass/refine_tiles.wgsl\";\npub(crate) const SPLIT_SHADER: &str = \"embedded://bevy_terrain/shaders/preprocess/split.wgsl\";\npub(crate) const STITCH_SHADER: &str = \"embedded://bevy_terrain/shaders/preprocess/stitch.wgsl\";\npub(crate) const DOWNSAMPLE_SHADER: &str =\n    \"embedded://bevy_terrain/shaders/preprocess/downsample.wgsl\";\n\n#[derive(Default, Resource)]\npub(crate) struct InternalShaders(Vec<Handle<Shader>>);\n\nimpl InternalShaders {\n    pub(crate) fn load(app: &mut App, shaders: &[&'static str]) {\n        let mut shaders = shaders\n            .iter()\n            .map(|&shader| app.world_mut().resource_mut::<AssetServer>().load(shader))\n            .collect_vec();\n\n        let mut internal_shaders = app.world_mut().resource_mut::<InternalShaders>();\n        internal_shaders.0.append(&mut shaders);\n    }\n}\n\npub(crate) fn load_terrain_shaders(app: &mut App) {\n    embedded_asset!(app, \"types.wgsl\");\n    embedded_asset!(app, \"attachments.wgsl\");\n    embedded_asset!(app, \"bindings.wgsl\");\n    embedded_asset!(app, \"functions.wgsl\");\n    embedded_asset!(app, \"debug.wgsl\");\n    embedded_asset!(app, \"render/vertex.wgsl\");\n    embedded_asset!(app, \"render/fragment.wgsl\");\n    embedded_asset!(app, \"tiling_prepass/prepare_prepass.wgsl\");\n    embedded_asset!(app, \"tiling_prepass/refine_tiles.wgsl\");\n\n    InternalShaders::load(\n        app,\n        &[\n            \"embedded://bevy_terrain/shaders/types.wgsl\",\n            \"embedded://bevy_terrain/shaders/attachments.wgsl\",\n            \"embedded://bevy_terrain/shaders/bindings.wgsl\",\n            \"embedded://bevy_terrain/shaders/functions.wgsl\",\n            \"embedded://bevy_terrain/shaders/debug.wgsl\",\n            \"embedded://bevy_terrain/shaders/render/vertex.wgsl\",\n            \"embedded://bevy_terrain/shaders/render/fragment.wgsl\",\n        ],\n    );\n}\n\npub(crate) fn load_preprocess_shaders(app: &mut App) {\n    embedded_asset!(app, \"preprocess/preprocessing.wgsl\");\n    embedded_asset!(app, \"preprocess/split.wgsl\");\n    embedded_asset!(app, \"preprocess/stitch.wgsl\");\n    embedded_asset!(app, \"preprocess/downsample.wgsl\");\n\n    InternalShaders::load(\n        app,\n        &[\"embedded://bevy_terrain/shaders/preprocess/preprocessing.wgsl\"],\n    );\n}\n"
  },
  {
    "path": "src/shaders/preprocess/downsample.wgsl",
    "content": "#import bevy_terrain::preprocessing::{AtlasTile, atlas, attachment, inside, pixel_coords, pixel_value, process_entry, is_border}\n\nstruct DownsampleData {\n    tile: AtlasTile,\n    child_tiles: array<AtlasTile, 4u>,\n    tile_index: u32,\n}\n\n@group(1) @binding(0)\nvar<uniform> downsample_data: DownsampleData;\n\noverride fn pixel_value(coords: vec2<u32>) -> vec4<f32> {\n    if (is_border(coords)) {\n        return vec4<f32>(0.0);\n    }\n\n    let tile_coords = coords - vec2<u32>(attachment.border_size);\n    let child_size = attachment.center_size / 2u;\n    let child_coords = 2u * (tile_coords % child_size) + vec2<u32>(attachment.border_size);\n    let child_index  = tile_coords.x / child_size + 2u * (tile_coords.y / child_size);\n\n    let child_tile = downsample_data.child_tiles[child_index];\n\n    var OFFSETS = array(vec2(0u, 0u), vec2(0u, 1u), vec2(1u, 0u), vec2(1u, 1u));\n\n    var value = vec4<f32>(0.0);\n    var count = 0.0;\n\n    for (var index = 0u; index < 4u; index += 1u) {\n        let child_value = textureLoad(atlas, child_coords + OFFSETS[index], child_tile.atlas_index, 0);\n        let is_valid  = any(child_value.xyz != vec3(0.0));\n\n        if (is_valid) {\n            value += child_value;\n            count += 1.0;\n        }\n    }\n\n    return value / count;\n}\n\n// Todo: respect memory coalescing\n@compute @workgroup_size(8, 8, 1)\nfn downsample(@builtin(global_invocation_id) invocation_id: vec3<u32>) {\n    process_entry(vec3<u32>(invocation_id.xy, downsample_data.tile_index));\n}"
  },
  {
    "path": "src/shaders/preprocess/preprocessing.wgsl",
    "content": "#define_import_path bevy_terrain::preprocessing\n\nconst FORMAT_R8: u32 = 2u;\nconst FORMAT_RGBA8: u32 = 0u;\nconst FORMAT_R16: u32 = 1u;\n\nconst INVALID_ATLAS_INDEX: u32 = 4294967295u;\n\nstruct TileCoordinate {\n    side: u32,\n    lod: u32,\n    x: u32,\n    y: u32,\n}\n\nstruct AtlasTile {\n    coordinate: TileCoordinate,\n    atlas_index: u32,\n    _padding_a: u32,\n    _padding_b: u32,\n    _padding_c: u32,\n}\n\nstruct AttachmentMeta {\n    format_id: u32,\n    lod_count: u32,\n    texture_size: u32,\n    border_size: u32,\n    center_size: u32,\n    pixels_per_entry: u32,\n    entries_per_side: u32,\n    entries_per_tile: u32,\n}\n\n@group(0) @binding(0)\nvar<storage, read_write> atlas_write_section: array<u32>;\n@group(0) @binding(1)\nvar atlas: texture_2d_array<f32>;\n@group(0) @binding(2)\nvar atlas_sampler: sampler;\n@group(0) @binding(3)\nvar<uniform> attachment: AttachmentMeta;\n\nfn inverse_mix(lower: vec2<f32>, upper: vec2<f32>, value: vec2<f32>) -> vec2<f32> {\n    return (value - lower) / (upper - lower);\n}\n\nfn inside(coords: vec2<u32>, bounds: vec4<u32>) -> bool {\n    return coords.x >= bounds.x &&\n           coords.x <  bounds.x + bounds.z &&\n           coords.y >= bounds.y &&\n           coords.y <  bounds.y + bounds.w;\n}\n\nfn is_border(coords: vec2<u32>) -> bool {\n    return !inside(coords, vec4<u32>(attachment.border_size, attachment.border_size, attachment.center_size, attachment.center_size));\n}\n\nfn pixel_coords(entry_coords: vec3<u32>, pixel_offset: u32) -> vec2<u32> {\n    return vec2<u32>(entry_coords.x * attachment.pixels_per_entry + pixel_offset, entry_coords.y);\n}\n\nvirtual fn pixel_value(coords: vec2<u32>) -> vec4<f32> { return vec4<f32>(0.0); }\n\nfn store_entry(entry_coords: vec3<u32>, entry_value: u32) {\n    let entry_index = entry_coords.z * attachment.entries_per_tile +\n                      entry_coords.y * attachment.entries_per_side +\n                      entry_coords.x;\n\n    atlas_write_section[entry_index] = entry_value;\n}\n\nfn process_entry(entry_coords: vec3<u32>) {\n    if (attachment.format_id == FORMAT_R8) {\n        let entry_value = pack4x8unorm(vec4<f32>(pixel_value(pixel_coords(entry_coords, 0u)).x,\n                                                 pixel_value(pixel_coords(entry_coords, 1u)).x,\n                                                 pixel_value(pixel_coords(entry_coords, 2u)).x,\n                                                 pixel_value(pixel_coords(entry_coords, 3u)).x));\n        store_entry(entry_coords, entry_value);\n    }\n    if (attachment.format_id == FORMAT_RGBA8) {\n        let entry_value = pack4x8unorm(pixel_value(pixel_coords(entry_coords, 0u)));\n        store_entry(entry_coords, entry_value);\n    }\n    if (attachment.format_id == FORMAT_R16) {\n        let entry_value = pack2x16unorm(vec2<f32>(pixel_value(pixel_coords(entry_coords, 0u)).x,\n                                              pixel_value(pixel_coords(entry_coords, 1u)).x));\n        store_entry(entry_coords, entry_value);\n    }\n}\n"
  },
  {
    "path": "src/shaders/preprocess/split.wgsl",
    "content": "#import bevy_terrain::preprocessing::{AtlasTile, atlas, attachment, pixel_coords, pixel_value, process_entry, is_border, inverse_mix}\n#import bevy_terrain::functions::{inside_square, tile_count};\n\nstruct SplitData {\n    tile: AtlasTile,\n    top_left: vec2<f32>,\n    bottom_right: vec2<f32>,\n    tile_index: u32,\n}\n\n@group(1) @binding(0)\nvar<uniform> split_data: SplitData;\n@group(1) @binding(1)\nvar source_tile: texture_2d<f32>;\n@group(1) @binding(2)\nvar source_tile_sampler: sampler;\n\noverride fn pixel_value(coords: vec2<u32>) -> vec4<f32> {\n    if (is_border(coords)) {\n        return vec4<f32>(0.0);\n    }\n\n    let tile_coordinate = split_data.tile.coordinate;\n    let tile_offset =  vec2<f32>(f32(tile_coordinate.x), f32(tile_coordinate.y));\n    let tile_coords = vec2<f32>(coords - vec2<u32>(attachment.border_size)) / f32(attachment.center_size);\n    let tile_scale = tile_count(tile_coordinate.lod);\n\n    var source_coords = (tile_offset + tile_coords) / tile_scale;\n\n    source_coords = inverse_mix(split_data.top_left, split_data.bottom_right, source_coords);\n\n    let value = textureSampleLevel(source_tile, source_tile_sampler, source_coords, 0.0);\n\n    let is_valid  = all(textureGather(0u, source_tile, source_tile_sampler, source_coords) != vec4<f32>(0.0));\n    let is_inside = inside_square(tile_coords, vec2<f32>(0.0), 1.0) == 1.0;\n\n    if (is_valid && is_inside) {\n        return value;\n    }\n    else {\n        return textureLoad(atlas, coords, split_data.tile.atlas_index, 0);\n    }\n}\n\n// Todo: respect memory coalescing\n@compute @workgroup_size(8, 8, 1)\nfn split(@builtin(global_invocation_id) invocation_id: vec3<u32>) {\n    process_entry(vec3<u32>(invocation_id.xy, split_data.tile_index));\n}"
  },
  {
    "path": "src/shaders/preprocess/stitch.wgsl",
    "content": "#import bevy_terrain::preprocessing::{AtlasTile, INVALID_ATLAS_INDEX, atlas, attachment, inside, pixel_coords, pixel_value, process_entry, is_border}\n\nstruct StitchData {\n    tile: AtlasTile,\n    neighbour_tiles: array<AtlasTile, 8u>,\n    tile_index: u32,\n}\n\n@group(1) @binding(0)\nvar<uniform> stitch_data: StitchData;\n\nfn project_to_side(coords: vec2<u32>, original_side: u32, projected_side: u32) -> vec2<u32> {\n    let PS = 0u;\n    let PT = 1u;\n    let NS = 2u;\n    let NT = 3u;\n\n    var EVEN_LIST = array(\n        vec2(PS, PT),\n        vec2(PS, PT),\n        vec2(NT, PS),\n        vec2(NT, NS),\n        vec2(PT ,NS),\n        vec2(PS, PT),\n    );\n    var ODD_LIST = array(\n        vec2(PS, PT),\n        vec2(PS, PT),\n        vec2(PT, NS),\n        vec2(PT, PS),\n        vec2(NT, PS),\n        vec2(PS, PT),\n    );\n\n    let index = (6u + projected_side - original_side) % 6u;\n    let info: vec2<u32> = select(ODD_LIST[index], EVEN_LIST[index], original_side % 2u == 0u);\n\n    var neighbour_coords: vec2<u32>;\n\n    if (info.x == PS)      { neighbour_coords.x =                                coords.x; }\n    else if (info.x == PT) { neighbour_coords.x =                                coords.y; }\n    else if (info.x == NS) { neighbour_coords.x = attachment.texture_size - 1u - coords.x; }\n    else if (info.x == NT) { neighbour_coords.x = attachment.texture_size - 1u - coords.y; }\n\n    if (info.y == PS)      { neighbour_coords.y =                                coords.x; }\n    else if (info.y == PT) { neighbour_coords.y =                                coords.y; }\n    else if (info.y == NS) { neighbour_coords.y = attachment.texture_size - 1u - coords.x; }\n    else if (info.y == NT) { neighbour_coords.y = attachment.texture_size - 1u - coords.y; }\n\n    return neighbour_coords;\n}\n\nfn neighbour_index(coords: vec2<u32>) -> u32 {\n    let center_size   = attachment.center_size;\n    let border_size = attachment.border_size;\n    let offset_size = attachment.border_size + attachment.center_size;\n\n    var bounds = array(\n        vec4(border_size,          0u, center_size, border_size),\n        vec4(offset_size, border_size, border_size, center_size),\n        vec4(border_size, offset_size, center_size, border_size),\n        vec4(         0u, border_size, border_size, center_size),\n        vec4(         0u,          0u, border_size, border_size),\n        vec4(offset_size,          0u, border_size, border_size),\n        vec4(offset_size, offset_size, border_size, border_size),\n        vec4(         0u, offset_size, border_size, border_size)\n    );\n\n    for (var neighbour_index = 0u; neighbour_index < 8u; neighbour_index += 1u) {\n        if (inside(coords, bounds[neighbour_index])) { return neighbour_index; }\n    }\n\n    return 0u;\n}\n\nfn neighbour_data(coords: vec2<u32>, neighbour_index: u32) -> vec4<f32> {\n    let center_size = i32(attachment.center_size);\n\n    var offsets = array(\n        vec2(           0,  center_size),\n        vec2(-center_size,            0),\n        vec2(           0, -center_size),\n        vec2( center_size,            0),\n        vec2( center_size,  center_size),\n        vec2(-center_size,  center_size),\n        vec2(-center_size, -center_size),\n        vec2( center_size, -center_size)\n    );\n\n    let neighbour_tile = stitch_data.neighbour_tiles[neighbour_index];\n    let neighbour_coords = project_to_side(vec2<u32>(vec2<i32>(coords) + offsets[neighbour_index]),\n                                           stitch_data.tile.coordinate.side,\n                                           neighbour_tile.coordinate.side);\n\n    return textureLoad(atlas, neighbour_coords, neighbour_tile.atlas_index, 0);\n}\n\nfn repeat_data(coords: vec2<u32>) -> vec4<f32> {\n    let repeat_coords = clamp(coords, vec2<u32>(attachment.border_size),\n                                      vec2<u32>(attachment.border_size + attachment.center_size - 1u));\n\n    return textureLoad(atlas, repeat_coords, stitch_data.tile.atlas_index, 0);\n}\n\noverride fn pixel_value(coords: vec2<u32>) -> vec4<f32> {\n    if (!is_border(coords)) {\n        return textureLoad(atlas, coords, stitch_data.tile.atlas_index, 0);\n    }\n\n    let neighbour_index = neighbour_index(coords);\n\n    if (stitch_data.neighbour_tiles[neighbour_index].atlas_index == INVALID_ATLAS_INDEX) {\n        return repeat_data(coords);\n    }\n    else {\n        return neighbour_data(coords, neighbour_index);\n    }\n}\n\n// Todo: respect memory coalescing\n@compute @workgroup_size(8, 8, 1)\nfn stitch(@builtin(global_invocation_id) invocation_id: vec3<u32>) {\n    process_entry(vec3<u32>(invocation_id.xy, stitch_data.tile_index));\n}"
  },
  {
    "path": "src/shaders/render/fragment.wgsl",
    "content": "#define_import_path bevy_terrain::fragment\n\n#import bevy_terrain::types::{Blend, AtlasTile, Coordinate}\n#import bevy_terrain::bindings::{config, view_config, geometry_tiles}\n#import bevy_terrain::functions::{compute_blend, lookup_tile}\n#import bevy_terrain::attachments::{sample_normal, sample_color}\n#import bevy_terrain::debug::{show_data_lod, show_geometry_lod, show_tile_tree, show_pixels}\n#import bevy_pbr::mesh_view_bindings::view\n#import bevy_pbr::pbr_types::{PbrInput, pbr_input_new}\n#import bevy_pbr::pbr_functions::{calculate_view, apply_pbr_lighting}\n\nstruct FragmentInput {\n    @builtin(position)     clip_position: vec4<f32>,\n    @location(0)           tile_index: u32,\n    @location(1)           coordinate_uv: vec2<f32>,\n    @location(2)           world_position: vec4<f32>,\n    @location(3)           world_normal: vec3<f32>,\n}\n\nstruct FragmentOutput {\n    @location(0)             color: vec4<f32>\n}\n\nstruct FragmentInfo {\n    coordinate: Coordinate,\n    view_distance: f32,\n    blend: Blend,\n    clip_position: vec4<f32>,\n    world_normal: vec3<f32>,\n    world_position: vec4<f32>,\n    color: vec4<f32>,\n    normal: vec3<f32>,\n}\n\nfn fragment_info(input: FragmentInput) -> FragmentInfo{\n    let tile          = geometry_tiles[input.tile_index];\n    let uv            = input.coordinate_uv;\n    let view_distance = distance(input.world_position.xyz, view.world_position);\n\n    var info: FragmentInfo;\n    info.coordinate     = Coordinate(tile.side, tile.lod, tile.xy, uv, dpdx(uv), dpdy(uv));\n    info.view_distance  = view_distance;\n    info.blend          = compute_blend(view_distance);\n    info.clip_position  = input.clip_position;\n    info.world_normal   = input.world_normal;\n    info.world_position = input.world_position;\n\n    return info;\n}\n\nfn fragment_output(info: ptr<function, FragmentInfo>, output: ptr<function, FragmentOutput>, color: vec4<f32>, normal: vec3<f32>) {\n#ifdef LIGHTING\n    var pbr_input: PbrInput                 = pbr_input_new();\n    pbr_input.material.base_color           = color;\n    pbr_input.material.perceptual_roughness = 1.0;\n    pbr_input.material.reflectance          = 0.0;\n    pbr_input.frag_coord                    = (*info).clip_position;\n    pbr_input.world_position                = (*info).world_position;\n    pbr_input.world_normal                  = (*info).world_normal;\n    pbr_input.N                             = normal;\n    pbr_input.V                             = calculate_view((*info).world_position, pbr_input.is_orthographic);\n\n    (*output).color = apply_pbr_lighting(pbr_input);\n#else\n    (*output).color = color;\n#endif\n}\n\nfn fragment_debug(info: ptr<function, FragmentInfo>, output: ptr<function, FragmentOutput>, tile: AtlasTile, normal: vec3<f32>) {\n#ifdef SHOW_DATA_LOD\n    (*output).color = show_data_lod((*info).blend, tile);\n#endif\n#ifdef SHOW_GEOMETRY_LOD\n    (*output).color = show_geometry_lod((*info).coordinate);\n#endif\n#ifdef SHOW_TILE_TREE\n    (*output).color = show_tile_tree((*info).coordinate);\n#endif\n#ifdef SHOW_PIXELS\n    (*output).color = mix((*output).color, show_pixels(tile), 0.5);\n#endif\n#ifdef SHOW_UV\n    (*output).color = vec4<f32>(tile.coordinate.uv, 0.0, 1.0);\n#endif\n#ifdef SHOW_NORMALS\n    (*output).color = vec4<f32>(normal, 1.0);\n#endif\n\n    // Todo: move this somewhere else\n    if ((*info).view_distance < view_config.precision_threshold_distance) {\n        (*output).color = mix((*output).color, vec4<f32>(0.1), 0.7);\n    }\n}\n\n@fragment\nfn fragment(input: FragmentInput) -> FragmentOutput {\n    var info = fragment_info(input);\n\n    let tile   = lookup_tile(info.coordinate, info.blend, 0u);\n    var color  = sample_color(tile);\n    var normal = sample_normal(tile, info.world_normal);\n\n    if (info.blend.ratio > 0.0) {\n        let tile2 = lookup_tile(info.coordinate, info.blend, 1u);\n        color     = mix(color,  sample_color(tile2),                     info.blend.ratio);\n        normal    = mix(normal, sample_normal(tile2, info.world_normal), info.blend.ratio);\n    }\n\n    var output: FragmentOutput;\n    fragment_output(&info, &output, color, normal);\n    fragment_debug(&info, &output, tile, normal);\n    return output;\n}\n\n"
  },
  {
    "path": "src/shaders/render/vertex.wgsl",
    "content": "#define_import_path bevy_terrain::vertex\n\n#import bevy_terrain::types::{Blend, AtlasTile, Coordinate}\n#import bevy_terrain::bindings::{config, view_config, geometry_tiles, terrain_model_approximation}\n#import bevy_terrain::functions::{lookup_tile, compute_tile_uv, compute_local_position, compute_relative_position, compute_morph, compute_blend, normal_local_to_world, position_local_to_world}\n#import bevy_terrain::attachments::{sample_height}\n#import bevy_pbr::mesh_view_bindings::view\n#import bevy_pbr::view_transformations::position_world_to_clip\n\nstruct VertexInput {\n    @builtin(vertex_index) vertex_index: u32,\n}\n\nstruct VertexOutput {\n    @builtin(position) clip_position: vec4<f32>,\n    @location(0)       tile_index: u32,\n    @location(1)       coordinate_uv: vec2<f32>,\n    @location(2)       world_position: vec4<f32>,\n    @location(3)       world_normal: vec3<f32>,\n}\n\nstruct VertexInfo {\n    tile_index: u32,\n    coordinate: Coordinate,\n    world_position: vec3<f32>,\n    world_normal: vec3<f32>,\n    blend: Blend,\n}\n\nfn vertex_info(input: VertexInput) -> VertexInfo {\n    let tile_index                 = input.vertex_index / view_config.vertices_per_tile;\n    let tile                       = geometry_tiles[tile_index];\n    let tile_uv                    = compute_tile_uv(input.vertex_index);\n    let approximate_coordinate     = Coordinate(tile.side, tile.lod, tile.xy, tile_uv);\n    let approximate_local_position = compute_local_position(approximate_coordinate);\n    let approximate_world_position = position_local_to_world(approximate_local_position);\n    let approximate_world_normal   = normal_local_to_world(approximate_local_position);\n    var approximate_view_distance  = distance(approximate_world_position + terrain_model_approximation.approximate_height * approximate_world_normal, view.world_position);\n\n#ifdef HIGH_PRECISION\n    let high_precision = approximate_view_distance < view_config.precision_threshold_distance;\n#else\n    let high_precision = false;\n#endif\n\n    var coordinate: Coordinate; var world_position: vec3<f32>; var world_normal: vec3<f32>;\n\n    if (high_precision) {\n        let approximate_relative_position = compute_relative_position(approximate_coordinate);\n        approximate_view_distance         = length(approximate_relative_position + terrain_model_approximation.approximate_height * approximate_world_normal);\n\n        coordinate            = compute_morph(approximate_coordinate, approximate_view_distance);\n        let relative_position = compute_relative_position(coordinate);\n        world_position        = view.world_position + relative_position;\n        world_normal          = approximate_world_normal;\n    } else {\n        coordinate         = compute_morph(approximate_coordinate, approximate_view_distance);\n        let local_position = compute_local_position(coordinate);\n        world_position     = position_local_to_world(local_position);\n        world_normal       = normal_local_to_world(local_position);\n    }\n\n    var info: VertexInfo;\n    info.tile_index     = tile_index;\n    info.coordinate     = coordinate;\n    info.world_position = world_position;\n    info.world_normal   = world_normal;\n    info.blend          = compute_blend(approximate_view_distance);\n\n    return info;\n}\n\nfn vertex_output(info: ptr<function, VertexInfo>, height: f32) -> VertexOutput {\n    let world_position = (*info).world_position + height * (*info).world_normal;\n\n    var output: VertexOutput;\n    output.clip_position  = position_world_to_clip(world_position);\n    output.tile_index     = (*info).tile_index;\n    output.coordinate_uv  = (*info).coordinate.uv;\n    output.world_position = vec4<f32>(world_position, 1.0);\n    output.world_normal   = (*info).world_normal;\n    return output;\n}\n\n@vertex\nfn vertex(input: VertexInput) -> VertexOutput {\n    var info = vertex_info(input);\n\n    let tile   = lookup_tile(info.coordinate, info.blend, 0u);\n    var height = sample_height(tile);\n\n    if (info.blend.ratio > 0.0) {\n        let tile2 = lookup_tile(info.coordinate, info.blend, 1u);\n        height    = mix(height, sample_height(tile2), info.blend.ratio);\n    }\n\n    return vertex_output(&info, height);\n}\n"
  },
  {
    "path": "src/shaders/tiling_prepass/prepare_prepass.wgsl",
    "content": "#import bevy_terrain::types::TileCoordinate\n#import bevy_terrain::bindings::{view_config, temporary_tiles, parameters, indirect_buffer}\n\n@compute @workgroup_size(1, 1, 1)\nfn prepare_root() {\n    parameters.counter = -1;\n    atomicStore(&parameters.child_index, i32(view_config.tile_count - 1u));\n    atomicStore(&parameters.final_index, 0);\n\n#ifdef SPHERICAL\n    parameters.tile_count = 6u;\n\n    for (var i: u32 = 0u; i < 6u; i = i + 1u) {\n        temporary_tiles[i] = TileCoordinate(i, 0u, vec2<u32>(0u));\n    }\n#else\n    parameters.tile_count = 1u;\n\n    temporary_tiles[0] = TileCoordinate(0u, 0u, vec2<u32>(0u));\n#endif\n\n    indirect_buffer.workgroup_count = vec3<u32>(1u, 1u, 1u);\n}\n\n@compute @workgroup_size(1, 1, 1)\nfn prepare_next() {\n    if (parameters.counter == 1) {\n        parameters.tile_count = u32(atomicExchange(&parameters.child_index, i32(view_config.tile_count - 1u)));\n    }\n    else {\n        parameters.tile_count = view_config.tile_count - 1u - u32(atomicExchange(&parameters.child_index, 0));\n    }\n\n    parameters.counter = -parameters.counter;\n    indirect_buffer.workgroup_count.x = (parameters.tile_count + 63u) / 64u;\n}\n\n@compute @workgroup_size(1, 1, 1)\nfn prepare_render() {\n    let tile_count = u32(atomicLoad(&parameters.final_index));\n    let vertex_count = view_config.vertices_per_tile * tile_count;\n\n    indirect_buffer.workgroup_count = vec3<u32>(vertex_count, 1u, 0u);\n}"
  },
  {
    "path": "src/shaders/tiling_prepass/refine_tiles.wgsl",
    "content": "#import bevy_terrain::types::{TileCoordinate, Coordinate}\n#import bevy_terrain::bindings::{config, culling_view, view_config, final_tiles, temporary_tiles, parameters, terrain_model_approximation}\n#import bevy_terrain::functions::{approximate_view_distance, compute_relative_position, position_local_to_world, normal_local_to_world, tile_count, compute_subdivision_coordinate}\n\nfn child_index() -> i32 {\n    return atomicAdd(&parameters.child_index, parameters.counter);\n}\n\nfn parent_index(id: u32) -> i32 {\n    return i32(view_config.tile_count - 1u) * clamp(parameters.counter, 0, 1) - i32(id) * parameters.counter;\n}\n\nfn final_index() -> i32 {\n    return atomicAdd(&parameters.final_index, 1);\n}\n\nfn should_be_divided(tile: TileCoordinate) -> bool {\n    let coordinate    = compute_subdivision_coordinate(Coordinate(tile.side, tile.lod, tile.xy, vec2<f32>(0.0)));\n    let view_distance = approximate_view_distance(coordinate, culling_view.world_position);\n\n    return view_distance < view_config.subdivision_distance / tile_count(tile.lod);\n}\n\nfn subdivide(tile: TileCoordinate) {\n    for (var i: u32 = 0u; i < 4u; i = i + 1u) {\n        let child_xy  = vec2<u32>((tile.xy.x << 1u) + (i & 1u), (tile.xy.y << 1u) + (i >> 1u & 1u));\n        let child_lod = tile.lod + 1u;\n\n        temporary_tiles[child_index()] = TileCoordinate(tile.side, child_lod, child_xy);\n    }\n}\n\n@compute @workgroup_size(64, 1, 1)\nfn refine_tiles(@builtin(global_invocation_id) invocation_id: vec3<u32>) {\n    if (invocation_id.x >= parameters.tile_count) { return; }\n\n    let tile = temporary_tiles[parent_index(invocation_id.x)];\n\n    if (should_be_divided(tile)) {\n        subdivide(tile);\n    } else {\n        final_tiles[final_index()] = tile;\n    }\n}\n"
  },
  {
    "path": "src/shaders/types.wgsl",
    "content": "#define_import_path bevy_terrain::types\n\nstruct TerrainConfig {\n    lod_count: u32,\n    min_height: f32,\n    max_height: f32,\n    scale: f32,\n}\n\nstruct TerrainViewConfig {\n    tree_size: u32,\n    tile_count: u32,\n    refinement_count: u32,\n    grid_size: f32,\n    vertices_per_row: u32,\n    vertices_per_tile: u32,\n    morph_distance: f32,\n    blend_distance: f32,\n    load_distance: f32,\n    subdivision_distance: f32,\n    morph_range: f32,\n    blend_range: f32,\n    precision_threshold_distance: f32,\n}\n\nstruct TileCoordinate {\n    side: u32,\n    lod: u32,\n    xy: vec2<u32>,\n}\n\nstruct Coordinate {\n    side: u32,\n    lod: u32,\n    xy: vec2<u32>,\n    uv: vec2<f32>,\n#ifdef FRAGMENT\n    uv_dx: vec2<f32>,\n    uv_dy: vec2<f32>,\n#endif\n}\n\nstruct Parameters {\n    tile_count: u32,\n    counter: i32,\n    child_index: atomic<i32>,\n    final_index: atomic<i32>,\n}\n\nstruct Blend {\n    lod: u32,\n    ratio: f32,\n}\n\nstruct TileTreeEntry {\n    atlas_index: u32,\n    atlas_lod: u32,\n}\n\n// A tile inside the tile atlas, looked up based on the view of a tile tree.\nstruct AtlasTile {\n    index: u32,\n    coordinate: Coordinate,\n}\n\nstruct BestLookup {\n    tile: AtlasTile,\n    tile_tree_uv: vec2<f32>,\n}\n\nstruct AttachmentConfig {\n    size: f32,\n    scale: f32,\n    offset: f32,\n    _padding: u32,\n}\n\nstruct SideParameter {\n    view_xy: vec2<i32>,\n    view_uv: vec2<f32>,\n    c: vec3<f32>,\n    c_s: vec3<f32>,\n    c_t: vec3<f32>,\n    c_ss: vec3<f32>,\n    c_st: vec3<f32>,\n    c_tt: vec3<f32>,\n}\n\nstruct TerrainModelApproximation {\n    origin_lod: u32,\n    approximate_height: f32,\n    sides: array<SideParameter, 6>,\n}\n\nstruct IndirectBuffer {\n    workgroup_count: vec3<u32>,\n}\n\nstruct CullingData {\n    world_position: vec3<f32>,\n    view_proj: mat4x4<f32>,\n    planes: array<vec4<f32>, 5>,\n}\n"
  },
  {
    "path": "src/terrain.rs",
    "content": "//! Types for configuring terrains.\n//!\n#[cfg(feature = \"high_precision\")]\nuse crate::big_space::{GridCell, GridTransformOwned, ReferenceFrame};\n\nuse crate::{\n    math::TerrainModel,\n    terrain_data::{tile_atlas::TileAtlas, AttachmentConfig},\n};\nuse bevy::{ecs::entity::EntityHashMap, prelude::*, render::view::NoFrustumCulling};\n\n/// Resource that stores components that are associated to a terrain entity.\n/// This is used to persist components in the render world.\n#[derive(Deref, DerefMut, Resource)]\npub struct TerrainComponents<C>(EntityHashMap<C>);\n\nimpl<C> Default for TerrainComponents<C> {\n    fn default() -> Self {\n        Self(default())\n    }\n}\n\n/// The configuration of a terrain.\n///\n/// Here you can define all fundamental parameters of the terrain.\n#[derive(Clone)]\npub struct TerrainConfig {\n    /// The count of level of detail layers.\n    pub lod_count: u32,\n    pub model: TerrainModel,\n    /// The amount of tiles the can be loaded simultaneously in the tile atlas.\n    pub atlas_size: u32,\n    /// The path to the terrain folder inside the assets directory.\n    pub path: String,\n    /// The attachments of the terrain.\n    pub attachments: Vec<AttachmentConfig>,\n}\n\nimpl Default for TerrainConfig {\n    fn default() -> Self {\n        Self {\n            lod_count: 1,\n            model: TerrainModel::sphere(default(), 1.0, 0.0, 1.0),\n            atlas_size: 1024,\n            path: default(),\n            attachments: default(),\n        }\n    }\n}\n\nimpl TerrainConfig {\n    pub fn add_attachment(mut self, attachment_config: AttachmentConfig) -> Self {\n        self.attachments.push(attachment_config);\n        self\n    }\n}\n\n/// The components of a terrain.\n///\n/// Does not include loader(s) and a material.\n#[derive(Bundle)]\npub struct TerrainBundle {\n    pub tile_atlas: TileAtlas,\n    #[cfg(feature = \"high_precision\")]\n    pub cell: GridCell,\n    pub transform: Transform,\n    pub global_transform: GlobalTransform,\n    pub visibility_bundle: VisibilityBundle,\n    pub no_frustum_culling: NoFrustumCulling,\n}\n\nimpl TerrainBundle {\n    /// Creates a new terrain bundle from the config.\n\n    pub fn new(\n        tile_atlas: TileAtlas,\n        #[cfg(feature = \"high_precision\")] frame: &ReferenceFrame,\n    ) -> Self {\n        #[cfg(feature = \"high_precision\")]\n        let GridTransformOwned { transform, cell } = tile_atlas.model.grid_transform(frame);\n        #[cfg(not(feature = \"high_precision\"))]\n        let transform = tile_atlas.model.transform();\n\n        Self {\n            tile_atlas,\n            transform,\n            #[cfg(feature = \"high_precision\")]\n            cell,\n            global_transform: default(),\n            visibility_bundle: VisibilityBundle {\n                visibility: Visibility::Visible,\n                inherited_visibility: default(),\n                view_visibility: default(),\n            },\n            no_frustum_culling: NoFrustumCulling,\n        }\n    }\n}\n"
  },
  {
    "path": "src/terrain_data/gpu_tile_atlas.rs",
    "content": "use crate::{\n    terrain::TerrainComponents,\n    terrain_data::{\n        tile_atlas::{\n            AtlasAttachment, AtlasTileAttachment, AtlasTileAttachmentWithData, TileAtlas,\n        },\n        AttachmentData, AttachmentFormat,\n    },\n    util::StaticBuffer,\n};\nuse bevy::{\n    prelude::*,\n    render::{\n        render_resource::{binding_types::*, *},\n        renderer::{RenderDevice, RenderQueue},\n        Extract, MainWorld,\n    },\n    tasks::{AsyncComputeTaskPool, Task},\n};\nuse itertools::Itertools;\nuse std::{iter, mem};\n\nconst COPY_BYTES_PER_ROW_ALIGNMENT: u32 = 256;\n\nfn align_byte_size(value: u32) -> u32 {\n    // only works for non zero values\n    value - 1 - (value - 1) % COPY_BYTES_PER_ROW_ALIGNMENT + COPY_BYTES_PER_ROW_ALIGNMENT\n}\n\npub(crate) fn create_attachment_layout(device: &RenderDevice) -> BindGroupLayout {\n    device.create_bind_group_layout(\n        None,\n        &BindGroupLayoutEntries::sequential(\n            ShaderStages::COMPUTE,\n            (\n                storage_buffer::<u32>(false), // atlas_write_section\n                texture_2d_array(TextureSampleType::Float { filterable: true }), // atlas\n                sampler(SamplerBindingType::Filtering), // atlas sampler\n                uniform_buffer::<AttachmentMeta>(false), // attachment meta\n            ),\n        ),\n    )\n}\n\n#[derive(Default, ShaderType)]\npub(crate) struct AttachmentMeta {\n    pub(crate) format_id: u32,\n    pub(crate) lod_count: u32,\n    pub(crate) texture_size: u32,\n    pub(crate) border_size: u32,\n    pub(crate) center_size: u32,\n    pub(crate) pixels_per_entry: u32,\n    pub(crate) entries_per_side: u32,\n    pub(crate) entries_per_tile: u32,\n}\n\n#[derive(Clone, Copy, Debug)]\npub(crate) struct AtlasBufferInfo {\n    lod_count: u32,\n    pub(crate) texture_size: u32,\n    pub(crate) border_size: u32,\n    pub(crate) center_size: u32,\n    format: AttachmentFormat,\n    mip_level_count: u32,\n\n    pixels_per_entry: u32,\n\n    entries_per_side: u32,\n    entries_per_tile: u32,\n\n    actual_side_size: u32,\n    aligned_side_size: u32,\n    actual_tile_size: u32,\n    aligned_tile_size: u32,\n\n    pub(crate) workgroup_count: UVec3,\n}\n\nimpl AtlasBufferInfo {\n    fn new(attachment: &AtlasAttachment, lod_count: u32) -> Self {\n        // Todo: adjust this code for pixel sizes larger than 4 byte\n        // This approach is currently limited to 1, 2, and 4 byte sized pixels\n        // Extending it to 8 and 16 sized pixels should be quite easy.\n        // However 3, 6, 12 sized pixels do and will not work!\n        // For them to work properly we will need to write into a texture instead of buffer.\n\n        let format = attachment.format;\n        let texture_size = attachment.texture_size;\n        let border_size = attachment.border_size;\n        let center_size = attachment.center_size;\n        let mip_level_count = attachment.mip_level_count;\n\n        let pixel_size = format.pixel_size();\n        let entry_size = mem::size_of::<u32>() as u32;\n        let pixels_per_entry = entry_size / pixel_size;\n\n        let actual_side_size = texture_size * pixel_size;\n        let aligned_side_size = align_byte_size(actual_side_size);\n        let actual_tile_size = texture_size * actual_side_size;\n        let aligned_tile_size = texture_size * aligned_side_size;\n\n        let entries_per_side = aligned_side_size / entry_size;\n        let entries_per_tile = texture_size * entries_per_side;\n\n        let workgroup_count = UVec3::new(entries_per_side / 8, texture_size / 8, 1);\n\n        Self {\n            lod_count,\n            border_size,\n            center_size,\n            texture_size,\n            mip_level_count,\n            pixels_per_entry,\n            entries_per_side,\n            entries_per_tile,\n            actual_side_size,\n            aligned_side_size,\n            actual_tile_size,\n            aligned_tile_size,\n            format,\n            workgroup_count,\n        }\n    }\n\n    fn image_copy_texture<'a>(\n        &'a self,\n        texture: &'a Texture,\n        index: u32,\n        mip_level: u32,\n    ) -> ImageCopyTexture {\n        ImageCopyTexture {\n            texture,\n            mip_level,\n            origin: Origin3d {\n                z: index,\n                ..default()\n            },\n            aspect: TextureAspect::All,\n        }\n    }\n\n    fn image_copy_buffer<'a>(&'a self, buffer: &'a Buffer, index: u32) -> ImageCopyBuffer {\n        ImageCopyBuffer {\n            buffer,\n            layout: ImageDataLayout {\n                bytes_per_row: Some(self.aligned_side_size),\n                rows_per_image: Some(self.texture_size),\n                offset: self.buffer_size(index) as BufferAddress,\n            },\n        }\n    }\n\n    fn image_copy_size(&self, mip_level: u32) -> Extent3d {\n        Extent3d {\n            width: self.texture_size >> mip_level,\n            height: self.texture_size >> mip_level,\n            depth_or_array_layers: 1,\n        }\n    }\n\n    fn buffer_size(&self, slots: u32) -> u32 {\n        slots * self.aligned_tile_size\n    }\n\n    fn attachment_meta(&self) -> AttachmentMeta {\n        AttachmentMeta {\n            format_id: self.format.id(),\n            lod_count: self.lod_count,\n            texture_size: self.texture_size,\n            border_size: self.border_size,\n            center_size: self.center_size,\n            pixels_per_entry: self.pixels_per_entry,\n            entries_per_side: self.entries_per_side,\n            entries_per_tile: self.entries_per_tile,\n        }\n    }\n}\n\npub(crate) struct GpuAtlasAttachment {\n    pub(crate) name: String,\n    pub(crate) buffer_info: AtlasBufferInfo,\n\n    pub(crate) atlas_texture: Texture,\n    pub(crate) atlas_write_section: StaticBuffer<()>,\n    pub(crate) download_buffers: Vec<StaticBuffer<()>>,\n    pub(crate) bind_group: BindGroup,\n\n    pub(crate) max_atlas_write_slots: u32,\n    pub(crate) atlas_write_slots: Vec<AtlasTileAttachment>,\n    pub(crate) upload_tiles: Vec<AtlasTileAttachmentWithData>,\n    pub(crate) download_tiles: Vec<Task<AtlasTileAttachmentWithData>>,\n}\n\nimpl GpuAtlasAttachment {\n    pub(crate) fn new(\n        device: &RenderDevice,\n        attachment: &AtlasAttachment,\n        tile_atlas: &TileAtlas,\n    ) -> Self {\n        let name = attachment.name.clone();\n        let max_atlas_write_slots = tile_atlas.state.max_atlas_write_slots;\n        let atlas_write_slots = Vec::with_capacity(max_atlas_write_slots as usize);\n\n        let buffer_info = AtlasBufferInfo::new(attachment, tile_atlas.lod_count);\n\n        // dbg!(&buffer_info);\n\n        let atlas_texture = device.create_texture(&TextureDescriptor {\n            label: Some(&format!(\"{name}_attachment\")),\n            size: Extent3d {\n                width: buffer_info.texture_size,\n                height: buffer_info.texture_size,\n                depth_or_array_layers: tile_atlas.atlas_size,\n            },\n            mip_level_count: attachment.mip_level_count,\n            sample_count: 1,\n            dimension: TextureDimension::D2,\n            format: buffer_info.format.render_format(),\n            usage: TextureUsages::COPY_DST\n                | TextureUsages::COPY_SRC\n                | TextureUsages::TEXTURE_BINDING,\n            view_formats: &[buffer_info.format.processing_format()],\n        });\n\n        let atlas_view = atlas_texture.create_view(&TextureViewDescriptor {\n            format: Some(buffer_info.format.processing_format()),\n            ..default()\n        });\n\n        let atlas_sampler = device.create_sampler(&SamplerDescriptor {\n            mag_filter: FilterMode::Linear,\n            min_filter: FilterMode::Linear,\n            mipmap_filter: FilterMode::Linear,\n            ..default()\n        });\n\n        let atlas_write_section = StaticBuffer::empty_sized(\n            format!(\"{name}_atlas_write_section\").as_str(),\n            device,\n            buffer_info.buffer_size(max_atlas_write_slots) as BufferAddress,\n            BufferUsages::COPY_DST | BufferUsages::COPY_SRC | BufferUsages::STORAGE,\n        );\n\n        let attachment_meta_buffer = StaticBuffer::create(\n            format!(\"{name}_attachment_meta\").as_str(),\n            device,\n            &buffer_info.attachment_meta(),\n            BufferUsages::UNIFORM,\n        );\n\n        let bind_group = device.create_bind_group(\n            format!(\"{name}attachment_bind_group\").as_str(),\n            &create_attachment_layout(device),\n            &BindGroupEntries::sequential((\n                &atlas_write_section,\n                &atlas_view,\n                &atlas_sampler,\n                &attachment_meta_buffer,\n            )),\n        );\n\n        Self {\n            name,\n            buffer_info,\n            atlas_texture,\n            atlas_write_section,\n            download_buffers: default(),\n            bind_group,\n            max_atlas_write_slots,\n            atlas_write_slots,\n            upload_tiles: default(),\n            download_tiles: default(),\n        }\n    }\n\n    pub(crate) fn reserve_write_slot(&mut self, tile: AtlasTileAttachment) -> Option<u32> {\n        if self.atlas_write_slots.len() < self.max_atlas_write_slots as usize {\n            self.atlas_write_slots.push(tile);\n            Some(self.atlas_write_slots.len() as u32 - 1)\n        } else {\n            None\n        }\n    }\n\n    pub(crate) fn copy_tiles_to_write_section(&self, command_encoder: &mut CommandEncoder) {\n        for (section_index, tile) in self.atlas_write_slots.iter().enumerate() {\n            command_encoder.copy_texture_to_buffer(\n                self.buffer_info\n                    .image_copy_texture(&self.atlas_texture, tile.atlas_index, 0),\n                self.buffer_info\n                    .image_copy_buffer(&self.atlas_write_section, section_index as u32),\n                self.buffer_info.image_copy_size(0),\n            );\n        }\n    }\n\n    pub(crate) fn copy_tiles_from_write_section(&self, command_encoder: &mut CommandEncoder) {\n        for (section_index, tile) in self.atlas_write_slots.iter().enumerate() {\n            command_encoder.copy_buffer_to_texture(\n                self.buffer_info\n                    .image_copy_buffer(&self.atlas_write_section, section_index as u32),\n                self.buffer_info\n                    .image_copy_texture(&self.atlas_texture, tile.atlas_index, 0),\n                self.buffer_info.image_copy_size(0),\n            );\n        }\n    }\n\n    fn upload_tiles(&mut self, queue: &RenderQueue) {\n        for tile in self.upload_tiles.drain(..) {\n            let mut start = 0;\n\n            for mip_level in 0..self.buffer_info.mip_level_count {\n                let side_size = self.buffer_info.actual_side_size >> mip_level;\n                let texture_size = self.buffer_info.texture_size >> mip_level;\n                let end = start + (side_size * texture_size) as usize;\n\n                queue.write_texture(\n                    self.buffer_info.image_copy_texture(\n                        &self.atlas_texture,\n                        tile.tile.atlas_index,\n                        mip_level,\n                    ),\n                    &tile.data.bytes()[start..end],\n                    ImageDataLayout {\n                        offset: 0,\n                        bytes_per_row: Some(side_size),\n                        rows_per_image: Some(texture_size),\n                    },\n                    self.buffer_info.image_copy_size(mip_level),\n                );\n\n                start = end;\n            }\n        }\n    }\n\n    pub(crate) fn download_tiles(&self, command_encoder: &mut CommandEncoder) {\n        for (tile, download_buffer) in iter::zip(&self.atlas_write_slots, &self.download_buffers) {\n            command_encoder.copy_texture_to_buffer(\n                self.buffer_info\n                    .image_copy_texture(&self.atlas_texture, tile.atlas_index, 0),\n                self.buffer_info.image_copy_buffer(download_buffer, 0),\n                self.buffer_info.image_copy_size(0),\n            );\n        }\n    }\n\n    fn create_download_buffers(&mut self, device: &RenderDevice) {\n        self.download_buffers = (0..self.atlas_write_slots.len())\n            .map(|i| {\n                StaticBuffer::empty_sized(\n                    format!(\"{}_download_buffer_{i}\", self.name).as_str(),\n                    device,\n                    self.buffer_info.aligned_tile_size as BufferAddress,\n                    BufferUsages::COPY_DST | BufferUsages::MAP_READ,\n                )\n            })\n            .collect_vec();\n    }\n\n    fn start_downloading_tiles(&mut self) {\n        let buffer_info = self.buffer_info;\n        let download_buffers = mem::take(&mut self.download_buffers);\n        let atlas_write_slots = mem::take(&mut self.atlas_write_slots);\n\n        self.download_tiles = iter::zip(atlas_write_slots, download_buffers)\n            .map(|(tile, download_buffer)| {\n                AsyncComputeTaskPool::get().spawn(async move {\n                    let (tx, rx) = async_channel::bounded(1);\n\n                    let buffer_slice = download_buffer.slice(..);\n\n                    buffer_slice.map_async(MapMode::Read, move |_| {\n                        tx.try_send(()).unwrap();\n                    });\n\n                    rx.recv().await.unwrap();\n\n                    let mut data = buffer_slice.get_mapped_range().to_vec();\n\n                    download_buffer.unmap();\n                    drop(download_buffer);\n\n                    if data.len() != buffer_info.actual_tile_size as usize {\n                        let actual_side_size = buffer_info.actual_side_size as usize;\n                        let aligned_side_size = buffer_info.aligned_side_size as usize;\n\n                        let mut take_offset = aligned_side_size;\n                        let mut place_offset = actual_side_size;\n\n                        for _ in 1..buffer_info.texture_size {\n                            data.copy_within(\n                                take_offset..take_offset + aligned_side_size,\n                                place_offset,\n                            );\n                            take_offset += aligned_side_size;\n                            place_offset += actual_side_size;\n                        }\n\n                        data.truncate(buffer_info.actual_tile_size as usize);\n                    }\n\n                    AtlasTileAttachmentWithData {\n                        tile,\n                        data: AttachmentData::from_bytes(&data, buffer_info.format),\n                        texture_size: buffer_info.texture_size,\n                    }\n                })\n            })\n            .collect_vec();\n    }\n}\n\n/// Stores the GPU representation of the [`TileAtlas`] (array textures)\n/// alongside the data to update it.\n///\n/// All attachments of newly loaded tiles are copied into their according atlas attachment.\n#[derive(Component)]\npub struct GpuTileAtlas {\n    /// Stores the atlas attachments of the terrain.\n    pub(crate) attachments: Vec<GpuAtlasAttachment>,\n    pub(crate) is_spherical: bool,\n}\n\nimpl GpuTileAtlas {\n    /// Creates a new gpu tile atlas and initializes its attachment textures.\n    fn new(device: &RenderDevice, tile_atlas: &TileAtlas) -> Self {\n        let attachments = tile_atlas\n            .attachments\n            .iter()\n            .map(|attachment| GpuAtlasAttachment::new(device, attachment, tile_atlas))\n            .collect_vec();\n\n        Self {\n            attachments,\n            is_spherical: tile_atlas.model.is_spherical(),\n        }\n    }\n\n    /// Initializes the [`GpuTileAtlas`] of newly created terrains.\n    pub(crate) fn initialize(\n        device: Res<RenderDevice>,\n        mut gpu_tile_atlases: ResMut<TerrainComponents<GpuTileAtlas>>,\n        mut tile_atlases: Extract<Query<(Entity, &TileAtlas), Added<TileAtlas>>>,\n    ) {\n        for (terrain, tile_atlas) in tile_atlases.iter_mut() {\n            gpu_tile_atlases.insert(terrain, GpuTileAtlas::new(&device, tile_atlas));\n        }\n    }\n\n    /// Extracts the tiles that have finished loading from all [`TileAtlas`]es into the\n    /// corresponding [`GpuTileAtlas`]es.\n    pub(crate) fn extract(\n        mut main_world: ResMut<MainWorld>,\n        mut gpu_tile_atlases: ResMut<TerrainComponents<GpuTileAtlas>>,\n    ) {\n        let mut tile_atlases = main_world.query::<(Entity, &mut TileAtlas)>();\n\n        for (terrain, mut tile_atlas) in tile_atlases.iter_mut(&mut main_world) {\n            let gpu_tile_atlas = gpu_tile_atlases.get_mut(&terrain).unwrap();\n\n            for (attachment, gpu_attachment) in\n                iter::zip(&mut tile_atlas.attachments, &mut gpu_tile_atlas.attachments)\n            {\n                mem::swap(\n                    &mut attachment.uploading_tiles,\n                    &mut gpu_attachment.upload_tiles,\n                );\n\n                attachment\n                    .downloading_tiles\n                    .extend(mem::take(&mut gpu_attachment.download_tiles));\n            }\n        }\n    }\n\n    /// Queues the attachments of the tiles that have finished loading to be copied into the\n    /// corresponding atlas attachments.\n    pub(crate) fn prepare(\n        device: Res<RenderDevice>,\n        queue: Res<RenderQueue>,\n        mut gpu_tile_atlases: ResMut<TerrainComponents<GpuTileAtlas>>,\n    ) {\n        for gpu_tile_atlas in gpu_tile_atlases.values_mut() {\n            for attachment in &mut gpu_tile_atlas.attachments {\n                attachment.create_download_buffers(&device);\n                attachment.upload_tiles(&queue);\n            }\n        }\n    }\n\n    pub(crate) fn cleanup(mut gpu_tile_atlases: ResMut<TerrainComponents<GpuTileAtlas>>) {\n        for gpu_tile_atlas in gpu_tile_atlases.values_mut() {\n            for attachment in &mut gpu_tile_atlas.attachments {\n                attachment.start_downloading_tiles();\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "src/terrain_data/gpu_tile_tree.rs",
    "content": "use crate::{\n    terrain_data::tile_tree::{TileTree, TileTreeEntry},\n    terrain_view::TerrainViewComponents,\n    util::StaticBuffer,\n};\nuse bevy::{\n    prelude::*,\n    render::{\n        render_resource::*,\n        renderer::{RenderDevice, RenderQueue},\n        Extract,\n    },\n};\nuse bytemuck::cast_slice;\nuse ndarray::{Array2, Array4};\nuse std::mem;\n\n/// Stores the GPU representation of the [`TileTree`] (array texture)\n/// alongside the data to update it.\n///\n/// The data is synchronized each frame by copying it from the [`TileTree`] to the texture.\n#[derive(Component)]\npub struct GpuTileTree {\n    pub(crate) tile_tree_buffer: StaticBuffer<()>,\n    pub(crate) origins_buffer: StaticBuffer<()>,\n    /// The current cpu tile_tree data. This is synced each frame with the tile_tree data.\n    data: Array4<TileTreeEntry>,\n    origins: Array2<UVec2>,\n}\n\nimpl GpuTileTree {\n    fn new(device: &RenderDevice, tile_tree: &TileTree) -> Self {\n        let tile_tree_buffer = StaticBuffer::empty_sized(\n            None,\n            device,\n            (tile_tree.data.len() * mem::size_of::<TileTreeEntry>()) as BufferAddress,\n            BufferUsages::STORAGE | BufferUsages::COPY_DST,\n        );\n\n        let origins_buffer = StaticBuffer::empty_sized(\n            None,\n            device,\n            (tile_tree.origins.len() * mem::size_of::<UVec2>()) as BufferAddress,\n            BufferUsages::STORAGE | BufferUsages::COPY_DST,\n        );\n\n        Self {\n            tile_tree_buffer,\n            origins_buffer,\n            data: default(),\n            origins: default(),\n        }\n    }\n\n    /// Initializes the [`GpuTileTree`] of newly created terrains.\n    pub(crate) fn initialize(\n        device: Res<RenderDevice>,\n        mut gpu_tile_trees: ResMut<TerrainViewComponents<GpuTileTree>>,\n        tile_trees: Extract<Res<TerrainViewComponents<TileTree>>>,\n    ) {\n        for (&(terrain, view), tile_tree) in tile_trees.iter() {\n            if gpu_tile_trees.contains_key(&(terrain, view)) {\n                return;\n            }\n\n            gpu_tile_trees.insert((terrain, view), GpuTileTree::new(&device, tile_tree));\n        }\n    }\n\n    /// Extracts the current data from all [`TileTree`]s into the corresponding [`GpuTileTree`]s.\n    pub(crate) fn extract(\n        mut gpu_tile_trees: ResMut<TerrainViewComponents<GpuTileTree>>,\n        tile_trees: Extract<Res<TerrainViewComponents<TileTree>>>,\n    ) {\n        for (&(terrain, view), tile_tree) in tile_trees.iter() {\n            let gpu_tile_tree = gpu_tile_trees.get_mut(&(terrain, view)).unwrap();\n\n            gpu_tile_tree.data = tile_tree.data.clone();\n            gpu_tile_tree.origins = tile_tree.origins.clone();\n        }\n    }\n\n    /// Prepares the tile_tree data to be copied into the tile_tree texture.\n    pub(crate) fn prepare(\n        queue: Res<RenderQueue>,\n        mut gpu_tile_trees: ResMut<TerrainViewComponents<GpuTileTree>>,\n    ) {\n        for gpu_tile_tree in gpu_tile_trees.values_mut() {\n            let data = cast_slice(gpu_tile_tree.data.as_slice().unwrap());\n            gpu_tile_tree.tile_tree_buffer.update_bytes(&queue, data);\n\n            let origins = cast_slice(gpu_tile_tree.origins.as_slice().unwrap());\n            gpu_tile_tree.origins_buffer.update_bytes(&queue, origins);\n        }\n    }\n}\n"
  },
  {
    "path": "src/terrain_data/mod.rs",
    "content": "//! This module contains the two fundamental data structures of the terrain:\n//! the [`TileTree`] and the [`TileAtlas`].\n//!\n//! # Explanation\n//! Each terrain possesses one [`TileAtlas`], which can be configured\n//! to store any [`AtlasAttachment`](tile_atlas::AtlasAttachment) required (eg. height, density, albedo, splat, edc.)\n//! These attachments can vary in resolution and texture format.\n//!\n//! To decide which tiles should be currently loaded you can create multiple\n//! [`TileTree`] views that correspond to one tile atlas.\n//! These tile_trees request and release tiles from the tile atlas based on their quality\n//! setting (`load_distance`).\n//! Additionally they are then used to access the best loaded data at any position.\n//!\n//! Both the tile atlas and the tile_trees also have a corresponding GPU representation,\n//! which can be used to access the terrain data in shaders.\n\nuse crate::{\n    terrain_data::{tile_atlas::TileAtlas, tile_tree::TileTree},\n    util::CollectArray,\n};\nuse bevy::{math::DVec3, prelude::*, render::render_resource::*};\nuse bincode::{Decode, Encode};\nuse bytemuck::cast_slice;\nuse itertools::iproduct;\nuse std::iter;\n\npub mod gpu_tile_atlas;\npub mod gpu_tile_tree;\npub mod tile_atlas;\npub mod tile_tree;\n\npub const INVALID_ATLAS_INDEX: u32 = u32::MAX;\npub const INVALID_LOD: u32 = u32::MAX;\n\n/// The data format of an attachment.\n#[derive(Encode, Decode, Clone, Copy, Debug)]\npub enum AttachmentFormat {\n    /// Three channels  8 bit\n    Rgb8,\n    /// Four  channels  8 bit\n    Rgba8,\n    /// One   channel  16 bit\n    R16,\n    /// Two   channels 16 bit\n    Rg16,\n}\n\nimpl AttachmentFormat {\n    pub(crate) fn id(self) -> u32 {\n        match self {\n            AttachmentFormat::Rgb8 => 5,\n            AttachmentFormat::Rgba8 => 0,\n            AttachmentFormat::R16 => 1,\n            AttachmentFormat::Rg16 => 3,\n        }\n    }\n    pub(crate) fn render_format(self) -> TextureFormat {\n        match self {\n            AttachmentFormat::Rgb8 => TextureFormat::Rgba8UnormSrgb,\n            AttachmentFormat::Rgba8 => TextureFormat::Rgba8UnormSrgb,\n            AttachmentFormat::R16 => TextureFormat::R16Unorm,\n            AttachmentFormat::Rg16 => TextureFormat::Rg16Unorm,\n        }\n    }\n\n    pub(crate) fn processing_format(self) -> TextureFormat {\n        match self {\n            AttachmentFormat::Rgb8 => TextureFormat::Rgba8Unorm,\n            AttachmentFormat::Rgba8 => TextureFormat::Rgba8Unorm,\n            AttachmentFormat::R16 => TextureFormat::R16Unorm,\n            AttachmentFormat::Rg16 => TextureFormat::Rg16Unorm,\n        }\n    }\n\n    pub(crate) fn pixel_size(self) -> u32 {\n        match self {\n            AttachmentFormat::Rgb8 => 3,\n            AttachmentFormat::Rgba8 => 4,\n            AttachmentFormat::R16 => 2,\n            AttachmentFormat::Rg16 => 4,\n        }\n    }\n}\n\n/// Configures an attachment.\n#[derive(Encode, Decode, Clone, Debug)]\npub struct AttachmentConfig {\n    /// The name of the attachment.\n    pub name: String,\n    pub texture_size: u32,\n    /// The overlapping border size around the tile, used to prevent sampling artifacts.\n    pub border_size: u32,\n    pub mip_level_count: u32,\n    /// The format of the attachment.\n    pub format: AttachmentFormat,\n}\n\nimpl Default for AttachmentConfig {\n    fn default() -> Self {\n        Self {\n            name: \"\".to_string(),\n            texture_size: 512,\n            border_size: 1,\n            mip_level_count: 1,\n            format: AttachmentFormat::R16,\n        }\n    }\n}\n\n#[derive(Clone)]\npub(crate) enum AttachmentData {\n    None,\n    /// Three channels  8 bit\n    // Rgb8(Vec<(u8, u8, u8)>), Can not be represented currently\n    /// Four  channels  8 bit\n    Rgba8(Vec<[u8; 4]>),\n    /// One   channel  16 bit\n    R16(Vec<u16>),\n    /// Two   channels 16 bit\n    Rg16(Vec<[u16; 2]>),\n}\n\nimpl AttachmentData {\n    pub(crate) fn from_bytes(data: &[u8], format: AttachmentFormat) -> Self {\n        match format {\n            AttachmentFormat::Rgb8 => unimplemented!(),\n            AttachmentFormat::Rgba8 => Self::Rgba8(cast_slice(data).to_vec()),\n            AttachmentFormat::R16 => Self::R16(cast_slice(data).to_vec()),\n            AttachmentFormat::Rg16 => Self::Rg16(cast_slice(data).to_vec()),\n        }\n    }\n\n    pub(crate) fn bytes(&self) -> &[u8] {\n        match self {\n            AttachmentData::Rgba8(data) => cast_slice(data),\n            AttachmentData::R16(data) => cast_slice(data),\n            AttachmentData::Rg16(data) => cast_slice(data),\n            AttachmentData::None => panic!(\"Attachment has no data.\"),\n        }\n    }\n\n    pub(crate) fn generate_mipmaps(&mut self, texture_size: u32, mip_level_count: u32) {\n        fn generate_mipmap_rgba8(\n            data: &mut Vec<[u8; 4]>,\n            parent_size: usize,\n            child_size: usize,\n            start: usize,\n        ) {\n            for (child_y, child_x) in iproduct!(0..child_size, 0..child_size) {\n                let mut value = [0u64; 4];\n\n                for i in 0..4 {\n                    let parent_x = (child_x << 1) + (i >> 1);\n                    let parent_y = (child_y << 1) + (i & 1);\n\n                    let index = start + parent_y * parent_size + parent_x;\n\n                    iter::zip(&mut value, data[index]).for_each(|(value, v)| *value += v as u64);\n                }\n\n                let value = value.iter().map(|value| (value / 4) as u8).collect_array();\n\n                data.push(value);\n            }\n        }\n\n        fn generate_mipmap_r16(\n            data: &mut Vec<u16>,\n            parent_size: usize,\n            child_size: usize,\n            start: usize,\n        ) {\n            for (child_y, child_x) in iproduct!(0..child_size, 0..child_size) {\n                let mut value = 0;\n                let mut count = 0;\n\n                for (parent_x, parent_y) in\n                    iproduct!(0..2, 0..2).map(|(x, y)| ((child_x << 1) + x, (child_y << 1) + y))\n                {\n                    let index = start + parent_y * parent_size + parent_x;\n                    let data = data[index] as u32;\n\n                    if data != 0 {\n                        value += data;\n                        count += 1;\n                    }\n                }\n\n                let value = if count == 0 {\n                    0\n                } else {\n                    (value / count) as u16\n                };\n\n                data.push(value);\n            }\n        }\n\n        let mut start = 0;\n        let mut parent_size = texture_size as usize;\n\n        for _mip_level in 1..mip_level_count {\n            let child_size = parent_size >> 1;\n\n            match self {\n                AttachmentData::Rgba8(data) => {\n                    generate_mipmap_rgba8(data, parent_size, child_size, start)\n                }\n                AttachmentData::R16(data) => {\n                    generate_mipmap_r16(data, parent_size, child_size, start)\n                }\n                _ => {}\n            }\n\n            start += parent_size * parent_size;\n            parent_size = child_size;\n        }\n    }\n\n    pub(crate) fn sample(&self, uv: Vec2, size: u32) -> Vec4 {\n        let uv = uv * size as f32 - 0.5;\n\n        let remainder = uv % 1.0;\n        let uv = uv.as_ivec2();\n\n        let mut values = [[Vec4::ZERO; 2]; 2];\n\n        for (x, y) in iproduct!(0..2, 0..2) {\n            let index = (uv.y + y) * size as i32 + (uv.x + x);\n\n            values[x as usize][y as usize] = match self {\n                AttachmentData::None => Vec4::splat(0.0),\n                AttachmentData::Rgba8(data) => {\n                    let value = data[index as usize];\n                    Vec4::new(\n                        value[0] as f32 / u8::MAX as f32,\n                        value[1] as f32 / u8::MAX as f32,\n                        value[2] as f32 / u8::MAX as f32,\n                        value[3] as f32 / u8::MAX as f32,\n                    )\n                }\n                AttachmentData::R16(data) => {\n                    let value = data[index as usize];\n                    Vec4::new(value as f32 / u16::MAX as f32, 0.0, 0.0, 0.0)\n                }\n                AttachmentData::Rg16(data) => {\n                    let value = data[index as usize];\n                    Vec4::new(\n                        value[0] as f32 / u16::MAX as f32,\n                        value[1] as f32 / u16::MAX as f32,\n                        0.0,\n                        0.0,\n                    )\n                }\n            };\n        }\n\n        Vec4::lerp(\n            Vec4::lerp(values[0][0], values[0][1], remainder.y),\n            Vec4::lerp(values[1][0], values[1][1], remainder.y),\n            remainder.x,\n        )\n    }\n}\n\npub fn sample_attachment(\n    tile_tree: &TileTree,\n    tile_atlas: &TileAtlas,\n    attachment_index: u32,\n    sample_world_position: DVec3,\n) -> Vec4 {\n    let model = &tile_atlas.model;\n\n    // translate the sample position onto the terrain's surface\n    // this is necessary to compute a valid blend LOD and ratio\n    let surface_position =\n        model.surface_position(sample_world_position, tile_tree.approximate_height as f64);\n\n    let (lod, blend_ratio) = tile_tree.compute_blend(surface_position);\n\n    let lookup = tile_tree.lookup_tile(surface_position, lod, model);\n    let mut value = tile_atlas.sample_attachment(lookup, attachment_index);\n\n    if blend_ratio > 0.0 {\n        let lookup2 = tile_tree.lookup_tile(surface_position, lod - 1, model);\n        value = Vec4::lerp(\n            value,\n            tile_atlas.sample_attachment(lookup2, attachment_index),\n            blend_ratio,\n        );\n    }\n\n    value\n}\n\npub fn sample_height(\n    tile_tree: &TileTree,\n    tile_atlas: &TileAtlas,\n    sample_world_position: DVec3,\n) -> f32 {\n    f32::lerp(\n        tile_atlas.model.min_height,\n        tile_atlas.model.max_height,\n        sample_attachment(tile_tree, tile_atlas, 0, sample_world_position).x,\n    )\n}\n"
  },
  {
    "path": "src/terrain_data/tile_atlas.rs",
    "content": "use crate::{\n    formats::TC,\n    math::{TerrainModel, TileCoordinate},\n    prelude::{AttachmentConfig, AttachmentFormat},\n    terrain::TerrainConfig,\n    terrain_data::{\n        tile_tree::{TileLookup, TileTree, TileTreeEntry},\n        AttachmentData, INVALID_ATLAS_INDEX, INVALID_LOD,\n    },\n    terrain_view::TerrainViewComponents,\n};\nuse anyhow::Result;\nuse bevy::{\n    prelude::*,\n    render::render_resource::*,\n    tasks::{futures_lite::future, AsyncComputeTaskPool, Task},\n    utils::{HashMap, HashSet},\n};\nuse image::{io::Reader, DynamicImage, ImageBuffer, Luma, LumaA, Rgb, Rgba};\nuse itertools::Itertools;\nuse std::{collections::VecDeque, fs, mem, ops::DerefMut};\n\npub type Rgb8Image = ImageBuffer<Rgb<u8>, Vec<u8>>;\npub type Rgba8Image = ImageBuffer<Rgba<u8>, Vec<u8>>;\npub type R16Image = ImageBuffer<Luma<u16>, Vec<u16>>;\npub type Rg16Image = ImageBuffer<LumaA<u16>, Vec<u16>>;\n\nconst STORE_PNG: bool = false;\n\n#[derive(Copy, Clone, Debug, Default, ShaderType)]\npub struct AtlasTile {\n    pub(crate) coordinate: TileCoordinate,\n    #[size(16)]\n    pub(crate) atlas_index: u32,\n}\n\nimpl AtlasTile {\n    pub fn new(tile_coordinate: TileCoordinate, atlas_index: u32) -> Self {\n        Self {\n            coordinate: tile_coordinate,\n            atlas_index,\n        }\n    }\n    pub fn attachment(self, attachment_index: u32) -> AtlasTileAttachment {\n        AtlasTileAttachment {\n            coordinate: self.coordinate,\n            atlas_index: self.atlas_index,\n            attachment_index,\n        }\n    }\n}\n\nimpl From<AtlasTileAttachment> for AtlasTile {\n    fn from(tile: AtlasTileAttachment) -> Self {\n        Self {\n            coordinate: tile.coordinate,\n            atlas_index: tile.atlas_index,\n        }\n    }\n}\n\n#[derive(Copy, Clone, Debug, Default)]\npub struct AtlasTileAttachment {\n    pub(crate) coordinate: TileCoordinate,\n    pub(crate) atlas_index: u32,\n    pub(crate) attachment_index: u32,\n}\n\n#[derive(Clone)]\npub(crate) struct AtlasTileAttachmentWithData {\n    pub(crate) tile: AtlasTileAttachment,\n    pub(crate) data: AttachmentData,\n    pub(crate) texture_size: u32,\n}\n\nimpl AtlasTileAttachmentWithData {\n    pub(crate) fn start_saving(self, path: String) -> Task<AtlasTileAttachment> {\n        AsyncComputeTaskPool::get().spawn(async move {\n            if STORE_PNG {\n                let path = self.tile.coordinate.path(&path, \"png\");\n\n                let image = match self.data {\n                    AttachmentData::Rgba8(data) => {\n                        let data = data.into_iter().flatten().collect_vec();\n                        DynamicImage::from(\n                            Rgba8Image::from_raw(self.texture_size, self.texture_size, data)\n                                .unwrap(),\n                        )\n                    }\n                    AttachmentData::R16(data) => DynamicImage::from(\n                        R16Image::from_raw(self.texture_size, self.texture_size, data).unwrap(),\n                    ),\n                    AttachmentData::Rg16(data) => {\n                        let data = data.into_iter().flatten().collect_vec();\n                        DynamicImage::from(\n                            Rg16Image::from_raw(self.texture_size, self.texture_size, data)\n                                .unwrap(),\n                        )\n                    }\n                    AttachmentData::None => panic!(\"Attachment has not data.\"),\n                };\n\n                image.save(&path).unwrap();\n\n                println!(\"Finished saving tile: {path}\");\n            } else {\n                let path = self.tile.coordinate.path(&path, \"bin\");\n\n                fs::write(path, self.data.bytes()).unwrap();\n\n                // println!(\"Finished saving tile: {path}\");\n            }\n\n            self.tile\n        })\n    }\n\n    pub(crate) fn start_loading(\n        tile: AtlasTileAttachment,\n        path: String,\n        texture_size: u32,\n        format: AttachmentFormat,\n        mip_level_count: u32,\n    ) -> Task<Result<Self>> {\n        AsyncComputeTaskPool::get().spawn(async move {\n            let mut data = if STORE_PNG {\n                let path = tile.coordinate.path(&path, \"png\");\n\n                let mut reader = Reader::open(path)?;\n                reader.no_limits();\n                let image = reader.decode().unwrap();\n                AttachmentData::from_bytes(image.as_bytes(), format)\n            } else {\n                let path = tile.coordinate.path(&path, \"bin\");\n\n                let bytes = fs::read(path)?;\n\n                AttachmentData::from_bytes(&bytes, format)\n            };\n\n            data.generate_mipmaps(texture_size, mip_level_count);\n\n            Ok(Self {\n                tile,\n                data,\n                texture_size: 0,\n            })\n        })\n    }\n}\n\n/// An attachment of a [`TileAtlas`].\npub struct AtlasAttachment {\n    pub(crate) name: String,\n    pub(crate) path: String,\n    pub(crate) texture_size: u32,\n    pub(crate) center_size: u32,\n    pub(crate) border_size: u32,\n    scale: f32,\n    offset: f32,\n    pub(crate) mip_level_count: u32,\n    pub(crate) format: AttachmentFormat,\n    pub(crate) data: Vec<AttachmentData>,\n\n    pub(crate) saving_tiles: Vec<Task<AtlasTileAttachment>>,\n    pub(crate) loading_tiles: Vec<Task<Result<AtlasTileAttachmentWithData>>>,\n    pub(crate) uploading_tiles: Vec<AtlasTileAttachmentWithData>,\n    pub(crate) downloading_tiles: Vec<Task<AtlasTileAttachmentWithData>>,\n}\n\nimpl AtlasAttachment {\n    fn new(config: &AttachmentConfig, tile_atlas_size: u32, path: &str) -> Self {\n        let name = config.name.clone();\n        let path = format!(\"assets/{path}/data/{name}\");\n        let center_size = config.texture_size - 2 * config.border_size;\n\n        Self {\n            name,\n            path,\n            texture_size: config.texture_size,\n            center_size,\n            border_size: config.border_size,\n            scale: center_size as f32 / config.texture_size as f32,\n            offset: config.border_size as f32 / config.texture_size as f32,\n            mip_level_count: config.mip_level_count,\n            format: config.format,\n            data: vec![AttachmentData::None; tile_atlas_size as usize],\n            saving_tiles: default(),\n            loading_tiles: default(),\n            uploading_tiles: default(),\n            downloading_tiles: default(),\n        }\n    }\n\n    fn update(&mut self, atlas_state: &mut TileAtlasState) {\n        self.loading_tiles.retain_mut(|tile| {\n            future::block_on(future::poll_once(tile)).map_or(true, |tile| {\n                if let Ok(tile) = tile {\n                    atlas_state.loaded_tile_attachment(tile.tile);\n                    self.uploading_tiles.push(tile.clone());\n                    self.data[tile.tile.atlas_index as usize] = tile.data;\n                } else {\n                    atlas_state.load_slots += 1;\n                }\n\n                false\n            })\n        });\n\n        self.downloading_tiles.retain_mut(|tile| {\n            future::block_on(future::poll_once(tile)).map_or(true, |tile| {\n                atlas_state.downloaded_tile_attachment(tile.tile);\n                self.data[tile.tile.atlas_index as usize] = tile.data;\n                false\n            })\n        });\n\n        self.saving_tiles.retain_mut(|task| {\n            future::block_on(future::poll_once(task)).map_or(true, |tile| {\n                atlas_state.saved_tile_attachment(tile);\n                false\n            })\n        });\n    }\n\n    fn load(&mut self, tile: AtlasTileAttachment) {\n        // Todo: build customizable loader abstraction\n        self.loading_tiles\n            .push(AtlasTileAttachmentWithData::start_loading(\n                tile,\n                self.path.clone(),\n                self.texture_size,\n                self.format,\n                self.mip_level_count,\n            ));\n    }\n\n    fn save(&mut self, tile: AtlasTileAttachment) {\n        self.saving_tiles.push(\n            AtlasTileAttachmentWithData {\n                tile: tile,\n                data: self.data[tile.atlas_index as usize].clone(),\n                texture_size: self.texture_size,\n            }\n            .start_saving(self.path.clone()),\n        );\n    }\n\n    fn sample(&self, lookup: TileLookup) -> Vec4 {\n        if lookup.atlas_index == INVALID_ATLAS_INDEX {\n            return Vec4::splat(0.0); // Todo: Handle this better\n        }\n\n        let data = &self.data[lookup.atlas_index as usize];\n        let uv = lookup.atlas_uv * self.scale + self.offset;\n\n        data.sample(uv, self.texture_size)\n    }\n}\n\n/// The current state of a tile of a [`TileAtlas`].\n///\n/// This indicates, whether the tile is loading or loaded and ready to be used.\n#[derive(Clone, Copy)]\nenum LoadingState {\n    /// The tile is loading, but can not be used yet.\n    Loading(u32),\n    /// The tile is loaded and can be used.\n    Loaded,\n}\n\n/// The internal representation of a present tile in a [`TileAtlas`].\nstruct TileState {\n    /// Indicates whether or not the tile is loading or loaded.\n    state: LoadingState,\n    /// The index of the tile inside the atlas.\n    atlas_index: u32,\n    /// The count of [`TileTrees`] that have requested this tile.\n    requests: u32,\n}\n\npub(crate) struct TileAtlasState {\n    tile_states: HashMap<TileCoordinate, TileState>,\n    unused_tiles: VecDeque<AtlasTile>,\n    pub(crate) existing_tiles: HashSet<TileCoordinate>,\n\n    attachment_count: u32,\n\n    to_load: VecDeque<AtlasTileAttachment>,\n    load_slots: u32,\n    to_save: VecDeque<AtlasTileAttachment>,\n    pub(crate) save_slots: u32,\n    pub(crate) max_save_slots: u32,\n\n    pub(crate) download_slots: u32,\n    pub(crate) max_download_slots: u32,\n\n    pub(crate) max_atlas_write_slots: u32,\n}\n\nimpl TileAtlasState {\n    fn new(\n        atlas_size: u32,\n        attachment_count: u32,\n        existing_tiles: HashSet<TileCoordinate>,\n    ) -> Self {\n        let unused_tiles = (0..atlas_size)\n            .map(|atlas_index| AtlasTile::new(TileCoordinate::INVALID, atlas_index))\n            .collect();\n\n        Self {\n            tile_states: default(),\n            unused_tiles,\n            existing_tiles,\n            attachment_count,\n            to_save: default(),\n            to_load: default(),\n            save_slots: 64,\n            max_save_slots: 64,\n            load_slots: 64,\n            download_slots: 128,\n            max_download_slots: 128,\n            max_atlas_write_slots: 32,\n        }\n    }\n\n    fn update(&mut self, attachments: &mut [AtlasAttachment]) {\n        while self.save_slots > 0 {\n            if let Some(tile) = self.to_save.pop_front() {\n                attachments[tile.attachment_index as usize].save(tile);\n                self.save_slots -= 1;\n            } else {\n                break;\n            }\n        }\n\n        while self.load_slots > 0 {\n            if let Some(tile) = self.to_load.pop_front() {\n                attachments[tile.attachment_index as usize].load(tile);\n                self.load_slots -= 1;\n            } else {\n                break;\n            }\n        }\n    }\n\n    fn loaded_tile_attachment(&mut self, tile: AtlasTileAttachment) {\n        self.load_slots += 1;\n\n        let tile_state = self.tile_states.get_mut(&tile.coordinate).unwrap();\n\n        tile_state.state = match tile_state.state {\n            LoadingState::Loading(1) => LoadingState::Loaded,\n            LoadingState::Loading(n) => LoadingState::Loading(n - 1),\n            LoadingState::Loaded => {\n                panic!(\"Loaded more attachments, than registered with the tile atlas.\")\n            }\n        };\n    }\n\n    fn saved_tile_attachment(&mut self, _tile: AtlasTileAttachment) {\n        self.save_slots += 1;\n    }\n\n    fn downloaded_tile_attachment(&mut self, _tile: AtlasTileAttachment) {\n        self.download_slots += 1;\n    }\n\n    fn get_tile(&mut self, tile_coordinate: TileCoordinate) -> AtlasTile {\n        if tile_coordinate == TileCoordinate::INVALID {\n            return AtlasTile::new(TileCoordinate::INVALID, INVALID_ATLAS_INDEX);\n        }\n\n        let atlas_index = if self.existing_tiles.contains(&tile_coordinate) {\n            self.tile_states.get(&tile_coordinate).unwrap().atlas_index\n        } else {\n            INVALID_ATLAS_INDEX\n        };\n\n        AtlasTile::new(tile_coordinate, atlas_index)\n    }\n\n    fn allocate_tile(&mut self) -> u32 {\n        let unused_tile = self.unused_tiles.pop_front().expect(\"Atlas out of indices\");\n\n        self.tile_states.remove(&unused_tile.coordinate);\n\n        unused_tile.atlas_index\n    }\n\n    fn get_or_allocate_tile(&mut self, tile_coordinate: TileCoordinate) -> AtlasTile {\n        if tile_coordinate == TileCoordinate::INVALID {\n            return AtlasTile::new(TileCoordinate::INVALID, INVALID_ATLAS_INDEX);\n        }\n\n        self.existing_tiles.insert(tile_coordinate);\n\n        let atlas_index = if let Some(tile) = self.tile_states.get(&tile_coordinate) {\n            tile.atlas_index\n        } else {\n            let atlas_index = self.allocate_tile();\n\n            self.tile_states.insert(\n                tile_coordinate,\n                TileState {\n                    requests: 1,\n                    state: LoadingState::Loaded,\n                    atlas_index,\n                },\n            );\n\n            atlas_index\n        };\n\n        AtlasTile::new(tile_coordinate, atlas_index)\n    }\n\n    fn request_tile(&mut self, tile_coordinate: TileCoordinate) {\n        if !self.existing_tiles.contains(&tile_coordinate) {\n            return;\n        }\n\n        let mut tile_states = mem::take(&mut self.tile_states);\n\n        // check if the tile is already present else start loading it\n        if let Some(tile) = tile_states.get_mut(&tile_coordinate) {\n            if tile.requests == 0 {\n                // the tile is now used again\n                self.unused_tiles\n                    .retain(|unused_tile| tile.atlas_index != unused_tile.atlas_index);\n            }\n\n            tile.requests += 1;\n        } else {\n            // Todo: implement better loading strategy\n            let atlas_index = self.allocate_tile();\n\n            tile_states.insert(\n                tile_coordinate,\n                TileState {\n                    requests: 1,\n                    state: LoadingState::Loading(self.attachment_count),\n                    atlas_index,\n                },\n            );\n\n            for attachment_index in 0..self.attachment_count {\n                self.to_load.push_back(AtlasTileAttachment {\n                    coordinate: tile_coordinate,\n                    atlas_index,\n                    attachment_index,\n                });\n            }\n        }\n\n        self.tile_states = tile_states;\n    }\n\n    fn release_tile(&mut self, tile_coordinate: TileCoordinate) {\n        if !self.existing_tiles.contains(&tile_coordinate) {\n            return;\n        }\n\n        let tile = self\n            .tile_states\n            .get_mut(&tile_coordinate)\n            .expect(\"Tried releasing a tile, which is not present.\");\n        tile.requests -= 1;\n\n        if tile.requests == 0 {\n            // the tile is not used anymore\n            self.unused_tiles\n                .push_back(AtlasTile::new(tile_coordinate, tile.atlas_index));\n        }\n    }\n\n    fn get_best_tile(&self, tile_coordinate: TileCoordinate) -> TileTreeEntry {\n        let mut best_tile_coordinate = tile_coordinate;\n\n        loop {\n            if best_tile_coordinate == TileCoordinate::INVALID\n                || best_tile_coordinate.lod == INVALID_LOD\n            {\n                // highest lod is not loaded\n                return TileTreeEntry {\n                    atlas_index: INVALID_ATLAS_INDEX,\n                    atlas_lod: INVALID_LOD,\n                };\n            }\n\n            if let Some(atlas_tile) = self.tile_states.get(&best_tile_coordinate) {\n                if matches!(atlas_tile.state, LoadingState::Loaded) {\n                    // found best loaded tile\n                    return TileTreeEntry {\n                        atlas_index: atlas_tile.atlas_index,\n                        atlas_lod: best_tile_coordinate.lod,\n                    };\n                }\n            }\n\n            best_tile_coordinate = best_tile_coordinate.parent();\n        }\n    }\n}\n\n/// A sparse storage of all terrain attachments, which streams data in and out of memory\n/// depending on the decisions of the corresponding [`TileTree`]s.\n///\n/// A tile is considered present and assigned an [`u32`] as soon as it is\n/// requested by any tile_tree. Then the tile atlas will start loading all of its attachments\n/// by storing the [`TileCoordinate`] (for one frame) in `load_events` for which\n/// attachment-loading-systems can listen.\n/// Tiles that are not being used by any tile_tree anymore are cached (LRU),\n/// until new atlas indices are required.\n///\n/// The [`u32`] can be used for accessing the attached data in systems by the CPU\n/// and in shaders by the GPU.\n#[derive(Component)]\npub struct TileAtlas {\n    pub(crate) attachments: Vec<AtlasAttachment>,\n    // stores the attachment data\n    pub(crate) state: TileAtlasState,\n    pub(crate) path: String,\n    pub(crate) atlas_size: u32,\n    pub(crate) lod_count: u32,\n    pub(crate) model: TerrainModel,\n}\n\nimpl TileAtlas {\n    /// Creates a new tile_tree from a terrain config.\n    pub fn new(config: &TerrainConfig) -> Self {\n        let attachments = config\n            .attachments\n            .iter()\n            .map(|attachment| AtlasAttachment::new(attachment, config.atlas_size, &config.path))\n            .collect_vec();\n\n        let existing_tiles = Self::load_tile_config(&config.path);\n\n        let state =\n            TileAtlasState::new(config.atlas_size, attachments.len() as u32, existing_tiles);\n\n        Self {\n            model: config.model.clone(),\n            attachments,\n            state,\n            path: config.path.to_string(),\n            atlas_size: config.atlas_size,\n            lod_count: config.lod_count,\n        }\n    }\n\n    pub fn get_tile(&mut self, tile_coordinate: TileCoordinate) -> AtlasTile {\n        self.state.get_tile(tile_coordinate)\n    }\n\n    pub fn get_or_allocate_tile(&mut self, tile_coordinate: TileCoordinate) -> AtlasTile {\n        self.state.get_or_allocate_tile(tile_coordinate)\n    }\n\n    pub fn save(&mut self, tile: AtlasTileAttachment) {\n        self.state.to_save.push_back(tile);\n    }\n\n    pub(super) fn get_best_tile(&self, tile_coordinate: TileCoordinate) -> TileTreeEntry {\n        self.state.get_best_tile(tile_coordinate)\n    }\n\n    pub(super) fn sample_attachment(&self, tile_lookup: TileLookup, attachment_index: u32) -> Vec4 {\n        self.attachments[attachment_index as usize].sample(tile_lookup)\n    }\n\n    /// Updates the tile atlas according to all corresponding tile_trees.\n    pub(crate) fn update(\n        mut tile_trees: ResMut<TerrainViewComponents<TileTree>>,\n        mut tile_atlases: Query<&mut TileAtlas>,\n    ) {\n        for mut tile_atlas in tile_atlases.iter_mut() {\n            let TileAtlas {\n                state, attachments, ..\n            } = tile_atlas.deref_mut();\n\n            state.update(attachments);\n\n            for attachment in attachments {\n                attachment.update(state);\n            }\n        }\n\n        for (&(terrain, _view), tile_tree) in tile_trees.iter_mut() {\n            let mut tile_atlas = tile_atlases.get_mut(terrain).unwrap();\n\n            for tile_coordinate in tile_tree.released_tiles.drain(..) {\n                tile_atlas.state.release_tile(tile_coordinate);\n            }\n\n            for tile_coordinate in tile_tree.requested_tiles.drain(..) {\n                tile_atlas.state.request_tile(tile_coordinate);\n            }\n        }\n    }\n\n    /// Saves the tile configuration of the terrain, which stores the [`TileCoordinate`]s of all the tiles\n    /// of the terrain.\n    pub(crate) fn save_tile_config(&self) {\n        let tc = TC {\n            tiles: self.state.existing_tiles.iter().copied().collect_vec(),\n        };\n\n        tc.save_file(format!(\"assets/{}/config.tc\", &self.path))\n            .unwrap();\n    }\n\n    /// Loads the tile configuration of the terrain, which stores the [`TileCoordinate`]s of all the tiles\n    /// of the terrain.\n    pub(crate) fn load_tile_config(path: &str) -> HashSet<TileCoordinate> {\n        if let Ok(tc) = TC::load_file(format!(\"assets/{}/config.tc\", path)) {\n            tc.tiles.into_iter().collect()\n        } else {\n            println!(\"Tile config not found.\");\n            HashSet::default()\n        }\n    }\n}\n"
  },
  {
    "path": "src/terrain_data/tile_tree.rs",
    "content": "use crate::{\n    math::{Coordinate, TerrainModel, TileCoordinate},\n    terrain_data::{sample_height, tile_atlas::TileAtlas, INVALID_ATLAS_INDEX, INVALID_LOD},\n    terrain_view::{TerrainViewComponents, TerrainViewConfig},\n    util::inverse_mix,\n};\nuse bevy::{\n    math::{DVec2, DVec3},\n    prelude::*,\n};\nuse bytemuck::{Pod, Zeroable};\nuse itertools::iproduct;\nuse ndarray::{Array2, Array4};\nuse std::iter;\n\n/// The current state of a tile of a [`TileTree`].\n///\n/// This indicates, whether or not the tile should be loaded into the [`TileAtlas`).\n#[derive(Clone, Copy, PartialEq, Eq)]\nenum RequestState {\n    /// The tile should be loaded.\n    Requested,\n    /// The tile does not have to be loaded.\n    Released,\n}\n\n/// The internal representation of a tile in a [`TileTree`].\nstruct TileState {\n    /// The current tile coordinate at the tile_tree position.\n    coordinate: TileCoordinate,\n    /// Indicates, whether the tile is currently demanded or released.\n    state: RequestState,\n}\n\nimpl Default for TileState {\n    fn default() -> Self {\n        Self {\n            coordinate: TileCoordinate::INVALID,\n            state: RequestState::Released,\n        }\n    }\n}\n\n/// An entry of the [`TileTree`], used to access the best currently loaded tile\n/// of the [`TileAtlas`] on the CPU.\n///\n/// These entries are synced each frame with their equivalent representations in the\n/// [`GpuTileTree`](super::gpu_tile_tree::GpuTileTree) for access on the GPU.\n#[repr(C)]\n#[derive(Clone, Copy, Debug, Zeroable, Pod)]\npub(super) struct TileTreeEntry {\n    /// The atlas index of the best entry.\n    pub(super) atlas_index: u32,\n    /// The atlas lod of the best entry.\n    pub(super) atlas_lod: u32,\n}\n\nimpl Default for TileTreeEntry {\n    fn default() -> Self {\n        Self {\n            atlas_index: INVALID_ATLAS_INDEX,\n            atlas_lod: INVALID_LOD,\n        }\n    }\n}\n\n#[allow(dead_code)]\n#[derive(Clone, Copy, Debug)]\npub(super) struct TileLookup {\n    pub(super) atlas_index: u32,\n    pub(super) atlas_lod: u32,\n    pub(super) atlas_uv: Vec2,\n}\n\nimpl TileLookup {\n    pub(super) const INVALID: Self = Self {\n        atlas_index: INVALID_ATLAS_INDEX,\n        atlas_lod: INVALID_LOD,\n        atlas_uv: Vec2::ZERO,\n    };\n}\n\n/// A quadtree-like view of a terrain, that requests and releases tiles from the [`TileAtlas`]\n/// depending on the distance to the viewer.\n///\n/// It can be used to access the best currently loaded tile of the [`TileAtlas`].\n/// Additionally its sends this data to the GPU via the\n/// [`GpuTileTree`](super::gpu_tile_tree::GpuTileTree) so that it can be utilised\n/// in shaders as well.\n///\n/// Each view (camera, shadow-casting light) that should consider the terrain has to\n/// have an associated tile tree.\n///\n/// This tile tree is a \"cube\" with a size of (`tree_size`x`tree_size`x`lod_count`), where each layer\n/// corresponds to a lod. These layers are wrapping (modulo `tree_size`), that means that\n/// the tile tree is always centered under the viewer and only considers `tree_size` / 2 tiles\n/// in each direction.\n///\n/// Each frame the tile tree determines the state of each tile via the\n/// `compute_requests` methode.\n/// After the [`TileAtlas`] has adjusted to these requests, the tile tree retrieves the best\n/// currently loaded tiles from the tile atlas via the `adjust` methode, which can later be used to access the terrain data.\n#[derive(Component)]\npub struct TileTree {\n    pub(super) origins: Array2<UVec2>,\n    /// The current cpu tile_tree data. This is synced each frame with the gpu tile_tree data.\n    pub(super) data: Array4<TileTreeEntry>,\n    /// Tiles that are no longer required by this tile_tree.\n    pub(super) released_tiles: Vec<TileCoordinate>,\n    /// Tiles that are requested to be loaded by this tile_tree.\n    pub(super) requested_tiles: Vec<TileCoordinate>,\n    /// The internal tile states of the tile_tree.\n    tiles: Array4<TileState>,\n    /// The count of level of detail layers.\n    lod_count: u32,\n    /// The count of tiles in x and y direction per layer.\n    pub(crate) tree_size: u32,\n    pub(crate) geometry_tile_count: u32,\n    pub(crate) refinement_count: u32,\n    pub(crate) grid_size: u32,\n    pub(crate) morph_distance: f64,\n    pub(crate) blend_distance: f64,\n    pub(crate) load_distance: f64,\n    pub(crate) subdivision_distance: f64,\n    pub(crate) precision_threshold_distance: f64,\n    pub(crate) morph_range: f32,\n    pub(crate) blend_range: f32,\n    pub(crate) origin_lod: u32,\n    pub(crate) view_world_position: DVec3,\n    pub(crate) approximate_height: f32,\n}\n\nimpl TileTree {\n    /// Creates a new tile_tree from a terrain and a terrain view config.\n    pub fn new(tile_atlas: &TileAtlas, view_config: &TerrainViewConfig) -> Self {\n        let model = &tile_atlas.model;\n        let scale = model.scale();\n\n        Self {\n            lod_count: tile_atlas.lod_count,\n            tree_size: view_config.tree_size,\n            geometry_tile_count: view_config.geometry_tile_count,\n            refinement_count: view_config.refinement_count,\n            grid_size: view_config.grid_size,\n            morph_distance: view_config.morph_distance * scale,\n            blend_distance: view_config.blend_distance * scale,\n            load_distance: view_config.load_distance * scale,\n            subdivision_distance: view_config.morph_distance\n                * scale\n                * (1.0 + view_config.subdivision_tolerance),\n            morph_range: view_config.morph_range,\n            blend_range: view_config.blend_range,\n            precision_threshold_distance: view_config.precision_threshold_distance * scale,\n            origin_lod: view_config.origin_lod,\n            view_world_position: default(),\n            approximate_height: (model.min_height + model.max_height) / 2.0,\n            origins: Array2::default((model.side_count() as usize, tile_atlas.lod_count as usize)),\n            data: Array4::default((\n                model.side_count() as usize,\n                tile_atlas.lod_count as usize,\n                view_config.tree_size as usize,\n                view_config.tree_size as usize,\n            )),\n            tiles: Array4::default((\n                model.side_count() as usize,\n                tile_atlas.lod_count as usize,\n                view_config.tree_size as usize,\n                view_config.tree_size as usize,\n            )),\n            released_tiles: default(),\n            requested_tiles: default(),\n        }\n    }\n\n    fn compute_tree_xy(coordinate: Coordinate, tile_count: f64) -> DVec2 {\n        // scale and clamp the coordinate to the tile tree bounds\n        (coordinate.uv * tile_count).min(DVec2::splat(tile_count - 0.000001))\n    }\n\n    fn compute_origin(&self, coordinate: Coordinate, lod: u32) -> UVec2 {\n        let tile_count = TileCoordinate::count(lod) as f64;\n        let tree_xy = Self::compute_tree_xy(coordinate, tile_count);\n\n        (tree_xy - 0.5 * self.tree_size as f64)\n            .round()\n            .clamp(\n                DVec2::splat(0.0),\n                DVec2::splat(tile_count - self.tree_size as f64),\n            )\n            .as_uvec2()\n    }\n\n    fn compute_tile_distance(\n        &self,\n        tile: TileCoordinate,\n        view_coordinate: Coordinate,\n        model: &TerrainModel,\n    ) -> f64 {\n        let tile_count = TileCoordinate::count(tile.lod) as f64;\n        let tile_xy = IVec2::new(tile.x as i32, tile.y as i32);\n        let view_tile_xy = Self::compute_tree_xy(view_coordinate, tile_count);\n        let tile_offset = view_tile_xy.as_ivec2() - tile_xy;\n        let mut offset = view_tile_xy % 1.0;\n\n        if tile_offset.x < 0 {\n            offset.x = 0.0;\n        } else if tile_offset.x > 0 {\n            offset.x = 1.0;\n        }\n        if tile_offset.y < 0 {\n            offset.y = 0.0;\n        } else if tile_offset.y > 0 {\n            offset.y = 1.0;\n        }\n\n        let tile_world_position =\n            Coordinate::new(tile.side, (tile_xy.as_dvec2() + offset) / tile_count)\n                .world_position(model, self.approximate_height);\n\n        tile_world_position.distance(self.view_world_position)\n    }\n\n    pub(super) fn compute_blend(&self, sample_world_position: DVec3) -> (u32, f32) {\n        let view_distance = self.view_world_position.distance(sample_world_position);\n        let target_lod = (self.blend_distance / view_distance)\n            .log2()\n            .min(self.lod_count as f64 - 0.00001) as f32;\n        let lod = target_lod as u32;\n\n        let ratio = if lod == 0 {\n            0.0\n        } else {\n            inverse_mix(lod as f32 + self.blend_range, lod as f32, target_lod)\n        };\n\n        (lod, ratio)\n    }\n\n    pub(super) fn lookup_tile(\n        &self,\n        world_position: DVec3,\n        tree_lod: u32,\n        model: &TerrainModel,\n    ) -> TileLookup {\n        let coordinate = Coordinate::from_world_position(world_position, model);\n\n        let tile_count = TileCoordinate::count(tree_lod) as f64;\n        let tree_xy = Self::compute_tree_xy(coordinate, tile_count);\n\n        let entry = self.data[[\n            coordinate.side as usize,\n            tree_lod as usize,\n            tree_xy.x as usize % self.tree_size as usize,\n            tree_xy.y as usize % self.tree_size as usize,\n        ]];\n\n        if entry.atlas_lod == INVALID_LOD {\n            return TileLookup::INVALID;\n        }\n\n        TileLookup {\n            atlas_index: entry.atlas_index,\n            atlas_lod: entry.atlas_lod,\n            atlas_uv: ((tree_xy / (1 << (tree_lod - entry.atlas_lod)) as f64) % 1.0).as_vec2(),\n        }\n    }\n\n    fn update(&mut self, view_position: DVec3, tile_atlas: &TileAtlas) {\n        let model = &tile_atlas.model;\n        self.view_world_position = view_position;\n\n        let view_coordinate = Coordinate::from_world_position(self.view_world_position, model);\n\n        for side in 0..model.side_count() {\n            let view_coordinate = view_coordinate.project_to_side(side, model);\n\n            for lod in 0..tile_atlas.lod_count {\n                let origin = self.compute_origin(view_coordinate, lod);\n                self.origins[(side as usize, lod as usize)] = origin;\n\n                for (x, y) in iproduct!(0..self.tree_size, 0..self.tree_size) {\n                    let tile_coordinate = TileCoordinate {\n                        side,\n                        lod,\n                        x: origin.x + x,\n                        y: origin.y + y,\n                    };\n\n                    let tile_distance =\n                        self.compute_tile_distance(tile_coordinate, view_coordinate, model);\n                    let load_distance =\n                        self.load_distance / TileCoordinate::count(tile_coordinate.lod) as f64;\n\n                    let state = if lod == 0 || tile_distance < load_distance {\n                        RequestState::Requested\n                    } else {\n                        RequestState::Released\n                    };\n\n                    let tile = &mut self.tiles[[\n                        side as usize,\n                        lod as usize,\n                        (tile_coordinate.x % self.tree_size) as usize,\n                        (tile_coordinate.y % self.tree_size) as usize,\n                    ]];\n\n                    // check if tile_tree slot refers to a new tile\n                    if tile_coordinate != tile.coordinate {\n                        // release old tile\n                        if tile.state == RequestState::Requested {\n                            tile.state = RequestState::Released;\n                            self.released_tiles.push(tile.coordinate);\n                        }\n\n                        tile.coordinate = tile_coordinate;\n                    }\n\n                    // request or release tile based on its distance to the view\n                    match (tile.state, state) {\n                        (RequestState::Released, RequestState::Requested) => {\n                            tile.state = RequestState::Requested;\n                            self.requested_tiles.push(tile.coordinate);\n                        }\n                        (RequestState::Requested, RequestState::Released) => {\n                            tile.state = RequestState::Released;\n                            self.released_tiles.push(tile.coordinate);\n                        }\n                        (_, _) => {}\n                    }\n                }\n            }\n        }\n    }\n\n    /// Traverses all tile_trees and updates the tile states,\n    /// while selecting newly requested and released tiles.\n    pub(crate) fn compute_requests(\n        mut tile_trees: ResMut<TerrainViewComponents<TileTree>>,\n        tile_atlases: Query<&TileAtlas>,\n        #[cfg(feature = \"high_precision\")] frames: crate::big_space::ReferenceFrames,\n        #[cfg(feature = \"high_precision\")] view_transforms: Query<\n            crate::big_space::GridTransformReadOnly,\n        >,\n        #[cfg(not(feature = \"high_precision\"))] view_transforms: Query<&Transform>,\n    ) {\n        for (&(terrain, view), tile_tree) in tile_trees.iter_mut() {\n            let tile_atlas = tile_atlases.get(terrain).unwrap();\n            let view_transform = view_transforms.get(view).unwrap();\n\n            #[cfg(feature = \"high_precision\")]\n            let frame = frames.parent_frame(terrain).unwrap();\n            #[cfg(feature = \"high_precision\")]\n            let view_position = view_transform.position_double(frame);\n            #[cfg(not(feature = \"high_precision\"))]\n            let view_position = view_transform.translation.as_dvec3();\n\n            tile_tree.update(view_position, tile_atlas);\n        }\n    }\n\n    /// Adjusts all tile_trees to their corresponding tile atlas\n    /// by updating the entries with the best available tiles.\n    pub(crate) fn adjust_to_tile_atlas(\n        mut tile_trees: ResMut<TerrainViewComponents<TileTree>>,\n        tile_atlases: Query<&TileAtlas>,\n    ) {\n        for (&(terrain, _view), tile_tree) in tile_trees.iter_mut() {\n            let tile_atlas = tile_atlases.get(terrain).unwrap();\n\n            for (tile, entry) in iter::zip(&tile_tree.tiles, &mut tile_tree.data) {\n                *entry = tile_atlas.get_best_tile(tile.coordinate);\n            }\n        }\n    }\n\n    pub(crate) fn approximate_height(\n        mut tile_trees: ResMut<TerrainViewComponents<TileTree>>,\n        tile_atlases: Query<&TileAtlas>,\n    ) {\n        for (&(terrain, _view), tile_tree) in tile_trees.iter_mut() {\n            let tile_atlas = tile_atlases.get(terrain).unwrap();\n\n            tile_tree.approximate_height =\n                sample_height(tile_tree, tile_atlas, tile_tree.view_world_position);\n        }\n    }\n}\n"
  },
  {
    "path": "src/terrain_view.rs",
    "content": "//! Types for configuring terrain views.\n\nuse bevy::{prelude::*, utils::HashMap};\n\n/// Resource that stores components that are associated to a terrain entity and a view entity.\n#[derive(Deref, DerefMut, Resource)]\npub struct TerrainViewComponents<C>(HashMap<(Entity, Entity), C>);\n\nimpl<C> Default for TerrainViewComponents<C> {\n    fn default() -> Self {\n        Self(default())\n    }\n}\n\n/// The configuration of a terrain view.\n///\n/// A terrain view describes the quality settings the corresponding terrain will be rendered with.\n#[derive(Clone)]\npub struct TerrainViewConfig {\n    /// The count of tiles in x and y direction per tile tree layer.\n    pub tree_size: u32,\n    /// The size of the tile buffer.\n    pub geometry_tile_count: u32,\n    /// The amount of steps the tile list will be refined.\n    pub refinement_count: u32,\n    /// The number of rows and columns of the tile grid.\n    pub grid_size: u32,\n    /// The percentage tolerance added to the morph distance during tile subdivision.\n    /// This is required to counteracted the distortion of the subdivision distance estimation near the corners of the cube sphere.\n    /// For planar terrains this can be set to zero and for spherical / ellipsoidal terrains a value of around 0.1 is necessary.\n    pub subdivision_tolerance: f64,\n    pub precision_threshold_distance: f64,\n    pub load_distance: f64,\n    /// The distance measured in tile sizes between adjacent LOD layers.\n    /// This currently has to be larger than about 6, since the tiles can only morph to the adjacent layer.\n    /// Should the morph distance be too small, this will result in morph transitions suddenly being canceled, by the next LOD.\n    /// This is dependent on the morph distance, the morph ratio and the subdivision tolerance. It can be debug with the show tiles debug view.\n    pub morph_distance: f64,\n    pub blend_distance: f64,\n    /// The morph percentage of the mesh.\n    pub morph_range: f32,\n    /// The blend percentage in the vertex and fragment shader.\n    pub blend_range: f32,\n    pub origin_lod: u32,\n}\n\nimpl Default for TerrainViewConfig {\n    fn default() -> Self {\n        Self {\n            tree_size: 8,\n            geometry_tile_count: 1000000,\n            refinement_count: 30,\n            grid_size: 16,\n            subdivision_tolerance: 0.1,\n            load_distance: 2.5,\n            morph_distance: 16.0,\n            blend_distance: 2.0,\n            morph_range: 0.2,\n            blend_range: 0.2,\n            precision_threshold_distance: 0.001,\n            origin_lod: 10,\n        }\n    }\n}\n"
  },
  {
    "path": "src/util.rs",
    "content": "use bevy::render::{\n    render_resource::{encase::internal::WriteInto, *},\n    renderer::{RenderDevice, RenderQueue},\n};\nuse itertools::Itertools;\nuse std::{fmt::Debug, marker::PhantomData, ops::Deref};\n\npub(crate) fn inverse_mix(a: f32, b: f32, value: f32) -> f32 {\n    return f32::clamp((value - a) / (b - a), 0.0, 1.0);\n}\n\npub trait CollectArray: Iterator {\n    fn collect_array<const T: usize>(self) -> [Self::Item; T]\n    where\n        Self: Sized,\n        <Self as Iterator>::Item: Debug,\n    {\n        self.collect_vec().try_into().unwrap()\n    }\n}\n\nimpl<T> CollectArray for T where T: Iterator + ?Sized {}\nenum Scratch {\n    None,\n    Uniform(encase::UniformBuffer<Vec<u8>>),\n    Storage(encase::StorageBuffer<Vec<u8>>),\n}\n\nimpl Scratch {\n    fn new(usage: BufferUsages) -> Self {\n        if usage.contains(BufferUsages::UNIFORM) {\n            Self::Uniform(encase::UniformBuffer::new(Vec::new()))\n        } else if usage.contains(BufferUsages::STORAGE) {\n            Self::Storage(encase::StorageBuffer::new(Vec::new()))\n        } else {\n            Self::None\n        }\n    }\n\n    fn write<T: ShaderType + WriteInto>(&mut self, value: &T) {\n        match self {\n            Scratch::None => panic!(\"Can't write to an buffer without a scratch buffer.\"),\n            Scratch::Uniform(scratch) => scratch.write(value).unwrap(),\n            Scratch::Storage(scratch) => scratch.write(value).unwrap(),\n        }\n    }\n\n    fn contents(&self) -> &[u8] {\n        match self {\n            Scratch::None => panic!(\"Can't get the contents of a buffer without a scratch buffer.\"),\n            Scratch::Uniform(scratch) => scratch.as_ref(),\n            Scratch::Storage(scratch) => scratch.as_ref(),\n        }\n    }\n}\n\npub struct StaticBuffer<T> {\n    buffer: Buffer,\n    value: Option<T>,\n    scratch: Scratch,\n    _marker: PhantomData<T>,\n}\n\nimpl<T> StaticBuffer<T> {\n    pub fn empty_sized<'a>(\n        label: impl Into<Option<&'a str>>,\n        device: &RenderDevice,\n        size: BufferAddress,\n        usage: BufferUsages,\n    ) -> Self {\n        let buffer = device.create_buffer(&BufferDescriptor {\n            label: label.into(),\n            size,\n            usage,\n            mapped_at_creation: false,\n        });\n\n        Self {\n            buffer,\n            value: None,\n            scratch: Scratch::new(usage),\n            _marker: PhantomData,\n        }\n    }\n\n    pub fn update_bytes(&self, queue: &RenderQueue, bytes: &[u8]) {\n        queue.write_buffer(&self.buffer, 0, bytes);\n    }\n}\n\nimpl<T: ShaderType + Default> StaticBuffer<T> {\n    pub fn empty<'a>(\n        label: impl Into<Option<&'a str>>,\n        device: &RenderDevice,\n        usage: BufferUsages,\n    ) -> Self {\n        let buffer = device.create_buffer(&BufferDescriptor {\n            label: label.into(),\n            size: T::min_size().get(),\n            usage,\n            mapped_at_creation: false,\n        });\n\n        Self {\n            buffer,\n            value: None,\n            scratch: Scratch::new(usage),\n            _marker: PhantomData,\n        }\n    }\n}\n\nimpl<T: ShaderType + WriteInto> StaticBuffer<T> {\n    pub fn create<'a>(\n        label: impl Into<Option<&'a str>>,\n        device: &RenderDevice,\n        value: &T,\n        usage: BufferUsages,\n    ) -> Self {\n        let mut scratch = Scratch::new(usage);\n        scratch.write(&value);\n\n        let buffer = device.create_buffer_with_data(&BufferInitDescriptor {\n            label: label.into(),\n            usage,\n            contents: scratch.contents(),\n        });\n\n        Self {\n            buffer,\n            value: None,\n            scratch,\n            _marker: PhantomData,\n        }\n    }\n\n    pub fn value(&self) -> &T {\n        self.value.as_ref().unwrap()\n    }\n\n    pub fn set_value(&mut self, value: T) {\n        self.value = Some(value);\n    }\n\n    pub fn update(&mut self, queue: &RenderQueue) {\n        if let Some(value) = &self.value {\n            self.scratch.write(value);\n\n            queue.write_buffer(&self.buffer, 0, self.scratch.contents());\n        }\n    }\n}\n\nimpl<T> Deref for StaticBuffer<T> {\n    type Target = Buffer;\n\n    #[inline]\n    fn deref(&self) -> &Self::Target {\n        &self.buffer\n    }\n}\n\nimpl<'a, T> IntoBinding<'a> for &'a StaticBuffer<T> {\n    #[inline]\n    fn into_binding(self) -> BindingResource<'a> {\n        self.buffer.as_entire_binding()\n    }\n}\n"
  }
]