Full Code of kurtkuehnert/bevy_terrain for AI

main 5a88daf4a419 cached
57 files
300.6 KB
73.3k tokens
405 symbols
1 requests
Download .txt
Showing preview only (318K chars total). Download the full file or copy to clipboard to get everything.
Repository: kurtkuehnert/bevy_terrain
Branch: main
Commit: 5a88daf4a419
Files: 57
Total size: 300.6 KB

Directory structure:
gitextract_d9qm2zah/

├── .gitattributes
├── .gitignore
├── Cargo.toml
├── LICENSE-APACHE
├── LICENSE-MIT
├── README.md
├── assets/
│   └── shaders/
│       ├── planar.wgsl
│       └── spherical.wgsl
├── docs/
│   ├── development.md
│   └── implementation.md
├── examples/
│   ├── minimal.rs
│   ├── planar.rs
│   ├── preprocess_planar.rs
│   ├── preprocess_spherical.rs
│   └── spherical.rs
└── src/
    ├── big_space.rs
    ├── debug/
    │   ├── camera.rs
    │   └── mod.rs
    ├── formats/
    │   ├── mod.rs
    │   └── tiff.rs
    ├── lib.rs
    ├── math/
    │   ├── coordinate.rs
    │   ├── ellipsoid.rs
    │   ├── mod.rs
    │   └── terrain_model.rs
    ├── plugin.rs
    ├── preprocess/
    │   ├── gpu_preprocessor.rs
    │   ├── mod.rs
    │   └── preprocessor.rs
    ├── render/
    │   ├── culling_bind_group.rs
    │   ├── mod.rs
    │   ├── terrain_bind_group.rs
    │   ├── terrain_material.rs
    │   ├── terrain_view_bind_group.rs
    │   └── tiling_prepass.rs
    ├── shaders/
    │   ├── attachments.wgsl
    │   ├── bindings.wgsl
    │   ├── debug.wgsl
    │   ├── functions.wgsl
    │   ├── mod.rs
    │   ├── preprocess/
    │   │   ├── downsample.wgsl
    │   │   ├── preprocessing.wgsl
    │   │   ├── split.wgsl
    │   │   └── stitch.wgsl
    │   ├── render/
    │   │   ├── fragment.wgsl
    │   │   └── vertex.wgsl
    │   ├── tiling_prepass/
    │   │   ├── prepare_prepass.wgsl
    │   │   └── refine_tiles.wgsl
    │   └── types.wgsl
    ├── terrain.rs
    ├── terrain_data/
    │   ├── gpu_tile_atlas.rs
    │   ├── gpu_tile_tree.rs
    │   ├── mod.rs
    │   ├── tile_atlas.rs
    │   └── tile_tree.rs
    ├── terrain_view.rs
    └── util.rs

================================================
FILE CONTENTS
================================================

================================================
FILE: .gitattributes
================================================
*.tif filter=lfs diff=lfs merge=lfs -text


================================================
FILE: .gitignore
================================================
.idea
Cargo.lock
target
assets/**/data/*
assets/**/config.tc
assets/terrains/spherical/source/height/200m.tif

================================================
FILE: Cargo.toml
================================================
[package]
name = "bevy_terrain"
description = "Terrain Rendering for the Bevy Engine."
version = "0.1.0-dev"
license = "MIT OR Apache-2.0"
edition = "2021"
categories = ["game-engines", "rendering", "graphics"]
keywords = ["gamedev", "graphics", "bevy", "terrain"]
exclude = ["assets/*"]
readme = "README.md"
authors = ["Kurt Kühnert <kurt@kuehnert.dev>"]
repository = "https://github.com/kurtkuehnert/bevy_terrain"

[features]
high_precision = ["dep:big_space"]

[dependencies]
bevy = "0.14.0" #{ git="https://github.com/bevyengine/bevy/", branch="main" }
ndarray = "0.15"
itertools = "0.12"
image = "0.25"
tiff = "0.9"
lru = "0.12"
bitflags = "2.4"
bytemuck = "1.14"
anyhow = "1.0"
bincode = "2.0.0-rc.3"
async-channel = "2.1"
big_space = { version = "0.7", optional = true }

[[example]]
name = "preprocess_planar"
path = "examples/preprocess_planar.rs"
required-features = ["bevy/embedded_watcher"]

[package.metadata.example.preprocess_planar]
name = "Preprocess Planar"
description = "Preprocesses the terrain data for the planar examples."

[[example]]
name = "minimal"
path = "examples/minimal.rs"
required-features = ["bevy/embedded_watcher"]

[package.metadata.example.minial]
name = "Minimal"
description = "Renders a basic flat terrain with only the base attachment."

[[example]]
name = "planar"
path = "examples/planar.rs"
required-features = ["high_precision", "bevy/embedded_watcher"]

[package.metadata.example.planar]
name = "Planar Advanced"
description = "Renders a flat terrain with the base attachment and an albedo texture, using a custom shader."

[[example]]
name = "preprocess_spherical"
path = "examples/preprocess_spherical.rs"
required-features = ["bevy/embedded_watcher"]

[package.metadata.example.preprocess_spherical]
name = "Preprocess Spherical"
description = "Preprocesses the terrain data for the spherical examples."

[[example]]
name = "spherical"
path = "examples/spherical.rs"
required-features = ["high_precision", "bevy/embedded_watcher"]

[package.metadata.example.spherical]
name = "Spherical"
description = "Renders a spherical terrain using a custom shader."



================================================
FILE: LICENSE-APACHE
================================================
                                 Apache License
                           Version 2.0, January 2004
                        http://www.apache.org/licenses/

   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION

   1. Definitions.

      "License" shall mean the terms and conditions for use, reproduction,
      and distribution as defined by Sections 1 through 9 of this document.

      "Licensor" shall mean the copyright owner or entity authorized by
      the copyright owner that is granting the License.

      "Legal Entity" shall mean the union of the acting entity and all
      other entities that control, are controlled by, or are under common
      control with that entity. For the purposes of this definition,
      "control" means (i) the power, direct or indirect, to cause the
      direction or management of such entity, whether by contract or
      otherwise, or (ii) ownership of fifty percent (50%) or more of the
      outstanding shares, or (iii) beneficial ownership of such entity.

      "You" (or "Your") shall mean an individual or Legal Entity
      exercising permissions granted by this License.

      "Source" form shall mean the preferred form for making modifications,
      including but not limited to software source code, documentation
      source, and configuration files.

      "Object" form shall mean any form resulting from mechanical
      transformation or translation of a Source form, including but
      not limited to compiled object code, generated documentation,
      and conversions to other media types.

      "Work" shall mean the work of authorship, whether in Source or
      Object form, made available under the License, as indicated by a
      copyright notice that is included in or attached to the work
      (an example is provided in the Appendix below).

      "Derivative Works" shall mean any work, whether in Source or Object
      form, that is based on (or derived from) the Work and for which the
      editorial revisions, annotations, elaborations, or other modifications
      represent, as a whole, an original work of authorship. For the purposes
      of this License, Derivative Works shall not include works that remain
      separable from, or merely link (or bind by name) to the interfaces of,
      the Work and Derivative Works thereof.

      "Contribution" shall mean any work of authorship, including
      the original version of the Work and any modifications or additions
      to that Work or Derivative Works thereof, that is intentionally
      submitted to Licensor for inclusion in the Work by the copyright owner
      or by an individual or Legal Entity authorized to submit on behalf of
      the copyright owner. For the purposes of this definition, "submitted"
      means any form of electronic, verbal, or written communication sent
      to the Licensor or its representatives, including but not limited to
      communication on electronic mailing lists, source code control systems,
      and issue tracking systems that are managed by, or on behalf of, the
      Licensor for the purpose of discussing and improving the Work, but
      excluding communication that is conspicuously marked or otherwise
      designated in writing by the copyright owner as "Not a Contribution."

      "Contributor" shall mean Licensor and any individual or Legal Entity
      on behalf of whom a Contribution has been received by Licensor and
      subsequently incorporated within the Work.

   2. Grant of Copyright License. Subject to the terms and conditions of
      this License, each Contributor hereby grants to You a perpetual,
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
      copyright license to reproduce, prepare Derivative Works of,
      publicly display, publicly perform, sublicense, and distribute the
      Work and such Derivative Works in Source or Object form.

   3. Grant of Patent License. Subject to the terms and conditions of
      this License, each Contributor hereby grants to You a perpetual,
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
      (except as stated in this section) patent license to make, have made,
      use, offer to sell, sell, import, and otherwise transfer the Work,
      where such license applies only to those patent claims licensable
      by such Contributor that are necessarily infringed by their
      Contribution(s) alone or by combination of their Contribution(s)
      with the Work to which such Contribution(s) was submitted. If You
      institute patent litigation against any entity (including a
      cross-claim or counterclaim in a lawsuit) alleging that the Work
      or a Contribution incorporated within the Work constitutes direct
      or contributory patent infringement, then any patent licenses
      granted to You under this License for that Work shall terminate
      as of the date such litigation is filed.

   4. Redistribution. You may reproduce and distribute copies of the
      Work or Derivative Works thereof in any medium, with or without
      modifications, and in Source or Object form, provided that You
      meet the following conditions:

      (a) You must give any other recipients of the Work or
          Derivative Works a copy of this License; and

      (b) You must cause any modified files to carry prominent notices
          stating that You changed the files; and

      (c) You must retain, in the Source form of any Derivative Works
          that You distribute, all copyright, patent, trademark, and
          attribution notices from the Source form of the Work,
          excluding those notices that do not pertain to any part of
          the Derivative Works; and

      (d) If the Work includes a "NOTICE" text file as part of its
          distribution, then any Derivative Works that You distribute must
          include a readable copy of the attribution notices contained
          within such NOTICE file, excluding those notices that do not
          pertain to any part of the Derivative Works, in at least one
          of the following places: within a NOTICE text file distributed
          as part of the Derivative Works; within the Source form or
          documentation, if provided along with the Derivative Works; or,
          within a display generated by the Derivative Works, if and
          wherever such third-party notices normally appear. The contents
          of the NOTICE file are for informational purposes only and
          do not modify the License. You may add Your own attribution
          notices within Derivative Works that You distribute, alongside
          or as an addendum to the NOTICE text from the Work, provided
          that such additional attribution notices cannot be construed
          as modifying the License.

      You may add Your own copyright statement to Your modifications and
      may provide additional or different license terms and conditions
      for use, reproduction, or distribution of Your modifications, or
      for any such Derivative Works as a whole, provided Your use,
      reproduction, and distribution of the Work otherwise complies with
      the conditions stated in this License.

   5. Submission of Contributions. Unless You explicitly state otherwise,
      any Contribution intentionally submitted for inclusion in the Work
      by You to the Licensor shall be under the terms and conditions of
      this License, without any additional terms or conditions.
      Notwithstanding the above, nothing herein shall supersede or modify
      the terms of any separate license agreement you may have executed
      with Licensor regarding such Contributions.

   6. Trademarks. This License does not grant permission to use the trade
      names, trademarks, service marks, or product names of the Licensor,
      except as required for reasonable and customary use in describing the
      origin of the Work and reproducing the content of the NOTICE file.

   7. Disclaimer of Warranty. Unless required by applicable law or
      agreed to in writing, Licensor provides the Work (and each
      Contributor provides its Contributions) on an "AS IS" BASIS,
      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
      implied, including, without limitation, any warranties or conditions
      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
      PARTICULAR PURPOSE. You are solely responsible for determining the
      appropriateness of using or redistributing the Work and assume any
      risks associated with Your exercise of permissions under this License.

   8. Limitation of Liability. In no event and under no legal theory,
      whether in tort (including negligence), contract, or otherwise,
      unless required by applicable law (such as deliberate and grossly
      negligent acts) or agreed to in writing, shall any Contributor be
      liable to You for damages, including any direct, indirect, special,
      incidental, or consequential damages of any character arising as a
      result of this License or out of the use or inability to use the
      Work (including but not limited to damages for loss of goodwill,
      work stoppage, computer failure or malfunction, or any and all
      other commercial damages or losses), even if such Contributor
      has been advised of the possibility of such damages.

   9. Accepting Warranty or Additional Liability. While redistributing
      the Work or Derivative Works thereof, You may choose to offer,
      and charge a fee for, acceptance of support, warranty, indemnity,
      or other liability obligations and/or rights consistent with this
      License. However, in accepting such obligations, You may act only
      on Your own behalf and on Your sole responsibility, not on behalf
      of any other Contributor, and only if You agree to indemnify,
      defend, and hold each Contributor harmless for any liability
      incurred by, or claims asserted against, such Contributor by reason
      of your accepting any such warranty or additional liability.

   END OF TERMS AND CONDITIONS


================================================
FILE: LICENSE-MIT
================================================
MIT License

Copyright (c) 2023 Kurt Kühnert
Copyright (c) 2023 Argeo

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

================================================
FILE: README.md
================================================
# Bevy Terrain

![GitHub](https://img.shields.io/github/license/Ku95/bevy_terrain)
![Crates.io](https://img.shields.io/crates/v/bevy_terrain)
![docs.rs](https://img.shields.io/docsrs/bevy_terrain)
![Discord](https://img.shields.io/discord/999221999517843456?label=discord)

Bevy Terrain is a plugin for rendering terrains with the Bevy game engine.

![](https://user-images.githubusercontent.com/51823519/202845032-0537e929-b13c-410b-8072-4c5b5df9830d.png)
(Data Source: Federal Office of Topography, [©swisstopo](https://www.swisstopo.admin.ch/en/home.html))

**Warning:** This plugin is still in early development, so expect the API to change and possibly break you existing
code.

Bevy terrain was developed as part of my [bachelor thesis](https://github.com/kurtkuehnert/terrain_renderer) on the
topic of large-scale terrain rendering.
Now that this project is finished I am planning on adding more features related to game development and rendering
virtual worlds.
If you would like to help me build an extensive open-source terrain rendering library for the Bevy game engine, feel
free to contribute to the project.
Also, join the Bevy Terrain [Discord server](https://discord.gg/7mtZWEpA82) for help, feedback, or to discuss feature
ideas.

## Examples

Currently, there are two examples.

The basic one showcases the different debug views of the terrain. See controls down below.

The advanced one showcases how to use the Bevy material system for texturing,
as well as how to add additional terrain attachments.
Use the `A` Key to toggle between the custom material and the albedo attachment.

Before running the examples you have to preprocess the terrain data this may take a while.
Once the data is preprocessed you can disable it by commenting out the preprocess line.

## Documentation

The `docs` folder contains a
high-level [implementation overview](https://github.com/kurtkuehnert/bevy_terrain/blob/main/docs/implementation.md),
as well as, the [development status](https://github.com/kurtkuehnert/bevy_terrain/blob/main/docs/development.md),
enumerating the features that I am planning on implementing next, of the project.
If you would like to contribute to the project this is a good place to start. Simply pick an issue/feature and discuss
the details with me on Discord or GitHub.
I would also recommend you to take a look at
my [thesis](https://github.com/kurtkuehnert/terrain_renderer/blob/main/Thesis.pdf).
There I present the basics of terrain rendering (chapter 2), common approaches (chapter 3) and a detailed explanation of
method used by `bevy_terrain` (chapter 4).

## Debug Controls

These are the debug controls of the plugin.
Use them to fly over the terrain, experiment with the quality settings and enter the different debug views.

- `T` - toggle camera movement
- move the mouse to look around
- press the arrow keys to move the camera horizontally
- use `PageUp` and `PageDown` to move the camera vertically
- use `Home` and `End` to increase/decrease the camera's movement speed

- `W` - toggle wireframe view
- `P` - toggle tile view
- `L` - toggle lod view
- `U` - toggle uv view
- `C` - toggle tile view
- `D` - toggle mesh morph
- `A` - toggle albedo
- `B` - toggle base color black / white
- `S` - toggle lighting
- `G` - toggle filtering bilinear / trilinear + anisotropic
- `F` - freeze frustum culling
- `H` - decrease tile scale
- `J` - increase tile scale
- `N` - decrease grid size
- `E` - increase grid size
- `I` - decrease view distance
- `O` - increase view distance

<!---
## Supported Bevy Versions

| `bevy_terrain` | `bevy` |
|----------------|--------|
| 0.1.0          | 0.9    |
--->

## Attribution

The planar terrain dataset is generated using the free version of the Gaia Terrain Generator.
The spherical terrain example dataset is a reprojected version of the GEBCO_2023 Grid dataset.

GEBCO Compilation Group (2023) GEBCO 2023 Grid (doi:10.5285/f98b053b-0cbc-6c23-e053-6c86abc0af7b)

## License

Bevy Terrain source code (this excludes the datasets in the assets directory) is dual-licensed under either

* MIT License (LICENSE-MIT or http://opensource.org/licenses/MIT)
* Apache License, Version 2.0 (LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0)

at your option.


================================================
FILE: assets/shaders/planar.wgsl
================================================
#import bevy_terrain::types::AtlasTile
#import bevy_terrain::attachments::{sample_attachment0 as sample_height, sample_normal, sample_attachment1 as sample_albedo}
#import bevy_terrain::fragment::{FragmentInput, FragmentOutput, fragment_info, fragment_output, fragment_debug}
#import bevy_terrain::functions::lookup_tile
#import bevy_pbr::pbr_types::{PbrInput, pbr_input_new}

@group(3) @binding(0)
var gradient: texture_1d<f32>;
@group(3) @binding(1)
var gradient_sampler: sampler;

fn sample_color(tile: AtlasTile) -> vec4<f32> {
#ifdef ALBEDO
    return sample_albedo(tile);
#else
    let height = sample_height(tile).x;

    return textureSampleLevel(gradient, gradient_sampler, pow(height, 0.9), 0.0);
#endif
}

@fragment
fn fragment(input: FragmentInput) -> FragmentOutput {
    var info = fragment_info(input);

    let tile   = lookup_tile(info.coordinate, info.blend, 0u);
    var color  = sample_color(tile);
    var normal = sample_normal(tile, info.world_normal);

    if (info.blend.ratio > 0.0) {
        let tile2 = lookup_tile(info.coordinate, info.blend, 1u);
        color     = mix(color,  sample_color(tile2),                     info.blend.ratio);
        normal    = mix(normal, sample_normal(tile2, info.world_normal), info.blend.ratio);
    }

    var output: FragmentOutput;
    fragment_output(&info, &output, color, normal);
    fragment_debug(&info, &output, tile, normal);
    return output;
}


================================================
FILE: assets/shaders/spherical.wgsl
================================================
#import bevy_terrain::types::{AtlasTile}
#import bevy_terrain::bindings::config
#import bevy_terrain::attachments::{sample_height, sample_normal}
#import bevy_terrain::fragment::{FragmentInput, FragmentOutput, fragment_info, fragment_output, fragment_debug}
#import bevy_terrain::functions::lookup_tile
#import bevy_pbr::pbr_types::{PbrInput, pbr_input_new}
#import bevy_pbr::pbr_functions::{calculate_view, apply_pbr_lighting}


@group(3) @binding(0)
var gradient: texture_1d<f32>;
@group(3) @binding(1)
var gradient_sampler: sampler;

fn sample_color(tile: AtlasTile) -> vec4<f32> {
    let height = sample_height(tile);

    var color: vec4<f32>;

    if (height < 0.0) {
        color = textureSampleLevel(gradient, gradient_sampler, mix(0.0, 0.075, pow(height / config.min_height, 0.25)), 0.0);
    }
    else {
        color = textureSampleLevel(gradient, gradient_sampler, mix(0.09, 1.0, pow(height / config.max_height * 2.0, 1.0)), 0.0);
    }

    return color;
}

@fragment
fn fragment(input: FragmentInput) -> FragmentOutput {
    var info = fragment_info(input);

    let tile   = lookup_tile(info.coordinate, info.blend, 0u);
    var color  = sample_color(tile);
    var normal = sample_normal(tile, info.world_normal);

    if (info.blend.ratio > 0.0) {
        let tile2 = lookup_tile(info.coordinate, info.blend, 1u);
        color     = mix(color,  sample_color(tile2),                     info.blend.ratio);
        normal    = mix(normal, sample_normal(tile2, info.world_normal), info.blend.ratio);
    }

    var output: FragmentOutput;
    fragment_output(&info, &output, color, normal);
    fragment_debug(&info, &output, tile, normal);
    return output;
}


================================================
FILE: docs/development.md
================================================
# Development Status Bevy Terrain

This document assesses the current status of the `bevy_terrain` plugin.
I built this plugin as part of my bachelor thesis, which focused on rendering large-scale terrains.
The thesis and its project can be found [here](https://github.com/kurtkuehnert/terrain_renderer).

For that, I set out to solve two key problems of terrain rendering. 
For one, I developed the Uniform Distance-Dependent Level of Detail (UDLOD) algorithm to represent the terrain geometry, and for another, 
I came up with the Chunked Clipmap data structure used to represent the terrain data. 
Both are implemented as part of bevy terrain and work quite well for rendering large-scale terrains.

Now that I have finished my thesis I would like to continue working on this project and extend its capabilities. 
The topic of terrain rendering is vast, and thus I can not work on all the stuff at once.
In the following, I will list a couple of features that I would like to integrate into this crate in the future. 
I 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. 
Additionally, there are still plenty of improvements, bug fixes, and optimizations to be completed on the already existing implementation.

## Features

- Procedural Texturing
- Shadow Rendering
- Real-Time Editing
- Collision
- Path-Finding
- Spherical Terrain

### Procedural Texturing

Probably the biggest missing puzzle piece of this plugin is support for procedural texturing using splat maps or something similar. 
Currently, texturing has to be implemented manually in the terrain shader (see the advanced example for reference). 
I would like to support this use case in a more integrated manner in the future. Unfortunately, 
I am not familiar with the terrain texturing systems of other engines (e.g. Unity, Unreal, Godot) 
or have any experience texturing and building my own terrains. 
I would greatly appreciate it if anyone can share some requirements for this area of terrain rendering. 
Also, a prototype of a custom texturing system would be a great resource to develop further ideas.

### Shadow Rendering

Another important capability that is currently missing is the support for large-scale shadow rendering. 
This would be probably implemented using cascading shadow maps or a similar method.
Currently, Bevy itself does not implement a system we could use for this yet. 
Regardless, I think reusing Bevy’s implementation would be the best choice in the future.

### Real-Time Editing

One 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. 
This is not only important for sculpting the terrain of your game, but also for texturing, vegetation placement, etc.
This is going to be my next focus area and I would like to discuss designs and additional requirements with anyone interested.

### Collision

Same 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. 
Integrating the collision of the terrain with rapier would enable many types of games and is a commonly requested feature.

### Path-Finding

Similar 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.

### Spherical Terrain

I think that with a little design work the current two-dimensional terrain rendering method could be extended to the spherical terrain.
However, I am unsure how much of the existing code could be extended and reused. Maybe planet rendering would require its entirely separate crate.


================================================
FILE: docs/implementation.md
================================================
# Implementation Overview Bevy Terrain

This document serves as a general overview of the implementation of the `bevy_terrain` plugin.

Currently, this crate provides two fundamental capabilities.
For one, the UDLOD algorithm approximates the terrain geometry, and for another, the Chunked Clipmap stores the terrain
data in a convenient and randomly accessible data structure.

Both are described in detail in
my [bachelor thesis]([https://github.com/kurtkuehnert/terrain_renderer/blob/main/Thesis.pdf](https://github.com/kurtkuehnert/terrain_renderer/blob/main/Thesis.pdf)).
To understand the implementation of this crate and the reasons behind some design decisions, I recommend that you read
at least the entire chapter 4.
If you are unfamiliar with terrain rendering in general, taking a look at chapter 2 will prove beneficial as well.

In the following, I will now explain how both of these systems are currently implemented, what limitations they possess,
and how they should work in the future.
Furthermore, I have listed a couple of todos outlining the essence of these issues.
If any of them sound interesting to you, and you would like to work on them, please get in touch with me, so we can
discuss solutions.
These are certainly not all problems of the current implementation, so if you notice anything else, please let me know,
so I can add it here.

## Terrain Geometry

### Ideal

Ideally, we would like to represent the terrain geometry without any error according to our source data. Unfortunately,
rendering each data point as a vertex is not scalable, nor efficient.

### Reality

That is why we need a sophisticated level of detail (LOD) algorithm that minimizes the error introduced by its
approximation of the geometry.

### Solution

One such solution is the Uniform Distance-Dependent Level of Detail (UDLOD) algorithm that I have developed as part of
my thesis (for a detailed explanation read section 4.4).
It divides the terrain into numerous small tiles in parallel on the GPU. They are then rendered using a single indirect
draw call and morphed together (in the vertex shader) to form a continuous surface with an approximately uniform
tessellation in screen space.

### Issues

For any LOD algorithm, an appropriate crack-avoiding and morphing strategy are important to eliminate and reduce visual
discrepancies as much as possible.
UDLOD uses a slightly modified version of the CDLOD morphing scheme.

The UDLOD algorithm can be used to tessellate procedural ground details like rocks or cobblestones as well.
Therefore, simply increase the tile_tree depth using the `additional_refinement` parameter.

Even though the tessellation produced by UDLOD is somewhat uniform with respect to the distance, it does not take
factors like the terrain's roughness and the viewing angle into account.
Generally, the current UDLOD algorithm tiers to cover the worst-case terrain roughness like many other algorithms (
GeoMipmap, GeoClipmap, PGM, CDLOD, FarCry5).
I believe that we can still develop more efficient LOD algorithms that scale favorably for large-scale terrains in the
future.

The culling is currently pretty bare-bones.
We could probably implement most of the techniques researched by the Far Cry 5 terrain renderer as well.

Currently, the prepass is pretty inefficient, because the shader occupancy is very low (the prepass is still plenty
fast, but could be improved).
I think that this could be resolved by using the atomic operations more cleverly and reducing the shader dispatches in
general.
In the past, I have tried doing all the work in a single pass.
Unfortunately, that did not work, but maybe someone can figure out a better solution.

The frustum culling uses a 2D min-max height data attachment to approximate the bounding volumes of each tile correctly.
This is currently stored with the same resolution as the source height data, but only a fraction of this resolution is
actually required.

### Todo

- [x]  come up with a smooth morphing strategy that solves the geometry crack problem as well
- [x]  implement bounding box frustum culling
- [x]  further refine the geometry for procedural details (rocks, cobblestone)
- [ ]  explore different LOD algorithms (maybe apply the clipmap idea to CBTs?)
- [ ]  try incorporating a screen space error metric, local terrain roughness, or the viewing angle
- [ ]  implement more advanced culling solutions (occlusion, backface)
- [ ]  try reducing the compute shader dispatches in the prepass phase
- [ ]  store min-max height data, required by frustum culling, at a way lower resolution
- [ ]  experiment with hardware tessellation or mesh shaders

## Terrain Data

### Ideal

Ideally, we would like to store any desired information at any desired resolution across the terrain’s surface.
For example, a terrain could require a heightmap with a resolution of 0.5m, an albedo map with a resolution of 0.2m, and
a vegetation map (for placing trees and bushes) with a resolution of 1m.
Each of these three different kinds of terrain data are called terrain attachments.
This terrain data should be available in any system and shader of our application. Additionally, we would like to access
the data at any position and sample a value with distant-dependent accuracy.
Finally, some use cases require the ability to sample some attachments like the albedo or splat data trilinearly and
anisotropically to mitigate aliasing artifacts.

### Reality

Because we are using height-map-based terrain these attachments should be stored as large two-dimensional textures.
However, due to the size of most landscapes, using a single texture would quickly use up all of our video memory.
That is why we need to partition and adjust the loaded data according to our view.
Additionally, it is important that we can share this terrain data efficiently between multiple views for use cases like
split-screen or shadow rendering.

### Solution

To solve this, I have developed the chunked clipmap data structure (if you are unfamiliar with the concept, I encourage
you to read section 4.5 of my thesis).
It divides the entire terrain data into one large tile_tree, covering the entire terrain.
This requires that all terrain data has to be preprocessed into small square textures: the tiles of the tile_tree.
Each tile possesses one texture per attachment. To allow for different resolutions of the attachments (e.g. the
height data should be twice as accurate as our splat map), the size of these textures has to be different as well.
Following the same example, this would mean that the height textures would have a size of 100x100 and the splat textures
a size of 50x50 pixels.

### Issues

Because of our compound representation of the terrain data, consisting of many small textures, some problems arise
during texture filtering.
The biggest issue is that adjacent tiles do not line up perfectly due to missing texture information at the border.
This causes noticeable texture seams between adjacent tiles.
To remedy this issue we have to duplicate the border data between adjacent tiles.
This complicates our preprocessing but results in a completely seamless terrain data representation.

For trilinear filtering, we additionally require mipmap information.
Currently, bevy does not support mipmap generation.
That is why I have implemented a simple mipmap creation function, which is executed after the tile textures have
been loaded. Unfortunately, my simple approach only works on textures with a side length equal to a power of two (e.g.
256x256, 512x512).
This needlessly limits the resolutions of our terrain data.
In the future, I would like to generate the mipmaps for any texture size.

As mentioned above the terrain data has to be loaded depending on our current view position.
Currently, I load all tiles inside the `load_distance` around the viewer.
There is no prioritization or load balancing. I would like to explore different loading strategies (e.g. distance only,
view frustum based, etc.) to enable use cases like streaming data from a web server.
For that, the strategy would have to minimize the loading requests while maximizing the visual quality.
When streaming from disk this wasn't a problem yet.

Additionally, the plugin panics if the tile atlas is out of indices (i.e. the maximum amount of tiles is
loaded).
This is unacceptable in production use.
Here we would have to come up with a strategy of prioritizing which tiles to keep and which ones to discard in
order to accommodate more important ones.

The tile loading code itself is currently pretty inefficient.
Due to the nature of the bevy image abstraction, all textures are duplicated multiple times.
Hopefully in the near future, once the asset processing has been reworked, it will be easier to express loading parts of
an array texture directly.

To divide the terrain into the numerous tile textures I use a 3-step preprocessing algorithm.
This is implemented pretty inefficiently.
If you are interested in optimizing data transformation code, this should be the task for you :D.

To save space the terrain data is compressed using common image formats, when it is stored on the hard-drive.
To unfortunately the encoding of PNGs is quite slow.
That is why I came up with the [DTM image format](https://github.com/kurtkuehnert/dtm).
It uses a sequential compression technique similar to the QOI format.
DTM works quite well for the shallow terrain I used for testing, but is not ideal for the steep and hilly terrains used
in most games.
There are probably significant gains to be had in this area.

Another huge challenge regarding the terrain data is its modification in real-time.
Workflows like sculpting, texturing, etc. do require the ability to update the terrain data in a visual manner.
This topic is vast and will require extensive investigation before we can settle on a final design.
If you have experience/ideas please let me know.

### Todo

- [x]  duplicate border information to eliminate texture seams
- [x]  generate mipmaps to enable trilinear filtering
- [ ]  Incorporate better mipmap generation for any texture size.
- [ ]  different loading strategies
- [ ]  handle tile atlas out of indices
- [ ]  improve loading to tile atlas (i.e. loading layers of an array texture), remove excessive
  duplication/copying
- [ ]  improve the preprocessing with caching, GPU acceleration, etc.
- [ ]  explore the usage of more efficient image formats
- [ ]  investigate real-time modification


================================================
FILE: examples/minimal.rs
================================================
use bevy::math::DVec3;
use bevy::prelude::*;
use bevy_terrain::prelude::*;

const PATH: &str = "terrains/planar";
const TERRAIN_SIZE: f64 = 1000.0;
const HEIGHT: f32 = 250.0;
const TEXTURE_SIZE: u32 = 512;
const LOD_COUNT: u32 = 4;

fn main() {
    App::new()
        .add_plugins((
            DefaultPlugins,
            TerrainPlugin,
            TerrainMaterialPlugin::<DebugTerrainMaterial>::default(),
            TerrainDebugPlugin,
        ))
        .add_systems(Startup, setup)
        .run();
}

fn setup(
    mut commands: Commands,
    mut materials: ResMut<Assets<DebugTerrainMaterial>>,
    mut tile_trees: ResMut<TerrainViewComponents<TileTree>>,
    mut meshes: ResMut<Assets<Mesh>>,
) {
    // Configure all the important properties of the terrain, as well as its attachments.
    let config = TerrainConfig {
        lod_count: LOD_COUNT,
        model: TerrainModel::planar(DVec3::new(0.0, -100.0, 0.0), TERRAIN_SIZE, 0.0, HEIGHT),
        path: PATH.to_string(),
        ..default()
    }
    .add_attachment(AttachmentConfig {
        name: "height".to_string(),
        texture_size: TEXTURE_SIZE,
        border_size: 2,
        mip_level_count: 4,
        format: AttachmentFormat::R16,
    });

    // Configure the quality settings of the terrain view. Adapt the settings to your liking.
    let view_config = TerrainViewConfig::default();

    let tile_atlas = TileAtlas::new(&config);
    let tile_tree = TileTree::new(&tile_atlas, &view_config);

    let terrain = commands
        .spawn((
            TerrainBundle::new(tile_atlas),
            materials.add(DebugTerrainMaterial::default()),
        ))
        .id();

    let view = commands.spawn(DebugCameraBundle::default()).id();

    tile_trees.insert((terrain, view), tile_tree);

    commands.spawn(PbrBundle {
        mesh: meshes.add(Cuboid::from_length(10.0)),
        transform: Transform::from_translation(Vec3::new(
            TERRAIN_SIZE as f32 / 2.0,
            100.0,
            TERRAIN_SIZE as f32 / 2.0,
        )),
        ..default()
    });
}


================================================
FILE: examples/planar.rs
================================================
use bevy::math::DVec3;
use bevy::{prelude::*, reflect::TypePath, render::render_resource::*};
use bevy_terrain::prelude::*;

const PATH: &str = "terrains/planar";
const TERRAIN_SIZE: f64 = 2000.0;
const HEIGHT: f32 = 500.0;
const TEXTURE_SIZE: u32 = 512;
const LOD_COUNT: u32 = 8;

#[derive(Asset, AsBindGroup, TypePath, Clone)]
pub struct TerrainMaterial {
    #[texture(0, dimension = "1d")]
    #[sampler(1)]
    gradient: Handle<Image>,
}

impl Material for TerrainMaterial {
    fn fragment_shader() -> ShaderRef {
        "shaders/planar.wgsl".into()
    }
}

fn main() {
    App::new()
        .add_plugins((
            DefaultPlugins.build().disable::<TransformPlugin>(),
            TerrainPlugin,
            TerrainDebugPlugin, // enable debug settings and controls
            TerrainMaterialPlugin::<TerrainMaterial>::default(),
        ))
        .add_systems(Startup, setup)
        .run();
}

fn setup(
    mut commands: Commands,
    mut images: ResMut<LoadingImages>,
    mut materials: ResMut<Assets<TerrainMaterial>>,
    mut tile_trees: ResMut<TerrainViewComponents<TileTree>>,
    asset_server: Res<AssetServer>,
) {
    let gradient = asset_server.load("textures/gradient2.png");
    images.load_image(
        &gradient,
        TextureDimension::D1,
        TextureFormat::Rgba8UnormSrgb,
    );

    // Configure all the important properties of the terrain, as well as its attachments.
    let config = TerrainConfig {
        lod_count: LOD_COUNT,
        model: TerrainModel::planar(DVec3::new(0.0, -100.0, 0.0), TERRAIN_SIZE, 0.0, HEIGHT),
        path: PATH.to_string(),
        ..default()
    }
    .add_attachment(AttachmentConfig {
        name: "height".to_string(),
        texture_size: TEXTURE_SIZE,
        border_size: 2,
        mip_level_count: 4,
        format: AttachmentFormat::R16,
    })
    .add_attachment(AttachmentConfig {
        name: "albedo".to_string(),
        texture_size: TEXTURE_SIZE,
        border_size: 2,
        mip_level_count: 4,
        format: AttachmentFormat::Rgba8,
    });

    // Configure the quality settings of the terrain view. Adapt the settings to your liking.
    let view_config = TerrainViewConfig::default();

    let tile_atlas = TileAtlas::new(&config);
    let tile_tree = TileTree::new(&tile_atlas, &view_config);

    commands.spawn_big_space(ReferenceFrame::default(), |root| {
        let frame = root.frame().clone();

        let terrain = root
            .spawn_spatial((
                TerrainBundle::new(tile_atlas, &frame),
                materials.add(TerrainMaterial { gradient }),
            ))
            .id();

        let view = root.spawn_spatial(DebugCameraBundle::default()).id();

        tile_trees.insert((terrain, view), tile_tree);
    });
}


================================================
FILE: examples/preprocess_planar.rs
================================================
use bevy::prelude::*;
use bevy_terrain::prelude::*;

const PATH: &str = "terrains/planar";
const TEXTURE_SIZE: u32 = 512;
const LOD_COUNT: u32 = 4;

fn main() {
    App::new()
        .add_plugins((DefaultPlugins, TerrainPlugin, TerrainPreprocessPlugin))
        .add_systems(Startup, setup)
        .run();
}

fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
    let config = TerrainConfig {
        lod_count: LOD_COUNT,
        path: PATH.to_string(),
        ..default()
    }
    .add_attachment(AttachmentConfig {
        name: "height".to_string(),
        texture_size: TEXTURE_SIZE,
        border_size: 2,
        format: AttachmentFormat::R16,
        ..default()
    })
    .add_attachment(AttachmentConfig {
        name: "albedo".to_string(),
        texture_size: TEXTURE_SIZE,
        border_size: 2,
        format: AttachmentFormat::Rgba8,
        ..default()
    });

    let mut tile_atlas = TileAtlas::new(&config);

    let preprocessor = Preprocessor::new()
        .clear_attachment(0, &mut tile_atlas)
        .clear_attachment(1, &mut tile_atlas)
        .preprocess_tile(
            PreprocessDataset {
                attachment_index: 0,
                path: format!("{PATH}/source/height.png"),
                lod_range: 0..LOD_COUNT,
                ..default()
            },
            &asset_server,
            &mut tile_atlas,
        )
        .preprocess_tile(
            PreprocessDataset {
                attachment_index: 1,
                path: format!("{PATH}/source/albedo.png"),
                lod_range: 0..LOD_COUNT,
                ..default()
            },
            &asset_server,
            &mut tile_atlas,
        );

    commands.spawn((tile_atlas, preprocessor));
}


================================================
FILE: examples/preprocess_spherical.rs
================================================
use bevy::prelude::*;
use bevy_terrain::prelude::*;

const PATH: &str = "terrains/spherical";
const TEXTURE_SIZE: u32 = 512;
const LOD_COUNT: u32 = 5;

fn main() {
    App::new()
        .add_plugins((
            DefaultPlugins.build().disable::<TransformPlugin>(),
            TerrainPlugin,
            TerrainPreprocessPlugin,
        ))
        .add_systems(Startup, setup)
        .run();
}

fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
    let config = TerrainConfig {
        lod_count: LOD_COUNT,
        path: PATH.to_string(),
        atlas_size: 2048,
        ..default()
    }
    .add_attachment(AttachmentConfig {
        name: "height".to_string(),
        texture_size: TEXTURE_SIZE,
        border_size: 2,
        format: AttachmentFormat::R16,
        ..default()
    });

    let mut tile_atlas = TileAtlas::new(&config);

    let preprocessor = Preprocessor::new()
        .clear_attachment(0, &mut tile_atlas)
        .preprocess_spherical(
            SphericalDataset {
                attachment_index: 0,
                paths: (0..6)
                    .map(|side| format!("{PATH}/source/height/face{side}.tif"))
                    .collect(),
                lod_range: 0..LOD_COUNT,
            },
            &asset_server,
            &mut tile_atlas,
        );

    commands.spawn((tile_atlas, preprocessor));
}


================================================
FILE: examples/spherical.rs
================================================
use bevy::{math::DVec3, prelude::*, reflect::TypePath, render::render_resource::*};
use bevy_terrain::prelude::*;

const PATH: &str = "terrains/spherical";
const RADIUS: f64 = 6371000.0;
const MAJOR_AXES: f64 = 6378137.0;
const MINOR_AXES: f64 = 6356752.314245;
const MIN_HEIGHT: f32 = -12000.0;
const MAX_HEIGHT: f32 = 9000.0;
const TEXTURE_SIZE: u32 = 512;
const LOD_COUNT: u32 = 16;

#[derive(Asset, AsBindGroup, TypePath, Clone)]
pub struct TerrainMaterial {
    #[texture(0, dimension = "1d")]
    #[sampler(1)]
    gradient: Handle<Image>,
}

impl Material for TerrainMaterial {
    fn fragment_shader() -> ShaderRef {
        "shaders/spherical.wgsl".into()
    }
}

fn main() {
    App::new()
        .add_plugins((
            DefaultPlugins.build().disable::<TransformPlugin>(),
            TerrainPlugin,
            TerrainMaterialPlugin::<TerrainMaterial>::default(),
            TerrainDebugPlugin, // enable debug settings and controls
        ))
        // .insert_resource(ClearColor(Color::WHITE))
        .add_systems(Startup, setup)
        .run();
}

fn setup(
    mut commands: Commands,
    mut images: ResMut<LoadingImages>,
    mut meshes: ResMut<Assets<Mesh>>,
    mut materials: ResMut<Assets<TerrainMaterial>>,
    mut tile_trees: ResMut<TerrainViewComponents<TileTree>>,
    asset_server: Res<AssetServer>,
) {
    let gradient = asset_server.load("textures/gradient.png");
    images.load_image(
        &gradient,
        TextureDimension::D1,
        TextureFormat::Rgba8UnormSrgb,
    );

    // Configure all the important properties of the terrain, as well as its attachments.
    let config = TerrainConfig {
        lod_count: LOD_COUNT,
        model: TerrainModel::ellipsoid(DVec3::ZERO, MAJOR_AXES, MINOR_AXES, MIN_HEIGHT, MAX_HEIGHT),
        // model: TerrainModel::ellipsoid(
        //     DVec3::ZERO,
        //     6378137.0,
        //     6378137.0 * 0.5,
        //     MIN_HEIGHT,
        //     MAX_HEIGHT,
        // ),
        // model: TerrainModel::sphere(DVec3::ZERO, RADIUS),
        path: PATH.to_string(),
        ..default()
    }
    .add_attachment(AttachmentConfig {
        name: "height".to_string(),
        texture_size: TEXTURE_SIZE,
        border_size: 2,
        mip_level_count: 4,
        format: AttachmentFormat::R16,
    });

    // Configure the quality settings of the terrain view. Adapt the settings to your liking.
    let view_config = TerrainViewConfig::default();

    let tile_atlas = TileAtlas::new(&config);
    let tile_tree = TileTree::new(&tile_atlas, &view_config);

    commands.spawn_big_space(ReferenceFrame::default(), |root| {
        let frame = root.frame().clone();

        let terrain = root
            .spawn_spatial((
                TerrainBundle::new(tile_atlas, &frame),
                materials.add(TerrainMaterial {
                    gradient: gradient.clone(),
                }),
            ))
            .id();

        let view = root
            .spawn_spatial(DebugCameraBundle::new(
                -DVec3::X * RADIUS * 3.0,
                RADIUS,
                &frame,
            ))
            .id();

        tile_trees.insert((terrain, view), tile_tree);

        let sun_position = DVec3::new(-1.0, 1.0, -1.0) * RADIUS * 10.0;
        let (sun_cell, sun_translation) = frame.translation_to_grid(sun_position);

        root.spawn_spatial((
            PbrBundle {
                mesh: meshes.add(Sphere::new(RADIUS as f32 * 2.0).mesh().build()),
                transform: Transform::from_translation(sun_translation),
                ..default()
            },
            sun_cell,
        ));

        root.spawn_spatial(PbrBundle {
            mesh: meshes.add(Cuboid::from_length(RADIUS as f32 * 0.1)),
            ..default()
        });
    });
}


================================================
FILE: src/big_space.rs
================================================
pub use big_space::{BigSpaceCommands, FloatingOrigin};

pub type GridPrecision = i32;

pub type BigSpacePlugin = big_space::BigSpacePlugin<GridPrecision>;
pub type ReferenceFrame = big_space::reference_frame::ReferenceFrame<GridPrecision>;
pub type ReferenceFrames<'w, 's> =
    big_space::reference_frame::local_origin::ReferenceFrames<'w, 's, GridPrecision>;
pub type GridCell = big_space::GridCell<GridPrecision>;
pub type GridTransform = big_space::world_query::GridTransform<GridPrecision>;
pub type GridTransformReadOnly = big_space::world_query::GridTransformReadOnly<GridPrecision>;
pub type GridTransformOwned = big_space::world_query::GridTransformOwned<GridPrecision>;
pub type GridTransformItem<'w> = big_space::world_query::GridTransformItem<'w, GridPrecision>;


================================================
FILE: src/debug/camera.rs
================================================
#[cfg(feature = "high_precision")]
use crate::big_space::{
    FloatingOrigin, GridCell, GridTransform, GridTransformItem, ReferenceFrame, ReferenceFrames,
};

use bevy::{input::mouse::MouseMotion, math::DVec3, prelude::*};

#[derive(Bundle)]
pub struct DebugCameraBundle {
    pub camera: Camera3dBundle,
    pub controller: DebugCameraController,
    #[cfg(feature = "high_precision")]
    pub cell: GridCell,
    #[cfg(feature = "high_precision")]
    pub origin: FloatingOrigin,
}

impl Default for DebugCameraBundle {
    fn default() -> Self {
        Self {
            camera: default(),
            controller: default(),
            #[cfg(feature = "high_precision")]
            cell: default(),
            #[cfg(feature = "high_precision")]
            origin: FloatingOrigin,
        }
    }
}

impl DebugCameraBundle {
    #[cfg(feature = "high_precision")]
    pub fn new(position: DVec3, speed: f64, frame: &ReferenceFrame) -> Self {
        let (cell, translation) = frame.translation_to_grid(position);

        Self {
            camera: Camera3dBundle {
                transform: Transform::from_translation(translation).looking_to(Vec3::X, Vec3::Y),
                projection: PerspectiveProjection {
                    near: 0.000001,
                    ..default()
                }
                .into(),
                ..default()
            },
            cell,
            controller: DebugCameraController {
                translation_speed: speed,
                ..default()
            },
            ..default()
        }
    }

    #[cfg(not(feature = "high_precision"))]
    pub fn new(position: Vec3, speed: f64) -> Self {
        Self {
            camera: Camera3dBundle {
                transform: Transform::from_translation(position).looking_to(Vec3::X, Vec3::Y),
                projection: PerspectiveProjection {
                    near: 0.000001,
                    ..default()
                }
                .into(),
                ..default()
            },
            controller: DebugCameraController {
                translation_speed: speed,
                ..default()
            },
            ..default()
        }
    }
}

#[derive(Clone, Debug, Reflect, Component)]
pub struct DebugCameraController {
    pub enabled: bool,
    /// Smoothness of translation, from `0.0` to `1.0`.
    pub translational_smoothness: f64,
    /// Smoothness of rotation, from `0.0` to `1.0`.
    pub rotational_smoothness: f32,
    pub translation_speed: f64,
    pub rotation_speed: f32,
    pub acceleration_speed: f64,
    pub translation_velocity: DVec3,
    pub rotation_velocity: Vec2,
}

impl Default for DebugCameraController {
    fn default() -> Self {
        Self {
            enabled: false,
            translational_smoothness: 0.9,
            rotational_smoothness: 0.8,
            translation_speed: 10e1,
            rotation_speed: 1e-1,
            acceleration_speed: 4.0,
            translation_velocity: Default::default(),
            rotation_velocity: Default::default(),
        }
    }
}

pub fn camera_controller(
    #[cfg(feature = "high_precision")] frames: ReferenceFrames,
    time: Res<Time>,
    keyboard: Res<ButtonInput<KeyCode>>,
    mut mouse_move: EventReader<MouseMotion>,
    #[cfg(feature = "high_precision")] mut camera: Query<(
        Entity,
        GridTransform,
        &mut DebugCameraController,
    )>,
    #[cfg(not(feature = "high_precision"))] mut camera: Query<(
        &mut Transform,
        &mut DebugCameraController,
    )>,
) {
    #[cfg(feature = "high_precision")]
    let (
        camera,
        GridTransformItem {
            mut transform,
            mut cell,
        },
        mut controller,
    ) = camera.single_mut();
    #[cfg(feature = "high_precision")]
    let frame = frames.parent_frame(camera).unwrap();

    #[cfg(not(feature = "high_precision"))]
    let (mut transform, mut controller) = camera.single_mut();

    keyboard
        .just_pressed(KeyCode::KeyT)
        .then(|| controller.enabled = !controller.enabled);

    if !controller.enabled {
        return;
    }

    let mut translation_direction = DVec3::ZERO; // x: left/right, y: up/down, z: forward/backward
    let rotation_direction = mouse_move.read().map(|m| -m.delta).sum::<Vec2>(); // x: yaw, y: pitch, z: roll
    let mut acceleration = 0.0;

    keyboard
        .pressed(KeyCode::ArrowLeft)
        .then(|| translation_direction.x -= 1.0);
    keyboard
        .pressed(KeyCode::ArrowRight)
        .then(|| translation_direction.x += 1.0);
    keyboard
        .pressed(KeyCode::PageUp)
        .then(|| translation_direction.y += 1.0);
    keyboard
        .pressed(KeyCode::PageDown)
        .then(|| translation_direction.y -= 1.0);
    keyboard
        .pressed(KeyCode::ArrowUp)
        .then(|| translation_direction.z -= 1.0);
    keyboard
        .pressed(KeyCode::ArrowDown)
        .then(|| translation_direction.z += 1.0);
    keyboard.pressed(KeyCode::Home).then(|| acceleration -= 1.0);
    keyboard.pressed(KeyCode::End).then(|| acceleration += 1.0);

    translation_direction = transform.rotation.as_dquat() * translation_direction;

    let dt = time.delta_seconds_f64();
    let lerp_translation = 1.0 - controller.translational_smoothness.clamp(0.0, 0.999);
    let lerp_rotation = 1.0 - controller.rotational_smoothness.clamp(0.0, 0.999);

    let translation_velocity_target = translation_direction * controller.translation_speed * dt;
    let rotation_velocity_target = rotation_direction * controller.rotation_speed * dt as f32;

    controller.translation_velocity = controller
        .translation_velocity
        .lerp(translation_velocity_target, lerp_translation);
    controller.rotation_velocity = controller
        .rotation_velocity
        .lerp(rotation_velocity_target, lerp_rotation);
    controller.translation_speed *= 1.0 + acceleration * controller.acceleration_speed * dt;

    let (yaw, pitch, _) = transform.rotation.to_euler(EulerRot::YXZ);
    let new_yaw = (yaw + controller.rotation_velocity.x) % std::f32::consts::TAU;
    let new_pitch = (pitch + controller.rotation_velocity.y)
        .clamp(-std::f32::consts::FRAC_PI_2, std::f32::consts::FRAC_PI_2);

    #[cfg(feature = "high_precision")]
    {
        let (cell_delta, translation_delta) =
            frame.translation_to_grid(controller.translation_velocity);

        *cell += cell_delta;
        transform.translation += translation_delta;
    }
    #[cfg(not(feature = "high_precision"))]
    {
        transform.translation += controller.translation_velocity.as_vec3();
    }

    transform.rotation = Quat::from_euler(EulerRot::YXZ, new_yaw, new_pitch, 0.0);
}


================================================
FILE: src/debug/mod.rs
================================================
//! Contains a debug resource and systems controlling it to visualize different internal
//! data of the plugin.
use crate::{
    debug::camera::camera_controller, terrain_data::tile_tree::TileTree,
    terrain_view::TerrainViewComponents,
};
use bevy::{
    asset::LoadState,
    prelude::*,
    render::{render_resource::*, Extract, RenderApp},
    transform::TransformSystem,
    window::PrimaryWindow,
};

pub mod camera;

#[derive(Asset, AsBindGroup, TypePath, Clone, Default)]
pub struct DebugTerrainMaterial {}

impl Material for DebugTerrainMaterial {}

/// Adds a terrain debug config, a debug camera and debug control systems.
pub struct TerrainDebugPlugin;

impl Plugin for TerrainDebugPlugin {
    fn build(&self, app: &mut App) {
        app.init_resource::<DebugTerrain>()
            .init_resource::<LoadingImages>()
            .add_systems(Startup, (debug_lighting, debug_window))
            .add_systems(
                Update,
                (toggle_debug, update_view_parameter, finish_loading_images),
            )
            .add_systems(
                PostUpdate,
                camera_controller.before(TransformSystem::TransformPropagate),
            );

        app.sub_app_mut(RenderApp)
            .init_resource::<DebugTerrain>()
            .add_systems(ExtractSchedule, extract_debug);
    }
}

#[derive(Clone, Resource)]
pub struct DebugTerrain {
    pub wireframe: bool,
    pub show_data_lod: bool,
    pub show_geometry_lod: bool,
    pub show_tile_tree: bool,
    pub show_pixels: bool,
    pub show_uv: bool,
    pub show_normals: bool,
    pub morph: bool,
    pub blend: bool,
    pub tile_tree_lod: bool,
    pub lighting: bool,
    pub sample_grad: bool,
    pub high_precision: bool,
    pub freeze: bool,
    pub test1: bool,
    pub test2: bool,
    pub test3: bool,
}

impl Default for DebugTerrain {
    fn default() -> Self {
        Self {
            wireframe: false,
            show_data_lod: false,
            show_geometry_lod: false,
            show_tile_tree: false,
            show_pixels: false,
            show_uv: false,
            show_normals: false,
            morph: true,
            blend: true,
            tile_tree_lod: false,
            lighting: true,
            sample_grad: true,
            high_precision: true,
            freeze: false,
            test1: false,
            test2: false,
            test3: false,
        }
    }
}

pub fn extract_debug(mut debug: ResMut<DebugTerrain>, extracted_debug: Extract<Res<DebugTerrain>>) {
    *debug = extracted_debug.clone();
}

pub fn toggle_debug(input: Res<ButtonInput<KeyCode>>, mut debug: ResMut<DebugTerrain>) {
    if input.just_pressed(KeyCode::KeyW) {
        debug.wireframe = !debug.wireframe;
        println!(
            "Toggled the wireframe view {}.",
            if debug.wireframe { "on" } else { "off" }
        )
    }
    if input.just_pressed(KeyCode::KeyL) {
        debug.show_data_lod = !debug.show_data_lod;
        println!(
            "Toggled the terrain data LOD view {}.",
            if debug.show_data_lod { "on" } else { "off" }
        )
    }
    if input.just_pressed(KeyCode::KeyY) {
        debug.show_geometry_lod = !debug.show_geometry_lod;
        println!(
            "Toggled the terrain geometry LOD view {}.",
            if debug.show_geometry_lod { "on" } else { "off" }
        )
    }
    if input.just_pressed(KeyCode::KeyQ) {
        debug.show_tile_tree = !debug.show_tile_tree;
        println!(
            "Toggled the tile tree LOD view {}.",
            if debug.show_tile_tree { "on" } else { "off" }
        )
    }
    if input.just_pressed(KeyCode::KeyP) {
        debug.show_pixels = !debug.show_pixels;
        println!(
            "Toggled the pixel view {}.",
            if debug.show_pixels { "on" } else { "off" }
        )
    }
    if input.just_pressed(KeyCode::KeyU) {
        debug.show_uv = !debug.show_uv;
        println!(
            "Toggled the uv view {}.",
            if debug.show_uv { "on" } else { "off" }
        )
    }
    if input.just_pressed(KeyCode::KeyB) {
        debug.show_normals = !debug.show_normals;
        println!(
            "Toggled the normals view {}.",
            if debug.show_normals { "on" } else { "off" }
        )
    }
    if input.just_pressed(KeyCode::KeyM) {
        debug.morph = !debug.morph;
        println!(
            "Toggled morphing {}.",
            if debug.morph { "on" } else { "off" }
        )
    }
    if input.just_pressed(KeyCode::KeyK) {
        debug.blend = !debug.blend;
        println!(
            "Toggled blending {}.",
            if debug.blend { "on" } else { "off" }
        )
    }
    if input.just_pressed(KeyCode::KeyZ) {
        debug.tile_tree_lod = !debug.tile_tree_lod;
        println!(
            "Toggled tile tree lod {}.",
            if debug.tile_tree_lod { "on" } else { "off" }
        )
    }
    if input.just_pressed(KeyCode::KeyS) {
        debug.lighting = !debug.lighting;
        println!(
            "Toggled the lighting {}.",
            if debug.lighting { "on" } else { "off" }
        )
    }
    if input.just_pressed(KeyCode::KeyG) {
        debug.sample_grad = !debug.sample_grad;
        println!(
            "Toggled the texture sampling using gradients {}.",
            if debug.sample_grad { "on" } else { "off" }
        )
    }
    if input.just_pressed(KeyCode::KeyH) {
        debug.high_precision = !debug.high_precision;
        println!(
            "Toggled high precision coordinates {}.",
            if debug.high_precision { "on" } else { "off" }
        )
    }
    if input.just_pressed(KeyCode::KeyF) {
        debug.freeze = !debug.freeze;
        println!(
            "{} the view frustum.",
            if debug.freeze { "Froze" } else { "Unfroze" }
        )
    }
    if input.just_pressed(KeyCode::Digit1) {
        debug.test1 = !debug.test1;
        println!(
            "Toggled the debug flag 1 {}.",
            if debug.test1 { "on" } else { "off" }
        )
    }
    if input.just_pressed(KeyCode::Digit2) {
        debug.test2 = !debug.test2;
        println!(
            "Toggled the debug flag 2 {}.",
            if debug.test2 { "on" } else { "off" }
        )
    }
    if input.just_pressed(KeyCode::Digit3) {
        debug.test3 = !debug.test3;
        println!(
            "Toggled the debug flag 3 {}.",
            if debug.test3 { "on" } else { "off" }
        )
    }
}

pub fn update_view_parameter(
    input: Res<ButtonInput<KeyCode>>,
    mut tile_trees: ResMut<TerrainViewComponents<TileTree>>,
) {
    for tile_tree in &mut tile_trees.values_mut() {
        if input.just_pressed(KeyCode::KeyN) {
            tile_tree.blend_distance -= 0.25;
            println!(
                "Decreased the blend distance to {}.",
                tile_tree.blend_distance
            );
        }
        if input.just_pressed(KeyCode::KeyE) {
            tile_tree.blend_distance += 0.25;
            println!(
                "Increased the blend distance to {}.",
                tile_tree.blend_distance
            );
        }

        if input.just_pressed(KeyCode::KeyI) {
            tile_tree.morph_distance -= 0.25;
            println!(
                "Decreased the morph distance to {}.",
                tile_tree.morph_distance
            );
        }
        if input.just_pressed(KeyCode::KeyO) {
            tile_tree.morph_distance += 0.25;
            println!(
                "Increased the morph distance to {}.",
                tile_tree.morph_distance
            );
        }

        if input.just_pressed(KeyCode::KeyX) && tile_tree.grid_size > 2 {
            tile_tree.grid_size -= 2;
            println!("Decreased the grid size to {}.", tile_tree.grid_size);
        }
        if input.just_pressed(KeyCode::KeyJ) {
            tile_tree.grid_size += 2;
            println!("Increased the grid size to {}.", tile_tree.grid_size);
        }
    }
}

pub(crate) fn debug_lighting(mut commands: Commands) {
    commands.spawn(DirectionalLightBundle {
        directional_light: DirectionalLight {
            illuminance: 5000.0,
            ..default()
        },
        transform: Transform::from_xyz(-1.0, 1.0, -1.0).looking_at(Vec3::ZERO, Vec3::Y),
        ..default()
    });
    commands.insert_resource(AmbientLight {
        brightness: 100.0,
        ..default()
    });
}

pub fn debug_window(mut window: Query<&mut Window, With<PrimaryWindow>>) {
    let mut window = window.single_mut();
    window.cursor.visible = false;
}

#[derive(Resource, Default)]
pub struct LoadingImages(Vec<(AssetId<Image>, TextureDimension, TextureFormat)>);

impl LoadingImages {
    pub fn load_image(
        &mut self,
        handle: &Handle<Image>,
        dimension: TextureDimension,
        format: TextureFormat,
    ) -> &mut Self {
        self.0.push((handle.id(), dimension, format));
        self
    }
}

fn finish_loading_images(
    asset_server: Res<AssetServer>,
    mut loading_images: ResMut<LoadingImages>,
    mut images: ResMut<Assets<Image>>,
) {
    loading_images.0.retain(|&(id, dimension, format)| {
        if asset_server.load_state(id) == LoadState::Loaded {
            let image = images.get_mut(id).unwrap();
            image.texture_descriptor.dimension = dimension;
            image.texture_descriptor.format = format;

            false
        } else {
            true
        }
    });
}


================================================
FILE: src/formats/mod.rs
================================================
pub mod tiff;

use crate::math::TileCoordinate;
use anyhow::Result;
use bincode::{config, Decode, Encode};
use std::{fs, path::Path};

#[derive(Encode, Decode, Debug)]
pub struct TC {
    pub tiles: Vec<TileCoordinate>,
}

impl TC {
    pub fn decode_alloc(encoded: &[u8]) -> Result<Self> {
        let config = config::standard();
        let decoded = bincode::decode_from_slice(encoded, config)?;
        Ok(decoded.0)
    }

    pub fn encode_alloc(&self) -> Result<Vec<u8>> {
        let config = config::standard();
        let encoded = bincode::encode_to_vec(self, config)?;
        Ok(encoded)
    }

    pub fn load_file<P: AsRef<Path>>(path: P) -> Result<Self> {
        let encoded = fs::read(path)?;
        Self::decode_alloc(&encoded)
    }

    pub fn save_file<P: AsRef<Path>>(&self, path: P) -> Result<()> {
        let encoded = self.encode_alloc()?;
        fs::write(path, encoded)?;
        Ok(())
    }
}


================================================
FILE: src/formats/tiff.rs
================================================
use bevy::{
    asset::{io::Reader, AssetLoader, AsyncReadExt, LoadContext},
    prelude::*,
    render::{
        render_asset::RenderAssetUsages,
        render_resource::{Extent3d, TextureDimension, TextureFormat},
        texture::TextureError,
    },
};
use bytemuck::cast_slice;
use std::io::Cursor;
use tiff::decoder::{Decoder, DecodingResult};

#[derive(Default)]
pub struct TiffLoader;
impl AssetLoader for TiffLoader {
    type Asset = Image;
    type Settings = ();
    type Error = TextureError;
    async fn load<'a>(
        &'a self,
        reader: &'a mut Reader<'_>,
        _settings: &'a Self::Settings,
        _load_context: &'a mut LoadContext<'_>,
    ) -> Result<Image, Self::Error> {
        let mut bytes = Vec::new();
        reader.read_to_end(&mut bytes).await.unwrap();

        let mut decoder = Decoder::new(Cursor::new(bytes)).unwrap();

        let (width, height) = decoder.dimensions().unwrap();

        let data = match decoder.read_image().unwrap() {
            DecodingResult::U8(data) => cast_slice(&data).to_vec(),
            DecodingResult::U16(data) => cast_slice(&data).to_vec(),
            DecodingResult::U32(data) => cast_slice(&data).to_vec(),
            DecodingResult::U64(data) => cast_slice(&data).to_vec(),
            DecodingResult::F32(data) => cast_slice(&data).to_vec(),
            DecodingResult::F64(data) => cast_slice(&data).to_vec(),
            DecodingResult::I8(data) => cast_slice(&data).to_vec(),
            DecodingResult::I16(data) => cast_slice(&data).to_vec(),
            DecodingResult::I32(data) => cast_slice(&data).to_vec(),
            DecodingResult::I64(data) => cast_slice(&data).to_vec(),
        };

        Ok(Image::new(
            Extent3d {
                width,
                height,
                depth_or_array_layers: 1,
            },
            TextureDimension::D2,
            data,
            TextureFormat::R16Unorm,
            RenderAssetUsages::default(),
        ))
    }

    fn extensions(&self) -> &[&str] {
        &["tif", "tiff"]
    }
}


================================================
FILE: src/lib.rs
================================================
//! This crate provides the ability to render beautiful height-field terrains of any size.
//! This is achieved in extensible and modular manner, so that the terrain data
//! can be accessed from nearly anywhere (systems, shaders) [^note].
//!
//! # Background
//! There are three critical questions that each terrain renderer has to solve:
//!
//! ## How to store, manage and access the terrain data?
//! Each terrain has different types of textures associated with it.
//! For example a simple one might only need height and albedo information.
//! Because terrains can be quite large the space required for all of these so called
//! attachments, can/should not be stored in RAM and VRAM all at once.
//! Thus they have to be streamed in and out depending on the positions of the
//! viewers (cameras, lights, etc.).
//! Therefore the terrain is subdivided into a giant tile_tree, whose tiles store their
//! section of these attachments.
//! This crate uses the chunked clipmap data structure, which consist of two pieces working together.
//! The wrapping [`TileTree`](prelude::TileTree) views together with
//! the [`TileAtlas`](prelude::TileAtlas) (the data structure
//! that stores all of the currently loaded data) can be used to efficiently retrieve
//! the best currently available data at any position for terrains of any size.
//! See the [`terrain_data`] module for more information.
//!
//! ## How to best approximate the terrain geometry?
//! Even a small terrain with a height map of 1000x1000 pixels would require 1 million vertices
//! to be rendered each frame per view, with an naive approach without an lod strategy.
//! To better distribute the vertices over the screen there exist many different algorithms.
//! This crate comes with its own default terrain geometry algorithm, called the
//! Uniform Distance-Dependent Level of Detail (UDLOD), which was developed with performance and
//! quality scalability in mind.
//! See the [`render`] module for more information.
//! You can also implement a different algorithm yourself and only use the terrain
//! data structures to solve the first question.
//!
//! ## How to shade the terrain?
//! The third and most important challenge of terrain rendering is the shading. This is a very
//! project specific problem and thus there does not exist a one-size-fits-all solution.
//! You can define your own terrain [Material](bevy::prelude::Material) and shader with all the
//! detail textures tailored to your application.
//! In the future this plugin will provide modular shader functions to make techniques like splat
//! mapping, triplane mapping, etc. easier.
//! Additionally a virtual texturing solution might be integrated to achieve better performance.
//!
//! [^note]: Some of these claims are not yet fully implemented.

#[cfg(feature = "high_precision")]
pub mod big_space;
pub mod debug;
pub mod formats;
pub mod math;
pub mod plugin;
pub mod preprocess;
pub mod render;
pub mod shaders;
pub mod terrain;
pub mod terrain_data;
pub mod terrain_view;
pub mod util;

pub mod prelude {
    //! `use bevy_terrain::prelude::*;` to import common components, bundles, and plugins.
    // #[doc(hidden)]

    #[cfg(feature = "high_precision")]
    pub use crate::big_space::{BigSpaceCommands, ReferenceFrame};

    pub use crate::{
        debug::{
            camera::{DebugCameraBundle, DebugCameraController},
            DebugTerrainMaterial, LoadingImages, TerrainDebugPlugin,
        },
        math::TerrainModel,
        plugin::TerrainPlugin,
        preprocess::{
            preprocessor::Preprocessor,
            preprocessor::{PreprocessDataset, SphericalDataset},
            TerrainPreprocessPlugin,
        },
        render::terrain_material::TerrainMaterialPlugin,
        terrain::{TerrainBundle, TerrainConfig},
        terrain_data::{
            tile_atlas::TileAtlas, tile_tree::TileTree, AttachmentConfig, AttachmentFormat,
        },
        terrain_view::{TerrainViewComponents, TerrainViewConfig},
    };
}


================================================
FILE: src/math/coordinate.rs
================================================
use crate::math::{TerrainModel, C_SQR};
use bevy::{
    math::{DVec2, DVec3, IVec2},
    render::render_resource::ShaderType,
};
use bincode::{Decode, Encode};
use std::fmt;

const NEIGHBOURING_SIDES: [[u32; 5]; 6] = [
    [0, 4, 2, 1, 5],
    [1, 0, 2, 3, 5],
    [2, 0, 4, 3, 1],
    [3, 2, 4, 5, 1],
    [4, 2, 0, 5, 3],
    [5, 4, 0, 1, 3],
];

#[derive(Clone, Copy)]
enum SideInfo {
    Fixed0,
    Fixed1,
    PositiveS,
    PositiveT,
}

impl SideInfo {
    const EVEN_LIST: [[SideInfo; 2]; 6] = [
        [SideInfo::PositiveS, SideInfo::PositiveT],
        [SideInfo::Fixed0, SideInfo::PositiveT],
        [SideInfo::Fixed0, SideInfo::PositiveS],
        [SideInfo::PositiveT, SideInfo::PositiveS],
        [SideInfo::PositiveT, SideInfo::Fixed0],
        [SideInfo::PositiveS, SideInfo::Fixed0],
    ];
    const ODD_LIST: [[SideInfo; 2]; 6] = [
        [SideInfo::PositiveS, SideInfo::PositiveT],
        [SideInfo::PositiveS, SideInfo::Fixed1],
        [SideInfo::PositiveT, SideInfo::Fixed1],
        [SideInfo::PositiveT, SideInfo::PositiveS],
        [SideInfo::Fixed1, SideInfo::PositiveS],
        [SideInfo::Fixed1, SideInfo::PositiveT],
    ];

    fn project_to_side(side: u32, other_side: u32) -> [SideInfo; 2] {
        let index = ((6 + other_side - side) % 6) as usize;

        if side % 2 == 0 {
            SideInfo::EVEN_LIST[index]
        } else {
            SideInfo::ODD_LIST[index]
        }
    }
}

/// Describes a location on the unit cube sphere.
/// The side index refers to one of the six cube faces and the uv coordinate describes the location within this side.
#[derive(Copy, Clone, Debug, Default)]
pub struct Coordinate {
    pub side: u32,
    pub uv: DVec2,
}

impl Coordinate {
    pub fn new(side: u32, uv: DVec2) -> Self {
        Self { side, uv }
    }

    /// Calculates the coordinate for for the local position on the unit cube sphere.
    pub(crate) fn from_world_position(world_position: DVec3, model: &TerrainModel) -> Self {
        let local_position = model.position_world_to_local(world_position);

        let (side, uv) = if model.is_spherical() {
            let normal = local_position;
            let abs_normal = normal.abs();

            let (side, uv) = if abs_normal.x > abs_normal.y && abs_normal.x > abs_normal.z {
                if normal.x < 0.0 {
                    (0, DVec2::new(-normal.z / normal.x, normal.y / normal.x))
                } else {
                    (3, DVec2::new(-normal.y / normal.x, normal.z / normal.x))
                }
            } else if abs_normal.z > abs_normal.y {
                if normal.z > 0.0 {
                    (1, DVec2::new(normal.x / normal.z, -normal.y / normal.z))
                } else {
                    (4, DVec2::new(normal.y / normal.z, -normal.x / normal.z))
                }
            } else {
                if normal.y > 0.0 {
                    (2, DVec2::new(normal.x / normal.y, normal.z / normal.y))
                } else {
                    (5, DVec2::new(-normal.z / normal.y, -normal.x / normal.y))
                }
            };

            let w = uv * ((1.0 + C_SQR) / (1.0 + C_SQR * uv * uv)).powf(0.5);
            let uv = 0.5 * w + 0.5;

            (side, uv)
        } else {
            let uv = DVec2::new(local_position.x + 0.5, local_position.z + 0.5)
                .clamp(DVec2::ZERO, DVec2::ONE);

            (0, uv)
        };

        Self { side, uv }
    }

    pub(crate) fn world_position(self, model: &TerrainModel, height: f32) -> DVec3 {
        let local_position = if model.is_spherical() {
            let w = (self.uv - 0.5) / 0.5;
            let uv = w / (1.0 + C_SQR - C_SQR * w * w).powf(0.5);

            match self.side {
                0 => DVec3::new(-1.0, -uv.y, uv.x),
                1 => DVec3::new(uv.x, -uv.y, 1.0),
                2 => DVec3::new(uv.x, 1.0, uv.y),
                3 => DVec3::new(1.0, -uv.x, uv.y),
                4 => DVec3::new(uv.y, -uv.x, -1.0),
                5 => DVec3::new(uv.y, -1.0, uv.x),
                _ => unreachable!(),
            }
            .normalize()
        } else {
            DVec3::new(self.uv.x - 0.5, 0.0, self.uv.y - 0.5)
        };

        model.position_local_to_world(local_position, height as f64)
    }

    /// Projects the coordinate onto one of the six cube faces.
    /// Thereby it chooses the closest location on this face to the original coordinate.
    pub(crate) fn project_to_side(self, side: u32, model: &TerrainModel) -> Self {
        if model.is_spherical() {
            let info = SideInfo::project_to_side(self.side, side);

            let uv = info
                .map(|info| match info {
                    SideInfo::Fixed0 => 0.0,
                    SideInfo::Fixed1 => 1.0,
                    SideInfo::PositiveS => self.uv.x,
                    SideInfo::PositiveT => self.uv.y,
                })
                .into();

            Self { side, uv }
        } else {
            self
        }
    }
}

/// The global coordinate and identifier of a tile.
#[derive(Copy, Clone, Default, Debug, Hash, Eq, PartialEq, ShaderType, Encode, Decode)]
pub struct TileCoordinate {
    /// The side of the cube sphere the tile is located on.
    pub side: u32,
    /// The lod of the tile, where 0 is the highest level of detail with the smallest size
    /// and highest resolution
    pub lod: u32,
    /// The x position of the tile in tile sizes.
    pub x: u32,
    /// The y position of the tile in tile sizes.
    pub y: u32,
}

impl TileCoordinate {
    pub const INVALID: TileCoordinate = TileCoordinate {
        side: u32::MAX,
        lod: u32::MAX,
        x: u32::MAX,
        y: u32::MAX,
    };

    pub fn new(side: u32, lod: u32, x: u32, y: u32) -> Self {
        Self { side, lod, x, y }
    }

    pub fn count(lod: u32) -> u32 {
        1 << lod
    }

    pub fn path(self, path: &str, extension: &str) -> String {
        format!("{path}/{self}.{extension}")
    }

    pub fn parent(self) -> Self {
        Self {
            side: self.side,
            lod: self.lod.wrapping_sub(1),
            x: self.x >> 1,
            y: self.y >> 1,
        }
    }

    pub fn children(self) -> impl Iterator<Item = Self> {
        (0..4).map(move |index| {
            TileCoordinate::new(
                self.side,
                self.lod + 1,
                (self.x << 1) + index % 2,
                (self.y << 1) + index / 2,
            )
        })
    }

    pub fn neighbours(self, spherical: bool) -> impl Iterator<Item = Self> {
        const OFFSETS: [IVec2; 8] = [
            IVec2::new(0, -1),
            IVec2::new(1, 0),
            IVec2::new(0, 1),
            IVec2::new(-1, 0),
            IVec2::new(-1, -1),
            IVec2::new(1, -1),
            IVec2::new(1, 1),
            IVec2::new(-1, 1),
        ];

        OFFSETS.iter().map(move |&offset| {
            let neighbour_position = IVec2::new(self.x as i32, self.y as i32) + offset;

            self.neighbour_coordinate(neighbour_position, spherical)
        })
    }

    fn neighbour_coordinate(self, neighbour_position: IVec2, spherical: bool) -> Self {
        let tile_count = Self::count(self.lod) as i32;

        if spherical {
            let edge_index = match neighbour_position {
                IVec2 { x, y }
                    if x < 0 && y < 0
                        || x < 0 && y >= tile_count
                        || x >= tile_count && y < 0
                        || x >= tile_count && y >= tile_count =>
                {
                    return Self::INVALID;
                }
                IVec2 { x, .. } if x < 0 => 1,
                IVec2 { y, .. } if y < 0 => 2,
                IVec2 { x, .. } if x >= tile_count => 3,
                IVec2 { y, .. } if y >= tile_count => 4,
                _ => 0,
            };

            let neighbour_position = neighbour_position
                .clamp(IVec2::ZERO, IVec2::splat(tile_count - 1))
                .as_uvec2();

            let neighbour_side = NEIGHBOURING_SIDES[self.side as usize][edge_index];

            let info = SideInfo::project_to_side(self.side, neighbour_side);

            let [x, y] = info.map(|info| match info {
                SideInfo::Fixed0 => 0,
                SideInfo::Fixed1 => tile_count as u32 - 1,
                SideInfo::PositiveS => neighbour_position.x,
                SideInfo::PositiveT => neighbour_position.y,
            });

            Self::new(neighbour_side, self.lod, x, y)
        } else {
            if neighbour_position.x < 0
                || neighbour_position.y < 0
                || neighbour_position.x >= tile_count
                || neighbour_position.y >= tile_count
            {
                Self::INVALID
            } else {
                Self::new(
                    self.side,
                    self.lod,
                    neighbour_position.x as u32,
                    neighbour_position.y as u32,
                )
            }
        }
    }
}

impl fmt::Display for TileCoordinate {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
        write!(f, "{}_{}_{}_{}", self.side, self.lod, self.x, self.y)
    }
}


================================================
FILE: src/math/ellipsoid.rs
================================================
use bevy::math::{DVec2, DVec3, Vec3Swizzles};
use std::cmp::Ordering;

// Adapted from https://www.geometrictools.com/Documentation/DistancePointEllipseEllipsoid.pdf
// Original licensed under Creative Commons Attribution 4.0 International License
// http://creativecommons.org/licenses/by/4.0/

// After 1074 iterations, s0 == s1 == s
// This should probably be relaxed to only limit the error s1-s0 to a constant e.
const MAX_ITERATIONS: usize = 1074;

pub fn project_point_ellipsoid(e: DVec3, y: DVec3) -> DVec3 {
    let sign = y.signum();
    let y = y.xzy().abs();

    let x = if y.z > 0.0 {
        if y.y > 0.0 {
            if y.x > 0.0 {
                let z = y / e;
                let g = z.length_squared() - 1.0;

                if g != 0.0 {
                    let r = DVec3::new((e.x * e.x) / (e.z * e.z), (e.y * e.y) / (e.z * e.z), 1.0);

                    r * y / (get_root_3d(r, z, g) + r)
                } else {
                    y
                }
            } else {
                project_point_ellipse(e.yz(), y.yz()).extend(0.0).zxy()
            }
        } else {
            if y.x > 0.0 {
                project_point_ellipse(e.xz(), y.xz()).extend(0.0).xzy()
            } else {
                DVec3::new(0.0, 0.0, e.z)
            }
        }
    } else {
        let denom0 = e.x * e.x - e.z * e.z;
        let denom1 = e.y * e.y - e.z * e.z;
        let numer0 = e.x * y.x;
        let numer1 = e.y * y.y;

        let mut x = None;

        if numer0 < denom0 && numer1 < denom1 {
            let xde0 = numer0 / denom0;
            let xde1 = numer1 / denom1;
            let xde0sqr = xde0 * xde0;
            let xde1sqr = xde1 * xde1;
            let discr = 1.0 - xde0sqr - xde1sqr;

            if discr > 0.0 {
                x = Some(e * DVec3::new(xde0, xde1, discr.sqrt()));
            }
        }

        x.unwrap_or_else(|| project_point_ellipse(e.xy(), y.xy()).extend(0.0))
    };

    sign * x.xzy()
}

fn project_point_ellipse(e: DVec2, y: DVec2) -> DVec2 {
    if y.y > 0.0 {
        if y.x > 0.0 {
            let z = y / e;
            let g = z.length_squared() - 1.0;

            if g != 0.0 {
                let r = DVec2::new((e.x * e.x) / (e.y * e.y), 1.0);
                r * y / (get_root_2d(r, z, g) + r)
            } else {
                y
            }
        } else {
            DVec2::new(0.0, e.y)
        }
    } else {
        let numer0 = e.x * y.x;
        let denom0 = e.x * e.x - e.y * e.y;
        if numer0 < denom0 {
            let xde0 = numer0 / denom0;
            DVec2::new(e.x * xde0, e.y * (1.0 - xde0 * xde0).sqrt())
        } else {
            DVec2::new(e.x, 0.0)
        }
    }
}

fn get_root_3d(r: DVec3, z: DVec3, g: f64) -> f64 {
    let n = r * z;

    let mut s0 = z.z - 1.0;
    let mut s1 = if g < 0.0 { 0.0 } else { n.length() - 1.0 };
    let mut s = 0.0;

    for _ in 0..MAX_ITERATIONS {
        s = (s0 + s1) / 2.0;
        if s == s0 || s == s1 {
            break;
        }

        let ratio = n / (s + r);
        let g = ratio.length_squared() - 1.0;

        match g.total_cmp(&0.0) {
            Ordering::Less => s1 = s,
            Ordering::Equal => break,
            Ordering::Greater => s0 = s,
        }
    }

    s
}

fn get_root_2d(r: DVec2, z: DVec2, g: f64) -> f64 {
    let n = r * z;

    let mut s0 = z.y - 1.0;
    let mut s1 = if g < 0.0 { 0.0 } else { n.length() - 1.0 };
    let mut s = 0.0;

    for _ in 0..MAX_ITERATIONS {
        s = (s0 + s1) / 2.0;
        if s == s0 || s == s1 {
            break;
        }

        let ratio = n / (s + r);
        let g = ratio.length_squared() - 1.0;

        match g.total_cmp(&0.0) {
            Ordering::Less => s1 = s,
            Ordering::Equal => break,
            Ordering::Greater => s0 = s,
        }
    }

    s
}


================================================
FILE: src/math/mod.rs
================================================
mod coordinate;
mod ellipsoid;
mod terrain_model;

pub use crate::math::{
    coordinate::{Coordinate, TileCoordinate},
    terrain_model::{
        generate_terrain_model_approximation, TerrainModel, TerrainModelApproximation,
    },
};

/// The square of the parameter c of the algebraic sigmoid function, used to convert between uv and st coordinates.
const C_SQR: f64 = 0.87 * 0.87;


================================================
FILE: src/math/terrain_model.rs
================================================
use crate::{
    math::{coordinate::Coordinate, ellipsoid::project_point_ellipsoid, TileCoordinate, C_SQR},
    terrain_data::tile_atlas::TileAtlas,
    terrain_data::tile_tree::TileTree,
    terrain_view::TerrainViewComponents,
};
use bevy::{
    math::{DMat3, DMat4, DQuat, DVec2, DVec3, IVec2},
    prelude::*,
    render::render_resource::ShaderType,
};

/// One matrix per side, which shuffles the a, b, and c component to their corresponding position.
const SIDE_MATRICES: [DMat3; 6] = [
    DMat3::from_cols_array(&[-1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, -1.0, 0.0]),
    DMat3::from_cols_array(&[0.0, 0.0, 1.0, 1.0, 0.0, 0.0, 0.0, -1.0, 0.0]),
    DMat3::from_cols_array(&[0.0, 1.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0]),
    DMat3::from_cols_array(&[1.0, 0.0, 0.0, 0.0, -1.0, 0.0, 0.0, 0.0, 1.0]),
    DMat3::from_cols_array(&[0.0, 0.0, -1.0, 0.0, -1.0, 0.0, 1.0, 0.0, 0.0]),
    DMat3::from_cols_array(&[0.0, -1.0, 0.0, 0.0, 0.0, 1.0, 1.0, 0.0, 0.0]),
];

#[derive(Clone)]
pub enum TerrainKind {
    PLANAR {
        side_length: f64,
    },
    SPHERICAL {
        radius: f64,
    },
    ELLIPSOIDAL {
        ellipsoid_from_world: DMat4,
        major_axis: f64,
        minor_axis: f64,
    },
}

// Todo: keep in sync with terrain transform, make this authoritative?

#[derive(Clone)]
pub struct TerrainModel {
    pub(crate) kind: TerrainKind,
    pub(crate) min_height: f32,
    pub(crate) max_height: f32,
    translation: DVec3,
    scale: DVec3,
    rotation: DQuat,
    world_from_local: DMat4,
    local_from_world: DMat4,
}

impl TerrainModel {
    pub(crate) fn is_spherical(&self) -> bool {
        match self.kind {
            TerrainKind::PLANAR { .. } => false,
            TerrainKind::SPHERICAL { .. } => true,
            TerrainKind::ELLIPSOIDAL { .. } => true,
        }
    }

    fn from_scale_rotation_translation(
        scale: DVec3,
        rotation: DQuat,
        translation: DVec3,
        min_height: f32,
        max_height: f32,
        kind: TerrainKind,
    ) -> Self {
        let world_from_local = DMat4::from_scale_rotation_translation(scale, rotation, translation);
        let local_from_world = world_from_local.inverse();

        Self {
            kind,
            min_height,
            max_height,
            translation,
            scale,
            rotation,
            world_from_local,
            local_from_world,
        }
    }

    pub fn planar(position: DVec3, side_length: f64, min_height: f32, max_height: f32) -> Self {
        Self::from_scale_rotation_translation(
            DVec3::splat(side_length),
            DQuat::IDENTITY,
            position,
            min_height,
            max_height,
            TerrainKind::PLANAR { side_length },
        )
    }

    pub fn sphere(position: DVec3, radius: f64, min_height: f32, max_height: f32) -> Self {
        Self::from_scale_rotation_translation(
            DVec3::splat(radius),
            DQuat::IDENTITY,
            position,
            min_height,
            max_height,
            TerrainKind::SPHERICAL { radius },
        )
    }

    pub fn ellipsoid(
        position: DVec3,
        major_axis: f64,
        minor_axis: f64,
        min_height: f32,
        max_height: f32,
    ) -> Self {
        let rotation = DQuat::IDENTITY; // ::from_rotation_x(45.0_f64.to_radians());
        let ellipsoid_from_world = DMat4::from_rotation_translation(rotation, position).inverse();

        Self::from_scale_rotation_translation(
            DVec3::new(major_axis, minor_axis, major_axis),
            rotation,
            position,
            min_height,
            max_height,
            TerrainKind::ELLIPSOIDAL {
                ellipsoid_from_world,
                major_axis,
                minor_axis,
            },
        )
    }

    pub(crate) fn position_local_to_world(&self, local_position: DVec3, height: f64) -> DVec3 {
        let world_position = self.world_from_local.transform_point3(local_position);
        let world_normal = self
            .world_from_local
            .transform_vector3(if self.is_spherical() {
                local_position
            } else {
                DVec3::Y
            })
            .normalize();

        world_position + height * world_normal
    }

    pub(crate) fn position_world_to_local(&self, world_position: DVec3) -> DVec3 {
        match self.kind {
            TerrainKind::PLANAR { .. } => {
                DVec3::new(1.0, 0.0, 1.0) * self.local_from_world.transform_point3(world_position)
            }

            TerrainKind::SPHERICAL { .. } => self
                .local_from_world
                .transform_point3(world_position)
                .normalize(),
            TerrainKind::ELLIPSOIDAL {
                ellipsoid_from_world,
                major_axis,
                minor_axis,
            } => {
                let ellipsoid_position = ellipsoid_from_world.transform_point3(world_position);
                let surface_position = project_point_ellipsoid(
                    DVec3::new(major_axis, major_axis, minor_axis),
                    ellipsoid_position,
                );
                self.local_from_world
                    .transform_point3(surface_position)
                    .normalize()
            }
        }
    }

    pub(crate) fn surface_position(&self, world_position: DVec3, height: f64) -> DVec3 {
        self.position_local_to_world(self.position_world_to_local(world_position), height)
    }

    pub(crate) fn side_count(&self) -> u32 {
        if self.is_spherical() {
            6
        } else {
            1
        }
    }

    pub(crate) fn scale(&self) -> f64 {
        match self.kind {
            TerrainKind::PLANAR { side_length } => side_length / 2.0,
            TerrainKind::SPHERICAL { radius } => radius,
            TerrainKind::ELLIPSOIDAL {
                major_axis,
                minor_axis,
                ..
            } => (major_axis + minor_axis) / 2.0,
        }
    }

    #[cfg(not(feature = "high_precision"))]
    pub(crate) fn transform(&self) -> Transform {
        Transform {
            translation: self.translation.as_vec3(),
            scale: self.scale.as_vec3(),
            rotation: self.rotation.as_quat(),
        }
    }

    #[cfg(feature = "high_precision")]
    pub(crate) fn grid_transform(
        &self,
        frame: &crate::big_space::ReferenceFrame,
    ) -> crate::big_space::GridTransformOwned {
        let (cell, translation) = frame.translation_to_grid(self.translation);

        crate::big_space::GridTransformOwned {
            transform: Transform {
                translation,
                scale: self.scale.as_vec3(),
                rotation: self.rotation.as_quat(),
            },
            cell,
        }
    }
}

/// Parameters of the view used to compute the position of a location on the sphere's surface relative to the view.
/// This can be calculated directly using f64 operations, or approximated using a Taylor series and f32 operations.
///
/// The idea behind the approximation, is to map from st coordinates relative to the view, to world positions relative to the view.
/// 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.
#[derive(Copy, Clone, Debug, Default, ShaderType)]
pub(crate) struct SideParameter {
    /// The tile index of the origin tile projected to this side.
    pub(crate) origin_xy: IVec2,
    /// The offset between the view st coordinate and the origin st coordinate.
    /// 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.
    pub(crate) origin_uv: Vec2,
    /// The constant coefficient of the series.
    /// Describes the offset between the location vertically under view and the view position.
    pub(crate) c: Vec3,
    /// The linear coefficient of the series with respect to s.
    pub(crate) c_s: Vec3,
    /// The linear coefficient of the series with respect to t.
    pub(crate) c_t: Vec3,
    /// The quadratic coefficient of the series with respect to s and s.
    /// This value is pre-multiplied with 0.5.
    pub(crate) c_ss: Vec3,
    /// The quadratic coefficient of the series with respect to s and t.
    pub(crate) c_st: Vec3,
    /// The quadratic coefficient of the series with respect to t and t.
    /// This value is pre-multiplied with 0.5.
    pub(crate) c_tt: Vec3,
}

#[derive(Clone, Debug, Default, ShaderType)]
pub struct TerrainModelApproximation {
    /// The reference tile, which is used to accurately determine the relative st coordinate in the shader.
    /// The tile under the view (with the origin lod) is the origin for the Taylor series.
    pub(crate) origin_lod: u32,
    pub(crate) approximate_height: f32,
    /// The parameters of the six cube sphere faces.
    pub(crate) sides: [SideParameter; 6],
}

impl TerrainModelApproximation {
    /// Computes the view parameters based on the it's world position.
    pub(crate) fn compute(
        tile_tree: &TileTree,
        tile_atlas: &TileAtlas,
    ) -> TerrainModelApproximation {
        let origin_count = TileCoordinate::count(tile_tree.origin_lod) as f64;

        // Coordinate of the location vertically below the view.
        let view_coordinate =
            Coordinate::from_world_position(tile_tree.view_world_position, &tile_atlas.model);

        // We want to approximate the position relative to the view using a second order Taylor series.
        // For that, we have to calculate the Taylor coefficients for each cube side separately.
        // As the basis, we use the view coordinate projected to the specific side.
        // Then we calculate the relative position vector and derivatives at the view coordinate.

        // u(s)=(2s-1)/sqrt(1-4cs(s-1))
        // v(t)=(2t-1)/sqrt(1-4ct(t-1))
        // l(s,t)=sqrt(1+u(s)^2+v(t)^2)
        // a(s,t)=1/l(s,t)
        // b(s,t)=u(s)/l(s,t)
        // c(s,t)=v(t)/l(s,t)

        let mut sides = [SideParameter::default(); 6];

        for (side, &sm) in SIDE_MATRICES.iter().enumerate() {
            let view_coordinate = view_coordinate.project_to_side(side as u32, &tile_atlas.model);
            let view_xy = (view_coordinate.uv * origin_count).as_ivec2();
            let view_uv = (view_coordinate.uv * origin_count).fract().as_vec2();

            let DVec2 { x: s, y: t } = view_coordinate.uv;

            let u_denom = (1.0 - 4.0 * C_SQR * s * (s - 1.0)).sqrt();
            let u = (2.0 * s - 1.0) / u_denom;
            let u_ds = 2.0 * (C_SQR + 1.0) / u_denom.powi(3);
            let u_dss = 12.0 * C_SQR * (C_SQR + 1.0) * (2.0 * s - 1.0) / u_denom.powi(5);

            let v_denom = (1.0 - 4.0 * C_SQR * t * (t - 1.0)).sqrt();
            let v = (2.0 * t - 1.0) / v_denom;
            let v_dt = 2.0 * (C_SQR + 1.0) / v_denom.powi(3);
            let v_dtt = 12.0 * C_SQR * (C_SQR + 1.0) * (2.0 * t - 1.0) / v_denom.powi(5);

            let l = (1.0 + u * u + v * v).sqrt();
            let l_ds = u * u_ds / l;
            let l_dt = v * v_dt / l;
            let l_dss = (u * u_dss * l * l + (v * v + 1.0) * u_ds * u_ds) / l.powi(3);
            let l_dst = -(u * v * u_ds * v_dt) / l.powi(3);
            let l_dtt = (v * v_dtt * l * l + (u * u + 1.0) * v_dt * v_dt) / l.powi(3);

            let a = 1.0;
            let a_ds = -l_ds;
            let a_dt = -l_dt;
            let a_dss = 2.0 * l_ds * l_ds - l * l_dss;
            let a_dst = 2.0 * l_ds * l_dt - l * l_dst;
            let a_dtt = 2.0 * l_dt * l_dt - l * l_dtt;

            let b = u;
            let b_ds = -u * l_ds + l * u_ds;
            let b_dt = -u * l_dt;
            let b_dss = 2.0 * u * l_ds * l_ds - l * (2.0 * u_ds * l_ds + u * l_dss) + u_dss * l * l;
            let b_dst = 2.0 * u * l_ds * l_dt - l * (u_ds * l_dt + u * l_dst);
            let b_dtt = 2.0 * u * l_dt * l_dt - l * u * l_dtt;

            let c = v;
            let c_ds = -v * l_ds;
            let c_dt = -v * l_dt + l * v_dt;
            let c_dss = 2.0 * v * l_ds * l_ds - l * v * l_dss;
            let c_dst = 2.0 * v * l_ds * l_dt - l * (v_dt * l_ds + v * l_dst);
            let c_dtt = 2.0 * v * l_dt * l_dt - l * (2.0 * v_dt * l_dt + v * l_dtt) + v_dtt * l * l;

            // The model matrix is used to transform the local position and directions into the corresponding world position and directions.
            // p is transformed as a point, takes the model position into account
            // the other coefficients are transformed as vectors, discards the translation
            let m = tile_atlas.model.world_from_local;
            let p = m.transform_point3(sm * DVec3::new(a, b, c) / l);
            let p_ds = m.transform_vector3(sm * DVec3::new(a_ds, b_ds, c_ds) / l.powi(2));
            let p_dt = m.transform_vector3(sm * DVec3::new(a_dt, b_dt, c_dt) / l.powi(2));
            let p_dss = m.transform_vector3(sm * DVec3::new(a_dss, b_dss, c_dss) / l.powi(3));
            let p_dst = m.transform_vector3(sm * DVec3::new(a_dst, b_dst, c_dst) / l.powi(3));
            let p_dtt = m.transform_vector3(sm * DVec3::new(a_dtt, b_dtt, c_dtt) / l.powi(3));

            sides[side] = SideParameter {
                origin_xy: view_xy,
                origin_uv: view_uv,
                c: (p - tile_tree.view_world_position).as_vec3(),
                c_s: p_ds.as_vec3(),
                c_t: p_dt.as_vec3(),
                c_ss: (p_dss / 2.0).as_vec3(),
                c_st: p_dst.as_vec3(),
                c_tt: (p_dtt / 2.0).as_vec3(),
            };
        }

        TerrainModelApproximation {
            origin_lod: tile_tree.origin_lod,
            approximate_height: tile_tree.approximate_height,
            sides,
        }
    }
}

pub fn generate_terrain_model_approximation(
    tile_trees: Res<TerrainViewComponents<TileTree>>,
    tile_atlases: Query<&TileAtlas>,
    mut terrain_model_approximations: ResMut<TerrainViewComponents<TerrainModelApproximation>>,
) {
    for (&(terrain, view), tile_tree) in tile_trees.iter() {
        let tile_atlas = tile_atlases.get(terrain).unwrap();

        terrain_model_approximations.insert(
            (terrain, view),
            TerrainModelApproximation::compute(tile_tree, tile_atlas),
        );
    }
}


================================================
FILE: src/plugin.rs
================================================
use crate::{
    math::{generate_terrain_model_approximation, TerrainModelApproximation},
    render::{
        culling_bind_group::CullingBindGroup,
        terrain_bind_group::TerrainData,
        terrain_view_bind_group::TerrainViewData,
        tiling_prepass::{
            queue_tiling_prepass, TilingPrepassItem, TilingPrepassLabel, TilingPrepassNode,
            TilingPrepassPipelines,
        },
    },
    shaders::{load_terrain_shaders, InternalShaders},
    terrain::TerrainComponents,
    terrain_data::{
        gpu_tile_atlas::GpuTileAtlas, gpu_tile_tree::GpuTileTree, tile_atlas::TileAtlas,
        tile_tree::TileTree,
    },
    terrain_view::TerrainViewComponents,
};
use bevy::{
    prelude::*,
    render::{
        graph::CameraDriverLabel,
        render_graph::RenderGraph,
        render_resource::*,
        view::{check_visibility, VisibilitySystems},
        Render, RenderApp, RenderSet,
    },
};

/// The plugin for the terrain renderer.
pub struct TerrainPlugin;

impl Plugin for TerrainPlugin {
    fn build(&self, app: &mut App) {
        #[cfg(feature = "high_precision")]
        app.add_plugins(crate::big_space::BigSpacePlugin::default());

        app.init_resource::<InternalShaders>()
            .init_resource::<TerrainViewComponents<TileTree>>()
            .init_resource::<TerrainViewComponents<TerrainModelApproximation>>()
            .add_systems(
                PostUpdate,
                check_visibility::<With<TileAtlas>>.in_set(VisibilitySystems::CheckVisibility),
            )
            .add_systems(
                Last,
                (
                    TileTree::compute_requests,
                    TileAtlas::update,
                    TileTree::adjust_to_tile_atlas,
                    TileTree::approximate_height,
                    generate_terrain_model_approximation,
                )
                    .chain(),
            );

        app.sub_app_mut(RenderApp)
            .init_resource::<TerrainComponents<GpuTileAtlas>>()
            .init_resource::<TerrainComponents<TerrainData>>()
            .init_resource::<TerrainViewComponents<GpuTileTree>>()
            .init_resource::<TerrainViewComponents<TerrainViewData>>()
            .init_resource::<TerrainViewComponents<CullingBindGroup>>()
            .init_resource::<TerrainViewComponents<TilingPrepassItem>>()
            .add_systems(
                ExtractSchedule,
                (
                    GpuTileAtlas::initialize,
                    GpuTileAtlas::extract.after(GpuTileAtlas::initialize),
                    GpuTileTree::initialize,
                    GpuTileTree::extract.after(GpuTileTree::initialize),
                    TerrainData::initialize.after(GpuTileAtlas::initialize),
                    TerrainData::extract.after(TerrainData::initialize),
                    TerrainViewData::initialize.after(GpuTileTree::initialize),
                    TerrainViewData::extract.after(TerrainViewData::initialize),
                ),
            )
            .add_systems(
                Render,
                (
                    (
                        GpuTileTree::prepare,
                        GpuTileAtlas::prepare,
                        TerrainData::prepare,
                        TerrainViewData::prepare,
                        CullingBindGroup::prepare,
                    )
                        .in_set(RenderSet::Prepare),
                    queue_tiling_prepass.in_set(RenderSet::Queue),
                    GpuTileAtlas::cleanup
                        .before(World::clear_entities)
                        .in_set(RenderSet::Cleanup),
                ),
            );
    }

    fn finish(&self, app: &mut App) {
        load_terrain_shaders(app);

        let render_app = app
            .sub_app_mut(RenderApp)
            .init_resource::<TilingPrepassPipelines>()
            .init_resource::<SpecializedComputePipelines<TilingPrepassPipelines>>();

        let mut render_graph = render_app.world_mut().resource_mut::<RenderGraph>();
        render_graph.add_node(TilingPrepassLabel, TilingPrepassNode);
        render_graph.add_node_edge(TilingPrepassLabel, CameraDriverLabel);
    }
}


================================================
FILE: src/preprocess/gpu_preprocessor.rs
================================================
use crate::{
    preprocess::{
        preprocessor::{PreprocessTask, PreprocessTaskType, Preprocessor},
        TerrainPreprocessItem,
    },
    terrain::TerrainComponents,
    terrain_data::{
        gpu_tile_atlas::GpuTileAtlas,
        tile_atlas::{AtlasTile, TileAtlas},
    },
    util::StaticBuffer,
};
use bevy::{
    prelude::*,
    render::{
        render_asset::RenderAssets,
        render_resource::{binding_types::*, *},
        renderer::RenderDevice,
        texture::GpuImage,
        Extract,
    },
};
use std::collections::VecDeque;

pub(crate) struct ProcessingTask {
    pub(crate) task: PreprocessTask,
    pub(crate) bind_group: Option<BindGroup>,
}

#[derive(Clone, Debug, ShaderType)]
pub(crate) struct SplitData {
    tile: AtlasTile,
    top_left: Vec2,
    bottom_right: Vec2,
    tile_index: u32,
}

#[derive(Clone, Debug, ShaderType)]
struct StitchData {
    tile: AtlasTile,
    neighbour_tiles: [AtlasTile; 8],
    tile_index: u32,
}

#[derive(Clone, Debug, ShaderType)]
struct DownsampleData {
    tile: AtlasTile,
    child_tiles: [AtlasTile; 4],
    tile_index: u32,
}

pub(crate) fn create_split_layout(device: &RenderDevice) -> BindGroupLayout {
    device.create_bind_group_layout(
        None,
        &BindGroupLayoutEntries::sequential(
            ShaderStages::COMPUTE,
            (
                uniform_buffer::<SplitData>(false), // split_tile_data
                texture_2d(TextureSampleType::Float { filterable: true }), // tile
                sampler(SamplerBindingType::Filtering), // tile_sampler
            ),
        ),
    )
}

pub(crate) fn create_stitch_layout(device: &RenderDevice) -> BindGroupLayout {
    device.create_bind_group_layout(
        None,
        &BindGroupLayoutEntries::single(ShaderStages::COMPUTE, uniform_buffer::<StitchData>(false)),
    )
}

pub(crate) fn create_downsample_layout(device: &RenderDevice) -> BindGroupLayout {
    device.create_bind_group_layout(
        None,
        &BindGroupLayoutEntries::single(
            ShaderStages::COMPUTE,
            uniform_buffer::<DownsampleData>(false),
        ),
    )
}

pub(crate) struct GpuPreprocessor {
    pub(crate) ready_tasks: VecDeque<PreprocessTask>,
    pub(crate) processing_tasks: Vec<ProcessingTask>,
}

impl GpuPreprocessor {
    pub(crate) fn new() -> Self {
        Self {
            ready_tasks: default(),
            processing_tasks: vec![],
        }
    }

    pub(crate) fn initialize(
        mut gpu_preprocessors: ResMut<TerrainComponents<GpuPreprocessor>>,
        terrains: Extract<Query<Entity, Added<TileAtlas>>>,
    ) {
        for terrain in terrains.iter() {
            gpu_preprocessors.insert(terrain, GpuPreprocessor::new());
        }
    }

    pub(crate) fn extract(
        mut gpu_preprocessors: ResMut<TerrainComponents<GpuPreprocessor>>,
        preprocessors: Extract<Query<(Entity, &Preprocessor)>>,
    ) {
        for (terrain, preprocessor) in preprocessors.iter() {
            let gpu_preprocessor = gpu_preprocessors.get_mut(&terrain).unwrap();

            // Todo: mem take using &mut world?
            gpu_preprocessor
                .ready_tasks
                .extend(preprocessor.ready_tasks.clone().into_iter());
        }
    }

    #[allow(clippy::too_many_arguments)]
    pub(crate) fn prepare(
        device: Res<RenderDevice>,
        images: Res<RenderAssets<GpuImage>>,
        preprocess_items: Res<TerrainComponents<TerrainPreprocessItem>>,
        mut gpu_preprocessors: ResMut<TerrainComponents<GpuPreprocessor>>,
        mut gpu_tile_atlases: ResMut<TerrainComponents<GpuTileAtlas>>,
        pipeline_cache: Res<PipelineCache>,
    ) {
        for (&terrain, item) in preprocess_items.iter() {
            if !item.is_loaded(&pipeline_cache) {
                continue;
            }

            let gpu_preprocessor = gpu_preprocessors.get_mut(&terrain).unwrap();
            let gpu_tile_atlas = gpu_tile_atlases.get_mut(&terrain).unwrap();

            gpu_preprocessor.processing_tasks.clear();

            while !gpu_preprocessor.ready_tasks.is_empty() {
                let task = gpu_preprocessor.ready_tasks.back().unwrap();
                let attachment =
                    &mut gpu_tile_atlas.attachments[task.tile.attachment_index as usize];

                if let Some(section_index) = attachment.reserve_write_slot(task.tile) {
                    let task = gpu_preprocessor.ready_tasks.pop_back().unwrap();

                    let bind_group = match &task.task_type {
                        PreprocessTaskType::Split {
                            tile_data,
                            top_left,
                            bottom_right,
                        } => {
                            let tile_data = images.get(tile_data).unwrap();

                            let split_buffer = StaticBuffer::create(
                                format!("{}_split_buffer", attachment.name).as_str(),
                                &device,
                                &SplitData {
                                    tile: task.tile.into(),
                                    top_left: *top_left,
                                    bottom_right: *bottom_right,
                                    tile_index: section_index,
                                },
                                BufferUsages::UNIFORM,
                            );

                            Some(device.create_bind_group(
                                format!("{}_split_bind_group", attachment.name).as_str(),
                                &create_split_layout(&device),
                                &BindGroupEntries::sequential((
                                    &split_buffer,
                                    &tile_data.texture_view,
                                    &tile_data.sampler,
                                )),
                            ))
                        }
                        PreprocessTaskType::Stitch { neighbour_tiles } => {
                            let stitch_buffer = StaticBuffer::create(
                                format!("{}_stitch_buffer", attachment.name).as_str(),
                                &device,
                                &StitchData {
                                    tile: task.tile.into(),
                                    neighbour_tiles: *neighbour_tiles,
                                    tile_index: section_index,
                                },
                                BufferUsages::UNIFORM,
                            );

                            Some(device.create_bind_group(
                                format!("{}_stitch_bind_group", attachment.name).as_str(),
                                &create_stitch_layout(&device),
                                &BindGroupEntries::single(&stitch_buffer),
                            ))
                        }
                        PreprocessTaskType::Downsample { child_tiles } => {
                            let downsample_buffer = StaticBuffer::create(
                                format!("{}_downsample_buffer", attachment.name).as_str(),
                                &device,
                                &DownsampleData {
                                    tile: task.tile.into(),
                                    child_tiles: *child_tiles,
                                    tile_index: section_index,
                                },
                                BufferUsages::UNIFORM,
                            );

                            Some(device.create_bind_group(
                                format!("{}_downsample_bind_group", attachment.name).as_str(),
                                &create_downsample_layout(&device),
                                &BindGroupEntries::single(&downsample_buffer),
                            ))
                        }
                        _ => break,
                    };

                    gpu_preprocessor
                        .processing_tasks
                        .push(ProcessingTask { task, bind_group });
                } else {
                    break;
                }
            }
        }
    }
}


================================================
FILE: src/preprocess/mod.rs
================================================
use crate::{
    formats::tiff::TiffLoader,
    preprocess::{
        gpu_preprocessor::{
            create_downsample_layout, create_split_layout, create_stitch_layout, GpuPreprocessor,
        },
        preprocessor::{preprocessor_load_tile, select_ready_tasks, PreprocessTaskType},
    },
    shaders::{load_preprocess_shaders, DOWNSAMPLE_SHADER, SPLIT_SHADER, STITCH_SHADER},
    terrain::TerrainComponents,
    terrain_data::gpu_tile_atlas::{create_attachment_layout, GpuTileAtlas},
};
use bevy::{
    prelude::*,
    render::{
        graph::CameraDriverLabel,
        render_graph::{self, RenderGraph, RenderLabel},
        render_resource::*,
        renderer::{RenderContext, RenderDevice},
        Render, RenderApp, RenderSet,
    },
};

pub mod gpu_preprocessor;
pub mod preprocessor;

#[derive(Debug, Hash, PartialEq, Eq, Clone, RenderLabel)]
pub struct TerrainPreprocessLabel;

bitflags::bitflags! {
    #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
    #[repr(transparent)]
    pub struct TerrainPreprocessPipelineKey: u32 {
        const NONE       = 1 << 0;
        const SPLIT      = 1 << 1;
        const STITCH     = 1 << 2;
        const DOWNSAMPLE = 1 << 3;
    }
}

pub(crate) struct TerrainPreprocessItem {
    split_pipeline: CachedComputePipelineId,
    stitch_pipeline: CachedComputePipelineId,
    downsample_pipeline: CachedComputePipelineId,
}

impl TerrainPreprocessItem {
    fn pipelines<'a>(
        &'a self,
        pipeline_cache: &'a PipelineCache,
    ) -> Option<(&ComputePipeline, &ComputePipeline, &ComputePipeline)> {
        Some((
            pipeline_cache.get_compute_pipeline(self.split_pipeline)?,
            pipeline_cache.get_compute_pipeline(self.stitch_pipeline)?,
            pipeline_cache.get_compute_pipeline(self.downsample_pipeline)?,
        ))
    }

    pub(crate) fn is_loaded(&self, pipeline_cache: &PipelineCache) -> bool {
        self.pipelines(pipeline_cache).is_some()
    }
}

#[derive(Resource)]
pub struct TerrainPreprocessPipelines {
    attachment_layout: BindGroupLayout,
    split_layout: BindGroupLayout,
    stitch_layout: BindGroupLayout,
    downsample_layout: BindGroupLayout,
    split_shader: Handle<Shader>,
    stitch_shader: Handle<Shader>,
    downsample_shader: Handle<Shader>,
}

impl FromWorld for TerrainPreprocessPipelines {
    fn from_world(world: &mut World) -> Self {
        let device = world.resource::<RenderDevice>();
        let asset_server = world.resource::<AssetServer>();

        let attachment_layout = create_attachment_layout(device);
        let split_layout = create_split_layout(device);
        let stitch_layout = create_stitch_layout(device);
        let downsample_layout = create_downsample_layout(device);

        let split_shader = asset_server.load(SPLIT_SHADER);
        let stitch_shader = asset_server.load(STITCH_SHADER);
        let downsample_shader = asset_server.load(DOWNSAMPLE_SHADER);

        Self {
            attachment_layout,
            split_layout,
            stitch_layout,
            downsample_layout,
            split_shader,
            stitch_shader,
            downsample_shader,
        }
    }
}

impl SpecializedComputePipeline for TerrainPreprocessPipelines {
    type Key = TerrainPreprocessPipelineKey;

    fn specialize(&self, key: Self::Key) -> ComputePipelineDescriptor {
        let mut layout = default();
        let mut shader = default();
        let mut entry_point = default();

        let shader_defs = vec![];

        if key.contains(TerrainPreprocessPipelineKey::SPLIT) {
            layout = vec![self.attachment_layout.clone(), self.split_layout.clone()];
            shader = self.split_shader.clone();
            entry_point = "split".into();
        }
        if key.contains(TerrainPreprocessPipelineKey::STITCH) {
            layout = vec![self.attachment_layout.clone(), self.stitch_layout.clone()];
            shader = self.stitch_shader.clone();
            entry_point = "stitch".into();
        }
        if key.contains(TerrainPreprocessPipelineKey::DOWNSAMPLE) {
            layout = vec![
                self.attachment_layout.clone(),
                self.downsample_layout.clone(),
            ];
            shader = self.downsample_shader.clone();
            entry_point = "downsample".into();
        }

        ComputePipelineDescriptor {
            label: Some("terrain_preprocess_pipeline".into()),
            layout,
            push_constant_ranges: default(),
            shader,
            shader_defs,
            entry_point,
        }
    }
}

pub struct TerrainPreprocessNode;

impl render_graph::Node for TerrainPreprocessNode {
    fn run<'w>(
        &self,
        _graph: &mut render_graph::RenderGraphContext,
        context: &mut RenderContext<'w>,
        world: &'w World,
    ) -> Result<(), render_graph::NodeRunError> {
        let preprocess_items = world.resource::<TerrainComponents<TerrainPreprocessItem>>();
        let pipeline_cache = world.resource::<PipelineCache>();
        let preprocess_data = world.resource::<TerrainComponents<GpuPreprocessor>>();
        let gpu_tile_atlases = world.resource::<TerrainComponents<GpuTileAtlas>>();

        context.add_command_buffer_generation_task(move |device| {
            let mut command_encoder =
                device.create_command_encoder(&CommandEncoderDescriptor::default());

            for (&terrain, preprocess_item) in preprocess_items.iter() {
                let Some((split_pipeline, stitch_pipeline, downsample_pipeline)) =
                    preprocess_item.pipelines(pipeline_cache)
                else {
                    continue;
                };

                let preprocess_data = preprocess_data.get(&terrain).unwrap();
                let gpu_tile_atlas = gpu_tile_atlases.get(&terrain).unwrap();

                for attachment in &gpu_tile_atlas.attachments {
                    attachment.copy_tiles_to_write_section(&mut command_encoder);
                }

                if !preprocess_data.processing_tasks.is_empty() {
                    let mut compute_pass =
                        command_encoder.begin_compute_pass(&ComputePassDescriptor::default());

                    for task in &preprocess_data.processing_tasks {
                        let attachment =
                            &gpu_tile_atlas.attachments[task.task.tile.attachment_index as usize];

                        let pipeline = match task.task.task_type {
                            PreprocessTaskType::Split { .. } => split_pipeline,
                            PreprocessTaskType::Stitch { .. } => stitch_pipeline,
                            PreprocessTaskType::Downsample { .. } => downsample_pipeline,
                            _ => continue,
                        };

                        compute_pass.set_pipeline(pipeline);
                        compute_pass.set_bind_group(0, &attachment.bind_group, &[]);
                        compute_pass.set_bind_group(1, task.bind_group.as_ref().unwrap(), &[]);
                        compute_pass.dispatch_workgroups(
                            attachment.buffer_info.workgroup_count.x,
                            attachment.buffer_info.workgroup_count.y,
                            attachment.buffer_info.workgroup_count.z,
                        );
                    }
                }

                for attachment in &gpu_tile_atlas.attachments {
                    attachment.copy_tiles_from_write_section(&mut command_encoder);

                    attachment.download_tiles(&mut command_encoder);

                    // if !attachment.atlas_write_slots.is_empty() {
                    //     println!(
                    //         "Ran preprocessing pipeline with {} tiles.",
                    //         attachment.atlas_write_slots.len()
                    //     )
                    // }
                }
            }

            command_encoder.finish()
        });

        Ok(())
    }
}

pub(crate) fn queue_terrain_preprocess(
    pipeline_cache: Res<PipelineCache>,
    preprocess_pipelines: ResMut<TerrainPreprocessPipelines>,
    mut pipelines: ResMut<SpecializedComputePipelines<TerrainPreprocessPipelines>>,
    mut preprocess_items: ResMut<TerrainComponents<TerrainPreprocessItem>>,
    gpu_tile_atlas: Res<TerrainComponents<GpuTileAtlas>>,
) {
    for &terrain in gpu_tile_atlas.keys() {
        let split_pipeline = pipelines.specialize(
            &pipeline_cache,
            &preprocess_pipelines,
            TerrainPreprocessPipelineKey::SPLIT,
        );
        let stitch_pipeline = pipelines.specialize(
            &pipeline_cache,
            &preprocess_pipelines,
            TerrainPreprocessPipelineKey::STITCH,
        );
        let downsample_pipeline = pipelines.specialize(
            &pipeline_cache,
            &preprocess_pipelines,
            TerrainPreprocessPipelineKey::DOWNSAMPLE,
        );

        preprocess_items.insert(
            terrain,
            TerrainPreprocessItem {
                split_pipeline,
                stitch_pipeline,
                downsample_pipeline,
            },
        );
    }
}

pub struct TerrainPreprocessPlugin;

impl Plugin for TerrainPreprocessPlugin {
    fn build(&self, app: &mut App) {
        app.init_asset_loader::<TiffLoader>()
            .add_systems(Update, (select_ready_tasks, preprocessor_load_tile));

        app.sub_app_mut(RenderApp)
            .init_resource::<TerrainComponents<GpuPreprocessor>>()
            .init_resource::<TerrainComponents<TerrainPreprocessItem>>()
            .add_systems(
                ExtractSchedule,
                (
                    GpuPreprocessor::initialize,
                    GpuPreprocessor::extract.after(GpuPreprocessor::initialize),
                ),
            )
            .add_systems(
                Render,
                (
                    queue_terrain_preprocess.in_set(RenderSet::Queue),
                    GpuPreprocessor::prepare
                        .in_set(RenderSet::PrepareAssets)
                        .before(GpuTileAtlas::prepare),
                ),
            );
    }

    fn finish(&self, app: &mut App) {
        load_preprocess_shaders(app);

        let render_app = app
            .sub_app_mut(RenderApp)
            .init_resource::<SpecializedComputePipelines<TerrainPreprocessPipelines>>()
            .init_resource::<TerrainPreprocessPipelines>();

        let mut render_graph = render_app.world_mut().resource_mut::<RenderGraph>();
        render_graph.add_node(TerrainPreprocessLabel, TerrainPreprocessNode);
        render_graph.add_node_edge(TerrainPreprocessLabel, CameraDriverLabel);
    }
}


================================================
FILE: src/preprocess/preprocessor.rs
================================================
use crate::{
    math::TileCoordinate,
    terrain_data::{
        tile_atlas::{AtlasTile, AtlasTileAttachment, TileAtlas},
        AttachmentFormat,
    },
    util::CollectArray,
};
use bevy::{prelude::*, render::texture::ImageSampler};
use itertools::{iproduct, Itertools};
use std::{
    collections::VecDeque,
    fs,
    ops::{DerefMut, Range},
    time::Instant,
};

pub fn reset_directory(directory: &str) {
    let _ = fs::remove_file(format!("{directory}/../../config.tc"));
    let _ = fs::remove_dir_all(directory);
    fs::create_dir_all(directory).unwrap();
}

pub(crate) struct LoadingTile {
    id: AssetId<Image>,
    format: AttachmentFormat,
}

pub struct SphericalDataset {
    pub attachment_index: u32,
    pub paths: Vec<String>,
    pub lod_range: Range<u32>,
}

pub struct PreprocessDataset {
    pub attachment_index: u32,
    pub path: String,
    pub side: u32,
    pub top_left: Vec2,
    pub bottom_right: Vec2,
    pub lod_range: Range<u32>,
}

impl Default for PreprocessDataset {
    fn default() -> Self {
        Self {
            attachment_index: 0,
            path: "".to_string(),
            side: 0,
            top_left: Vec2::splat(0.0),
            bottom_right: Vec2::splat(1.0),
            lod_range: 0..1,
        }
    }
}

impl PreprocessDataset {
    fn overlapping_tiles(&self, lod: u32) -> impl Iterator<Item = TileCoordinate> + '_ {
        let tile_count = TileCoordinate::count(lod);

        let lower = (self.top_left * tile_count as f32).as_uvec2();
        let upper = (self.bottom_right * tile_count as f32).ceil().as_uvec2();

        iproduct!(lower.x..upper.x, lower.y..upper.y)
            .map(move |(x, y)| TileCoordinate::new(self.side, lod, x, y))
    }
}

#[derive(Clone)]
pub(crate) enum PreprocessTaskType {
    Split {
        tile_data: Handle<Image>,
        top_left: Vec2,
        bottom_right: Vec2,
    },
    Stitch {
        neighbour_tiles: [AtlasTile; 8],
    },
    Downsample {
        child_tiles: [AtlasTile; 4],
    },
    Save,
    Barrier,
}

// Todo: store tile_coordinate, task_type, tile_dependencies and tile dependencies
// loop over all tasks, take n, allocate/load tile and its dependencies, process task
#[derive(Clone)]
pub(crate) struct PreprocessTask {
    pub(crate) tile: AtlasTileAttachment,
    pub(crate) task_type: PreprocessTaskType,
}

impl PreprocessTask {
    fn is_ready(&self, asset_server: &AssetServer, tile_atlas: &TileAtlas) -> bool {
        match &self.task_type {
            PreprocessTaskType::Split { tile_data, .. } => {
                asset_server.is_loaded_with_dependencies(tile_data)
            }
            PreprocessTaskType::Stitch { .. } => true,
            PreprocessTaskType::Downsample { .. } => true,
            PreprocessTaskType::Barrier => {
                tile_atlas.state.download_slots == tile_atlas.state.max_download_slots
            }
            PreprocessTaskType::Save => true,
        }
    }

    #[allow(dead_code)]
    fn debug(&self) {
        match &self.task_type {
            PreprocessTaskType::Split { .. } => {
                println!("Splitting tile: {}", self.tile.coordinate)
            }
            PreprocessTaskType::Stitch { .. } => {
                println!("Stitching tile: {}", self.tile.coordinate)
            }
            PreprocessTaskType::Downsample { .. } => {
                println!("Downsampling tile: {}", self.tile.coordinate)
            }
            PreprocessTaskType::Save => {
                println!("Started saving tile: {}", self.tile.coordinate)
            }
            PreprocessTaskType::Barrier => println!("Barrier"),
        }
    }

    fn barrier() -> Self {
        Self {
            tile: default(),
            task_type: PreprocessTaskType::Barrier,
        }
    }

    fn save(
        tile_coordinate: TileCoordinate,
        tile_atlas: &mut TileAtlas,
        dataset: &PreprocessDataset,
    ) -> Self {
        let tile = tile_atlas
            .get_or_allocate_tile(tile_coordinate)
            .attachment(dataset.attachment_index);

        Self {
            tile,
            task_type: PreprocessTaskType::Save,
        }
    }

    fn split(
        tile_coordinate: TileCoordinate,
        tile_atlas: &mut TileAtlas,
        dataset: &PreprocessDataset,
        tile_data: Handle<Image>,
    ) -> Self {
        let tile = tile_atlas
            .get_or_allocate_tile(tile_coordinate)
            .attachment(dataset.attachment_index);

        Self {
            tile,
            task_type: PreprocessTaskType::Split {
                tile_data,
                top_left: dataset.top_left,
                bottom_right: dataset.bottom_right,
            },
        }
    }

    fn stitch(
        tile_coordinate: TileCoordinate,
        tile_atlas: &mut TileAtlas,
        dataset: &PreprocessDataset,
    ) -> Self {
        let tile = tile_atlas
            .get_or_allocate_tile(tile_coordinate)
            .attachment(dataset.attachment_index);

        let neighbour_tiles = tile
            .coordinate
            .neighbours(tile_atlas.model.is_spherical())
            .map(|coordinate| tile_atlas.get_tile(coordinate))
            .collect_array();

        Self {
            tile,
            task_type: PreprocessTaskType::Stitch { neighbour_tiles },
        }
    }

    fn downsample(
        tile_coordinate: TileCoordinate,
        tile_atlas: &mut TileAtlas,
        dataset: &PreprocessDataset,
    ) -> Self {
        let tile = tile_atlas
            .get_or_allocate_tile(tile_coordinate)
            .attachment(dataset.attachment_index);

        let child_tiles = tile
            .coordinate
            .children()
            .map(|coordinate| tile_atlas.get_tile(coordinate))
            .collect_array();

        Self {
            tile,
            task_type: PreprocessTaskType::Downsample { child_tiles },
        }
    }
}

#[derive(Component)]
pub struct Preprocessor {
    pub(crate) loading_tiles: Vec<LoadingTile>,
    pub(crate) task_queue: VecDeque<PreprocessTask>,
    pub(crate) ready_tasks: Vec<PreprocessTask>,

    pub(crate) start_time: Option<Instant>,
    loaded: bool,
}

impl Preprocessor {
    pub fn new() -> Self {
        Self {
            loading_tiles: default(),
            task_queue: default(),
            ready_tasks: default(),
            start_time: None,
            loaded: false,
        }
    }

    fn split_and_downsample(
        &mut self,
        dataset: &PreprocessDataset,
        asset_server: &AssetServer,
        tile_atlas: &mut TileAtlas,
    ) {
        let tile_handle = asset_server.load(&dataset.path);

        self.loading_tiles.push(LoadingTile {
            id: tile_handle.id(),
            format: tile_atlas.attachments[dataset.attachment_index as usize].format,
        });

        let mut lods = dataset.lod_range.clone().rev();

        for tile_coordinate in dataset.overlapping_tiles(lods.next().unwrap()) {
            self.task_queue.push_back(PreprocessTask::split(
                tile_coordinate,
                tile_atlas,
                dataset,
                tile_handle.clone(),
            ));
        }

        for lod in lods {
            self.task_queue.push_back(PreprocessTask::barrier());

            for tile_coordinate in dataset.overlapping_tiles(lod) {
                self.task_queue.push_back(PreprocessTask::downsample(
                    tile_coordinate,
                    tile_atlas,
                    dataset,
                ));
            }
        }
    }

    fn stitch_and_save_layer(
        &mut self,
        dataset: &PreprocessDataset,
        tile_atlas: &mut TileAtlas,
        lod: u32,
    ) {
        for tile_coordinate in dataset.overlapping_tiles(lod) {
            self.task_queue
                .push_back(PreprocessTask::stitch(tile_coordinate, tile_atlas, dataset));
        }

        self.task_queue.push_back(PreprocessTask::barrier());

        for tile_coordinate in dataset.overlapping_tiles(lod) {
            self.task_queue
                .push_back(PreprocessTask::save(tile_coordinate, tile_atlas, dataset));
        }
    }

    pub fn clear_attachment(self, attachment_index: u32, tile_atlas: &mut TileAtlas) -> Self {
        let attachment = &mut tile_atlas.attachments[attachment_index as usize];
        tile_atlas.state.existing_tiles.clear();
        reset_directory(&attachment.path);

        self
    }

    pub fn preprocess_tile(
        mut self,
        dataset: PreprocessDataset,
        asset_server: &AssetServer,
        tile_atlas: &mut TileAtlas,
    ) -> Self {
        self.split_and_downsample(&dataset, asset_server, tile_atlas);
        self.task_queue.push_back(PreprocessTask::barrier());

        for lod in dataset.lod_range.clone() {
            self.stitch_and_save_layer(&dataset, tile_atlas, lod);
        }

        self
    }

    pub fn preprocess_spherical(
        mut self,
        dataset: SphericalDataset,
        asset_server: &AssetServer,
        tile_atlas: &mut TileAtlas,
    ) -> Self {
        let side_datasets = (0..6)
            .map(|side| PreprocessDataset {
                attachment_index: dataset.attachment_index,
                path: dataset.paths[side as usize].clone(),
                side,
                lod_range: dataset.lod_range.clone(),
                ..default()
            })
            .collect_vec();

        for dataset in &side_datasets {
            self.split_and_downsample(dataset, asset_server, tile_atlas);
        }

        self.task_queue.push_back(PreprocessTask::barrier());

        for lod in dataset.lod_range {
            for dataset in &side_datasets {
                self.stitch_and_save_layer(dataset, tile_atlas, lod);
            }
        }

        self
    }
}

pub(crate) fn select_ready_tasks(
    asset_server: Res<AssetServer>,
    mut terrains: Query<(&mut Preprocessor, &mut TileAtlas)>,
) {
    for (mut preprocessor, mut tile_atlas) in terrains.iter_mut() {
        let Preprocessor {
            task_queue,
            ready_tasks,
            start_time,
            ..
        } = preprocessor.deref_mut();

        if let Some(time) = start_time {
            if task_queue.is_empty()
                && tile_atlas.state.download_slots == tile_atlas.state.max_download_slots
                && tile_atlas.state.save_slots == tile_atlas.state.max_save_slots
            {
                println!("Preprocessing took {:?}", time.elapsed());

                tile_atlas.save_tile_config();
                // tile_atlas.state.existing_tiles.iter().for_each(|tile| {
                //     println!("{tile}");
                // });

                *start_time = None;
            }
        } else {
            break;
        }

        ready_tasks.clear();

        loop {
            if (tile_atlas.state.download_slots > 0)
                && task_queue
                    .front()
                    .map_or(false, |task| task.is_ready(&asset_server, &tile_atlas))
            {
                let task = task_queue.pop_front().unwrap();

                // task.debug();

                if matches!(task.task_type, PreprocessTaskType::Save) {
                    tile_atlas.save(task.tile);
                } else {
                    ready_tasks.push(task);
                    tile_atlas.state.download_slots -= 1;
                }
            } else {
                break;
            }
        }
    }
}

pub(crate) fn preprocessor_load_tile(
    mut preprocessors: Query<&mut Preprocessor>,
    mut images: ResMut<Assets<Image>>,
) {
    for mut preprocessor in preprocessors.iter_mut() {
        preprocessor.loading_tiles.retain_mut(|tile| {
            if let Some(image) = images.get_mut(tile.id) {
                image.texture_descriptor.format = tile.format.processing_format();
                image.sampler = ImageSampler::linear();
                false
            } else {
                true
            }
        });

        if !preprocessor.loaded && preprocessor.loading_tiles.is_empty() {
            println!("finished loading all tiles");
            preprocessor.loaded = true;
            preprocessor.start_time = Some(Instant::now());
        }
    }
}


================================================
FILE: src/render/culling_bind_group.rs
================================================
use crate::{
    terrain_data::gpu_tile_tree::GpuTileTree, terrain_view::TerrainViewComponents,
    util::StaticBuffer,
};
use bevy::{
    prelude::*,
    render::{
        render_resource::{binding_types::*, *},
        renderer::RenderDevice,
        view::ExtractedView,
    },
};
use std::ops::Deref;

pub(crate) fn create_culling_layout(device: &RenderDevice) -> BindGroupLayout {
    device.create_bind_group_layout(
        None,
        &BindGroupLayoutEntries::single(
            ShaderStages::COMPUTE,
            uniform_buffer::<CullingUniform>(false), // culling data
        ),
    )
}

pub fn planes(view_projection: &Mat4) -> [Vec4; 5] {
    let row3 = view_projection.row(3);
    let mut planes = [default(); 5];
    for (i, plane) in planes.iter_mut().enumerate() {
        let row = view_projection.row(i / 2);
        *plane = if (i & 1) == 0 && i != 4 {
            row3 + row
        } else {
            row3 - row
        };
    }

    planes
}

#[derive(Default, ShaderType)]
pub struct CullingUniform {
    world_position: Vec3,
    view_proj: Mat4,
    planes: [Vec4; 5],
}

impl From<&ExtractedView> for CullingUniform {
    fn from(view: &ExtractedView) -> Self {
        Self {
            world_position: view.world_from_view.translation(),
            view_proj: view.world_from_view.compute_matrix().inverse(),
            planes: default(),
        }
    }
}

#[derive(Component)]
pub struct CullingBindGroup(BindGroup);

impl Deref for CullingBindGroup {
    type Target = BindGroup;

    #[inline]
    fn deref(&self) -> &Self::Target {
        &self.0
    }
}

impl CullingBindGroup {
    fn new(device: &RenderDevice, culling_uniform: CullingUniform) -> Self {
        let culling_buffer = StaticBuffer::<CullingUniform>::create(
            None,
            device,
            &culling_uniform,
            BufferUsages::UNIFORM,
        );

        let bind_group = device.create_bind_group(
            None,
            &create_culling_layout(device),
            &BindGroupEntries::single(&culling_buffer),
        );

        Self(bind_group)
    }

    pub(crate) fn prepare(
        device: Res<RenderDevice>,
        gpu_tile_trees: Res<TerrainViewComponents<GpuTileTree>>,
        extracted_views: Query<&ExtractedView>,
        mut culling_bind_groups: ResMut<TerrainViewComponents<CullingBindGroup>>,
    ) {
        for &(terrain, view) in gpu_tile_trees.keys() {
            let extracted_view = extracted_views.get(view).unwrap();

            culling_bind_groups.insert(
                (terrain, view),
                CullingBindGroup::new(&device, extracted_view.into()),
            );
        }
    }
}


================================================
FILE: src/render/mod.rs
================================================
//! This module contains the implementation of the Uniform Distance-Dependent Level of Detail (UDLOD).
//!
//! This algorithm is responsible for approximating the terrain geometry.
//! Therefore tiny mesh tiles are refined in a tile_tree-like manner in a compute shader prepass for
//! each view. Then they are drawn using a single draw indirect call and morphed together to form
//! one continuous surface.

pub mod culling_bind_group;
pub mod terrain_bind_group;
pub mod terrain_material;
pub mod terrain_view_bind_group;
pub mod tiling_prepass;


================================================
FILE: src/render/terrain_bind_group.rs
================================================
use crate::{
    prelude::TileAtlas, terrain::TerrainComponents, terrain_data::gpu_tile_atlas::GpuTileAtlas,
    util::StaticBuffer,
};
use bevy::{
    ecs::{
        query::ROQueryItem,
        system::{lifetimeless::SRes, SystemParamItem},
    },
    pbr::{MeshTransforms, MeshUniform, PreviousGlobalTransform},
    prelude::*,
    render::{
        render_phase::{PhaseItem, RenderCommand, RenderCommandResult, TrackedRenderPass},
        render_resource::{binding_types::*, *},
        renderer::{RenderDevice, RenderQueue},
        texture::FallbackImage,
        Extract,
    },
};
use itertools::Itertools;
use std::iter;

pub(crate) fn create_terrain_layout(device: &RenderDevice) -> BindGroupLayout {
    device.create_bind_group_layout(
        None,
        &BindGroupLayoutEntries::sequential(
            ShaderStages::all(),
            (
                storage_buffer_read_only::<MeshUniform>(false), // mesh
                uniform_buffer::<TerrainConfigUniform>(false),  // terrain config
                uniform_buffer::<AttachmentUniform>(false),
                sampler(SamplerBindingType::Filtering), // atlas sampler
                texture_2d_array(TextureSampleType::Float { filterable: true }), // attachment 1
                texture_2d_array(TextureSampleType::Float { filterable: true }), // attachment 2
                texture_2d_array(TextureSampleType::Float { filterable: true }), // attachment 3
                texture_2d_array(TextureSampleType::Float { filterable: true }), // attachment 4
                texture_2d_array(TextureSampleType::Float { filterable: true }), // attachment 5
                texture_2d_array(TextureSampleType::Float { filterable: true }), // attachment 6
                texture_2d_array(TextureSampleType::Float { filterable: true }), // attachment 7
                texture_2d_array(TextureSampleType::Float { filterable: true }), // attachment 8
            ),
        ),
    )
}

#[derive(Default, ShaderType)]
struct AttachmentConfig {
    size: f32,
    scale: f32,
    offset: f32,
    _padding: u32,
}

#[derive(Default, ShaderType)]
struct AttachmentUniform {
    data: [AttachmentConfig; 8],
}

impl AttachmentUniform {
    fn new(tile_atlas: &GpuTileAtlas) -> Self {
        let mut uniform = Self::default();

        for (config, attachment) in iter::zip(&mut uniform.data, &tile_atlas.attachments) {
            config.size = attachment.buffer_info.center_size as f32;
            config.scale = attachment.buffer_info.center_size as f32
                / attachment.buffer_info.texture_size as f32;
            config.offset = attachment.buffer_info.border_size as f32
                / attachment.buffer_info.texture_size as f32;
        }

        uniform
    }
}

/// The terrain config data that is available in shaders.
#[derive(Default, ShaderType)]
struct TerrainConfigUniform {
    lod_count: u32,
    min_height: f32,
    max_height: f32,
    scale: f32,
}

impl TerrainConfigUniform {
    fn from_tile_atlas(tile_atlas: &TileAtlas) -> Self {
        Self {
            lod_count: tile_atlas.lod_count,
            min_height: tile_atlas.model.min_height,
            max_height: tile_atlas.model.max_height,
            scale: tile_atlas.model.scale() as f32,
        }
    }
}

pub struct TerrainData {
    mesh_buffer: StaticBuffer<MeshUniform>,
    pub(crate) terrain_bind_group: BindGroup,
}

impl TerrainData {
    fn new(
        device: &RenderDevice,
        fallback_image: &FallbackImage,
        tile_atlas: &TileAtlas,
        gpu_tile_atlas: &GpuTileAtlas,
    ) -> Self {
        let mesh_buffer = StaticBuffer::empty_sized(
            None,
            device,
            MeshUniform::SHADER_SIZE.get(),
            BufferUsages::STORAGE | BufferUsages::COPY_DST,
        );
        let terrain_config_buffer = StaticBuffer::create(
            None,
            device,
            &TerrainConfigUniform::from_tile_atlas(tile_atlas),
            BufferUsages::UNIFORM,
        );

        let atlas_sampler = device.create_sampler(&SamplerDescriptor {
            mag_filter: FilterMode::Linear,
            min_filter: FilterMode::Linear,
            mipmap_filter: FilterMode::Linear,
            anisotropy_clamp: 16, // Todo: make this customisable
            ..default()
        });

        let attachments = (0..8)
            .map(|i| {
                gpu_tile_atlas
                    .attachments
                    .get(i)
                    .map_or(fallback_image.d2_array.texture_view.clone(), |attachment| {
                        attachment.atlas_texture.create_view(&default())
                    })
            })
            .collect_vec();

        let attachment_uniform = AttachmentUniform::new(gpu_tile_atlas);
        let attachment_buffer =
            StaticBuffer::create(None, device, &attachment_uniform, BufferUsages::UNIFORM);

        let terrain_bind_group = device.create_bind_group(
            "terrain_bind_group",
            &create_terrain_layout(device),
            &BindGroupEntries::sequential((
                &mesh_buffer,
                &terrain_config_buffer,
                &attachment_buffer,
                &atlas_sampler,
                &attachments[0],
                &attachments[1],
                &attachments[2],
                &attachments[3],
                &attachments[4],
                &attachments[5],
                &attachments[6],
                &attachments[7],
            )),
        );

        Self {
            mesh_buffer,
            terrain_bind_group,
        }
    }

    pub(crate) fn initialize(
        device: Res<RenderDevice>,
        fallback_image: Res<FallbackImage>,
        mut terrain_data: ResMut<TerrainComponents<TerrainData>>,
        gpu_tile_atlases: Res<TerrainComponents<GpuTileAtlas>>,
        tile_atlases: Extract<Query<(Entity, &TileAtlas), Added<TileAtlas>>>,
    ) {
        for (terrain, tile_atlas) in &tile_atlases {
            let gpu_tile_atlas = gpu_tile_atlases.get(&terrain).unwrap();

            terrain_data.insert(
                terrain,
                TerrainData::new(&device, &fallback_image, tile_atlas.into(), gpu_tile_atlas),
            );
        }
    }

    pub(crate) fn extract(
        mut terrain_data: ResMut<TerrainComponents<TerrainData>>,
        terrains: Extract<
            Query<(Entity, &GlobalTransform, Option<&PreviousGlobalTransform>), With<TileAtlas>>,
        >,
    ) {
        for (terrain, transform, previous_transform) in terrains.iter() {
            let mesh_transforms = MeshTransforms {
                world_from_local: (&transform.affine()).into(),
                flags: 0,
                previous_world_from_local: (&previous_transform
                    .map(|t| t.0)
                    .unwrap_or(transform.affine()))
                    .into(),
            };
            let mesh_uniform = MeshUniform::new(&mesh_transforms, None);

            let terrain_data = terrain_data.get_mut(&terrain).unwrap();
            terrain_data.mesh_buffer.set_value(mesh_uniform);
        }
    }

    pub(crate) fn prepare(
        queue: Res<RenderQueue>,
        mut terrain_data: ResMut<TerrainComponents<TerrainData>>,
    ) {
        for terrain_data in &mut terrain_data.values_mut() {
            terrain_data.mesh_buffer.update(&queue);
        }
    }
}

pub struct SetTerrainBindGroup<const I: usize>;

impl<const I: usize, P: PhaseItem> RenderCommand<P> for SetTerrainBindGroup<I> {
    type Param = SRes<TerrainComponents<TerrainData>>;
    type ViewQuery = ();
    type ItemQuery = ();

    #[inline]
    fn render<'w>(
        item: &P,
        _: ROQueryItem<'w, Self::ViewQuery>,
        _: Option<ROQueryItem<'w, Self::ItemQuery>>,
        terrain_data: SystemParamItem<'w, '_, Self::Param>,
        pass: &mut TrackedRenderPass<'w>,
    ) -> RenderCommandResult {
        let data = terrain_data.into_inner().get(&item.entity()).unwrap();

        pass.set_bind_group(I, &data.terrain_bind_group, &[]);
        RenderCommandResult::Success
    }
}


================================================
FILE: src/render/terrain_material.rs
================================================
use crate::{
    debug::DebugTerrain,
    render::{
        terrain_bind_group::{create_terrain_layout, SetTerrainBindGroup},
        terrain_view_bind_group::{
            create_terrain_view_layout, DrawTerrainCommand, SetTerrainViewBindGroup,
        },
    },
    shaders::{DEFAULT_FRAGMENT_SHADER, DEFAULT_VERTEX_SHADER},
    terrain::TerrainComponents,
    terrain_data::gpu_tile_atlas::GpuTileAtlas,
};
use bevy::{
    core_pipeline::core_3d::{Opaque3d, Opaque3dBinKey},
    pbr::{
        MaterialPipeline, MeshPipeline, MeshPipelineViewLayoutKey, PreparedMaterial,
        RenderMaterialInstances, SetMaterialBindGroup, SetMeshViewBindGroup,
    },
    prelude::*,
    render::{
        extract_instances::ExtractInstancesPlugin,
        render_asset::{prepare_assets, RenderAssetPlugin, RenderAssets},
        render_phase::{
            AddRenderCommand, BinnedRenderPhaseType, DrawFunctions, SetItemPipeline,
            ViewBinnedRenderPhases,
        },
        render_resource::*,
        renderer::RenderDevice,
        texture::{BevyDefault, GpuImage},
        Render, RenderApp, RenderSet,
    },
};
use std::{hash::Hash, marker::PhantomData};

pub struct TerrainPipelineKey<M: Material> {
    pub flags: TerrainPipelineFlags,
    pub bind_group_data: M::Data,
}

impl<M: Material> Eq for TerrainPipelineKey<M> where M::Data: PartialEq {}

impl<M: Material> PartialEq for TerrainPipelineKey<M>
where
    M::Data: PartialEq,
{
    fn eq(&self, other: &Self) -> bool {
        self.flags == other.flags && self.bind_group_data == other.bind_group_data
    }
}

impl<M: Material> Clone for TerrainPipelineKey<M>
where
    M::Data: Clone,
{
    fn clone(&self) -> Self {
        Self {
            flags: self.flags,
            bind_group_data: self.bind_group_data.clone(),
        }
    }
}

impl<M: Material> Hash for TerrainPipelineKey<M>
where
    M::Data: Hash,
{
    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
        self.flags.hash(state);
        self.bind_group_data.hash(state);
    }
}

bitflags::bitflags! {
    #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
    #[repr(transparent)]
    pub struct TerrainPipelineFlags: u32 {
        const NONE               = 0;
        const SPHERICAL          = 1 <<  0;
        const WIREFRAME          = 1 <<  1;
        const SHOW_DATA_LOD      = 1 <<  2;
        const SHOW_GEOMETRY_LOD  = 1 <<  3;
        const SHOW_TILE_TREE     = 1 <<  4;
        const SHOW_PIXELS        = 1 <<  5;
        const SHOW_UV            = 1 <<  6;
        const SHOW_NORMALS       = 1 <<  7;
        const MORPH              = 1 <<  8;
        const BLEND              = 1 <<  9;
        const TILE_TREE_LOD      = 1 << 10;
        const LIGHTING           = 1 << 11;
        const SAMPLE_GRAD        = 1 << 12;
        const HIGH_PRECISION     = 1 << 13;
        const TEST1              = 1 << 14;
        const TEST2              = 1 << 15;
        const TEST3              = 1 << 16;
        const MSAA_RESERVED_BITS = TerrainPipelineFlags::MSAA_MASK_BITS << TerrainPipelineFlags::MSAA_SHIFT_BITS;
    }
}

impl TerrainPipelineFlags {
    const MSAA_MASK_BITS: u32 = 0b111111;
    const MSAA_SHIFT_BITS: u32 = 32 - 6;

    pub fn from_msaa_samples(msaa_samples: u32) -> Self {
        let msaa_bits = ((msaa_samples - 1) & Self::MSAA_MASK_BITS) << Self::MSAA_SHIFT_BITS;
        TerrainPipelineFlags::from_bits(msaa_bits).unwrap()
    }

    pub fn from_debug(debug: &DebugTerrain) -> Self {
        let mut key = TerrainPipelineFlags::NONE;

        if debug.wireframe {
            key |= TerrainPipelineFlags::WIREFRAME;
        }
        if debug.show_data_lod {
            key |= TerrainPipelineFlags::SHOW_DATA_LOD;
        }
        if debug.show_geometry_lod {
            key |= TerrainPipelineFlags::SHOW_GEOMETRY_LOD;
        }
        if debug.show_tile_tree {
            key |= TerrainPipelineFlags::SHOW_TILE_TREE;
        }
        if debug.show_pixels {
            key |= TerrainPipelineFlags::SHOW_PIXELS;
        }
        if debug.show_uv {
            key |= TerrainPipelineFlags::SHOW_UV;
        }
        if debug.show_normals {
            key |= TerrainPipelineFlags::SHOW_NORMALS;
        }
        if debug.morph {
            key |= TerrainPipelineFlags::MORPH;
        }
        if debug.blend {
            key |= TerrainPipelineFlags::BLEND;
        }
        if debug.tile_tree_lod {
            key |= TerrainPipelineFlags::TILE_TREE_LOD;
        }
        if debug.lighting {
            key |= TerrainPipelineFlags::LIGHTING;
        }
        if debug.sample_grad {
            key |= TerrainPipelineFlags::SAMPLE_GRAD;
        }
        if debug.high_precision {
            key |= TerrainPipelineFlags::HIGH_PRECISION;
        }
        if debug.test1 {
            key |= TerrainPipelineFlags::TEST1;
        }
        if debug.test2 {
            key |= TerrainPipelineFlags::TEST2;
        }
        if debug.test3 {
            key |= TerrainPipelineFlags::TEST3;
        }

        key
    }

    pub fn msaa_samples(&self) -> u32 {
        ((self.bits() >> Self::MSAA_SHIFT_BITS) & Self::MSAA_MASK_BITS) + 1
    }

    pub fn polygon_mode(&self) -> PolygonMode {
        match self.contains(TerrainPipelineFlags::WIREFRAME) {
            true => PolygonMode::Line,
            false => PolygonMode::Fill,
        }
    }

    pub fn shader_defs(&self) -> Vec<ShaderDefVal> {
        let mut shader_defs = Vec::new();

        if self.contains(TerrainPipelineFlags::SPHERICAL) {
            shader_defs.push("SPHERICAL".into());
        }
        if self.contains(TerrainPipelineFlags::SHOW_DATA_LOD) {
            shader_defs.push("SHOW_DATA_LOD".into());
        }
        if self.contains(TerrainPipelineFlags::SHOW_GEOMETRY_LOD) {
            shader_defs.push("SHOW_GEOMETRY_LOD".into());
        }
        if self.contains(TerrainPipelineFlags::SHOW_TILE_TREE) {
            shader_defs.push("SHOW_TILE_TREE".into());
        }
        if self.contains(TerrainPipelineFlags::SHOW_PIXELS) {
            shader_defs.push("SHOW_PIXELS".into())
        }
        if self.contains(TerrainPipelineFlags::SHOW_UV) {
            shader_defs.push("SHOW_UV".into());
        }
        if self.contains(TerrainPipelineFlags::SHOW_NORMALS) {
            shader_defs.push("SHOW_NORMALS".into())
        }
        if self.contains(TerrainPipelineFlags::MORPH) {
            shader_defs.push("MORPH".into());
        }
        if self.contains(TerrainPipelineFlags::BLEND) {
            shader_defs.push("BLEND".into());
        }
        if self.contains(TerrainPipelineFlags::TILE_TREE_LOD) {
            shader_defs.push("TILE_TREE_LOD".into());
        }
        if self.contains(TerrainPipelineFlags::LIGHTING) {
            shader_defs.push("LIGHTING".into());
        }
        if self.contains(TerrainPipelineFlags::SAMPLE_GRAD) {
            shader_defs.push("SAMPLE_GRAD".into());
        }
        if self.contains(TerrainPipelineFlags::HIGH_PRECISION) {
            shader_defs.push("HIGH_PRECISION".into());
        }
        if self.contains(TerrainPipelineFlags::TEST1) {
            shader_defs.push("TEST1".into());
        }
        if self.contains(TerrainPipelineFlags::TEST2) {
            shader_defs.push("TEST2".into());
        }
        if self.contains(TerrainPipelineFlags::TEST3) {
            shader_defs.push("TEST3".into());
        }

        shader_defs
    }
}

/// The pipeline used to render the terrain entities.
#[derive(Resource)]
pub struct TerrainRenderPipeline<M: Material> {
    pub(crate) view_layout: BindGroupLayout,
    pub(crate) view_layout_multisampled: BindGroupLayout,
    pub(crate) terrain_layout: BindGroupLayout,
    pub(crate) terrain_view_layout: BindGroupLayout,
    pub(crate) material_layout: BindGroupLayout,
    pub vertex_shader: Handle<Shader>,
    pub fragment_shader: Handle<Shader>,
    marker: PhantomData<M>,
}

impl<M: Material> FromWorld for TerrainRenderPipeline<M> {
    fn from_world(world: &mut World) -> Self {
        let device = world.resource::<RenderDevice>();
        let asset_server = world.resource::<AssetServer>();
        let mesh_pipeline = world.resource::<MeshPipeline>();

        let view_layout = mesh_pipeline
            .get_view_layout(MeshPipelineViewLayoutKey::empty())
            .clone();
        let view_layout_multisampled = mesh_pipeline
            .get_view_layout(MeshPipelineViewLayoutKey::MULTISAMPLED)
            .clone();
        let terrain_layout = create_terrain_layout(device);
        let terrain_view_layout = create_terrain_view_layout(device);
        let material_layout = M::bind_group_layout(device);

        let vertex_shader = match M::vertex_shader() {
            ShaderRef::Default => asset_server.load(DEFAULT_VERTEX_SHADER),
            ShaderRef::Handle(handle) => handle,
            ShaderRef::Path(path) => asset_server.load(path),
        };

        let fragment_shader = match M::fragment_shader() {
            ShaderRef::Default => asset_server.load(DEFAULT_FRAGMENT_SHADER),
            ShaderRef::Handle(handle) => handle,
            ShaderRef::Path(path) => asset_server.load(path),
        };

        Self {
            view_layout,
            view_layout_multisampled,
            terrain_layout,
            terrain_view_layout,
            material_layout,
            vertex_shader,
            fragment_shader,
            marker: PhantomData,
        }
    }
}

impl<M: Material> SpecializedRenderPipeline for TerrainRenderPipeline<M>
where
    M::Data: PartialEq + Eq + Hash + Clone,
{
    type Key = TerrainPipelineKey<M>;

    fn specialize(&self, key: Self::Key) -> RenderPipelineDescriptor {
        let mut shader_defs = key.flags.shader_defs();

        let mut bind_group_layout = match key.flags.msaa_samples() {
            1 => vec![self.view_layout.clone()],
            _ => {
                shader_defs.push("MULTISAMPLED".into());
                vec![self.view_layout_multisampled.clone()]
            }
        };

        bind_group_layout.push(self.terrain_layout.clone());
        bind_group_layout.push(self.terrain_view_layout.clone());
        bind_group_layout.push(self.material_layout.clone());

        let vertex_shader_defs = shader_defs.clone();
        let mut fragment_shader_defs = shader_defs.clone();
        fragment_shader_defs.push("FRAGMENT".into());

        RenderPipelineDescriptor {
            label: None,
            layout: bind_group_layout,
            push_constant_ranges: default(),
            vertex: VertexState {
                shader: self.vertex_shader.clone(),
                entry_point: "vertex".into(),
                shader_defs: vertex_shader_defs,
                buffers: Vec::new(),
            },
            primitive: PrimitiveState {
                front_face: FrontFace::Ccw,
                cull_mode: Some(Face::Back),
                unclipped_depth: false,
                polygon_mode: key.flags.polygon_mode(),
                conservative: false,
                topology: PrimitiveTopology::TriangleStrip,
                strip_index_format: None,
            },
            fragment: Some(FragmentState {
                shader: self.fragment_shader.clone(),
                shader_defs: fragment_shader_defs,
                entry_point: "fragment".into(),
                targets: vec![Some(ColorTargetState {
                    format: TextureFormat::bevy_default(),
                    blend: Some(BlendState::REPLACE),
                    write_mask: ColorWrites::ALL,
                })],
            }),
            depth_stencil: Some(DepthStencilState {
                format: TextureFormat::Depth32Float,
                depth_write_enabled: true,
                depth_compare: CompareFunction::Greater,
                stencil: StencilState {
                    front: StencilFaceState::IGNORE,
                    back: StencilFaceState::IGNORE,
                    read_mask: 0,
                    write_mask: 0,
                },
                bias: DepthBiasState {
                    constant: 0,
                    slope_scale: 0.0,
                    clamp: 0.0,
                },
            }),
            multisample: MultisampleState {
                count: key.flags.msaa_samples(),
                mask: !0,
                alpha_to_coverage_enabled: false,
            },
        }
    }
}

/// The draw function of the terrain. It sets the pipeline and the bind groups and then issues the
/// draw call.
pub(crate) type DrawTerrain<M> = (
    SetItemPipeline,
    SetMeshViewBindGroup<0>,
    SetTerrainBindGroup<1>,
    SetTerrainViewBindGroup<2>,
    SetMaterialBindGroup<M, 3>,
    DrawTerrainCommand,
);

/// Queses all terrain entities for rendering via the terrain pipeline.
#[allow(clippy::too_many_arguments)]
pub(crate) fn queue_terrain<M: Material>(
    draw_functions: Res<DrawFunctions<Opaque3d>>,
    msaa: Res<Msaa>,
    debug: Option<Res<DebugTerrain>>,
    render_materials: Res<RenderAssets<PreparedMaterial<M>>>,
    pipeline_cache: Res<PipelineCache>,
    terrain_pipeline: Res<TerrainRenderPipeline<M>>,
    mut pipelines: ResMut<SpecializedRenderPipelines<TerrainRenderPipeline<M>>>,
    mut opaque_render_phases: ResMut<ViewBinnedRenderPhases<Opaque3d>>,
    gpu_tile_atlases: Res<TerrainComponents<GpuTileAtlas>>,
    render_material_instances: Res<RenderMaterialInstances<M>>,
) where
    M::Data: PartialEq + Eq + Hash + Clone,
{
    for phase in opaque_render_phases.values_mut() {
        let draw_function = draw_functions.read().get_id::<DrawTerrain<M>>().unwrap();

        for (&terrain, &material_id) in render_material_instances.iter() {
            let gpu_tile_atlas = gpu_tile_atlases.get(&terrain).unwrap();
            if let Some(material) = render_materials.get(material_id) {
                let mut flags = TerrainPipelineFlags::from_msaa_samples(msaa.samples());

                if gpu_tile_atlas.is_spherical {
                    flags |= TerrainPipelineFlags::SPHERICAL;
                }

                if let Some(debug) = &debug {
                    flags |= TerrainPipelineFlags::from_debug(debug);
                } else {
                    flags |= TerrainPipelineFlags::LIGHTING
                        | TerrainPipelineFlags::MORPH
                        | TerrainPipelineFlags::BLEND
                        | TerrainPipelineFlags::SAMPLE_GRAD;
                }

                let key = TerrainPipelineKey {
                    flags,
                    bind_group_data: material.key.clone(),
                };

                let pipeline = pipelines.specialize(&pipeline_cache, &terrain_pipeline, key);

                phase.add(
                    Opaque3dBinKey {
                        pipeline,
                        draw_function,
                        asset_id: material_id.untyped(),
                        material_bind_group_id: None,
                        lightmap_image: None,
                    },
                    terrain,
                    BinnedRenderPhaseType::NonMesh,
                );
            }
        }
    }
}

/// This plugin adds a custom material for a terrain.
///
/// It can be used to render the terrain using a custom vertex and fragment shader.
pub struct TerrainMaterialPlugin<M: Material>(PhantomData<M>);

impl<M: Material> Default for TerrainMaterialPlugin<M> {
    fn default() -> Self {
        Self(Default::default())
    }
}

impl<M: Material> Plugin for TerrainMaterialPlugin<M>
where
    M::Data: PartialEq + Eq + Hash + Clone,
{
    fn build(&self, app: &mut App) {
        app.init_asset::<M>().add_plugins((
            ExtractInstancesPlugin::<AssetId<M>>::extract_visible(),
            RenderAssetPlugin::<PreparedMaterial<M>, GpuImage>::default(),
        ));

        app.sub_app_mut(RenderApp)
            .add_render_command::<Opaque3d, DrawTerrain<M>>()
            .add_systems(
                Render,
                queue_terrain::<M>
                    .in_set(RenderSet::QueueMeshes)
                    .after(prepare_assets::<PreparedMaterial<M>>),
            );
    }

    fn finish(&self, app: &mut App) {
        app.sub_app_mut(RenderApp)
            .init_resource::<TerrainRenderPipeline<M>>()
            .init_resource::<SpecializedRenderPipelines<TerrainRenderPipeline<M>>>()
            .init_resource::<MaterialPipeline<M>>(); // prepare assets depends on this to access the material layout
    }
}


================================================
FILE: src/render/terrain_view_bind_group.rs
================================================
use crate::{
    math::{TerrainModelApproximation, TileCoordinate},
    terrain_data::{gpu_tile_tree::GpuTileTree, tile_tree::TileTree},
    terrain_view::TerrainViewComponents,
    util::StaticBuffer,
};
use bevy::{
    ecs::{
        query::ROQueryItem,
        system::{lifetimeless::SRes, SystemParamItem},
    },
    prelude::*,
    render::{
        render_phase::{PhaseItem, RenderCommand, RenderCommandResult, TrackedRenderPass},
        render_resource::{binding_types::*, *},
        renderer::{RenderDevice, RenderQueue},
        Extract,
    },
};

pub(crate) fn create_prepare_indirect_layout(device: &RenderDevice) -> BindGroupLayout {
    device.create_bind_group_layout(
        None,
        &BindGroupLayoutEntries::single(
            ShaderStages::COMPUTE,
            storage_buffer::<Indirect>(false), // indirect buffer
        ),
    )
}

pub(crate) fn create_refine_tiles_layout(device: &RenderDevice) -> BindGroupLayout {
    device.create_bind_group_layout(
        None,
        &BindGroupLayoutEntries::sequential(
            ShaderStages::COMPUTE,
            (
                uniform_buffer::<TerrainViewConfigUniform>(false), // terrain view config
                uniform_buffer::<TerrainModelApproximation>(false), // model view approximation
                storage_buffer_read_only_sized(false, None),       // tile_tree
                storage_buffer_read_only_sized(false, None),       // origins
                storage_buffer_sized(false, None),                 // final tiles
                storage_buffer_sized(false, None),                 // temporary tiles
                storage_buffer::<Parameters>(false),               // parameters
            ),
        ),
    )
}

pub(crate) fn create_terrain_view_layout(device: &RenderDevice) -> BindGroupLayout {
    device.create_bind_group_layout(
        None,
        &BindGroupLayoutEntries::sequential(
            ShaderStages::VERTEX_FRAGMENT,
            (
                uniform_buffer::<TerrainViewConfigUniform>(false), // terrain view config
                uniform_buffer::<TerrainModelApproximation>(false), // model view approximation
                storage_buffer_read_only_sized(false, None),       // tile_tree
                storage_buffer_read_only_sized(false, None),       // origins
                storage_buffer_read_only_sized(false, None),       // tiles
            ),
        ),
    )
}

#[derive(Default, ShaderType)]
pub(crate) struct Indirect {
    x_or_vertex_count: u32,
    y_or_instance_count: u32,
    z_or_base_vertex: u32,
    base_instance: u32,
}

#[derive(Default, ShaderType)]
struct Parameters {
    tile_count: u32,
    counter: i32,
    child_index: i32,
    final_index: i32,
}

#[derive(Default, ShaderType)]
struct TerrainViewConfigUniform {
    tree_size: u32,
    geometry_tile_count: u32,
    refinement_count: u32,
    grid_size: f32,
    vertices_per_row: u32,
    vertices_per_tile: u32,
    morph_distance: f32,
    blend_distance: f32,
    load_distance: f32,
    subdivision_distance: f32,
    morph_range: f32,
    blend_range: f32,
    precision_threshold_distance: f32,
}

impl TerrainViewConfigUniform {
    fn from_tile_tree(tile_tree: &TileTree) -> Self {
        TerrainViewConfigUniform {
            tree_size: tile_tree.tree_size,
            geometry_tile_count: tile_tree.geometry_tile_count,
            refinement_count: tile_tree.refinement_count,
            grid_size: tile_tree.grid_size as f32,
            vertices_per_row: 2 * (tile_tree.grid_size + 2),
            vertices_per_tile: 2 * tile_tree.grid_size * (tile_tree.grid_size + 2),
            morph_distance: tile_tree.morph_distance as f32,
            blend_distance: tile_tree.blend_distance as f32,
            load_distance: tile_tree.load_distance as f32,
            subdivision_distance: tile_tree.subdivision_distance as f32,
            precision_threshold_distance: tile_tree.precision_threshold_distance as f32,
            morph_range: tile_tree.morph_range,
            blend_range: tile_tree.blend_range,
        }
    }
}

pub struct TerrainViewData {
    view_config_buffer: StaticBuffer<TerrainViewConfigUniform>,
    terrain_model_approximation_buffer: StaticBuffer<TerrainModelApproximation>,
    pub(super) indirect_buffer: StaticBuffer<Indirect>,
    pub(super) prepare_indirect_bind_group: BindGroup,
    pub(super) refine_tiles_bind_group: BindGroup,
    pub(super) terrain_view_bind_group: BindGroup,
}

impl TerrainViewData {
    fn new(device: &RenderDevice, tile_tree: &TileTree, gpu_tile_tree: &GpuTileTree) -> Self {
        // Todo: figure out a better way of limiting the tile buffer size
        let tile_buffer_size =
            TileCoordinate::min_size().get() * tile_tree.geometry_tile_count as BufferAddress;

        let view_config_buffer =
            StaticBuffer::empty(None, device, BufferUsages::UNIFORM | BufferUsages::COPY_DST);
        let indirect_buffer =
            StaticBuffer::empty(None, device, BufferUsages::STORAGE | BufferUsages::INDIRECT);
        let parameter_buffer =
            StaticBuffer::<Parameters>::empty(None, device, BufferUsages::STORAGE);
        let temporary_tile_buffer =
            StaticBuffer::<()>::empty_sized(None, device, tile_buffer_size, BufferUsages::STORAGE);
        let final_tile_buffer =
            StaticBuffer::<()>::empty_sized(None, device, tile_buffer_size, BufferUsages::STORAGE);
        let terrain_model_approximation_buffer = StaticBuffer::<TerrainModelApproximation>::empty(
            None,
            device,
            BufferUsages::UNIFORM | BufferUsages::COPY_DST,
        );

        let prepare_indirect_bind_group = device.create_bind_group(
            "prepare_indirect_bind_group",
            &create_prepare_indirect_layout(device),
            &BindGroupEntries::single(&indirect_buffer),
        );
        let refine_tiles_bind_group = device.create_bind_group(
            "refine_tiles_bind_group",
            &create_refine_tiles_layout(device),
            &BindGroupEntries::sequential((
                &view_config_buffer,
                &terrain_model_approximation_buffer,
                &gpu_tile_tree.tile_tree_buffer,
                &gpu_tile_tree.origins_buffer,
                &final_tile_buffer,
                &temporary_tile_buffer,
                &parameter_buffer,
            )),
        );
        let terrain_view_bind_group = device.create_bind_group(
            "terrain_view_bind_group",
            &create_terrain_view_layout(device),
            &BindGroupEntries::sequential((
                &view_config_buffer,
                &terrain_model_approximation_buffer,
                &gpu_tile_tree.tile_tree_buffer,
                &gpu_tile_tree.origins_buffer,
                &final_tile_buffer,
            )),
        );

        Self {
            view_config_buffer,
            terrain_model_approximation_buffer,
            indirect_buffer,
            prepare_indirect_bind_group,
            refine_tiles_bind_group,
            terrain_view_bind_group,
        }
    }

    pub(super) fn refinement_count(&self) -> u32 {
        self.view_config_buffer.value().refinement_count
    }

    pub(crate) fn initialize(
        device: Res<RenderDevice>,
        mut terrain_view_data: ResMut<TerrainViewComponents<TerrainViewData>>,
        gpu_tile_trees: Res<TerrainViewComponents<GpuTileTree>>,
        tile_trees: Extract<Res<TerrainViewComponents<TileTree>>>,
    ) {
        for (&(terrain, view), tile_tree) in tile_trees.iter() {
            if terrain_view_data.contains_key(&(terrain, view)) {
                return;
            }

            let gpu_tile_tree = gpu_tile_trees.get(&(terrain, view)).unwrap();

            terrain_view_data.insert(
                (terrain, view),
                TerrainViewData::new(&device, tile_tree, gpu_tile_tree),
            );
        }
    }

    pub(crate) fn extract(
        mut terrain_view_data: ResMut<TerrainViewComponents<TerrainViewData>>,
        tile_trees: Extract<Res<TerrainViewComponents<TileTree>>>,
        terrain_model_approximations: Extract<
            Res<TerrainViewComponents<TerrainModelApproximation>>,
        >,
    ) {
        for (&(terrain, view), tile_tree) in tile_trees.iter() {
            let terrain_view_data = terrain_view_data.get_mut(&(terrain, view)).unwrap();

            terrain_view_data
                .view_config_buffer
                .set_value(TerrainViewConfigUniform::from_tile_tree(tile_tree));

            terrain_view_data
                .terrain_model_approximation_buffer
                .set_value(
                    terrain_model_approximations
                        .get(&(terrain, view))
                        .unwrap()
                        .clone(),
                );
        }
    }

    pub(crate) fn prepare(
        queue: Res<RenderQueue>,
        mut terrain_view_data: ResMut<TerrainViewComponents<TerrainViewData>>,
    ) {
        for data in &mut terrain_view_data.values_mut() {
            data.view_config_buffer.update(&queue);
            data.terrain_model_approximation_buffer.update(&queue);
        }
    }
}

pub struct SetTerrainViewBindGroup<const I: usize>;

impl<const I: usize, P: PhaseItem> RenderCommand<P> for SetTerrainViewBindGroup<I> {
    type Param = SRes<TerrainViewComponents<TerrainViewData>>;
    type ViewQuery = Entity;
    type ItemQuery = ();

    #[inline]
    fn render<'w>(
        item: &P,
        view: ROQueryItem<'w, Self::ViewQuery>,
        _: Option<ROQueryItem<'w, Self::ItemQuery>>,
        terrain_view_data: SystemParamItem<'w, '_, Self::Param>,
        pass: &mut TrackedRenderPass<'w>,
    ) -> RenderCommandResult {
        let data = terrain_view_data
            .into_inner()
            .get(&(item.entity(), view))
            .unwrap();

        pass.set_bind_group(I, &data.terrain_view_bind_group, &[]);
        RenderCommandResult::Success
    }
}

pub(crate) struct DrawTerrainCommand;

impl<P: PhaseItem> RenderCommand<P> for DrawTerrainCommand {
    type Param = SRes<TerrainViewComponents<TerrainViewData>>;
    type ViewQuery = Entity;
    type ItemQuery = ();

    #[inline]
    fn render<'w>(
        item: &P,
        view: ROQueryItem<'w, Self::ViewQuery>,
        _: Option<ROQueryItem<'w, Self::ItemQuery>>,
        terrain_view_data: SystemParamItem<'w, '_, Self::Param>,
        pass: &mut TrackedRenderPass<'w>,
    ) -> RenderCommandResult {
        let data = terrain_view_data
            .into_inner()
            .get(&(item.entity(), view))
            .unwrap();

        pass.draw_indirect(&data.indirect_buffer, 0);

        RenderCommandResult::Success
    }
}


================================================
FILE: src/render/tiling_prepass.rs
================================================
use crate::terrain_data::gpu_tile_tree::GpuTileTree;
use crate::{
    debug::DebugTerrain,
    render::{
        culling_bind_group::{create_culling_layout, CullingBindGroup},
        terrain_bind_group::{create_terrain_layout, TerrainData},
        terrain_view_bind_group::{
            create_prepare_indirect_layout, create_refine_tiles_layout, TerrainViewData,
        },
    },
    shaders::{PREPARE_PREPASS_SHADER, REFINE_TILES_SHADER},
    terrain::TerrainComponents,
    terrain_data::gpu_tile_atlas::GpuTileAtlas,
    terrain_view::TerrainViewComponents,
};
use bevy::{
    prelude::*,
    render::{
        render_graph::{self, RenderLabel},
        render_resource::*,
        renderer::{RenderContext, RenderDevice},
    },
};

#[derive(Debug, Hash, PartialEq, Eq, Clone, RenderLabel)]
pub struct TilingPrepassLabel;

bitflags::bitflags! {
    #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
    #[repr(transparent)]
    pub struct TilingPrepassPipelineKey: u32 {
        const NONE           = 0;
        const REFINE_TILES   = 1 << 0;
        const PREPARE_ROOT   = 1 << 1;
        const PREPARE_NEXT   = 1 << 2;
        const PREPARE_RENDER = 1 << 3;
        const SPHERICAL      = 1 << 4;
        const TEST1          = 1 << 5;
        const TEST2          = 1 << 6;
        const TEST3          = 1 << 7;
    }
}

impl TilingPrepassPipelineKey {
    pub fn from_debug(debug: &DebugTerrain) -> Self {
        let mut key = TilingPrepassPipelineKey::NONE;

        if debug.test1 {
            key |= TilingPrepassPipelineKey::TEST1;
        }
        if debug.test2 {
            key |= TilingPrepassPipelineKey::TEST2;
        }
        if debug.test3 {
            key |= TilingPrepassPipelineKey::TEST3;
        }

        key
    }

    pub fn shader_defs(&self) -> Vec<ShaderDefVal> {
        let mut shader_defs = Vec::new();

        if self.contains(TilingPrepassPipelineKey::SPHERICAL) {
            shader_defs.push("SPHERICAL".into());
        }
        if self.contains(TilingPrepassPipelineKey::TEST1) {
            shader_defs.push("TEST1".into());
        }
        if self.contains(TilingPrepassPipelineKey::TEST2) {
            shader_defs.push("TEST2".into());
        }
        if self.contains(TilingPrepassPipelineKey::TEST3) {
            shader_defs.push("TEST3".into());
        }

        shader_defs
    }
}

pub(crate) struct TilingPrepassItem {
    refine_tiles_pipeline: CachedComputePipelineId,
    prepare_root_pipeline: CachedComputePipelineId,
    prepare_next_pipeline: CachedComputePipelineId,
    prepare_render_pipeline: CachedComputePipelineId,
}

impl TilingPrepassItem {
    fn pipelines<'a>(
        &'a self,
        pipeline_cache: &'a PipelineCache,
    ) -> Option<(
        &ComputePipeline,
        &ComputePipeline,
        &ComputePipeline,
        &ComputePipeline,
    )> {
        Some((
            pipeline_cache.get_compute_pipeline(self.refine_tiles_pipeline)?,
            pipeline_cache.get_compute_pipeline(self.prepare_root_pipeline)?,
            pipeline_cache.get_compute_pipeline(self.prepare_next_pipeline)?,
            pipeline_cache.get_compute_pipeline(self.prepare_render_pipeline)?,
        ))
    }
}

#[derive(Resource)]
pub struct TilingPrepassPipelines {
    pub(crate) prepare_indirect_layout: BindGroupLayout,
    pub(crate) refine_tiles_layout: BindGroupLayout,
    culling_data_layout: BindGroupLayout,
    terrain_layout: BindGroupLayout,
    prepare_prepass_shader: Handle<Shader>,
    refine_tiles_shader: Handle<Shader>,
}

impl FromWorld for TilingPrepassPipelines {
    fn from_world(world: &mut World) -> Self {
        let device = world.resource::<RenderDevice>();
        let asset_server = world.resource::<AssetServer>();

        let prepare_indirect_layout = create_prepare_indirect_layout(device);
        let refine_tiles_layout = create_refine_tiles_layout(device);
        let culling_data_layout = create_culling_layout(device);
        let terrain_layout = create_terrain_layout(device);

        let prepare_prepass_shader = asset_server.load(PREPARE_PREPASS_SHADER);
        let refine_tiles_shader = asset_server.load(REFINE_TILES_SHADER);

        TilingPrepassPipelines {
            prepare_indirect_layout,
            refine_tiles_layout,
            culling_data_layout,
            terrain_layout,
            prepare_prepass_shader,
            refine_tiles_shader,
        }
    }
}

impl SpecializedComputePipeline for TilingPrepassPipelines {
    type Key = TilingPrepassPipelineKey;

    fn specialize(&self, key: Self::Key) -> ComputePipelineDescriptor {
        let mut layout = default();
        let mut shader = default();
        let mut entry_point = default();

        let shader_defs = key.shader_defs();

        if key.contains(TilingPrepassPipelineKey::REFINE_TILES) {
            layout = vec![
                self.culling_data_layout.clone(),
                self.terrain_layout.clone(),
                self.refine_tiles_layout.clone(),
            ];
            shader = self.refine_tiles_shader.clone();
            entry_point = "refine_tiles".into();
        }
        if key.contains(TilingPrepassPipelineKey::PREPARE_ROOT) {
            layout = vec![
                self.culling_data_layout.clone(),
                self.terrain_layout.clone(),
                self.refine_tiles_layout.clone(),
                self.prepare_indirect_layout.clone(),
            ];
            shader = self.prepare_prepass_shader.clone();
            entry_point = "prepare_root".into();
        }
        if key.contains(TilingPrepassPipelineKey::PREPARE_NEXT) {
            layout = vec![
                self.culling_data_layout.clone(),
                self.terrain_layout.clone(),
                self.refine_tiles_layout.clone(),
                self.prepare_indirect_layout.clone(),
            ];
            shader = self.prepare_prepass_shader.clone();
            entry_point = "prepare_next".into();
        }
        if key.contains(TilingPrepassPipelineKey::PREPARE_RENDER) {
            layout = vec![
                self.culling_data_layout.clone(),
                self.terrain_layout.clone(),
                self.refine_tiles_layout.clone(),
                self.prepare_indirect_layout.clone(),
            ];
            shader = self.prepare_prepass_shader.clone();
            entry_point = "prepare_render".into();
        }

        ComputePipelineDescriptor {
            label: Some("tiling_prepass_pipeline".into()),
            layout,
            push_constant_ranges: default(),
            shader,
            shader_defs,
            entry_point,
        }
    }
}

pub struct TilingPrepassNode;

impl render_graph::Node for TilingPrepassNode {
    fn run<'w>(
        &self,
        _graph: &mut render_graph::RenderGraphContext,
        context: &mut RenderContext<'w>,
        world: &'w World,
    ) -> Result<(), render_graph::NodeRunError> {
        let prepass_items = world.resource::<TerrainViewComponents<TilingPrepassItem>>();
        let pipeline_cache = world.resource::<PipelineCache>();
        let terrain_data = world.resource::<TerrainComponents<TerrainData>>();
        let terrain_view_data = world.resource::<TerrainViewComponents<TerrainViewData>>();
        let culling_bind_groups = world.resource::<TerrainViewComponents<CullingBindGroup>>();
        let debug = world.get_resource::<DebugTerrain>();

        if debug.map(|debug| debug.freeze).unwrap_or(false) {
            return Ok(());
        }

        context.add_command_buffer_generation_task(move |device| {
            let mut command_encoder =
                device.create_command_encoder(&CommandEncoderDescriptor::default());
            let mut compute_pass =
                command_encoder.begin_compute_pass(&ComputePassDescriptor::default());

            for (&(terrain, view), prepass_item) in prepass_items.iter() {
                let Some((
                    refine_tiles_pipeline,
                    prepare_root_pipeline,
                    prepare_next_pipeline,
                    prepare_render_pipeline,
                )) = prepass_item.pipelines(pipeline_cache)
                else {
                    continue;
                };

                let culling_bind_group = culling_bind_groups.get(&(terrain, view)).unwrap();
                let terrain_data = terrain_data.get(&terrain).unwrap();
                let view_data = terrain_view_data.get(&(terrain, view)).unwrap();

                compute_pass.set_bind_group(0, culling_bind_group, &[]);
                compute_pass.set_bind_group(1, &terrain_data.terrain_bind_group, &[]);
                compute_pass.set_bind_group(2, &view_data.refine_tiles_bind_group, &[]);
                compute_pass.set_bind_group(3, &view_data.prepare_indirect_bind_group, &[]);

                compute_pass.set_pipeline(prepare_root_pipeline);
                compute_pass.dispatch_workgroups(1, 1, 1);

                for _ in 0..view_data.refinement_count() {
                    compute_pass.set_pipeline(refine_tiles_pipeline);
                    compute_pass.dispatch_workgroups_indirect(&view_data.indirect_buffer, 0);

                    compute_pass.set_pipeline(prepare_next_pipeline);
                    compute_pass.dispatch_workgroups(1, 1, 1);
                }

                compute_pass.set_pipeline(refine_tiles_pipeline);
                compute_pass.dispatch_workgroups_indirect(&view_data.indirect_buffer, 0);

                compute_pass.set_pipeline(prepare_render_pipeline);
                compute_pass.dispatch_workgroups(1, 1, 1);
            }

            drop(compute_pass);
            command_encoder.finish()
        });

        Ok(())
    }
}

pub(crate) fn queue_tiling_prepass(
    debug: Option<Res<DebugTerrain>>,
    pipeline_cache: Res<PipelineCache>,
    prepass_pipelines: ResMut<TilingPrepassPipelines>,
    mut pipelines: ResMut<SpecializedComputePipelines<TilingPrepassPipelines>>,
    mut prepass_items: ResMut<TerrainViewComponents<TilingPrepassItem>>,
    gpu_tile_trees: Res<TerrainViewComponents<GpuTileTree>>,
    gpu_tile_atlases: Res<TerrainComponents<GpuTileAtlas>>,
) {
    for &(terrain, view) in gpu_tile_trees.keys() {
        let gpu_tile_atlas = gpu_tile_atlases.get(&terrain).unwrap();

        let mut key = TilingPrepassPipelineKey::NONE;

        if gpu_tile_atlas.is_spherical {
            key |= TilingPrepassPipelineKey::SPHERICAL;
        }

        if let Some(debug) = &debug {
            key |= TilingPrepassPipelineKey::from_debug(debug);
        }

        let refine_tiles_pipeline = pipelines.specialize(
            &pipeline_cache,
            &prepass_pipelines,
            key | TilingPrepassPipelineKey::REFINE_TILES,
        );
        let prepare_root_pipeline = pipelines.specialize(
            &pipeline_cache,
            &prepass_pipelines,
            key | TilingPrepassPipelineKey::PREPARE_ROOT,
        );
        let prepare_next_pipeline = pipelines.specialize(
            &pipeline_cache,
            &prepass_pipelines,
            key | TilingPrepassPipelineKey::PREPARE_NEXT,
        );
        let prepare_render_pipeline = pipelines.specialize(
            &pipeline_cache,
            &prepass_pipelines,
            key | TilingPrepassPipelineKey::PREPARE_RENDER,
        );

        prepass_items.insert(
            (terrain, view),
            TilingPrepassItem {
                refine_tiles_pipeline,
                prepare_root_pipeline,
                prepare_next_pipeline,
                prepare_render_pipeline,
            },
        );
    }
}


================================================
FILE: src/shaders/attachments.wgsl
================================================
#define_import_path bevy_terrain::attachments

#import bevy_terrain::types::AtlasTile
#import bevy_terrain::bindings::{config, atlas_sampler, attachments, attachment0_atlas, attachment1_atlas, attachment2_atlas}
#import bevy_terrain::functions::tile_count

fn attachment_uv(uv: vec2<f32>, attachment_index: u32) -> vec2<f32> {
    let attachment = attachments[attachment_index];
    return uv * attachment.scale + attachment.offset;
}

fn sample_attachment0(tile: AtlasTile) -> vec4<f32> {
    let uv = attachment_uv(tile.coordinate.uv, 0u);

#ifdef FRAGMENT
#ifdef SAMPLE_GRAD
    return textureSampleGrad(attachment0_atlas, atlas_sampler, uv, tile.index, tile.coordinate.uv_dx, tile.coordinate.uv_dy);
#else
    return textureSampleLevel(attachment0_atlas, atlas_sampler, uv, tile.index, 0.0);
#endif
#else
    return textureSampleLevel(attachment0_atlas, atlas_sampler, uv, tile.index, 0.0);
#endif
}

fn sample_attachment1(tile: AtlasTile) -> vec4<f32> {
    let uv = attachment_uv(tile.coordinate.uv, 1u);

#ifdef FRAGMENT
#ifdef SAMPLE_GRAD
    return textureSampleGrad(attachment1_atlas, atlas_sampler, uv, tile.index, tile.coordinate.uv_dx, tile.coordinate.uv_dy);
#else
    return textureSampleLevel(attachment1_atlas, atlas_sampler, uv, tile.index, 0.0);
#endif
#else
    return textureSampleLevel(attachment1_atlas, atlas_sampler, uv, tile.index, 0.0);
#endif
}

fn sample_attachment1_gather0(tile: AtlasTile) -> vec4<f32> {
    let uv = attachment_uv(tile.coordinate.uv, 1u);
    return textureGather(0, attachment1_atlas, atlas_sampler, uv, tile.index);
}

fn sample_height(tile: AtlasTile) -> f32 {
    let height = sample_attachment0(tile).x;

    return mix(config.min_height, config.max_height, height);
}

fn sample_normal(tile: AtlasTile, vertex_normal: vec3<f32>) -> vec3<f32> {
    let uv = attachment_uv(tile.coordinate.uv, 0u);

#ifdef SPHERICAL
    var FACE_UP = array(
        vec3( 0.0, 1.0,  0.0),
        vec3( 0.0, 1.0,  0.0),
        vec3( 0.0, 0.0, -1.0),
        vec3( 0.0, 0.0, -1.0),
        vec3(-1.0, 0.0,  0.0),
        vec3(-1.0, 0.0,  0.0),
    );

    let face_up = FACE_UP[tile.coordinate.side];

    let normal    = normalize(vertex_normal);
    let tangent   = cross(face_up, normal);
    let bitangent = cross(normal, tangent);
    let TBN       = mat3x3(tangent, bitangent, normal);

    let side_length = 3.14159265359 / 4.0 * config.scale;
#else
    let TBN = mat3x3(1.0, 0.0, 0.0,
                     0.0, 0.0, 1.0,
                     0.0, 1.0, 0.0);

    let side_length = config.scale;
#endif

    // Todo: this is only an approximation of the S2 distance (pixels are not spaced evenly and they are not perpendicular)
    let pixels_per_side = attachments[0u].size * tile_count(tile.coordinate.lod);
    let distance_between_samples = side_length / pixels_per_side;
    let offset = 0.5 / attachments[0u].size;

#ifdef FRAGMENT
#ifdef SAMPLE_GRAD
    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);
    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);
    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);
    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);
#else
    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);
    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);
    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);
    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);
#endif
#else
    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);
    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);
    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);
    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);
#endif

    let surface_normal = normalize(vec3<f32>(left - right, down - up, distance_between_samples));

    return normalize(TBN * surface_normal);
}

fn sample_color(tile: AtlasTile) -> vec4<f32> {
    let height = sample_attachment0(tile).x;

    return vec4<f32>(height * 0.5);
}


================================================
FILE: src/shaders/bindings.wgsl
================================================
#define_import_path bevy_terrain::bindings

#import bevy_terrain::types::{TerrainViewConfig, TerrainConfig, TileTreeEntry, TileCoordinate, AttachmentConfig, TerrainModelApproximation, CullingData, IndirectBuffer, Parameters}
#import bevy_pbr::mesh_types::Mesh

// terrain bindings
@group(1) @binding(0)
var<storage> mesh: array<Mesh>;
@group(1) @binding(1)
var<uniform> config: TerrainConfig;
@group(1) @binding(2)
var<uniform> attachments: array<AttachmentConfig, 8u>;
@group(1) @binding(3)
var atlas_sampler: sampler;
@group(1) @binding(4)
var attachment0_atlas: texture_2d_array<f32>;
@group(1) @binding(5)
var attachment1_atlas: texture_2d_array<f32>;
@group(1) @binding(6)
var attachment2_atlas: texture_2d_array<f32>;
@group(1) @binding(7)
var attachment3_atlas: texture_2d_array<f32>;
@group(1) @binding(8)
var attachment4_atlas: texture_2d_array<f32>;
@group(1) @binding(9)
var attachment5_atlas: texture_2d_array<f32>;
@group(1) @binding(10)
var attachment6_atlas: texture_2d_array<f32>;
@group(1) @binding(11)
var attachment7_atlas: texture_2d_array<f32>;

// terrain view bindings
@group(2) @binding(0)
var<uniform> view_config: TerrainViewConfig;
@group(2) @binding(1)
var<uniform> terrain_model_approximation: TerrainModelApproximation;
@group(2) @binding(2)
var<storage> tile_tree: array<TileTreeEntry>;
@group(2) @binding(3)
var<storage> origins: array<vec2<u32>>;
@group(2) @binding(4)
var<storage> geometry_tiles: array<TileCoordinate>;

// refine geometry_tiles bindings
@group(2) @binding(4)
var<storage, read_write> final_tiles: array<TileCoordinate>;
@group(2) @binding(5)
var<storage, read_write> temporary_tiles: array<TileCoordinate>;
@group(2) @binding(6)
var<storage, read_write> parameters: Parameters;

@group(3) @binding(0)
var<storage, read_write> indirect_buffer: IndirectBuffer;

// culling bindings
@group(0) @binding(0)
var<uniform> culling_view: CullingData;

================================================
FILE: src/shaders/debug.wgsl
================================================
#define_import_path bevy_terrain::debug

#import bevy_terrain::types::{Coordinate, AtlasTile, Blend}
#import bevy_terrain::bindings::{config, tile_tree, view_config, geometry_tiles, attachments, origins, terrain_model_approximation}
#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}
#import bevy_pbr::mesh_view_bindings::view

fn index_color(index: u32) -> vec4<f32> {
    var COLOR_ARRAY = array(
        vec4(1.0, 0.0, 0.0, 1.0),
        vec4(0.0, 1.0, 0.0, 1.0),
        vec4(0.0, 0.0, 1.0, 1.0),
        vec4(1.0, 1.0, 0.0, 1.0),
        vec4(1.0, 0.0, 1.0, 1.0),
        vec4(0.0, 1.0, 1.0, 1.0),
    );

    return mix(COLOR_ARRAY[index % 6u], vec4<f32>(0.6), 0.2);
}

fn tile_tree_outlines(uv: vec2<f32>) -> f32 {
    let thickness = 0.015;

    return 1.0 - inside_square(uv, vec2<f32>(thickness), 1.0 - 2.0 * thickness);
}

fn checker_color(coordinate: Coordinate, ratio: f32) -> vec4<f32> {
    var color        = index_color(coordinate.lod);
    var parent_color = index_color(coordinate.lod - 1);
    color            = select(color,        mix(color,        vec4(0.0), 0.5), (coordinate.xy.x + coordinate.xy.y) % 2u == 0u);
    parent_color     = select(parent_color, mix(parent_color, vec4(0.0), 0.5), ((coordinate.xy.x >> 1) + (coordinate.xy.y >> 1)) % 2u == 0u);

    return mix(color, parent_color, ratio);
}

fn show_data_lod(blend: Blend, tile: AtlasTile) -> vec4<f32> {
#ifdef TILE_TREE_LOD
    let ratio = 0.0;
#else
    let ratio = select(0.0, blend.ratio, blend.lod == tile.coordinate.lod);
#endif

    var color = checker_color(tile.coordinate, ratio);

    if (ratio > 0.95 && blend.lod == tile.coordinate.lod) {
        color = mix(color, vec4<f32>(0.0), 0.8);
    }

#ifdef SPHERICAL
    color = mix(color, index_color(tile.coordinate.side), 0.3);
#endif

    return color;
}

fn show_geometry_lod(coordinate: Coordinate) -> vec4<f32> {
    let view_distance  = approximate_view_distance(coordinate, view.world_position);
    let target_lod     = log2(2.0 * view_config.morph_distance / view_distance);

#ifdef MORPH
    let ratio = select(inverse_mix(f32(coordinate.lod) + view_config.morph_range, f32(coordinate.lod), target_lod), 0.0, coordinate.lod == 0);
#else
    let ratio = 0.0;
#endif

    var color = checker_color(coordinate, ratio);

    if (distance(coordinate.uv, compute_subdivision_coordinate(coordinate).uv) < 0.1) {
        color = mix(index_color(coordinate.lod + 1), vec4(0.0), 0.7);
    }

    if (fract(target_lod) < 0.01 && target_lod >= 1.0) {
        color = mix(color, vec4<f32>(0.0), 0.8);
    }

#ifdef SPHERICAL
    color = mix(color, index_color(coordinate.side), 0.3);
#endif

    if (max(0.0, target_lod) < f32(coordinate.lod) - 1.0 + view_config.morph_range) {
        // The view_distance and morph range are not sufficient.
        // The same tile overlapps two morph zones.
        // -> increase morph distance
        color = vec4<f32>(1.0, 0.0, 0.0, 1.0);
    }
    if (floor(target_lod) > f32(coordinate.lod)) {
        // The view_distance and morph range are not sufficient.
        // The tile does have an insuffient LOD.
        // -> increase morph tolerance
        color = vec4<f32>(0.0, 1.0, 0.0, 1.0);
    }

    return color;
}
fn show_tile_tree(coordinate: Coordinate) -> vec4<f32> {
    let view_distance  = approximate_view_distance(coordinate, view.world_position);
    let target_lod     = log2(view_config.load_distance / view_distance);

    let best_lookup = lookup_best(coordinate);

    var color = checker_color(best_lookup.tile.coordinate, 0.0);
    color     = mix(color, vec4<f32>(0.1), tile_tree_outlines(best_lookup.tile_tree_uv));

    if (fract(target_lod) < 0.01 && target_lod >= 1.0) {
        color = mix(index_color(u32(target_lod)), vec4<f
Download .txt
gitextract_d9qm2zah/

├── .gitattributes
├── .gitignore
├── Cargo.toml
├── LICENSE-APACHE
├── LICENSE-MIT
├── README.md
├── assets/
│   └── shaders/
│       ├── planar.wgsl
│       └── spherical.wgsl
├── docs/
│   ├── development.md
│   └── implementation.md
├── examples/
│   ├── minimal.rs
│   ├── planar.rs
│   ├── preprocess_planar.rs
│   ├── preprocess_spherical.rs
│   └── spherical.rs
└── src/
    ├── big_space.rs
    ├── debug/
    │   ├── camera.rs
    │   └── mod.rs
    ├── formats/
    │   ├── mod.rs
    │   └── tiff.rs
    ├── lib.rs
    ├── math/
    │   ├── coordinate.rs
    │   ├── ellipsoid.rs
    │   ├── mod.rs
    │   └── terrain_model.rs
    ├── plugin.rs
    ├── preprocess/
    │   ├── gpu_preprocessor.rs
    │   ├── mod.rs
    │   └── preprocessor.rs
    ├── render/
    │   ├── culling_bind_group.rs
    │   ├── mod.rs
    │   ├── terrain_bind_group.rs
    │   ├── terrain_material.rs
    │   ├── terrain_view_bind_group.rs
    │   └── tiling_prepass.rs
    ├── shaders/
    │   ├── attachments.wgsl
    │   ├── bindings.wgsl
    │   ├── debug.wgsl
    │   ├── functions.wgsl
    │   ├── mod.rs
    │   ├── preprocess/
    │   │   ├── downsample.wgsl
    │   │   ├── preprocessing.wgsl
    │   │   ├── split.wgsl
    │   │   └── stitch.wgsl
    │   ├── render/
    │   │   ├── fragment.wgsl
    │   │   └── vertex.wgsl
    │   ├── tiling_prepass/
    │   │   ├── prepare_prepass.wgsl
    │   │   └── refine_tiles.wgsl
    │   └── types.wgsl
    ├── terrain.rs
    ├── terrain_data/
    │   ├── gpu_tile_atlas.rs
    │   ├── gpu_tile_tree.rs
    │   ├── mod.rs
    │   ├── tile_atlas.rs
    │   └── tile_tree.rs
    ├── terrain_view.rs
    └── util.rs
Download .txt
SYMBOL INDEX (405 symbols across 32 files)

FILE: examples/minimal.rs
  constant PATH (line 5) | const PATH: &str = "terrains/planar";
  constant TERRAIN_SIZE (line 6) | const TERRAIN_SIZE: f64 = 1000.0;
  constant HEIGHT (line 7) | const HEIGHT: f32 = 250.0;
  constant TEXTURE_SIZE (line 8) | const TEXTURE_SIZE: u32 = 512;
  constant LOD_COUNT (line 9) | const LOD_COUNT: u32 = 4;
  function main (line 11) | fn main() {
  function setup (line 23) | fn setup(

FILE: examples/planar.rs
  constant PATH (line 5) | const PATH: &str = "terrains/planar";
  constant TERRAIN_SIZE (line 6) | const TERRAIN_SIZE: f64 = 2000.0;
  constant HEIGHT (line 7) | const HEIGHT: f32 = 500.0;
  constant TEXTURE_SIZE (line 8) | const TEXTURE_SIZE: u32 = 512;
  constant LOD_COUNT (line 9) | const LOD_COUNT: u32 = 8;
  type TerrainMaterial (line 12) | pub struct TerrainMaterial {
  method fragment_shader (line 19) | fn fragment_shader() -> ShaderRef {
  function main (line 24) | fn main() {
  function setup (line 36) | fn setup(

FILE: examples/preprocess_planar.rs
  constant PATH (line 4) | const PATH: &str = "terrains/planar";
  constant TEXTURE_SIZE (line 5) | const TEXTURE_SIZE: u32 = 512;
  constant LOD_COUNT (line 6) | const LOD_COUNT: u32 = 4;
  function main (line 8) | fn main() {
  function setup (line 15) | fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {

FILE: examples/preprocess_spherical.rs
  constant PATH (line 4) | const PATH: &str = "terrains/spherical";
  constant TEXTURE_SIZE (line 5) | const TEXTURE_SIZE: u32 = 512;
  constant LOD_COUNT (line 6) | const LOD_COUNT: u32 = 5;
  function main (line 8) | fn main() {
  function setup (line 19) | fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {

FILE: examples/spherical.rs
  constant PATH (line 4) | const PATH: &str = "terrains/spherical";
  constant RADIUS (line 5) | const RADIUS: f64 = 6371000.0;
  constant MAJOR_AXES (line 6) | const MAJOR_AXES: f64 = 6378137.0;
  constant MINOR_AXES (line 7) | const MINOR_AXES: f64 = 6356752.314245;
  constant MIN_HEIGHT (line 8) | const MIN_HEIGHT: f32 = -12000.0;
  constant MAX_HEIGHT (line 9) | const MAX_HEIGHT: f32 = 9000.0;
  constant TEXTURE_SIZE (line 10) | const TEXTURE_SIZE: u32 = 512;
  constant LOD_COUNT (line 11) | const LOD_COUNT: u32 = 16;
  type TerrainMaterial (line 14) | pub struct TerrainMaterial {
  method fragment_shader (line 21) | fn fragment_shader() -> ShaderRef {
  function main (line 26) | fn main() {
  function setup (line 39) | fn setup(

FILE: src/big_space.rs
  type GridPrecision (line 3) | pub type GridPrecision = i32;
  type BigSpacePlugin (line 5) | pub type BigSpacePlugin = big_space::BigSpacePlugin<GridPrecision>;
  type ReferenceFrame (line 6) | pub type ReferenceFrame = big_space::reference_frame::ReferenceFrame<Gri...
  type ReferenceFrames (line 7) | pub type ReferenceFrames<'w, 's> =
  type GridCell (line 9) | pub type GridCell = big_space::GridCell<GridPrecision>;
  type GridTransform (line 10) | pub type GridTransform = big_space::world_query::GridTransform<GridPreci...
  type GridTransformReadOnly (line 11) | pub type GridTransformReadOnly = big_space::world_query::GridTransformRe...
  type GridTransformOwned (line 12) | pub type GridTransformOwned = big_space::world_query::GridTransformOwned...
  type GridTransformItem (line 13) | pub type GridTransformItem<'w> = big_space::world_query::GridTransformIt...

FILE: src/debug/camera.rs
  type DebugCameraBundle (line 9) | pub struct DebugCameraBundle {
    method new (line 33) | pub fn new(position: DVec3, speed: f64, frame: &ReferenceFrame) -> Self {
    method new (line 56) | pub fn new(position: Vec3, speed: f64) -> Self {
  method default (line 19) | fn default() -> Self {
  type DebugCameraController (line 77) | pub struct DebugCameraController {
  method default (line 91) | fn default() -> Self {
  function camera_controller (line 105) | pub fn camera_controller(

FILE: src/debug/mod.rs
  type DebugTerrainMaterial (line 18) | pub struct DebugTerrainMaterial {}
  type TerrainDebugPlugin (line 23) | pub struct TerrainDebugPlugin;
  method build (line 26) | fn build(&self, app: &mut App) {
  type DebugTerrain (line 46) | pub struct DebugTerrain {
  method default (line 67) | fn default() -> Self {
  function extract_debug (line 90) | pub fn extract_debug(mut debug: ResMut<DebugTerrain>, extracted_debug: E...
  function toggle_debug (line 94) | pub fn toggle_debug(input: Res<ButtonInput<KeyCode>>, mut debug: ResMut<...
  function update_view_parameter (line 216) | pub fn update_view_parameter(
  function debug_lighting (line 262) | pub(crate) fn debug_lighting(mut commands: Commands) {
  function debug_window (line 277) | pub fn debug_window(mut window: Query<&mut Window, With<PrimaryWindow>>) {
  type LoadingImages (line 283) | pub struct LoadingImages(Vec<(AssetId<Image>, TextureDimension, TextureF...
    method load_image (line 286) | pub fn load_image(
  function finish_loading_images (line 297) | fn finish_loading_images(

FILE: src/formats/mod.rs
  type TC (line 9) | pub struct TC {
    method decode_alloc (line 14) | pub fn decode_alloc(encoded: &[u8]) -> Result<Self> {
    method encode_alloc (line 20) | pub fn encode_alloc(&self) -> Result<Vec<u8>> {
    method load_file (line 26) | pub fn load_file<P: AsRef<Path>>(path: P) -> Result<Self> {
    method save_file (line 31) | pub fn save_file<P: AsRef<Path>>(&self, path: P) -> Result<()> {

FILE: src/formats/tiff.rs
  type TiffLoader (line 15) | pub struct TiffLoader;
  type Asset (line 17) | type Asset = Image;
  type Settings (line 18) | type Settings = ();
  type Error (line 19) | type Error = TextureError;
  method load (line 20) | async fn load<'a>(
  method extensions (line 59) | fn extensions(&self) -> &[&str] {

FILE: src/math/coordinate.rs
  constant NEIGHBOURING_SIDES (line 9) | const NEIGHBOURING_SIDES: [[u32; 5]; 6] = [
  type SideInfo (line 19) | enum SideInfo {
    constant EVEN_LIST (line 27) | const EVEN_LIST: [[SideInfo; 2]; 6] = [
    constant ODD_LIST (line 35) | const ODD_LIST: [[SideInfo; 2]; 6] = [
    method project_to_side (line 44) | fn project_to_side(side: u32, other_side: u32) -> [SideInfo; 2] {
  type Coordinate (line 58) | pub struct Coordinate {
    method new (line 64) | pub fn new(side: u32, uv: DVec2) -> Self {
    method from_world_position (line 69) | pub(crate) fn from_world_position(world_position: DVec3, model: &Terra...
    method world_position (line 110) | pub(crate) fn world_position(self, model: &TerrainModel, height: f32) ...
    method project_to_side (line 134) | pub(crate) fn project_to_side(self, side: u32, model: &TerrainModel) -...
  type TileCoordinate (line 156) | pub struct TileCoordinate {
    constant INVALID (line 169) | pub const INVALID: TileCoordinate = TileCoordinate {
    method new (line 176) | pub fn new(side: u32, lod: u32, x: u32, y: u32) -> Self {
    method count (line 180) | pub fn count(lod: u32) -> u32 {
    method path (line 184) | pub fn path(self, path: &str, extension: &str) -> String {
    method parent (line 188) | pub fn parent(self) -> Self {
    method children (line 197) | pub fn children(self) -> impl Iterator<Item = Self> {
    method neighbours (line 208) | pub fn neighbours(self, spherical: bool) -> impl Iterator<Item = Self> {
    method neighbour_coordinate (line 227) | fn neighbour_coordinate(self, neighbour_position: IVec2, spherical: bo...
    method fmt (line 283) | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {

FILE: src/math/ellipsoid.rs
  constant MAX_ITERATIONS (line 10) | const MAX_ITERATIONS: usize = 1074;
  function project_point_ellipsoid (line 12) | pub fn project_point_ellipsoid(e: DVec3, y: DVec3) -> DVec3 {
  function project_point_ellipse (line 65) | fn project_point_ellipse(e: DVec2, y: DVec2) -> DVec2 {
  function get_root_3d (line 92) | fn get_root_3d(r: DVec3, z: DVec3, g: f64) -> f64 {
  function get_root_2d (line 118) | fn get_root_2d(r: DVec2, z: DVec2, g: f64) -> f64 {

FILE: src/math/mod.rs
  constant C_SQR (line 13) | const C_SQR: f64 = 0.87 * 0.87;

FILE: src/math/terrain_model.rs
  constant SIDE_MATRICES (line 14) | const SIDE_MATRICES: [DMat3; 6] = [
  type TerrainKind (line 24) | pub enum TerrainKind {
  type TerrainModel (line 41) | pub struct TerrainModel {
    method is_spherical (line 53) | pub(crate) fn is_spherical(&self) -> bool {
    method from_scale_rotation_translation (line 61) | fn from_scale_rotation_translation(
    method planar (line 84) | pub fn planar(position: DVec3, side_length: f64, min_height: f32, max_...
    method sphere (line 95) | pub fn sphere(position: DVec3, radius: f64, min_height: f32, max_heigh...
    method ellipsoid (line 106) | pub fn ellipsoid(
    method position_local_to_world (line 130) | pub(crate) fn position_local_to_world(&self, local_position: DVec3, he...
    method position_world_to_local (line 144) | pub(crate) fn position_world_to_local(&self, world_position: DVec3) ->...
    method surface_position (line 171) | pub(crate) fn surface_position(&self, world_position: DVec3, height: f...
    method side_count (line 175) | pub(crate) fn side_count(&self) -> u32 {
    method scale (line 183) | pub(crate) fn scale(&self) -> f64 {
    method transform (line 196) | pub(crate) fn transform(&self) -> Transform {
    method grid_transform (line 205) | pub(crate) fn grid_transform(
  type SideParameter (line 228) | pub(crate) struct SideParameter {
  type TerrainModelApproximation (line 252) | pub struct TerrainModelApproximation {
    method compute (line 263) | pub(crate) fn compute(
  function generate_terrain_model_approximation (line 363) | pub fn generate_terrain_model_approximation(

FILE: src/plugin.rs
  type TerrainPlugin (line 32) | pub struct TerrainPlugin;
  method build (line 35) | fn build(&self, app: &mut App) {
  method finish (line 97) | fn finish(&self, app: &mut App) {

FILE: src/preprocess/gpu_preprocessor.rs
  type ProcessingTask (line 25) | pub(crate) struct ProcessingTask {
  type SplitData (line 31) | pub(crate) struct SplitData {
  type StitchData (line 39) | struct StitchData {
  type DownsampleData (line 46) | struct DownsampleData {
  function create_split_layout (line 52) | pub(crate) fn create_split_layout(device: &RenderDevice) -> BindGroupLay...
  function create_stitch_layout (line 66) | pub(crate) fn create_stitch_layout(device: &RenderDevice) -> BindGroupLa...
  function create_downsample_layout (line 73) | pub(crate) fn create_downsample_layout(device: &RenderDevice) -> BindGro...
  type GpuPreprocessor (line 83) | pub(crate) struct GpuPreprocessor {
    method new (line 89) | pub(crate) fn new() -> Self {
    method initialize (line 96) | pub(crate) fn initialize(
    method extract (line 105) | pub(crate) fn extract(
    method prepare (line 120) | pub(crate) fn prepare(

FILE: src/preprocess/mod.rs
  type TerrainPreprocessLabel (line 28) | pub struct TerrainPreprocessLabel;
  type TerrainPreprocessItem (line 41) | pub(crate) struct TerrainPreprocessItem {
    method pipelines (line 48) | fn pipelines<'a>(
    method is_loaded (line 59) | pub(crate) fn is_loaded(&self, pipeline_cache: &PipelineCache) -> bool {
  type TerrainPreprocessPipelines (line 65) | pub struct TerrainPreprocessPipelines {
  method from_world (line 76) | fn from_world(world: &mut World) -> Self {
  type Key (line 102) | type Key = TerrainPreprocessPipelineKey;
  method specialize (line 104) | fn specialize(&self, key: Self::Key) -> ComputePipelineDescriptor {
  type TerrainPreprocessNode (line 141) | pub struct TerrainPreprocessNode;
    method run (line 144) | fn run<'w>(
  function queue_terrain_preprocess (line 220) | pub(crate) fn queue_terrain_preprocess(
  type TerrainPreprocessPlugin (line 255) | pub struct TerrainPreprocessPlugin;
  method build (line 258) | fn build(&self, app: &mut App) {
  method finish (line 283) | fn finish(&self, app: &mut App) {

FILE: src/preprocess/preprocessor.rs
  function reset_directory (line 18) | pub fn reset_directory(directory: &str) {
  type LoadingTile (line 24) | pub(crate) struct LoadingTile {
  type SphericalDataset (line 29) | pub struct SphericalDataset {
  type PreprocessDataset (line 35) | pub struct PreprocessDataset {
    method overlapping_tiles (line 58) | fn overlapping_tiles(&self, lod: u32) -> impl Iterator<Item = TileCoor...
  method default (line 45) | fn default() -> Self {
  type PreprocessTaskType (line 70) | pub(crate) enum PreprocessTaskType {
  type PreprocessTask (line 89) | pub(crate) struct PreprocessTask {
    method is_ready (line 95) | fn is_ready(&self, asset_server: &AssetServer, tile_atlas: &TileAtlas)...
    method debug (line 110) | fn debug(&self) {
    method barrier (line 128) | fn barrier() -> Self {
    method save (line 135) | fn save(
    method split (line 150) | fn split(
    method stitch (line 170) | fn stitch(
    method downsample (line 191) | fn downsample(
  type Preprocessor (line 214) | pub struct Preprocessor {
    method new (line 224) | pub fn new() -> Self {
    method split_and_downsample (line 234) | fn split_and_downsample(
    method stitch_and_save_layer (line 271) | fn stitch_and_save_layer(
    method clear_attachment (line 290) | pub fn clear_attachment(self, attachment_index: u32, tile_atlas: &mut ...
    method preprocess_tile (line 298) | pub fn preprocess_tile(
    method preprocess_spherical (line 314) | pub fn preprocess_spherical(
  function select_ready_tasks (line 346) | pub(crate) fn select_ready_tasks(
  function preprocessor_load_tile (line 401) | pub(crate) fn preprocessor_load_tile(

FILE: src/render/culling_bind_group.rs
  function create_culling_layout (line 15) | pub(crate) fn create_culling_layout(device: &RenderDevice) -> BindGroupL...
  function planes (line 25) | pub fn planes(view_projection: &Mat4) -> [Vec4; 5] {
  type CullingUniform (line 41) | pub struct CullingUniform {
    method from (line 48) | fn from(view: &ExtractedView) -> Self {
  type CullingBindGroup (line 58) | pub struct CullingBindGroup(BindGroup);
    method new (line 70) | fn new(device: &RenderDevice, culling_uniform: CullingUniform) -> Self {
    method prepare (line 87) | pub(crate) fn prepare(
  type Target (line 61) | type Target = BindGroup;
  method deref (line 64) | fn deref(&self) -> &Self::Target {

FILE: src/render/terrain_bind_group.rs
  function create_terrain_layout (line 23) | pub(crate) fn create_terrain_layout(device: &RenderDevice) -> BindGroupL...
  type AttachmentConfig (line 47) | struct AttachmentConfig {
  type AttachmentUniform (line 55) | struct AttachmentUniform {
    method new (line 60) | fn new(tile_atlas: &GpuTileAtlas) -> Self {
  type TerrainConfigUniform (line 77) | struct TerrainConfigUniform {
    method from_tile_atlas (line 85) | fn from_tile_atlas(tile_atlas: &TileAtlas) -> Self {
  type TerrainData (line 95) | pub struct TerrainData {
    method new (line 101) | fn new(
    method initialize (line 168) | pub(crate) fn initialize(
    method extract (line 185) | pub(crate) fn extract(
    method prepare (line 207) | pub(crate) fn prepare(
  type SetTerrainBindGroup (line 217) | pub struct SetTerrainBindGroup<const I: usize>;
  type Param (line 220) | type Param = SRes<TerrainComponents<TerrainData>>;
  type ViewQuery (line 221) | type ViewQuery = ();
  type ItemQuery (line 222) | type ItemQuery = ();
  function render (line 225) | fn render<'w>(

FILE: src/render/terrain_material.rs
  type TerrainPipelineKey (line 35) | pub struct TerrainPipelineKey<M: Material> {
  method eq (line 46) | fn eq(&self, other: &Self) -> bool {
  method clone (line 55) | fn clone(&self) -> Self {
  method hash (line 67) | fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
  constant MSAA_MASK_BITS (line 100) | const MSAA_MASK_BITS: u32 = 0b111111;
  constant MSAA_SHIFT_BITS (line 101) | const MSAA_SHIFT_BITS: u32 = 32 - 6;
  method from_msaa_samples (line 103) | pub fn from_msaa_samples(msaa_samples: u32) -> Self {
  method from_debug (line 108) | pub fn from_debug(debug: &DebugTerrain) -> Self {
  method msaa_samples (line 163) | pub fn msaa_samples(&self) -> u32 {
  method polygon_mode (line 167) | pub fn polygon_mode(&self) -> PolygonMode {
  method shader_defs (line 174) | pub fn shader_defs(&self) -> Vec<ShaderDefVal> {
  type TerrainRenderPipeline (line 232) | pub struct TerrainRenderPipeline<M: Material> {
  method from_world (line 244) | fn from_world(world: &mut World) -> Self {
  type Key (line 288) | type Key = TerrainPipelineKey<M>;
  method specialize (line 290) | fn specialize(&self, key: Self::Key) -> RenderPipelineDescriptor {
  type DrawTerrain (line 365) | pub(crate) type DrawTerrain<M> = (
  function queue_terrain (line 376) | pub(crate) fn queue_terrain<M: Material>(
  type TerrainMaterialPlugin (line 437) | pub struct TerrainMaterialPlugin<M: Material>(PhantomData<M>);
  method default (line 440) | fn default() -> Self {
  method build (line 449) | fn build(&self, app: &mut App) {
  method finish (line 465) | fn finish(&self, app: &mut App) {

FILE: src/render/terrain_view_bind_group.rs
  function create_prepare_indirect_layout (line 21) | pub(crate) fn create_prepare_indirect_layout(device: &RenderDevice) -> B...
  function create_refine_tiles_layout (line 31) | pub(crate) fn create_refine_tiles_layout(device: &RenderDevice) -> BindG...
  function create_terrain_view_layout (line 49) | pub(crate) fn create_terrain_view_layout(device: &RenderDevice) -> BindG...
  type Indirect (line 66) | pub(crate) struct Indirect {
  type Parameters (line 74) | struct Parameters {
  type TerrainViewConfigUniform (line 82) | struct TerrainViewConfigUniform {
    method from_tile_tree (line 99) | fn from_tile_tree(tile_tree: &TileTree) -> Self {
  type TerrainViewData (line 118) | pub struct TerrainViewData {
    method new (line 128) | fn new(device: &RenderDevice, tile_tree: &TileTree, gpu_tile_tree: &Gp...
    method refinement_count (line 189) | pub(super) fn refinement_count(&self) -> u32 {
    method initialize (line 193) | pub(crate) fn initialize(
    method extract (line 213) | pub(crate) fn extract(
    method prepare (line 238) | pub(crate) fn prepare(
  type SetTerrainViewBindGroup (line 249) | pub struct SetTerrainViewBindGroup<const I: usize>;
  type Param (line 252) | type Param = SRes<TerrainViewComponents<TerrainViewData>>;
  type ViewQuery (line 253) | type ViewQuery = Entity;
  type ItemQuery (line 254) | type ItemQuery = ();
  function render (line 257) | fn render<'w>(
  type DrawTerrainCommand (line 274) | pub(crate) struct DrawTerrainCommand;
    type Param (line 277) | type Param = SRes<TerrainViewComponents<TerrainViewData>>;
    type ViewQuery (line 278) | type ViewQuery = Entity;
    type ItemQuery (line 279) | type ItemQuery = ();
    method render (line 282) | fn render<'w>(

FILE: src/render/tiling_prepass.rs
  type TilingPrepassLabel (line 26) | pub struct TilingPrepassLabel;
  method from_debug (line 45) | pub fn from_debug(debug: &DebugTerrain) -> Self {
  method shader_defs (line 61) | pub fn shader_defs(&self) -> Vec<ShaderDefVal> {
  type TilingPrepassItem (line 81) | pub(crate) struct TilingPrepassItem {
    method pipelines (line 89) | fn pipelines<'a>(
  type TilingPrepassPipelines (line 108) | pub struct TilingPrepassPipelines {
  method from_world (line 118) | fn from_world(world: &mut World) -> Self {
  type Key (line 142) | type Key = TilingPrepassPipelineKey;
  method specialize (line 144) | fn specialize(&self, key: Self::Key) -> ComputePipelineDescriptor {
  type TilingPrepassNode (line 202) | pub struct TilingPrepassNode;
    method run (line 205) | fn run<'w>(
  function queue_tiling_prepass (line 274) | pub(crate) fn queue_tiling_prepass(

FILE: src/shaders/mod.rs
  constant DEFAULT_VERTEX_SHADER (line 4) | pub const DEFAULT_VERTEX_SHADER: &str = "embedded://bevy_terrain/shaders...
  constant DEFAULT_FRAGMENT_SHADER (line 5) | pub const DEFAULT_FRAGMENT_SHADER: &str = "embedded://bevy_terrain/shade...
  constant PREPARE_PREPASS_SHADER (line 6) | pub const PREPARE_PREPASS_SHADER: &str =
  constant REFINE_TILES_SHADER (line 8) | pub const REFINE_TILES_SHADER: &str =
  constant SPLIT_SHADER (line 10) | pub(crate) const SPLIT_SHADER: &str = "embedded://bevy_terrain/shaders/p...
  constant STITCH_SHADER (line 11) | pub(crate) const STITCH_SHADER: &str = "embedded://bevy_terrain/shaders/...
  constant DOWNSAMPLE_SHADER (line 12) | pub(crate) const DOWNSAMPLE_SHADER: &str =
  type InternalShaders (line 16) | pub(crate) struct InternalShaders(Vec<Handle<Shader>>);
    method load (line 19) | pub(crate) fn load(app: &mut App, shaders: &[&'static str]) {
  function load_terrain_shaders (line 30) | pub(crate) fn load_terrain_shaders(app: &mut App) {
  function load_preprocess_shaders (line 55) | pub(crate) fn load_preprocess_shaders(app: &mut App) {

FILE: src/terrain.rs
  type TerrainComponents (line 15) | pub struct TerrainComponents<C>(EntityHashMap<C>);
  method default (line 18) | fn default() -> Self {
  type TerrainConfig (line 27) | pub struct TerrainConfig {
    method add_attachment (line 52) | pub fn add_attachment(mut self, attachment_config: AttachmentConfig) -...
  method default (line 40) | fn default() -> Self {
  type TerrainBundle (line 62) | pub struct TerrainBundle {
    method new (line 75) | pub fn new(

FILE: src/terrain_data/gpu_tile_atlas.rs
  constant COPY_BYTES_PER_ROW_ALIGNMENT (line 23) | const COPY_BYTES_PER_ROW_ALIGNMENT: u32 = 256;
  function align_byte_size (line 25) | fn align_byte_size(value: u32) -> u32 {
  function create_attachment_layout (line 30) | pub(crate) fn create_attachment_layout(device: &RenderDevice) -> BindGro...
  type AttachmentMeta (line 46) | pub(crate) struct AttachmentMeta {
  type AtlasBufferInfo (line 58) | pub(crate) struct AtlasBufferInfo {
    method new (line 80) | fn new(attachment: &AtlasAttachment, lod_count: u32) -> Self {
    method image_copy_texture (line 125) | fn image_copy_texture<'a>(
    method image_copy_buffer (line 142) | fn image_copy_buffer<'a>(&'a self, buffer: &'a Buffer, index: u32) -> ...
    method image_copy_size (line 153) | fn image_copy_size(&self, mip_level: u32) -> Extent3d {
    method buffer_size (line 161) | fn buffer_size(&self, slots: u32) -> u32 {
    method attachment_meta (line 165) | fn attachment_meta(&self) -> AttachmentMeta {
  type GpuAtlasAttachment (line 179) | pub(crate) struct GpuAtlasAttachment {
    method new (line 195) | pub(crate) fn new(
    method reserve_write_slot (line 276) | pub(crate) fn reserve_write_slot(&mut self, tile: AtlasTileAttachment)...
    method copy_tiles_to_write_section (line 285) | pub(crate) fn copy_tiles_to_write_section(&self, command_encoder: &mut...
    method copy_tiles_from_write_section (line 297) | pub(crate) fn copy_tiles_from_write_section(&self, command_encoder: &m...
    method upload_tiles (line 309) | fn upload_tiles(&mut self, queue: &RenderQueue) {
    method download_tiles (line 338) | pub(crate) fn download_tiles(&self, command_encoder: &mut CommandEncod...
    method create_download_buffers (line 349) | fn create_download_buffers(&mut self, device: &RenderDevice) {
    method start_downloading_tiles (line 362) | fn start_downloading_tiles(&mut self) {
  type GpuTileAtlas (line 420) | pub struct GpuTileAtlas {
    method new (line 428) | fn new(device: &RenderDevice, tile_atlas: &TileAtlas) -> Self {
    method initialize (line 442) | pub(crate) fn initialize(
    method extract (line 454) | pub(crate) fn extract(
    method prepare (line 480) | pub(crate) fn prepare(
    method cleanup (line 493) | pub(crate) fn cleanup(mut gpu_tile_atlases: ResMut<TerrainComponents<G...

FILE: src/terrain_data/gpu_tile_tree.rs
  type GpuTileTree (line 23) | pub struct GpuTileTree {
    method new (line 32) | fn new(device: &RenderDevice, tile_tree: &TileTree) -> Self {
    method initialize (line 56) | pub(crate) fn initialize(
    method extract (line 71) | pub(crate) fn extract(
    method prepare (line 84) | pub(crate) fn prepare(

FILE: src/terrain_data/mod.rs
  constant INVALID_ATLAS_INDEX (line 33) | pub const INVALID_ATLAS_INDEX: u32 = u32::MAX;
  constant INVALID_LOD (line 34) | pub const INVALID_LOD: u32 = u32::MAX;
  type AttachmentFormat (line 38) | pub enum AttachmentFormat {
    method id (line 50) | pub(crate) fn id(self) -> u32 {
    method render_format (line 58) | pub(crate) fn render_format(self) -> TextureFormat {
    method processing_format (line 67) | pub(crate) fn processing_format(self) -> TextureFormat {
    method pixel_size (line 76) | pub(crate) fn pixel_size(self) -> u32 {
  type AttachmentConfig (line 88) | pub struct AttachmentConfig {
  method default (line 100) | fn default() -> Self {
  type AttachmentData (line 112) | pub(crate) enum AttachmentData {
    method from_bytes (line 125) | pub(crate) fn from_bytes(data: &[u8], format: AttachmentFormat) -> Self {
    method bytes (line 134) | pub(crate) fn bytes(&self) -> &[u8] {
    method generate_mipmaps (line 143) | pub(crate) fn generate_mipmaps(&mut self, texture_size: u32, mip_level...
    method sample (line 221) | pub(crate) fn sample(&self, uv: Vec2, size: u32) -> Vec4 {
  function sample_attachment (line 267) | pub fn sample_attachment(
  function sample_height (line 297) | pub fn sample_height(

FILE: src/terrain_data/tile_atlas.rs
  type Rgb8Image (line 23) | pub type Rgb8Image = ImageBuffer<Rgb<u8>, Vec<u8>>;
  type Rgba8Image (line 24) | pub type Rgba8Image = ImageBuffer<Rgba<u8>, Vec<u8>>;
  type R16Image (line 25) | pub type R16Image = ImageBuffer<Luma<u16>, Vec<u16>>;
  type Rg16Image (line 26) | pub type Rg16Image = ImageBuffer<LumaA<u16>, Vec<u16>>;
  constant STORE_PNG (line 28) | const STORE_PNG: bool = false;
  type AtlasTile (line 31) | pub struct AtlasTile {
    method new (line 38) | pub fn new(tile_coordinate: TileCoordinate, atlas_index: u32) -> Self {
    method attachment (line 44) | pub fn attachment(self, attachment_index: u32) -> AtlasTileAttachment {
    method from (line 54) | fn from(tile: AtlasTileAttachment) -> Self {
  type AtlasTileAttachment (line 63) | pub struct AtlasTileAttachment {
  type AtlasTileAttachmentWithData (line 70) | pub(crate) struct AtlasTileAttachmentWithData {
    method start_saving (line 77) | pub(crate) fn start_saving(self, path: String) -> Task<AtlasTileAttach...
    method start_loading (line 118) | pub(crate) fn start_loading(
  type AtlasAttachment (line 153) | pub struct AtlasAttachment {
    method new (line 172) | fn new(config: &AttachmentConfig, tile_atlas_size: u32, path: &str) ->...
    method update (line 195) | fn update(&mut self, atlas_state: &mut TileAtlasState) {
    method load (line 226) | fn load(&mut self, tile: AtlasTileAttachment) {
    method save (line 238) | fn save(&mut self, tile: AtlasTileAttachment) {
    method sample (line 249) | fn sample(&self, lookup: TileLookup) -> Vec4 {
  type LoadingState (line 265) | enum LoadingState {
  type TileState (line 273) | struct TileState {
  type TileAtlasState (line 282) | pub(crate) struct TileAtlasState {
    method new (line 302) | fn new(
    method update (line 327) | fn update(&mut self, attachments: &mut [AtlasAttachment]) {
    method loaded_tile_attachment (line 347) | fn loaded_tile_attachment(&mut self, tile: AtlasTileAttachment) {
    method saved_tile_attachment (line 361) | fn saved_tile_attachment(&mut self, _tile: AtlasTileAttachment) {
    method downloaded_tile_attachment (line 365) | fn downloaded_tile_attachment(&mut self, _tile: AtlasTileAttachment) {
    method get_tile (line 369) | fn get_tile(&mut self, tile_coordinate: TileCoordinate) -> AtlasTile {
    method allocate_tile (line 383) | fn allocate_tile(&mut self) -> u32 {
    method get_or_allocate_tile (line 391) | fn get_or_allocate_tile(&mut self, tile_coordinate: TileCoordinate) ->...
    method request_tile (line 418) | fn request_tile(&mut self, tile_coordinate: TileCoordinate) {
    method release_tile (line 459) | fn release_tile(&mut self, tile_coordinate: TileCoordinate) {
    method get_best_tile (line 477) | fn get_best_tile(&self, tile_coordinate: TileCoordinate) -> TileTreeEn...
  type TileAtlas (line 519) | pub struct TileAtlas {
    method new (line 531) | pub fn new(config: &TerrainConfig) -> Self {
    method get_tile (line 553) | pub fn get_tile(&mut self, tile_coordinate: TileCoordinate) -> AtlasTi...
    method get_or_allocate_tile (line 557) | pub fn get_or_allocate_tile(&mut self, tile_coordinate: TileCoordinate...
    method save (line 561) | pub fn save(&mut self, tile: AtlasTileAttachment) {
    method get_best_tile (line 565) | pub(super) fn get_best_tile(&self, tile_coordinate: TileCoordinate) ->...
    method sample_attachment (line 569) | pub(super) fn sample_attachment(&self, tile_lookup: TileLookup, attach...
    method update (line 574) | pub(crate) fn update(
    method save_tile_config (line 605) | pub(crate) fn save_tile_config(&self) {
    method load_tile_config (line 616) | pub(crate) fn load_tile_config(path: &str) -> HashSet<TileCoordinate> {

FILE: src/terrain_data/tile_tree.rs
  type RequestState (line 20) | enum RequestState {
  type TileState (line 28) | struct TileState {
  method default (line 36) | fn default() -> Self {
  type TileTreeEntry (line 51) | pub(super) struct TileTreeEntry {
  method default (line 59) | fn default() -> Self {
  type TileLookup (line 69) | pub(super) struct TileLookup {
    constant INVALID (line 76) | pub(super) const INVALID: Self = Self {
  type TileTree (line 104) | pub struct TileTree {
    method new (line 135) | pub fn new(tile_atlas: &TileAtlas, view_config: &TerrainViewConfig) ->...
    method compute_tree_xy (line 175) | fn compute_tree_xy(coordinate: Coordinate, tile_count: f64) -> DVec2 {
    method compute_origin (line 180) | fn compute_origin(&self, coordinate: Coordinate, lod: u32) -> UVec2 {
    method compute_tile_distance (line 193) | fn compute_tile_distance(
    method compute_blend (line 223) | pub(super) fn compute_blend(&self, sample_world_position: DVec3) -> (u...
    method lookup_tile (line 239) | pub(super) fn lookup_tile(
    method update (line 268) | fn update(&mut self, view_position: DVec3, tile_atlas: &TileAtlas) {
    method compute_requests (line 337) | pub(crate) fn compute_requests(
    method adjust_to_tile_atlas (line 363) | pub(crate) fn adjust_to_tile_atlas(
    method approximate_height (line 376) | pub(crate) fn approximate_height(

FILE: src/terrain_view.rs
  type TerrainViewComponents (line 7) | pub struct TerrainViewComponents<C>(HashMap<(Entity, Entity), C>);
  method default (line 10) | fn default() -> Self {
  type TerrainViewConfig (line 19) | pub struct TerrainViewConfig {
  method default (line 48) | fn default() -> Self {

FILE: src/util.rs
  function inverse_mix (line 8) | pub(crate) fn inverse_mix(a: f32, b: f32, value: f32) -> f32 {
  type CollectArray (line 12) | pub trait CollectArray: Iterator {
    method collect_array (line 13) | fn collect_array<const T: usize>(self) -> [Self::Item; T]
  type Scratch (line 23) | enum Scratch {
    method new (line 30) | fn new(usage: BufferUsages) -> Self {
    method write (line 40) | fn write<T: ShaderType + WriteInto>(&mut self, value: &T) {
    method contents (line 48) | fn contents(&self) -> &[u8] {
  type StaticBuffer (line 57) | pub struct StaticBuffer<T> {
  function empty_sized (line 65) | pub fn empty_sized<'a>(
  function update_bytes (line 86) | pub fn update_bytes(&self, queue: &RenderQueue, bytes: &[u8]) {
  function empty (line 92) | pub fn empty<'a>(
  function create (line 114) | pub fn create<'a>(
  function value (line 137) | pub fn value(&self) -> &T {
  function set_value (line 141) | pub fn set_value(&mut self, value: T) {
  function update (line 145) | pub fn update(&mut self, queue: &RenderQueue) {
  type Target (line 155) | type Target = Buffer;
  method deref (line 158) | fn deref(&self) -> &Self::Target {
  function into_binding (line 165) | fn into_binding(self) -> BindingResource<'a> {
Condensed preview — 57 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (320K chars).
[
  {
    "path": ".gitattributes",
    "chars": 42,
    "preview": "*.tif filter=lfs diff=lfs merge=lfs -text\n"
  },
  {
    "path": ".gitignore",
    "chars": 109,
    "preview": ".idea\nCargo.lock\ntarget\nassets/**/data/*\nassets/**/config.tc\nassets/terrains/spherical/source/height/200m.tif"
  },
  {
    "path": "Cargo.toml",
    "chars": 2107,
    "preview": "[package]\nname = \"bevy_terrain\"\ndescription = \"Terrain Rendering for the Bevy Engine.\"\nversion = \"0.1.0-dev\"\nlicense = \""
  },
  {
    "path": "LICENSE-APACHE",
    "chars": 10173,
    "preview": "                                 Apache License\n                           Version 2.0, January 2004\n                   "
  },
  {
    "path": "LICENSE-MIT",
    "chars": 1093,
    "preview": "MIT License\n\nCopyright (c) 2023 Kurt Kühnert\nCopyright (c) 2023 Argeo\n\nPermission is hereby granted, free of charge, to "
  },
  {
    "path": "README.md",
    "chars": 4250,
    "preview": "# Bevy Terrain\n\n![GitHub](https://img.shields.io/github/license/Ku95/bevy_terrain)\n![Crates.io](https://img.shields.io/c"
  },
  {
    "path": "assets/shaders/planar.wgsl",
    "chars": 1423,
    "preview": "#import bevy_terrain::types::AtlasTile\n#import bevy_terrain::attachments::{sample_attachment0 as sample_height, sample_n"
  },
  {
    "path": "assets/shaders/spherical.wgsl",
    "chars": 1680,
    "preview": "#import bevy_terrain::types::{AtlasTile}\n#import bevy_terrain::bindings::config\n#import bevy_terrain::attachments::{samp"
  },
  {
    "path": "docs/development.md",
    "chars": 3753,
    "preview": "# Development Status Bevy Terrain\n\nThis document assesses the current status of the `bevy_terrain` plugin.\nI built this "
  },
  {
    "path": "docs/implementation.md",
    "chars": 10481,
    "preview": "# Implementation Overview Bevy Terrain\n\nThis document serves as a general overview of the implementation of the `bevy_te"
  },
  {
    "path": "examples/minimal.rs",
    "chars": 2052,
    "preview": "use bevy::math::DVec3;\nuse bevy::prelude::*;\nuse bevy_terrain::prelude::*;\n\nconst PATH: &str = \"terrains/planar\";\nconst "
  },
  {
    "path": "examples/planar.rs",
    "chars": 2762,
    "preview": "use bevy::math::DVec3;\nuse bevy::{prelude::*, reflect::TypePath, render::render_resource::*};\nuse bevy_terrain::prelude:"
  },
  {
    "path": "examples/preprocess_planar.rs",
    "chars": 1751,
    "preview": "use bevy::prelude::*;\nuse bevy_terrain::prelude::*;\n\nconst PATH: &str = \"terrains/planar\";\nconst TEXTURE_SIZE: u32 = 512"
  },
  {
    "path": "examples/preprocess_spherical.rs",
    "chars": 1370,
    "preview": "use bevy::prelude::*;\nuse bevy_terrain::prelude::*;\n\nconst PATH: &str = \"terrains/spherical\";\nconst TEXTURE_SIZE: u32 = "
  },
  {
    "path": "examples/spherical.rs",
    "chars": 3786,
    "preview": "use bevy::{math::DVec3, prelude::*, reflect::TypePath, render::render_resource::*};\nuse bevy_terrain::prelude::*;\n\nconst"
  },
  {
    "path": "src/big_space.rs",
    "chars": 775,
    "preview": "pub use big_space::{BigSpaceCommands, FloatingOrigin};\n\npub type GridPrecision = i32;\n\npub type BigSpacePlugin = big_spa"
  },
  {
    "path": "src/debug/camera.rs",
    "chars": 6700,
    "preview": "#[cfg(feature = \"high_precision\")]\nuse crate::big_space::{\n    FloatingOrigin, GridCell, GridTransform, GridTransformIte"
  },
  {
    "path": "src/debug/mod.rs",
    "chars": 9442,
    "preview": "//! Contains a debug resource and systems controlling it to visualize different internal\n//! data of the plugin.\nuse cra"
  },
  {
    "path": "src/formats/mod.rs",
    "chars": 928,
    "preview": "pub mod tiff;\n\nuse crate::math::TileCoordinate;\nuse anyhow::Result;\nuse bincode::{config, Decode, Encode};\nuse std::{fs,"
  },
  {
    "path": "src/formats/tiff.rs",
    "chars": 2061,
    "preview": "use bevy::{\n    asset::{io::Reader, AssetLoader, AsyncReadExt, LoadContext},\n    prelude::*,\n    render::{\n        rende"
  },
  {
    "path": "src/lib.rs",
    "chars": 4011,
    "preview": "//! This crate provides the ability to render beautiful height-field terrains of any size.\n//! This is achieved in exten"
  },
  {
    "path": "src/math/coordinate.rs",
    "chars": 9212,
    "preview": "use crate::math::{TerrainModel, C_SQR};\nuse bevy::{\n    math::{DVec2, DVec3, IVec2},\n    render::render_resource::Shader"
  },
  {
    "path": "src/math/ellipsoid.rs",
    "chars": 3828,
    "preview": "use bevy::math::{DVec2, DVec3, Vec3Swizzles};\nuse std::cmp::Ordering;\n\n// Adapted from https://www.geometrictools.com/Do"
  },
  {
    "path": "src/math/mod.rs",
    "chars": 387,
    "preview": "mod coordinate;\nmod ellipsoid;\nmod terrain_model;\n\npub use crate::math::{\n    coordinate::{Coordinate, TileCoordinate},\n"
  },
  {
    "path": "src/math/terrain_model.rs",
    "chars": 14384,
    "preview": "use crate::{\n    math::{coordinate::Coordinate, ellipsoid::project_point_ellipsoid, TileCoordinate, C_SQR},\n    terrain_"
  },
  {
    "path": "src/plugin.rs",
    "chars": 4199,
    "preview": "use crate::{\n    math::{generate_terrain_model_approximation, TerrainModelApproximation},\n    render::{\n        culling_"
  },
  {
    "path": "src/preprocess/gpu_preprocessor.rs",
    "chars": 8225,
    "preview": "use crate::{\n    preprocess::{\n        preprocessor::{PreprocessTask, PreprocessTaskType, Preprocessor},\n        Terrain"
  },
  {
    "path": "src/preprocess/mod.rs",
    "chars": 10708,
    "preview": "use crate::{\n    formats::tiff::TiffLoader,\n    preprocess::{\n        gpu_preprocessor::{\n            create_downsample_"
  },
  {
    "path": "src/preprocess/preprocessor.rs",
    "chars": 12237,
    "preview": "use crate::{\n    math::TileCoordinate,\n    terrain_data::{\n        tile_atlas::{AtlasTile, AtlasTileAttachment, TileAtla"
  },
  {
    "path": "src/render/culling_bind_group.rs",
    "chars": 2665,
    "preview": "use crate::{\n    terrain_data::gpu_tile_tree::GpuTileTree, terrain_view::TerrainViewComponents,\n    util::StaticBuffer,\n"
  },
  {
    "path": "src/render/mod.rs",
    "chars": 548,
    "preview": "//! This module contains the implementation of the Uniform Distance-Dependent Level of Detail (UDLOD).\n//!\n//! This algo"
  },
  {
    "path": "src/render/terrain_bind_group.rs",
    "chars": 8053,
    "preview": "use crate::{\n    prelude::TileAtlas, terrain::TerrainComponents, terrain_data::gpu_tile_atlas::GpuTileAtlas,\n    util::S"
  },
  {
    "path": "src/render/terrain_material.rs",
    "chars": 16456,
    "preview": "use crate::{\n    debug::DebugTerrain,\n    render::{\n        terrain_bind_group::{create_terrain_layout, SetTerrainBindGr"
  },
  {
    "path": "src/render/terrain_view_bind_group.rs",
    "chars": 10693,
    "preview": "use crate::{\n    math::{TerrainModelApproximation, TileCoordinate},\n    terrain_data::{gpu_tile_tree::GpuTileTree, tile_"
  },
  {
    "path": "src/render/tiling_prepass.rs",
    "chars": 11610,
    "preview": "use crate::terrain_data::gpu_tile_tree::GpuTileTree;\nuse crate::{\n    debug::DebugTerrain,\n    render::{\n        culling"
  },
  {
    "path": "src/shaders/attachments.wgsl",
    "chars": 5357,
    "preview": "#define_import_path bevy_terrain::attachments\n\n#import bevy_terrain::types::AtlasTile\n#import bevy_terrain::bindings::{c"
  },
  {
    "path": "src/shaders/bindings.wgsl",
    "chars": 1893,
    "preview": "#define_import_path bevy_terrain::bindings\n\n#import bevy_terrain::types::{TerrainViewConfig, TerrainConfig, TileTreeEntr"
  },
  {
    "path": "src/shaders/debug.wgsl",
    "chars": 4327,
    "preview": "#define_import_path bevy_terrain::debug\n\n#import bevy_terrain::types::{Coordinate, AtlasTile, Blend}\n#import bevy_terrai"
  },
  {
    "path": "src/shaders/functions.wgsl",
    "chars": 9497,
    "preview": "#define_import_path bevy_terrain::functions\n\n#import bevy_terrain::bindings::{mesh, config, origins, view_config, geomet"
  },
  {
    "path": "src/shaders/mod.rs",
    "chars": 2682,
    "preview": "use bevy::{asset::embedded_asset, prelude::*};\nuse itertools::Itertools;\n\npub const DEFAULT_VERTEX_SHADER: &str = \"embed"
  },
  {
    "path": "src/shaders/preprocess/downsample.wgsl",
    "chars": 1493,
    "preview": "#import bevy_terrain::preprocessing::{AtlasTile, atlas, attachment, inside, pixel_coords, pixel_value, process_entry, is"
  },
  {
    "path": "src/shaders/preprocess/preprocessing.wgsl",
    "chars": 2962,
    "preview": "#define_import_path bevy_terrain::preprocessing\n\nconst FORMAT_R8: u32 = 2u;\nconst FORMAT_RGBA8: u32 = 0u;\nconst FORMAT_R"
  },
  {
    "path": "src/shaders/preprocess/split.wgsl",
    "chars": 1715,
    "preview": "#import bevy_terrain::preprocessing::{AtlasTile, atlas, attachment, pixel_coords, pixel_value, process_entry, is_border,"
  },
  {
    "path": "src/shaders/preprocess/stitch.wgsl",
    "chars": 4601,
    "preview": "#import bevy_terrain::preprocessing::{AtlasTile, INVALID_ATLAS_INDEX, atlas, attachment, inside, pixel_coords, pixel_val"
  },
  {
    "path": "src/shaders/render/fragment.wgsl",
    "chars": 4155,
    "preview": "#define_import_path bevy_terrain::fragment\n\n#import bevy_terrain::types::{Blend, AtlasTile, Coordinate}\n#import bevy_ter"
  },
  {
    "path": "src/shaders/render/vertex.wgsl",
    "chars": 4173,
    "preview": "#define_import_path bevy_terrain::vertex\n\n#import bevy_terrain::types::{Blend, AtlasTile, Coordinate}\n#import bevy_terra"
  },
  {
    "path": "src/shaders/tiling_prepass/prepare_prepass.wgsl",
    "chars": 1409,
    "preview": "#import bevy_terrain::types::TileCoordinate\n#import bevy_terrain::bindings::{view_config, temporary_tiles, parameters, i"
  },
  {
    "path": "src/shaders/tiling_prepass/refine_tiles.wgsl",
    "chars": 1732,
    "preview": "#import bevy_terrain::types::{TileCoordinate, Coordinate}\n#import bevy_terrain::bindings::{config, culling_view, view_co"
  },
  {
    "path": "src/shaders/types.wgsl",
    "chars": 1781,
    "preview": "#define_import_path bevy_terrain::types\n\nstruct TerrainConfig {\n    lod_count: u32,\n    min_height: f32,\n    max_height:"
  },
  {
    "path": "src/terrain.rs",
    "chars": 2928,
    "preview": "//! Types for configuring terrains.\n//!\n#[cfg(feature = \"high_precision\")]\nuse crate::big_space::{GridCell, GridTransfor"
  },
  {
    "path": "src/terrain_data/gpu_tile_atlas.rs",
    "chars": 17782,
    "preview": "use crate::{\n    terrain::TerrainComponents,\n    terrain_data::{\n        tile_atlas::{\n            AtlasAttachment, Atla"
  },
  {
    "path": "src/terrain_data/gpu_tile_tree.rs",
    "chars": 3301,
    "preview": "use crate::{\n    terrain_data::tile_tree::{TileTree, TileTreeEntry},\n    terrain_view::TerrainViewComponents,\n    util::"
  },
  {
    "path": "src/terrain_data/mod.rs",
    "chars": 9912,
    "preview": "//! This module contains the two fundamental data structures of the terrain:\n//! the [`TileTree`] and the [`TileAtlas`]."
  },
  {
    "path": "src/terrain_data/tile_atlas.rs",
    "chars": 20534,
    "preview": "use crate::{\n    formats::TC,\n    math::{TerrainModel, TileCoordinate},\n    prelude::{AttachmentConfig, AttachmentFormat"
  },
  {
    "path": "src/terrain_data/tile_tree.rs",
    "chars": 14682,
    "preview": "use crate::{\n    math::{Coordinate, TerrainModel, TileCoordinate},\n    terrain_data::{sample_height, tile_atlas::TileAtl"
  },
  {
    "path": "src/terrain_view.rs",
    "chars": 2570,
    "preview": "//! Types for configuring terrain views.\n\nuse bevy::{prelude::*, utils::HashMap};\n\n/// Resource that stores components t"
  },
  {
    "path": "src/util.rs",
    "chars": 4366,
    "preview": "use bevy::render::{\n    render_resource::{encase::internal::WriteInto, *},\n    renderer::{RenderDevice, RenderQueue},\n};"
  }
]

About this extraction

This page contains the full source code of the kurtkuehnert/bevy_terrain GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 57 files (300.6 KB), approximately 73.3k tokens, and a symbol index with 405 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!