<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Michael Mironidis</title>
    <description>The latest articles on Forem by Michael Mironidis (@mikeam565).</description>
    <link>https://forem.com/mikeam565</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1199718%2F6195ba60-a5e5-481e-b56f-80e25146a818.jpg</url>
      <title>Forem: Michael Mironidis</title>
      <link>https://forem.com/mikeam565</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/mikeam565"/>
    <language>en</language>
    <item>
      <title>Rust Game Dev Log #6: Custom Vertex Shading using ExtendedMaterial</title>
      <dc:creator>Michael Mironidis</dc:creator>
      <pubDate>Mon, 22 Apr 2024 04:08:00 +0000</pubDate>
      <link>https://forem.com/mikeam565/rust-game-dev-log-6-custom-vertex-shading-using-extendedmaterial-4312</link>
      <guid>https://forem.com/mikeam565/rust-game-dev-log-6-custom-vertex-shading-using-extendedmaterial-4312</guid>
      <description>&lt;h2&gt;
  
  
  The most important optimization yet
&lt;/h2&gt;

&lt;p&gt;While the previous blog post's asynchronous grass generation code was a big step in the right direction, the biggest optimization yet has come: Custom vertex shading!&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem
&lt;/h2&gt;

&lt;p&gt;CPUs are fast, general-purpose processors for handling computations. GPUs are specially designed for parallel workloads (such as computing visuals for your high-res screen).&lt;/p&gt;

&lt;p&gt;From a hardware and game loop perspective, the CPU computes the game logic every frame while the GPU renders the results. Bevy calls these two abstractions the Main World and Render World.&lt;/p&gt;

&lt;p&gt;This CPU-&amp;gt;GPU pipeline is not an issue until large amounts of data are being passed, where it becomes a significant bottleneck to performance. You can see this from older versions of this project: low GPU usage, but also low FPS.&lt;/p&gt;

&lt;p&gt;In other words, misusing the CPU for large-scale parallelizable workloads instead of the GPU is a triple-threat of inefficiency: the CPU is just not as good at parallel workloads, the CPU-&amp;gt;GPU pipeline has limited bandwidth, and, as a result, the GPU sits largely underutilized.&lt;/p&gt;

&lt;h2&gt;
  
  
  The solution
&lt;/h2&gt;

&lt;p&gt;To move our data and logic to the GPU, we need to first understand what a shader is.&lt;/p&gt;

&lt;p&gt;A shader is a set of instructions the GPU uses as part of rendering pixels to the screen. There are often many shaders in a rendering pipeline. A vertex shader defines how the GPU processes vertices from the game world into clip-space (roughly, the position of the vertices relative to the camera), while a fragment shader is a later stage in the pipeline that runs the final computations on a pixel's color.&lt;/p&gt;

&lt;p&gt;We need to write a vertex shader that contains our wind simulation logic. We also need to provide that shader with any additional data it needs besides standard vertex data.&lt;/p&gt;

&lt;p&gt;This involves uncharted territory: Writing WGSL (WebGPU Shading Language). Bevy uses WGSL as a shader language because it converts into the shader languages for each of the rendering backends (Vulkan, DX12, OpenGL, etc), providing one consistent interface. It is in WGSL that we will define the input data format as well as the shader logic for the grass, while in Bevy we will register this new pipeline and provide the data. Fortunately, we can start from Bevy's existing shader implementations instead of starting from scratch.&lt;/p&gt;

&lt;p&gt;Additionally, Bevy thoughtfully provides an interface through which to append custom functionality onto their existing pbr (physics-based rendering) pipeline: ExtendedMaterial. Let's get to it!&lt;/p&gt;

&lt;h2&gt;
  
  
  The Rust side of things
&lt;/h2&gt;

&lt;p&gt;ExtendedMaterial provides a means of adding additional data and functionality to any material. We are going to extend StandardMaterial by using it as the &lt;code&gt;base&lt;/code&gt; of an ExtendedMaterial, and writing a GrassMaterialExtension for the &lt;code&gt;extension&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;First, we define the empty struct we will use to extend StandardMaterial. Here, we could pass some additional material data, but we don't have any for our use case.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#[derive(Asset, TypePath, AsBindGroup, Debug, Clone)]
pub struct GrassMaterialExtension {
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This allows us to instantiate a new ExtendedMaterial:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    let grass_material = ExtendedMaterial {
        base: grass_material_std,
        extension: grass_material_ext
    };
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Where &lt;code&gt;grass_material_std&lt;/code&gt; is this StandardMaterial instantiation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    StandardMaterial {
        base_color: Color::WHITE,
        double_sided: false,
        perceptual_roughness: 1.0,
        reflectance: 0.5,
        cull_mode: None,
        opaque_render_method: bevy::pbr::OpaqueRendererMethod::Forward,
        unlit: false,
        ..default()
    }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and grass_material_ext is just the GrassMaterialExtension instantiated:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    let grass_material_ext = GrassMaterialExtension {
    };
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This requires updating parts of our code from old references to &lt;code&gt;StandardMaterial&lt;/code&gt; to &lt;code&gt;ExtendedMaterial&amp;lt;StandardMaterial, GrassMaterialExtension&amp;gt;&lt;/code&gt;, and &lt;code&gt;PbrBundle&lt;/code&gt; to &lt;code&gt;MaterialMeshBundle&lt;/code&gt; with the appropriate generics.&lt;/p&gt;

&lt;p&gt;We then need to actually implement &lt;code&gt;MaterialExtension&lt;/code&gt; for &lt;code&gt;GrassMaterialExtension&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;impl MaterialExtension for GrassMaterialExtension {

    fn vertex_shader() -&amp;gt; bevy::render::render_resource::ShaderRef {
        "shaders/grass_shader.wgsl".into()
    }

    fn specialize(
        _pipeline: &amp;amp;MaterialExtensionPipeline,
        descriptor: &amp;amp;mut RenderPipelineDescriptor,
        layout: &amp;amp;MeshVertexBufferLayout,
        _key: MaterialExtensionKey&amp;lt;GrassMaterialExtension&amp;gt;,
    ) -&amp;gt; Result&amp;lt;(), SpecializedMeshPipelineError&amp;gt; {
        let mut pos_position = 0;
        let mut normal_position = 1;
        let mut color_position = 5;
        if let Some(label) = &amp;amp;mut descriptor.label {
            // println!("Label is: {}", label);
            if label == "pbr_prepass_pipeline" {
                pos_position = 0;
                normal_position = 3;
                color_position = 7;
            }
        }
        let vertex_layout = layout.get_layout(&amp;amp;[
            Mesh::ATTRIBUTE_POSITION.at_shader_location(pos_position),
            Mesh::ATTRIBUTE_NORMAL.at_shader_location(normal_position),
            Mesh::ATTRIBUTE_COLOR.at_shader_location(color_position),
            // Mesh::ATTRIBUTE_UV_0.at_shader_location(1),
            // Mesh::ATTRIBUTE_TANGENT.at_shader_location(4),
            ATTRIBUTE_STARTING_POSITION.at_shader_location(17),
            ATTRIBUTE_WORLD_POSITION.at_shader_location(18),
        ])?;
        descriptor.vertex.buffers = vec![vertex_layout];
        Ok(())
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Above, we see that the only shader function we implement is &lt;code&gt;vertex_shader&lt;/code&gt;. The rest of the shader functions will be the default StandardMaterial shaders. We also implement the &lt;code&gt;specialize&lt;/code&gt; function, which we use to define the &lt;code&gt;VertexBufferLayout&lt;/code&gt; provided to the shader. To fit into the existing pbr shaders, I looked at some of Bevy's shader code. &lt;a href="https://github.com/bevyengine/bevy/blob/570c43fdd51df9abecb5981c0c045d5ccb843278/crates/bevy_pbr/src/render/mesh.wgsl#L4"&gt;This&lt;/a&gt; is the default vertex struct and shader, &lt;a href="https://github.com/bevyengine/bevy/blob/570c43fdd51df9abecb5981c0c045d5ccb843278/crates/bevy_pbr/src/render/forward_io.wgsl"&gt;this&lt;/a&gt; is the Vertex struct definition for forward rendering, and &lt;a href="https://github.com/bevyengine/bevy/blob/570c43fdd51df9abecb5981c0c045d5ccb843278/crates/bevy_pbr/src/prepass/prepass_io.wgsl"&gt;this&lt;/a&gt; is the Vertex struct definition for prepass.&lt;/p&gt;

&lt;p&gt;Based off of these resources, I figured out that in forward passes, I needed to pass position, normal, and color data to 0, 1, and 5 respectively, but on prepass, I needed to pass those to 0, 3, and 7 (if you remove the if statement, an error is thrown during prepass about missing vertex data at location 7). So, in what feels like a hacky solution, I use the &lt;code&gt;descriptor.label&lt;/code&gt; to determine which pipeline we're in and assign the vertex buffer locations accordingly. I also pass two custom data buffers: the starting position of the vertex, and the world position of the base of the grass blade, both of which are necessary in order to simulate wind (and&lt;/p&gt;

&lt;p&gt;We also need to add these attributes to the mesh upon generation, adding the following lines to &lt;code&gt;generate_grass_geometry&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    mesh.insert_attribute(ATTRIBUTE_STARTING_POSITION, positions);
    mesh.insert_attribute(ATTRIBUTE_WORLD_POSITION, grass_offsets.clone());
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The shader code
&lt;/h2&gt;

&lt;p&gt;Writing the shader is fairly straightforward: We're going to copy over &lt;code&gt;mesh.wgsl&lt;/code&gt; to our own &lt;code&gt;grass_shader.wgsl&lt;/code&gt; file. We modify the &lt;code&gt;Vertex&lt;/code&gt; struct definition to include the two additional attributes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    @location(17) starting_position: vec3&amp;lt;f32&amp;gt;,
    @location(18) world_position: vec3&amp;lt;f32&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We add our computations to the vertex function, installing the crate &lt;code&gt;bevy_shader_utils&lt;/code&gt; and importing the convenient &lt;code&gt;perlin_noise_2d&lt;/code&gt; function it provides:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    // calculation of wind and new x, y, z coords
    var noise = perlin_noise_2d(vec2&amp;lt;f32&amp;gt;(vertex_no_morph.world_position.x/50.0 + globals.time * 0.5, vertex_no_morph.world_position.z/50.0 + globals.time * 0.5));

    var new_x = vertex_no_morph.starting_position.x + noise * ((vertex_no_morph.position.y-vertex_no_morph.world_position.y) / 2.4);
    var new_y = vertex_no_morph.position.y;
    var new_z = vertex_no_morph.starting_position.z + noise * ((vertex_no_morph.position.y-vertex_no_morph.world_position.y) / 2.4);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To actually use these calculations as the new position of the vertex, we modify the line in the function defining &lt;code&gt;out.world_position&lt;/code&gt; to the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    out.world_position = mesh_functions::mesh_position_local_to_world(model, vec4&amp;lt;f32&amp;gt;(vec3&amp;lt;f32&amp;gt;(new_x, new_y, new_z), 1.0));
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To recap, we have inserted additional vertex data to our mesh, defined an ExtendedMaterial that allows us to implement our own vertex shader and the vertex buffer layout for it that includes this additional data, and wrote a vertex shader adapted from the existing &lt;code&gt;meshes.wgsl&lt;/code&gt; that includes this additional data and uses it to simulate wind on each vertex.&lt;/p&gt;

&lt;p&gt;Finally, after removing the old CPU wind simulation logic, the impact is massive: over a million blades of fully-lit grass, all simulating wind, at over 60fps:&lt;br&gt;
&lt;a href="https://i.giphy.com/media/v1.Y2lkPTc5MGI3NjExYXNyaWUxMmdzcDRtMXVobWc5ejBjM25oNGY4bHdnMGV5Y2Q1dGNqNiZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/2gcp924aCus9q7ZKaH/giphy.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://i.giphy.com/media/v1.Y2lkPTc5MGI3NjExYXNyaWUxMmdzcDRtMXVobWc5ejBjM25oNGY4bHdnMGV5Y2Q1dGNqNiZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/2gcp924aCus9q7ZKaH/giphy.gif" alt="grass flowing in wind" width="480" height="259"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://i.giphy.com/media/v1.Y2lkPTc5MGI3NjExNjEwMjMweXJhcmttbGpzN2plZjg5b2xibGhmNG1na2tkbDloM3AzaSZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/5PvT7yB2duj2RFkoXv/giphy.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://i.giphy.com/media/v1.Y2lkPTc5MGI3NjExNjEwMjMweXJhcmttbGpzN2plZjg5b2xibGhmNG1na2tkbDloM3AzaSZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/5PvT7yB2duj2RFkoXv/giphy.gif" alt="grass flowing in wind" width="480" height="259"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Final notes
&lt;/h2&gt;

&lt;p&gt;My foray into shaders was fun, but not without challenges. Finding the best way to achieve what I wanted to do while fitting it into the existing complex shader logic of Bevy was tough even with &lt;code&gt;ExtendedMaterial&lt;/code&gt;. There are some examples to base things off of, but I still was shooting in the dark at times, mainly with my initial approach trying to write the custom vertex shader from scratch. For a while, I had it partially working, but lightning was broken. Fortunately, I finally realized I should just add onto the existing vertex shader in &lt;code&gt;mesh.wgsl&lt;/code&gt;, and things quickly fell into place from there.&lt;/p&gt;

&lt;p&gt;The impact this has to the project cannot be understated. This project went from handling ~200k blades of mostly static grass to over a million blades of grass, all simulating wind, at over 60fps. It is a dramatic performance increase that has leveled up the grass from just visually serviceable to stunning and practical for gameplay.&lt;/p&gt;

&lt;p&gt;Future optimizations for grass still exist, mainly LOD (level of detail). However, I am likely shifting my attention to more realistic terrain generation, since I recently saw an &lt;a href="https://www.youtube.com/watch?v=gsJHzBTPG0Y"&gt;incredible video&lt;/a&gt; about the subject.&lt;/p&gt;

&lt;p&gt;As always, check out the &lt;a href="https://github.com/mikeam565/first-game"&gt;repo&lt;/a&gt; for the latest updates!&lt;/p&gt;

</description>
      <category>rust</category>
      <category>bevy</category>
      <category>gamedev</category>
      <category>shader</category>
    </item>
    <item>
      <title>Rust Game Dev Log #5 | Endless Procedural Terrain Generation + Dynamic Asynchronously updated Grass</title>
      <dc:creator>Michael Mironidis</dc:creator>
      <pubDate>Mon, 01 Apr 2024 03:01:39 +0000</pubDate>
      <link>https://forem.com/mikeam565/rust-game-dev-log-5-improved-terrain-generation-dynamic-grass-in-an-endless-world-291i</link>
      <guid>https://forem.com/mikeam565/rust-game-dev-log-5-improved-terrain-generation-dynamic-grass-in-an-endless-world-291i</guid>
      <description>&lt;h2&gt;
  
  
  Massive update
&lt;/h2&gt;

&lt;p&gt;This is by far the biggest update yet, with significant updates to terrain generation and the grass system bringing me increasingly closer to my desired artistic objective. As a consequence, I've ramped up gameplay brainstorming, as this big open world needs something to do! But that will be for another post... Let's get into it!&lt;/p&gt;

&lt;h2&gt;
  
  
  Terrain
&lt;/h2&gt;

&lt;p&gt;The terrain update can be summarized to two changes, one minor (vertex coloring in lieu of textures) and one major (endless procedural terrain generation with improved height mapping).&lt;/p&gt;

&lt;h3&gt;
  
  
  Vertex coloring
&lt;/h3&gt;

&lt;p&gt;I opted to get rid of the terrain texture because texturing terrain is more work than expected (this is not Unity, I can't just paint terrain with various textures using a brush). Additionally, vertex coloring achieves 80% of what I need: a way to visually distinguish the geography and create depth. The end result was incorporating some logic into the terrain generation that set a vertex color based off the height, with some set heights marking the beginning and end of different terrain types (sand for the beach near water, green for temperate, white for peaks). Here's the code I use to choose a color based off of some input y coordinate:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

fn get_terrain_color(y: f32) -&amp;gt; [f32;4] {
    if y &amp;lt; HEIGHT_SAND { COLOR_SAND }
    else if y &amp;gt; HEIGHT_PEAKS { COLOR_PEAKS }
    else if y &amp;lt; HEIGHT_TEMPERATE_START {
        terrain_color_gradient(
            (y-HEIGHT_SAND)/(HEIGHT_TEMPERATE_START-HEIGHT_SAND),
            COLOR_SAND,
            COLOR_TEMPERATE
        )
    } else if y &amp;lt; HEIGHT_TEMPERATE_END {
        COLOR_TEMPERATE
    } else {
        terrain_color_gradient(
            (y-HEIGHT_TEMPERATE_END)/(HEIGHT_PEAKS-HEIGHT_TEMPERATE_END),
            COLOR_TEMPERATE,
            COLOR_PEAKS
        )
    }
}


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;The above code calls the following function:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

fn terrain_color_gradient(ratio: f32, rgba1: [f32; 4], rgba2: [f32; 4]) -&amp;gt; [f32;4] {
    let [r1, g1, b1, a1] = rgba1;
    let [r2, g2, b2, a2] = rgba2;

    [
        r1 + (r2-r1)*(ratio),
        g1 + (g2-g1)*(ratio),
        b1 + (b2-b1)*(ratio),
        a1 + (a2-a1)*(ratio)
    ]
}


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;This all basically just says, for certain areas, use one color, while for others, sample from a gradient between two colors, using some fraction to determine how much of the second color to use. The end result is extremely efficient for the goal of creating visually identifiable beaches, temperate regions, and peaks:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fti733onf98coxum77n2w.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fti733onf98coxum77n2w.png" alt="Bumpy mountainous island amongst smaller islands"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Stylistically I have opted for an almost light dusting of white at the peaks not even to indicate snow but to make the highest points feel "in the clouds". There's further work in using a browner color on steep inclines, but this involves calculating data that does not exist yet, which is the gradient each point.&lt;/p&gt;

&lt;h2&gt;
  
  
  Endless procedural terrain
&lt;/h2&gt;

&lt;p&gt;This functionality is somewhat of a bare working demo of endless procedural terrain. In short, we have one main terrain plane surrounded by lower-resolution distant terrain planes. After the player navigates a certain distance away from the center of the main terrain plane, it regenerates the terrain underneath the player. At the moment this causes the game to freeze as it regenerates the terrain synchronously. I intend to adapt the successful asynchronous grass code for the terrain to make the experience navigating the world more seamless, but for now, here's the code that does what is described above.&lt;/p&gt;

&lt;p&gt;The system registered to handle terrain generation:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

pub fn update_terrain(
    mut commands: Commands,
    mut meshes: ResMut&amp;lt;Assets&amp;lt;Mesh&amp;gt;&amp;gt;,
    mut materials: ResMut&amp;lt;Assets&amp;lt;StandardMaterial&amp;gt;&amp;gt;,
    asset_server: Res&amp;lt;AssetServer&amp;gt;,
    mut main_terrain: Query&amp;lt;(Entity,&amp;amp;mut Transform, &amp;amp;Handle&amp;lt;Mesh&amp;gt;), (With&amp;lt;Terrain&amp;gt;, With&amp;lt;MainTerrain&amp;gt;)&amp;gt;,
    mut distant_terrain: Query&amp;lt;(Entity,&amp;amp;mut Transform, &amp;amp;Handle&amp;lt;Mesh&amp;gt;), (With&amp;lt;Terrain&amp;gt;, Without&amp;lt;MainTerrain&amp;gt;)&amp;gt;,
    player: Query&amp;lt;&amp;amp;Transform, (With&amp;lt;player::Player&amp;gt;,Without&amp;lt;Terrain&amp;gt;)&amp;gt;,
) {
    if main_terrain.is_empty() { // scene start
        // spawn chunk at player
        let player_trans = player.get_single().unwrap().translation;
        spawn_terrain_chunk(&amp;amp;mut commands, &amp;amp;mut meshes, &amp;amp;mut materials, &amp;amp;asset_server, 0., 0., true, PLANE_SIZE, SUBDIVISIONS_LEVEL_1);
        // spawn chunks without player in them
        for (dx,dz) in [(1,0),(-1,0),(0,1),(0,-1),(1,1),(1,-1),(-1,1),(-1,-1)] {
            let calc_dx = dx as f32 * (PLANE_SIZE/2. + SIZE_NO_PLAYER/2.);
            let calc_dz = dz as f32 * (PLANE_SIZE/2. + SIZE_NO_PLAYER/2.);
            spawn_terrain_chunk(&amp;amp;mut commands, &amp;amp;mut meshes, &amp;amp;mut materials, &amp;amp;asset_server, player_trans.x + calc_dx, player_trans.z + calc_dz, false, SIZE_NO_PLAYER, SUBDIVISIONS_LEVEL_2);
        }
        spawn_water_plane(&amp;amp;mut commands, &amp;amp;mut meshes, &amp;amp;mut materials, &amp;amp;asset_server);
    } else { // main update logic
        if let Ok(terrain) = main_terrain.get_single_mut() {
            let (terrain_ent, terrain_trans, terrain_mesh) = terrain;
            let player_trans = player.get_single().unwrap();
            let mut delta: Option&amp;lt;Vec3&amp;gt; = None;

            // determine player triggering terrain refresh
            if (player_trans.translation.x - terrain_trans.translation.x).abs() &amp;gt; PLANE_SIZE/4. || (player_trans.translation.z - terrain_trans.translation.z).abs() &amp;gt; PLANE_SIZE/4. {
                delta = Some(player_trans.translation - terrain_trans.translation);
            }

            // if they have, regenerate the terrain
            if let Some(delta) = delta {
                println!("Player has triggered terrain regeneration");
                regenerate_terrain(&amp;amp;mut commands, &amp;amp;mut meshes, &amp;amp;mut materials, &amp;amp;asset_server, &amp;amp;mut main_terrain, &amp;amp;mut distant_terrain, delta);
            }
        }
    }
}


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;The &lt;code&gt;spawn_terrain_chunk&lt;/code&gt; function (features commented out code that used the texture):&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

fn spawn_terrain_chunk(
    commands: &amp;amp;mut Commands,
    meshes: &amp;amp;mut ResMut&amp;lt;Assets&amp;lt;Mesh&amp;gt;&amp;gt;,
    materials: &amp;amp;mut ResMut&amp;lt;Assets&amp;lt;StandardMaterial&amp;gt;&amp;gt;,
    asset_server: &amp;amp;Res&amp;lt;AssetServer&amp;gt;,
    x: f32, z: f32,
    contains_player: bool,
    size: f32,
    subdivisions: u32
) -&amp;gt; Entity {    
    let mesh = generate_terrain_mesh(x, z, size, subdivisions);

    let sampler_desc = ImageSamplerDescriptor {
        address_mode_u: ImageAddressMode::Repeat,
        address_mode_v: ImageAddressMode::Repeat,
        ..default()
    };
    let settings = move |s: &amp;amp;mut ImageLoaderSettings| {
        s.sampler = ImageSampler::Descriptor(sampler_desc.clone());
    };

    // let texture_handle = asset_server.load_with_settings("terrain/rocky_soil.png", settings.clone());
    // let normal_handle = asset_server.load_with_settings("terrain/rocky_soil_normal.png", settings);
    let terrain_material = StandardMaterial {
        // base_color: if contains_player { Color::WHITE } else { Color::WHITE }, // use to see difference in terrain chunks
        // base_color_texture: Some(texture_handle.clone()),
        // normal_map_texture: Some(normal_handle.clone()),
        alpha_mode: AlphaMode::Opaque,
        double_sided: true,
        perceptual_roughness: 1.0,
        reflectance: 0.4,
        cull_mode: Some(Face::Back),
        flip_normal_map_y: true,
        ..default()
    };

    // terrain
    let collider_shape = ComputedColliderShape::TriMesh;

    let mut binding = commands.spawn(PbrBundle {
            mesh: meshes.add(mesh.clone()),
            material: materials.add(terrain_material),
            transform: Transform::from_xyz(x,0.,z),
            ..default()
        });
    let parent_terrain = binding
        .insert(Terrain)
        .insert(Collider::from_bevy_mesh(&amp;amp;mesh, &amp;amp;collider_shape).unwrap()
    );
    if contains_player {
        parent_terrain.insert(MainTerrain);
    }
    parent_terrain.id()

}


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;The &lt;code&gt;regenerate_terrain&lt;/code&gt; function:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

fn regenerate_terrain(
    commands: &amp;amp;mut Commands,
    meshes: &amp;amp;mut ResMut&amp;lt;Assets&amp;lt;Mesh&amp;gt;&amp;gt;,
    materials: &amp;amp;mut ResMut&amp;lt;Assets&amp;lt;StandardMaterial&amp;gt;&amp;gt;,
    asset_server: &amp;amp;Res&amp;lt;AssetServer&amp;gt;,
    main_terrain: &amp;amp;mut Query&amp;lt;(Entity,&amp;amp;mut Transform, &amp;amp;Handle&amp;lt;Mesh&amp;gt;), (With&amp;lt;Terrain&amp;gt;, With&amp;lt;MainTerrain&amp;gt;)&amp;gt;,
    distant_terrain: &amp;amp;mut Query&amp;lt;(Entity,&amp;amp;mut Transform, &amp;amp;Handle&amp;lt;Mesh&amp;gt;), (With&amp;lt;Terrain&amp;gt;, Without&amp;lt;MainTerrain&amp;gt;)&amp;gt;,
    delta: Vec3
) {
    let collider_shape = ComputedColliderShape::TriMesh;

    // shift over and regen terrain
    for (ent, mut trans, mh) in main_terrain.iter_mut() {
        trans.translation = trans.translation + delta;
        trans.translation.y = 0.;
        let mesh = meshes.get_mut(mh).unwrap();
        let new_mesh = &amp;amp;mut generate_terrain_mesh(trans.translation.x, trans.translation.z, PLANE_SIZE, SUBDIVISIONS_LEVEL_1);
        *mesh = new_mesh.clone();
        commands.get_entity(ent).unwrap().insert(Collider::from_bevy_mesh(&amp;amp;mesh, &amp;amp;collider_shape).unwrap());
    }
    for (ent, mut trans, mh) in distant_terrain.iter_mut() {
        trans.translation = trans.translation + delta;
        trans.translation.y = 0.;
        let mesh = meshes.get_mut(mh).unwrap();
        let new_mesh = &amp;amp;mut generate_terrain_mesh(trans.translation.x, trans.translation.z, PLANE_SIZE, SUBDIVISIONS_LEVEL_2);
        *mesh = new_mesh.clone();
        // commands.get_entity(pl_ent).unwrap().insert(Collider::from_bevy_mesh(&amp;amp;mesh, &amp;amp;collider_shape).unwrap()); // no need for collider here atm
    }
}


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;The water plane is just a regular plane with a normal texture that scrolls by updating the mesh's UV coords every frame by some constant WATER_SCROLL_SPEED:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

fn update_water(
    mut commands: Commands,
    mut meshes: ResMut&amp;lt;Assets&amp;lt;Mesh&amp;gt;&amp;gt;,
    mut materials: ResMut&amp;lt;Assets&amp;lt;StandardMaterial&amp;gt;&amp;gt;,
    asset_server: Res&amp;lt;AssetServer&amp;gt;,
    mut water: Query&amp;lt;(Entity,&amp;amp;Handle&amp;lt;Mesh&amp;gt;), (With&amp;lt;Water&amp;gt;)&amp;gt;,
) {
    let Ok((water_ent, water_mesh_handle)) = water.get_single_mut() else {
        return
    };
    let water_mesh = meshes.get_mut(water_mesh_handle).unwrap();
    let water_uvs = water_mesh.attribute_mut(Mesh::ATTRIBUTE_UV_0).unwrap();
    let VertexAttributeValues::Float32x2(uv_attr) = water_uvs else {
        panic!("Unexpected vertex format, expected Float32x3");
    };
    for [x,y] in uv_attr.iter_mut() {
        *x = *x + WATER_SCROLL_SPEED;
        *y = *y + WATER_SCROLL_SPEED;
    }
}


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Finally, in fine-tuning the terrain generation for flatter terrain near the coastline and bumpier, rockier terrain on the mountains, I updated the code in &lt;code&gt;perlin.rs&lt;/code&gt; to the following:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

pub fn sample_terrain_height(terrain_perlin: &amp;amp;Perlin, x: f32, z: f32) -&amp;gt; f32 {
    terrain::BASE_LEVEL
    // + terrain_perlin.get([x as f64 / 100., z as f64 / 100.]) as f32 * HILL_HEIGHTS // hills
    // + terrain_perlin.get([z as f64 / 16., x as f64 / 16.]) as f32 * TERRAIN_BUMPINESS // finer detail
    + detail_component(terrain_perlin, x, z)
    + hill_component(terrain_perlin, x, z)
    + mountain_component(terrain_perlin, x, z)
}

fn detail_component(terrain_perlin: &amp;amp;Perlin, x: f32, z: f32) -&amp;gt; f32 {
    let mountain_sample = sample_mountain(terrain_perlin, x, z);
    terrain_perlin.get([z as f64 / 16., x as f64 / 16.]) as f32 * (mountain_sample/0.5)*TERRAIN_BUMPINESS // finer detail
}

fn hill_component(terrain_perlin: &amp;amp;Perlin, x: f32, z: f32) -&amp;gt; f32 {
    let mountain_sample = sample_mountain(terrain_perlin, x, z);

    terrain_perlin.get([x as f64 / 100., z as f64 / 100.]) as f32 * (mountain_sample/0.25)*HILL_HEIGHTS
}

fn mountain_component(terrain_perlin: &amp;amp;Perlin, x: f32, z: f32) -&amp;gt; f32 {
    let mountain_sample = sample_mountain(terrain_perlin, x, z);
    MOUNTAIN_HEIGHTS * mountain_sample/(1.4 - mountain_sample)
}

fn sample_mountain(terrain_perlin: &amp;amp;Perlin, x: f32, z: f32) -&amp;gt; f32 {
    terrain_perlin.get([x as f64 / 4096., z as f64 / 4096.]) as f32
}


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Basically, I split up the height sample into mountain, hill, and detail-scaled components. I use the mountain sample as a reference for the "main" height that determines how much of the hill and detail components to include. Here's an image where the lighting clearly highlights the geography:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Foi6slh5ilo07292d3qmg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Foi6slh5ilo07292d3qmg.png" alt="Sunrise on rocky islands"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The details can be messed around with, but with this code, we have serviceable endless procedurally generated terrain. Now to the grass!&lt;/p&gt;

&lt;h2&gt;
  
  
  Dynamic, asynchronously updated grass!
&lt;/h2&gt;

&lt;p&gt;This is the most significant update yet in terms of optimization and player experience, and it starts with adding a GrassGrid resource that just wraps a hashmap:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

struct GrassGrid(HashMap&amp;lt;(i32,i32), bool&amp;gt;);


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Let's break up the &lt;code&gt;update_grass&lt;/code&gt; function into two parts. First, the base state (aka when there's no grass present so it needs to be generated along with adding the GrassGrid as a resource):&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

let mut grass_grid = GrassGrid(HashMap::new());
// generate grid of grass
for i in -GRID_SIZE_HALF..=GRID_SIZE_HALF {
    for j in -GRID_SIZE_HALF..=GRID_SIZE_HALF {
        let a = x + i as f32 * GRASS_TILE_SIZE_1;
        let b = z + j as f32 * GRASS_TILE_SIZE_1;
        grass_grid.0.insert((a as i32, b as i32), true);
        let contains_player = (player_trans.translation.x - a).abs() &amp;lt; GRASS_TILE_SIZE_1/2. &amp;amp;&amp;amp; (player_trans.translation.z - b).abs() &amp;lt; GRASS_TILE_SIZE_1/2.;
        let color = if contains_player { Color::RED } else { Color::PURPLE };
        let (main_mat, main_grass, main_data) = generate_grass(&amp;amp;mut meshes, &amp;amp;mut materials, a, b, NUM_GRASS_1, GRASS_TILE_SIZE_1);
        commands.spawn(main_mat)
            .insert(main_grass)
            .insert(main_data)
            .insert(ContainsPlayer(contains_player))
            // .insert(ShowAabbGizmo {color: Some(color)})
            ;
    }
}
commands.spawn(grass_grid);


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;The second part is large, so consider opening the code side-by-side and following along with my explanation:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

let thread_pool = AsyncComputeTaskPool::get();
let mut grass_grid = grid.get_single_mut().unwrap();
let elapsed_time = time.elapsed_seconds_f64();
let mut grass_w_player: Option&amp;lt;Entity&amp;gt; = None;
for (ent, mh, grass_data, grass_trans, visibility, mut contains_player) in grass.iter_mut() {
    // remove or add ContainsPlayer if applicable
    if (player_trans.translation.x - grass_trans.translation.x).abs() &amp;gt;= GRASS_TILE_SIZE_1/2. || (player_trans.translation.z - grass_trans.translation.z).abs() &amp;gt;= GRASS_TILE_SIZE_1/2. {
        if contains_player.0 {
            *contains_player = ContainsPlayer(false);
        }
    } else {
        if !contains_player.0 {
            *contains_player = ContainsPlayer(true);
            // generate new grass
            for i in -GRID_SIZE_HALF..=GRID_SIZE_HALF {
                for j in -GRID_SIZE_HALF..=GRID_SIZE_HALF {
                    let a = grass_trans.translation.x + i as f32 * GRASS_TILE_SIZE_1;
                    let b = grass_trans.translation.z + j as f32 * GRASS_TILE_SIZE_1;
                    if let false = *grass_grid.0.get(&amp;amp;(a as i32,b as i32)).unwrap_or(&amp;amp;false) {
                        grass_grid.0.insert((a as i32, b as i32), true);
                        // todo: async way
                        let transform = Transform::from_xyz(a,0.,b);

                        let task_entity = commands.spawn_empty().id();
                        let task = thread_pool.spawn(async move {
                            let mut command_queue = CommandQueue::default();
                            let (mesh, grass_data) = generate_grass_mesh(a, b, NUM_GRASS_1, GRASS_TILE_SIZE_1);

                            command_queue.push(move |world: &amp;amp;mut World| {
                                let (grass_mesh_handle, grass_mat_handle) = {
                                    let mut system_state = SystemState::&amp;lt;(ResMut&amp;lt;Assets&amp;lt;Mesh&amp;gt;&amp;gt;, ResMut&amp;lt;Assets&amp;lt;StandardMaterial&amp;gt;&amp;gt;)&amp;gt;::new(world);
                                    let (mut meshes, mut mats) = system_state.get_mut(world);

                                    (meshes.add(mesh), mats.add(grass_material()))
                                };

                                world.entity_mut(task_entity)
                                .insert(PbrBundle {
                                    mesh: grass_mesh_handle,
                                    material: grass_mat_handle,
                                    transform,
                                    ..default()
                                })
                                .insert(Grass)
                                .insert(grass_data)
                                .insert(ContainsPlayer(false))
                                // .insert(ShowAabbGizmo {color: Some(Color::PURPLE)})
                                .remove::&amp;lt;GenGrassTask&amp;gt;();
                            });

                            command_queue
                        });

                        commands.entity(task_entity).insert(GenGrassTask(task)); // spawn a task marked GenGrassTask in the world to be handled by handle_tasks fn when complete

                    //     // old way (sync)
                    //     let (main_mat, main_grass, main_data) = generate_grass(&amp;amp;mut commands, &amp;amp;mut meshes, &amp;amp;mut materials, a, b, NUM_GRASS_1, GRASS_TILE_SIZE_1);
                    //     commands.spawn(main_mat)
                    //         .insert(main_grass)
                    //         .insert(main_data)
                    //         .insert(ContainsPlayer(false))
                    //         // .insert(ShowAabbGizmo {color: Some(Color::PURPLE)})
                    //         ;
                    }
                }
            }

        }
    }
    if contains_player.0 {
        grass_w_player = Some(ent);
    }
    // simulate wind only if close enough and if visible
    if (player_trans.translation.x - grass_trans.translation.x).abs() &amp;lt; WIND_SIM_TRIGGER_DISTANCE &amp;amp;&amp;amp; (player_trans.translation.z - grass_trans.translation.z).abs() &amp;lt; WIND_SIM_TRIGGER_DISTANCE &amp;amp;&amp;amp; visibility.get() {
        if let Some(mesh) = meshes.get_mut(mh) {
            apply_wind(mesh, grass_data, &amp;amp;perlin, elapsed_time, player_trans.translation.xz());
        }
    } else if (player_trans.translation.x - grass_trans.translation.x).abs() &amp;gt; DESPAWN_DISTANCE || (player_trans.translation.z - grass_trans.translation.z).abs() &amp;gt; DESPAWN_DISTANCE {
        grass_grid.0.insert((grass_trans.translation.x as i32, grass_trans.translation.z as i32), false);
        commands.get_entity(ent).unwrap().despawn_recursive();
    }
}

if let Some(grass_w_player) = grass_w_player {
    // update aabb color
    // commands.get_entity(grass_w_player).unwrap().insert(AabbGizmo {color: Some(Color::RED)});
}


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;If the player is within GRASS_TILE_SIZE_1/2 from this grass tile's center, then we check if ContainsPlayer is not true. If it isn't, that means we need to update the grid because the player has entered a tile that they previously were not in. So we enter a nested for loop to regenerate all the terrain based on whether or not it already has been generated (using the grass_grid hashmap to save time). The beauty here is in the asynchronous code: instead of generating the grass that needs to be generated all in the same frame, for each grass that needs to be generated we spawn a task into the AsyncComputeTaskPool and a corresponding task entity into the world. Each task generates a new grass mesh and material and pushes a command to a CommandQueue to add the mesh and materials to their respective Asset stores (returning the handles pointing to each) before inserting these and the rest of the grass-related components into the task entity. It also removes the &lt;code&gt;GenGrassTask&lt;/code&gt; component that wraps the task, indicating task completion. To make the above work, we have the &lt;code&gt;handle_tasks&lt;/code&gt; function that does the following:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

fn handle_tasks(mut commands: Commands, mut grass_tasks: Query&amp;lt;&amp;amp;mut GenGrassTask&amp;gt;) {
    for mut task in &amp;amp;mut grass_tasks {
        if let Some(mut commands_queue) = block_on(poll_once(&amp;amp;mut task.0)) {
            // append the returned command queue to have it execute later
            commands.append(&amp;amp;mut commands_queue);
        }
    }
}


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;It just queries for entities with &lt;code&gt;GenGrassTask&lt;/code&gt; components, and once they are complete, adds the returned &lt;code&gt;CommandsQueue&lt;/code&gt; to the Commands.&lt;/p&gt;

&lt;p&gt;Here is the end result, going from stuttering every time the player entered a new grass tile to no stutter at all:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://i.giphy.com/media/v1.Y2lkPTc5MGI3NjExbWtpNGZzbTFraG5mZXVjYWY5YjZvajljOW82anZkZ2t4N3FpdXh6dSZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/LIZdK3R4l6Pi42N649/giphy.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://i.giphy.com/media/v1.Y2lkPTc5MGI3NjExbWtpNGZzbTFraG5mZXVjYWY5YjZvajljOW82anZkZ2t4N3FpdXh6dSZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/LIZdK3R4l6Pi42N649/giphy.gif" alt="GIF of player moving around and grass updating asynchronously around them"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Smooth transitions
&lt;/h2&gt;

&lt;p&gt;With a dynamic grid system like above, its important that detail changes from tile to tile are not jarring. This will be especially true if in the future I implement grass density relative to player position. One jarring detail was the wind. Since it is still a CPU-bound task and slows down the game a lot, I restrict it only to tiles close to the player. As a consequence, when moving around, grass would snap into position as it became close enough to simulate. To combat this, I added code to reduce the effect of wind the farther from the player it is, tapering off to 0 by the wind simulation boundary. You can see this code in the &lt;code&gt;apply_wind&lt;/code&gt; function where I added the following coefficient to the &lt;code&gt;curve_amount&lt;/code&gt; variable:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

((WIND_SIM_DISTANCE - player_xz.distance(Vec2::new(*x,*z))) / WIND_SIM_DISTANCE).powi(2)


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Some additional decorations I've added are the bevy_atmosphere crate for a Nishita skybox to simulate a day-night cycle, and a point-light representing a torch that samples the perlin noise I already use for wind but for a flicker effect. These are relatively minor additions that you can check out, along with the rest of the code for this project, at &lt;a href="https://github.com/mikeam565/first-game" rel="noopener noreferrer"&gt;the repo&lt;/a&gt;!&lt;/p&gt;

&lt;p&gt;All of these come together for the following player experience:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://i.giphy.com/media/v1.Y2lkPTc5MGI3NjExNzBoODVvMHkxejloNm42YWZ1aDJveWxudGRxZHJtNDczZm96a2toZyZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/2cCTbGjbfCTWd1lZ7o/giphy.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://i.giphy.com/media/v1.Y2lkPTc5MGI3NjExNzBoODVvMHkxejloNm42YWZ1aDJveWxudGRxZHJtNDczZm96a2toZyZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/2cCTbGjbfCTWd1lZ7o/giphy.gif" alt="Walking through grass during sunset"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://i.giphy.com/media/v1.Y2lkPTc5MGI3NjExNmpuZXoyZjRrNnkyMjE3dGJhNXI5MjJkZTc5djgya21neHc3aDkzdCZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/s4mSwzWBN0h254acQ9/giphy.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://i.giphy.com/media/v1.Y2lkPTc5MGI3NjExNmpuZXoyZjRrNnkyMjE3dGJhNXI5MjJkZTc5djgya21neHc3aDkzdCZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/s4mSwzWBN0h254acQ9/giphy.gif" alt="Walking through grass during sunrise"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://i.giphy.com/media/v1.Y2lkPTc5MGI3NjExYmVvN3RxdnF3bWZxejF3ZTB0N2x6eDhscDJ6azJqZHpuajg1OGJxOSZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/RyXROeon3phTwej2x6/giphy.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://i.giphy.com/media/v1.Y2lkPTc5MGI3NjExYmVvN3RxdnF3bWZxejF3ZTB0N2x6eDhscDJ6azJqZHpuajg1OGJxOSZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/RyXROeon3phTwej2x6/giphy.gif" alt="Walking through grass during moonlit night"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I just love the way the light interacts with the grass (I made the stylistic choice of turning off shadows from the point light, which makes the grass look better, particularly at night). The state of the visuals is inspiring a lot of gameplay brainstorming these days...&lt;/p&gt;

&lt;h2&gt;
  
  
  What's next
&lt;/h2&gt;

&lt;p&gt;As impressive as this project is getting, there's still a decent performance hit to simulating wind on the grass. Recently, Bevy introduced the ability to spawn meshes only in the Render world. For static meshes, this can be quite useful, but for simulating wind, it still looks like I'm headed towards a custom render pipeline writing my own grass shader. This would involve moving wind simulation off of the CPU and onto the GPU per some wgsl shader file. Other grass optimizations include making grass blades wider farther away while also having less grass density. This would reduce vertex count but minimally affect the visuals. However, it can also only really be accomplished through a shader.&lt;/p&gt;

&lt;p&gt;Another major improvement that could come down the line is in terrain generation. I can at least update the regeneration to be asynchronous, but as part of moving off of the deprecated Plane struct, the successor of which lacks control over size and subdivisions, I will need to reconsider my approach.&lt;/p&gt;

&lt;p&gt;Additional features I'm looking to add are procedurally generated trees, paths, and points of interest, setting the stage for the open world gameplay I'd be introducing. This would require implementing further control over grass generation so that grass doesn't grow on paths, near tree roots, or anywhere else it shouldn't.&lt;/p&gt;

&lt;p&gt;Finally, as this project is increasing in visual quality and complexity, I'm finding low-resolution giphys to be insufficient in showcasing the results of my work. I'm considering transitioning these logs to Youtube in a vlog format, but am unsure about the added workload of writing a script, narrating, and editing videos. Regardless of how I document my progress, you can always keep up with the latest development by starring my &lt;a href="https://github.com/mikeam565/first-game" rel="noopener noreferrer"&gt;repo&lt;/a&gt;!&lt;/p&gt;

&lt;p&gt;That's all for this log. Please leave any comments if anything requires clarification, and thanks for reading!&lt;/p&gt;

</description>
      <category>rust</category>
      <category>bevy</category>
      <category>gamedev</category>
      <category>procedural</category>
    </item>
    <item>
      <title>Rust Game Dev Log #4: Terrain Generation!</title>
      <dc:creator>Michael Mironidis</dc:creator>
      <pubDate>Wed, 14 Feb 2024 04:11:39 +0000</pubDate>
      <link>https://forem.com/mikeam565/rust-game-dev-log-4-terrain-generation-1njb</link>
      <guid>https://forem.com/mikeam565/rust-game-dev-log-4-terrain-generation-1njb</guid>
      <description>&lt;h2&gt;
  
  
  Big changes!
&lt;/h2&gt;

&lt;p&gt;This dev log will cover the changes I have made implementing terrain. I will save Rapier physics integration for when it is more relevant to the work I'm doing, but if you'd like to see how I got a rectangular player character to navigate the terrain with a 3rd person camera, check out the &lt;a href="https://github.com/mikeam565/first-game" rel="noopener noreferrer"&gt;repo&lt;/a&gt;. Anyway, let's get right into it!&lt;/p&gt;

&lt;h2&gt;
  
  
  Terrain
&lt;/h2&gt;

&lt;p&gt;My initial approach in generating terrain was much like how I handled grass. I programmatically built the mesh in a nested for loop for the width and height of the terrain, accounting for a constant &lt;code&gt;TILE_WIDTH&lt;/code&gt;. That's all fine, but I realized after I started writing this post that I was reinventing existing logic. Bevy already comes with Plane generation, and if you go into the Bevy code for &lt;code&gt;impl From&amp;lt;Plane&amp;gt; for Mesh&lt;/code&gt;, you can compare it to my code for generating the terrain to see that they were very similar. So, as of 2/13, I modified the code to instead use this Plane object. This is the terrain generation code within the &lt;code&gt;setup_terrain&lt;/code&gt; function:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

    let num_vertices: usize = (SUBDIVISIONS as usize + 2)*(SUBDIVISIONS as usize + 2);
    let height_map = perlin::terrain_perlin();
    let mut uvs: Vec&amp;lt;[f32;2]&amp;gt; = Vec::with_capacity(num_vertices);
    let mut mesh: Mesh = bevy::prelude::shape::Plane {
        size: PLANE_SIZE,
        subdivisions: SUBDIVISIONS
    }.into();
    // get positions
    let pos_attr = mesh.attribute_mut(Mesh::ATTRIBUTE_POSITION).unwrap();
    let VertexAttributeValues::Float32x3(pos_attr) = pos_attr else {
        panic!("Unexpected vertex format, expected Float32x3");
    };
    // modify y with height sampling
    for i in 0..pos_attr.len() {
        let pos = pos_attr.get_mut(i).unwrap();
        pos[1] = sample_terrain_height(&amp;amp;height_map, pos[0], pos[2]);
        uvs.push([pos[0]/(TILE_WIDTH as f32*TEXTURE_SCALE), pos[2]/(TILE_WIDTH as f32*TEXTURE_SCALE)]);
    };

    mesh.insert_attribute(Mesh::ATTRIBUTE_UV_0, uvs);

    let _ = mesh.generate_tangents();



&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;First, I get the height_map (which currently is a perlin noise map with a specific seed) and also instantiate the uvs vector. I instantiate the Plane and then modify its position attributes (just like we saw when updating grass for wind simulation) based off of a sampling function &lt;code&gt;sample_terrain_height&lt;/code&gt; which regulates how we sample the height perlin so that we can then consistently place grass on terrain correctly without the grass knowing where the terrain actually is. We also are populating our uvs vector which will determine the tiling of the texture we use for the terrain. The position attribute is modified in place, but for the uvs, it is easier to just replace the attribute with our new vector. Finally, we generate tangents for light simulation with our normal map. Following this code, we need to provide a Handle for the texture and normal map we use. This is accomplished in the following code:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

    let sampler_desc = ImageSamplerDescriptor {
        address_mode_u: ImageAddressMode::Repeat,
        address_mode_v: ImageAddressMode::Repeat,
        ..default()
    };
    let settings = move |s: &amp;amp;mut ImageLoaderSettings| {
        s.sampler = ImageSampler::Descriptor(sampler_desc.clone());
    };

    let texture_handle = asset_server.load_with_settings("terrain/rocky_soil.png", settings.clone());
    let normal_handle = asset_server.load_with_settings("terrain/rocky_soil_normal.png", settings);
    let terrain_material = StandardMaterial {
        base_color: Color::WHITE,
        base_color_texture: Some(texture_handle.clone()),
        normal_map_texture: Some(normal_handle.clone()),
        alpha_mode: AlphaMode::Opaque,
        double_sided: true,
        perceptual_roughness: 1.0,
        reflectance: 0.4,
        cull_mode: Some(Face::Back),
        flip_normal_map_y: true,
        ..default()
    };


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Here, we define a sampler descriptor that tiles images. We then move that into settings for loading images. Finally, we load our soil texture and normal using those settings, and use them in the creation of our &lt;code&gt;terrain_material&lt;/code&gt;. The way Bevy handles such a thing feels funky, but this code gets the job done. By the way, the texture is a seamless texture I downloaded for free from the internet, and the normal map I created in GIMP 2.0 using a normal map plugin (I will look into adding this to the grass to give it rounded normals for more depth).&lt;/p&gt;

&lt;p&gt;The rest of the code spawns in the terrain, as well as a placeholder plane that represents water. That is, water is represented by a plane that spawns in at some constant WATER_LEVEL.&lt;/p&gt;

&lt;h2&gt;
  
  
  Updating grass generation with the new terrain and water
&lt;/h2&gt;

&lt;p&gt;Now that we have terrain and cheap water, we need to update the grass to generate accordingly. This ends up being incredibly simple: In our &lt;code&gt;generate_grass&lt;/code&gt; function, instead of &lt;code&gt;let y = 0;&lt;/code&gt;, we have:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

let y = sample_terrain_height(&amp;amp;terrain_perlin, x_offset, z_offset) - 0.2; // minus small amount to avoid floating


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;code&gt;sample_terrain_height&lt;/code&gt; is a pure function, meaning that the same input always yields the same output, that we use for retrieving the height of the terrain. We subtract 0.2 from the result just to avoid edge cases where the grass might show its base (eg. steep inclines). Now we just need to not generate grass in water, which just means only calling &lt;code&gt;generate_single_blade_verts&lt;/code&gt; if the &lt;code&gt;y&lt;/code&gt; value above is greater than terrain::WATER_LEVEL.&lt;br&gt;
The end result is really starting to come together!&lt;/p&gt;

&lt;p&gt;Here's a still of the progress so far:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6pdek6o4pbhs2nxy4wd0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6pdek6o4pbhs2nxy4wd0.png" alt="Grass on hills"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here's a gif with the grass waving:&lt;br&gt;
&lt;a href="https://i.giphy.com/media/v1.Y2lkPTc5MGI3NjExYXhuajhvZTlseGtzdHR4Y2FiaHN1NnZ4dHgzNGdyMDZpY2E3MmhpOSZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/pDXFeqVBvnw8sqeoRQ/giphy.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://i.giphy.com/media/v1.Y2lkPTc5MGI3NjExYXhuajhvZTlseGtzdHR4Y2FiaHN1NnZ4dHgzNGdyMDZpY2E3MmhpOSZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/pDXFeqVBvnw8sqeoRQ/giphy.gif" alt="Grass on hills"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And here's a quick flyover of the terrain!&lt;br&gt;
&lt;a href="https://i.giphy.com/media/v1.Y2lkPTc5MGI3NjExNTUyeGpucnI0dWt3NGE5bXpvZWlrOWxiY210OTRwM25ieHQ4dG42NSZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/0ZxSBYSHStmVHOfNIe/giphy.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://i.giphy.com/media/v1.Y2lkPTc5MGI3NjExNTUyeGpucnI0dWt3NGE5bXpvZWlrOWxiY210OTRwM25ieHQ4dG42NSZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/0ZxSBYSHStmVHOfNIe/giphy.gif" alt="Flyover of terrain partially covered in grass"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Final notes
&lt;/h2&gt;

&lt;p&gt;I ended up going back and correcting some of the grass functions such as rotation because it was not using the y position of the vertex relative to the grass base for applying curvature, and I also reverted back to only 3 vertices for each grass blade until I figure out how to implement a custom rendering pipeline for the grass so that I can have far more blades on screen at a time. I also turned off the shooting of capsules at the closest red rectangle because I found it distracting. It is important to keep your scope small when doing solo gamedev, and having that system constantly firing was putting gameplay on my mind when I am not ready to work on that yet.&lt;/p&gt;

&lt;p&gt;In all, this is progressing much faster than I anticipated, thanks in part to the incredible team behind Bevy providing intuitive APIs for accomplishing all of this. At this rate, I'll hopefully have realistic enough natural environments complete soon, and then will shift focus to some much needed optimization, such as generating terrain in chunks dynamically relative to player position, a custom render pipeline for the grass to implement GPU instancing, etc. Don't forget to follow me to keep up with developments on this project!&lt;/p&gt;

</description>
      <category>rust</category>
      <category>bevy</category>
      <category>gamedev</category>
    </item>
    <item>
      <title>Rust Game Dev Log #3: Grass Part 3 | Color Gradient!</title>
      <dc:creator>Michael Mironidis</dc:creator>
      <pubDate>Sun, 28 Jan 2024 20:39:52 +0000</pubDate>
      <link>https://forem.com/mikeam565/rust-game-dev-log-3-grass-part-3-color-gradient-2k6j</link>
      <guid>https://forem.com/mikeam565/rust-game-dev-log-3-grass-part-3-color-gradient-2k6j</guid>
      <description>&lt;h2&gt;
  
  
  Welcome back!
&lt;/h2&gt;

&lt;p&gt;After a busy couple of months at work and a relaxing vacation, I'm back with another Rust Game Dev Log! Last time, we left off on realistic wind simulation. Today, I'll be giving our grass a realistic touch by introducing vertex coloring to create a gradient that gives off the appearance of being dried out by the sun.&lt;/p&gt;

&lt;p&gt;This will be a short one, since my work on grass was put aside for implementing physics into the game using rapier3d, basic player movement, and a third person camera, as well as updating projectile logic to point in the direction it is initially fired. A post or two for these topics will come next, so stay tuned!&lt;/p&gt;

&lt;p&gt;As always, there may be some changes I have made to the code that I miss, usually in the form of small, basic optimizations fixing fast-and-loose initial iterations of code, so be sure to check out the &lt;a href="https://github.com/mikeam565/first-game"&gt;repo&lt;/a&gt; for the latest code.&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting started
&lt;/h2&gt;

&lt;p&gt;Since we will be coloring our vertices of our grass, we will change the &lt;code&gt;base_color&lt;/code&gt; of our grass material to white so that when we color our vertices, we get the exact colors we specify.&lt;/p&gt;

&lt;p&gt;Now, to programmatically define vertex coloring, we will need to know the base position of the grass blade so that we can use the relative y position of the vertex to do our calculation, like in the &lt;code&gt;apply_wind&lt;/code&gt; function. Otherwise, grass generated at different heights would have different colors.&lt;br&gt;
So, we update &lt;code&gt;generate_grass_geometry&lt;/code&gt; to take a reference to &lt;code&gt;grass_offsets&lt;/code&gt; as an argument. We then add a &lt;code&gt;colors&lt;/code&gt; vector that stores an array of four f32 representing the rgba values of the vertices. We populate this variable with a new function &lt;code&gt;generate_vertex_colors&lt;/code&gt; that takes a reference to the vertex positions vector we've already created and the grass_offsets pointer we've passed, and generates rgba values based off the relative y of the vertex:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;fn generate_vertex_colors(positions: &amp;amp;Vec&amp;lt;[f32; 3]&amp;gt;, grass_offsets: &amp;amp;Vec&amp;lt;[f32; 3]&amp;gt;) -&amp;gt; Vec&amp;lt;[f32; 4]&amp;gt; {
    positions.iter().enumerate().map(|(i,[x,y,z])| {
        let [_, base_y, _] = grass_offsets.get(i).unwrap();
        let modified_y = *y - base_y;
        [0.03*modified_y + 0.07,0.128,0.106 * -modified_y, 1.]
    }).collect()
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The actual calculation of the rgba value is up to you! I picked two different rgba values with a color picker and just modelled it off of those. The end result, as seen in the header, is quite nice!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjuunjudh8acjbc2aw1ta.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjuunjudh8acjbc2aw1ta.png" alt="Grass with a green to yellow gradient from bottom to top" width="800" height="429"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I wanted a subtle effect, but for more dried out looking grass you can increase the 0.07 base r value to 0.14 and get this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fywuyubd6dgpmhugai6np.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fywuyubd6dgpmhugai6np.png" alt="Grass with a yellower green to orange-yellow gradient from bottom to top" width="800" height="428"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Really, you can do anything you want by playing with how the rgba values are calculated!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff4slhwt26ifz4u4834cv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff4slhwt26ifz4u4834cv.png" alt="Grass with a red to white/grey gradient from bottom to top" width="800" height="428"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That's a huge impact on the visuals for not a lot of additional code!&lt;/p&gt;

&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;The interface in which the color gradient is determined should, in the long-term, be improved to be able to simply pass in two rgba values, one for the base and one for the top, and calculate the vertex coloring accordingly. &lt;/p&gt;

&lt;p&gt;That's all for this post! Be sure to follow me here on dev.to to keep up to date with developments on this project, and I encourage you to fork, open issues, or just play around with the &lt;a href="https://github.com/mikeam565/first-game"&gt;repo&lt;/a&gt; yourselves!&lt;/p&gt;

</description>
      <category>rust</category>
      <category>gamedev</category>
      <category>bevy</category>
    </item>
    <item>
      <title>Rust Game Dev Log #2: Grass Part 2 | Wind Simulation!</title>
      <dc:creator>Michael Mironidis</dc:creator>
      <pubDate>Wed, 29 Nov 2023 02:08:27 +0000</pubDate>
      <link>https://forem.com/mikeam565/rust-game-dev-log-2-grass-part-2-wind-simulation-447</link>
      <guid>https://forem.com/mikeam565/rust-game-dev-log-2-grass-part-2-wind-simulation-447</guid>
      <description>&lt;h2&gt;
  
  
  Where we left off
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://dev.to/mikeam565/rust-game-dev-log-1-grass-part-1-generating-a-field-of-grass-1pb"&gt;Last time&lt;/a&gt;, we went over how to get grass physically modelled in a scene in Rust with Bevy. Now, we want to give life to this grass by giving it some movement! We are going to accomplish this very simply: We will sample Perlin noise to get the displacement of the grass vertices from their original positions. We will apply this displacement based on a vertex's y position. The closer to the base of the grass blade the vertex is, the less we apply that displacement.&lt;/p&gt;

&lt;p&gt;Observant readers of the last post will notice I ignored the y position when generating grass and just use 0 as a hardcoded base y coordinate. While writing this post, I refactored grass.rs to support generating grass blades at different y coordinates, for now hardcoding it to still be 0. What this refactor enables is programmatically generating grass on terrain with varying elevation. A topic for another post...&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting things up
&lt;/h2&gt;

&lt;p&gt;For our Perlin noise, we use the &lt;a href="https://docs.rs/noise/latest/noise/"&gt;noise&lt;/a&gt; crate. We create a new file in util, &lt;code&gt;perlin.rs&lt;/code&gt;, with the following code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;use bevy::prelude::*;
use noise::Perlin;

pub const WIND_SEED: u32 = 0;
pub const GRASS_HEIGHT_SEED: u32 = 1;
pub const TERRAIN_SEED: u32 = 127;
#[derive(Component)]
pub struct PerlinNoiseEntity {
    pub wind: Perlin,
    pub grass_height: Perlin,
    pub terrain: Perlin
}

impl PerlinNoiseEntity {
    pub fn new() -&amp;gt; Self {
        PerlinNoiseEntity {
            wind: Perlin::new(WIND_SEED),
            grass_height: Perlin::new(GRASS_HEIGHT_SEED),
            terrain: Perlin::new(TERRAIN_SEED)
        }
    }
}

pub fn setup_perlin(mut commands: Commands) {
    commands.spawn(
        PerlinNoiseEntity::new()
    );
}

pub struct PerlinPlugin;

impl Plugin for PerlinPlugin {
    fn build(&amp;amp;self, app: &amp;amp;mut App) {
        app.add_systems(Startup, setup_perlin);
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This module features the &lt;code&gt;PerlinNoiseEntity&lt;/code&gt;, which wraps the perlins with their respective seeds that we will use for sampling. As we saw in the previous post, we set up a plugin that adds this struct to the world on Startup so we can have access to it across screen updates for consistent wind simulation.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Grass Update System
&lt;/h2&gt;

&lt;p&gt;In &lt;code&gt;grass.rs&lt;/code&gt;, we add a new function, &lt;code&gt;update_grass&lt;/code&gt;, and register it to the &lt;code&gt;GrassPlugin&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;impl Plugin for GrassPlugin {
    fn build(&amp;amp;self, app: &amp;amp;mut App) {
        app.add_systems(Startup, add_grass);
        app.add_systems(Update, update_grass);
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In &lt;code&gt;update_grass&lt;/code&gt;, we have our logic for all dynamic grass behavior. For now, it's just wind, but in the future it could be displacement based off of a player walking over it, fire, etc.&lt;br&gt;
Here's the code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;fn update_grass(
    mut commands: Commands,
    mut meshes: ResMut&amp;lt;Assets&amp;lt;Mesh&amp;gt;&amp;gt;,
    mut materials: ResMut&amp;lt;Assets&amp;lt;StandardMaterial&amp;gt;&amp;gt;,
    mut grass: Query&amp;lt;(&amp;amp;Handle&amp;lt;Mesh&amp;gt;, &amp;amp;Grass)&amp;gt;,
    mut perlin: Query&amp;lt;&amp;amp;PerlinNoiseEntity&amp;gt;,
    time: Res&amp;lt;Time&amp;gt;
) {
    let time = time.elapsed_seconds_f64();
    let (mesh_handle, grass) = grass.get_single_mut().unwrap();
    let mesh = meshes.get_mut(mesh_handle).unwrap();
    let perlin = perlin.get_single_mut().unwrap();
    apply_wind(mesh, grass, perlin, time);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Fairly straightforward, since the meat of the wind simulation is in &lt;code&gt;apply_wind&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;fn apply_wind(mesh: &amp;amp;mut Mesh, grass: &amp;amp;Grass, perlin: &amp;amp;PerlinNoiseEntity, time: f64) {
    let wind_perlin = perlin.wind;
    let pos_attr = mesh.attribute_mut(Mesh::ATTRIBUTE_POSITION).unwrap();
    let VertexAttributeValues::Float32x3(pos_attr) = pos_attr else {
        panic!("Unexpected vertex format, expected Float32x3");
    };

    for i in 0..pos_attr.len() {
        let pos = pos_attr.get_mut(i).unwrap(); // current vertex positions
        let initial = grass.initial_vertices.get(i).unwrap(); // initial vertex positions
        let grass_pos = grass.initial_positions.get(i).unwrap(); // initial grass positions

        let [x, y, z] = grass_pos;

        let relative_vertex_height = pos[1] - y;

        let curve_amount = WIND_STRENGTH * (sample_noise(&amp;amp;wind_perlin, *x, *z, time) * (relative_vertex_height.powf(CURVE_POWER)/GRASS_HEIGHT.powf(CURVE_POWER)));
        pos[0] = initial.x + curve_amount;
        pos[2] = initial.z + curve_amount;
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To summarize what happens in &lt;code&gt;apply_wind&lt;/code&gt;, we:&lt;br&gt;
(1) Grab the wind_perlin for sampling&lt;br&gt;
(2) Get the vertex positions in the mesh&lt;br&gt;
(3) Unwrap the positions into an array of f32 of length 3&lt;br&gt;
(4) In a loop over every current vertex position in the mesh, sample the perlin noise using the current time and the x and z positions of the grass (wind kind of moves up and down terrain, so we don't use the y position of the grass). We reduce the magnitude of the noise based off the y position of the vertex relative to the grass blade's base. A &lt;code&gt;CURVE_POWER&lt;/code&gt; of 1.0 gives the grass a linear displacement. Values greater than 1.0 give a curvature more realistic for taller grass. The current x and z positions are updated with the sum of the initial x and z positions and this &lt;code&gt;curve_amount&lt;/code&gt;. This does mean the wind direction is hardcoded, so another task would be to use perlin sampling for wind direction and modify the application of the displacement on the grass blades accordingly. Also, with greater values for &lt;code&gt;CURVE_POWER&lt;/code&gt; and &lt;code&gt;WIND_STRENGTH&lt;/code&gt;, it becomes apparent that the grass is growing and shrinking with the wind, so our approach has its limitations.&lt;/p&gt;

&lt;p&gt;Finally, the last piece is sampling the perlin noise in &lt;code&gt;sample_noise&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;fn sample_noise(perlin: &amp;amp;Perlin, x: f32, z: f32, time: f64) -&amp;gt; f32 {
    WIND_LEAN + perlin.get([WIND_SPEED * time + (x as f64/WIND_CONSISTENCY), WIND_SPEED * time + (z as f64/WIND_CONSISTENCY)]) as f32
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;First, we apply a base WIND_LEAN (how much we want our grass blades to already lean in a certain direction due to the wind). Then, we call &lt;code&gt;perlin.get&lt;/code&gt;, which takes a sample of 2d perlin noise. Without time, each blade of grass is &lt;em&gt;assigned&lt;/em&gt; based on it's x and z coordinates directly onto the 2d perlin "grid". We then use the increasing &lt;code&gt;time&lt;/code&gt; value to "scroll" along the grid. I am choosing to scroll diagonally, but you can just as easily remove the term with &lt;code&gt;time&lt;/code&gt; from one of the dimensions of the sample to scroll in either x or z. Finally, we have WIND_SPEED to control how fast we scroll through the noise. &lt;/p&gt;

&lt;p&gt;The end result is not too shabby!&lt;br&gt;
&lt;a href="https://i.giphy.com/media/v1.Y2lkPTc5MGI3NjExMHE4Nms0c3JiZzFuMmwzZDJ4dnd5bTc4ZjBoam91bGljZmw5MzJxOCZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/BQ7wOTTFN0xVVoSQHm/giphy.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://i.giphy.com/media/v1.Y2lkPTc5MGI3NjExMHE4Nms0c3JiZzFuMmwzZDJ4dnd5bTc4ZjBoam91bGljZmw5MzJxOCZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/BQ7wOTTFN0xVVoSQHm/giphy.gif" alt="wind_gif_1" width="480" height="259"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And here's one from a top view which gives a better view of the underlying perlin noise:&lt;br&gt;
&lt;a href="https://i.giphy.com/media/v1.Y2lkPTc5MGI3NjExdHNxbGQwMjNwMnBqYnRuODZ4NnBoNzA2ODEwOW1kcnVhcjNhdjV0ciZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/AyuW4U7Z2o08mLiAw7/giphy.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://i.giphy.com/media/v1.Y2lkPTc5MGI3NjExdHNxbGQwMjNwMnBqYnRuODZ4NnBoNzA2ODEwOW1kcnVhcjNhdjV0ciZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/AyuW4U7Z2o08mLiAw7/giphy.gif" alt="wind_gif_2" width="480" height="259"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;With that, we have a functioning, realistic-looking wind simulation on grass using perlin noise!&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion and Final Thoughts
&lt;/h2&gt;

&lt;p&gt;This has been a fun deliverable and is quickly approaching a passable condition. For further grass-related work, in no particular order, I intend to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Write more realistic calculation of displacement from wind to account for stronger wind conditions and solve grass growing and shrinking.&lt;/li&gt;
&lt;li&gt;Apply a rounded normal map to make these flat grasses look 3d.&lt;/li&gt;
&lt;li&gt;Place grass on a terrain with varying elevation&lt;/li&gt;
&lt;li&gt;Dynamically render grass in chunks, with closer grass being denser than farther chunks until a certain render distance where the grass no longer appears&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As always, check out the &lt;a href="https://github.com/mikeam565/first-game"&gt;repo&lt;/a&gt; for this project to keep up to date with the latest developments, and feel free to leave comments or suggestions!&lt;/p&gt;

</description>
      <category>rust</category>
      <category>bevy</category>
      <category>gamedev</category>
    </item>
    <item>
      <title>Rust Game Dev Log #1: Grass Part 1 | Generating a Field of Grass!</title>
      <dc:creator>Michael Mironidis</dc:creator>
      <pubDate>Sun, 19 Nov 2023 19:33:32 +0000</pubDate>
      <link>https://forem.com/mikeam565/rust-game-dev-log-1-grass-part-1-generating-a-field-of-grass-1pb</link>
      <guid>https://forem.com/mikeam565/rust-game-dev-log-1-grass-part-1-generating-a-field-of-grass-1pb</guid>
      <description>&lt;h2&gt;
  
  
  Background
&lt;/h2&gt;

&lt;p&gt;Gamedev is a great way for software engineers to scratch that itch of solving interesting problems with satisfying visual feedback. It's one thing to write Dijkstra's algorithm for a Leetcode question, but it's another to implement A* pathfinding for a videogame and see a player character move accordingly.  &lt;/p&gt;

&lt;p&gt;A few months ago I had seen some videos about wave-function collapse and saw one of the greatest implementations of it in the game Townscaper (the only App Store purchase I've ever made!), so I tried my hand at implementing it in Python with Pygame for a very simple &lt;a href="https://github.com/mikeam565/wave-function-collapse" rel="noopener noreferrer"&gt;terrain generator&lt;/a&gt;. I played around with unpopulated tile selection, and instead of using the tile with the least possible options, I let the user draw terrain and then automatically draw cliffs where incompatible tiles are adjacent. It was a quick Python project with Pygame, and it scratched that aforementioned itch for me. &lt;/p&gt;

&lt;p&gt;As I added features to it, however, I realized I was more serious about gamedev than I initially expected. I had dabbled with gamedev long ago as a middle schooler with Unity, but that never got very far since I never bothered to really learn how to code. Now, with a degree in CS + Math under my belt (and whether relevant or not, over a year of enterprise software engineering experience), I had come full circle. Time to try again.  &lt;/p&gt;

&lt;p&gt;Anyone with experience in gamedev would expect me to have redownloaded Unity, or Unreal Engine, or even tried Godot, but as the title gives away, I ended up merging my gamedev interest with my interest in Rust. A mature gamedev environment like Unity and Unreal already came with solutions to many of the problems I was interested in solving myself (eg. Grass!). That's not to say I'll never use either of those, but Rust's fledgling gamedev ecosystem makes it an exciting place to do work and contribute to. My goal with the Rust Game Dev Log is to help others in their own Rust game dev journeys and trigger conversations about how to best solve each of the problems I tackle. Ultimately, I am not necessarily doing this to make a game myself, but to solve fun problems and contribute to a growing commmunity.  &lt;/p&gt;

&lt;h2&gt;
  
  
  Getting Started
&lt;/h2&gt;

&lt;p&gt;I initially started with the subject of grass because of a &lt;a href="https://youtu.be/bp7REZBV4P4?si=6yFrxdCAy5v-EB-v" rel="noopener noreferrer"&gt;video&lt;/a&gt; from SimonDev on the same topic. That video led me to another gamedev youtuber, &lt;a href="https://www.youtube.com/@Acerola_t" rel="noopener noreferrer"&gt;Acerola&lt;/a&gt; who has solved the grass problem himself, as well as others like realistic water that I also plan to try out.  &lt;/p&gt;

&lt;p&gt;So I opened up a terminal, ran &lt;code&gt;cargo new &amp;lt;project-name&amp;gt;&lt;/code&gt; and installed some dependencies, namely &lt;a href="https://bevyengine.org/" rel="noopener noreferrer"&gt;Bevy&lt;/a&gt; as the underlying game engine, and the &lt;a href="https://docs.rs/noise/latest/noise/" rel="noopener noreferrer"&gt;Noise crate&lt;/a&gt; we would need for Perlin noise. I also added the &lt;a href="https://docs.rs/bevy-inspector-egui/latest/bevy_inspector_egui/" rel="noopener noreferrer"&gt;Bevy Inspector EGUI&lt;/a&gt; crate for inspecting entities in my scene and playing around with variables on the fly, and the &lt;a href="https://docs.rs/bevy_atmosphere/latest/bevy_atmosphere/" rel="noopener noreferrer"&gt;Bevy Atmosphere&lt;/a&gt; crate so I had something resembling the sky while I worked on my grass.  &lt;/p&gt;

&lt;h3&gt;
  
  
  Quick aside: What is Bevy?
&lt;/h3&gt;

&lt;p&gt;For anyone that hasn't used Bevy before, I highly recommend reading their already clear and concise &lt;a href="https://bevyengine.org/learn/book/getting-started/ecs/" rel="noopener noreferrer"&gt;quickstart&lt;/a&gt;, but in summary: Bevy is an ECS (Entity, Component, System) based game engine. Basically, instead of OOP with lots of global variables and logic tightly coupled to those objects, Bevy uses Entities with bundled Components that are acted upon by Systems that you register with the main app. These systems can then query for components directly, which allows for logic to be completely decoupled from data. It's super intuitive and it feels like magic.  &lt;/p&gt;

&lt;h3&gt;
  
  
  Refocus
&lt;/h3&gt;

&lt;p&gt;To get some basic scene going, I used some of the &lt;a href="https://github.com/bevyengine/bevy/blob/main/examples/3d/3d_scene.rs" rel="noopener noreferrer"&gt;examples&lt;/a&gt; on Bevy's github. I swapped out the point light for a directional light, made a flat plane that would serve as the terrain for now, and messed around a bit with the cubes to make one cube shoot at the other cubes with gravity and some compensation for it. (I am debating on whether to implement my own physics engine or to use &lt;a href="https://github.com/dimforge/bevy_rapier" rel="noopener noreferrer"&gt;Rapier&lt;/a&gt;, so I'll hold off on a blog post about game logic. For now, grass!)  &lt;/p&gt;

&lt;h2&gt;
  
  
  The first grass blade
&lt;/h2&gt;

&lt;p&gt;Keeping in line with my goal for this project, I wanted to generate the grass entirely from code. For more detailed meshes this obviously is not realistic, but for something simple like grass, it's easy. To draw our grass blade, I initially only used three vertices. I eventually ended up adding 4 more vertices to ultimately have a blade of grass modeled by 5 triangles.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

fn generate_single_blade_verts(x: f32, z: f32, blade_number: u32, blade_height: f32) -&amp;gt; (Vec&amp;lt;Vec3&amp;gt;, Vec&amp;lt;u32&amp;gt;) {
    let blade_number_shift = blade_number*GRASS_BLADE_VERTICES;
    // vertex transforms
    let t1 = Transform::from_xyz(x, 0.0, z);
    let t2 = Transform::from_xyz(x+GRASS_WIDTH, 0.0, z);
    let t3 = Transform::from_xyz(x, blade_height/3.0, z);
    let t4 = Transform::from_xyz(x+GRASS_WIDTH, blade_height/3.0, z);
    let t5 = Transform::from_xyz(x, 2.0*blade_height/3.0, z);
    let t6 = Transform::from_xyz(x + GRASS_WIDTH, 2.0*blade_height/3.0, z);
    let t7 = Transform::from_xyz(x+(GRASS_WIDTH/2.0), blade_height, z);

    let mut transforms = vec![t1,t2,t3,t4,t5,t6,t7];

    // // physical randomization of grass blades
    // rotate grass randomly around y
    apply_y_rotation(&amp;amp;mut transforms, x, z);

    // curve the grass all one way
    apply_curve(&amp;amp;mut transforms, x, z);

    // rotate grass again
    apply_y_rotation(&amp;amp;mut transforms, x, z);

    let verts: Vec&amp;lt;Vec3&amp;gt; = transforms.iter().map(|t| t.translation).collect();

    let indices: Vec&amp;lt;u32&amp;gt; = vec![
        blade_number_shift+0, blade_number_shift+1, blade_number_shift+2,
        blade_number_shift+2, blade_number_shift+1, blade_number_shift+3,
        blade_number_shift+2, blade_number_shift+3, blade_number_shift+4,
        blade_number_shift+4, blade_number_shift+2, blade_number_shift+3,
        blade_number_shift+4, blade_number_shift+3, blade_number_shift+5,
        blade_number_shift+4, blade_number_shift+5, blade_number_shift+6,
    ];
    (verts, indices)
}


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Here you can see it's pretty clear how a grass blade is generated:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;At some given x and z (y is up and down), with some &lt;code&gt;blade_height&lt;/code&gt;, generate grass blade number &lt;code&gt;blade_number&lt;/code&gt; by defining 7 vertices relative to x, z, the passed in &lt;code&gt;blade_height&lt;/code&gt;, and the constant &lt;code&gt;GRASS_WIDTH&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;apply_y_rotation&lt;/code&gt; to curve the blade around the y axis&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;apply_curve&lt;/code&gt; curves the blade of grass in a defined direction by some random amount&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;apply_y_rotation&lt;/code&gt; again so that the grass blades don't all curve in the same direction&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;We pass in the &lt;code&gt;blade_height&lt;/code&gt; instead of using a constant because we don't want a constant blade height, that's unrealistic.&lt;/p&gt;

&lt;p&gt;We pass in the &lt;code&gt;blade_number&lt;/code&gt; because we're not defining a new Mesh for each blade of grass, instead adding these vertices to a giant grass mesh. Defining each blade of grass as its own mesh tanks your fps and is completely unnecessary. As a result, we needed to keep track of which blade of grass the verts corresponded to so that we could provide the correct ordering, or indices.&lt;/p&gt;

&lt;h3&gt;
  
  
  Quick aside: Vertex ordering
&lt;/h3&gt;

&lt;p&gt;In most rendering frameworks (if not all), a triangle's face is rendered on the side of the vertices where their ordering is counterclockwise.&lt;br&gt;
For example, in 2d space, if vertices a, b, and c are:  &lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

a = [0,0]
b = [1,0]
c = [0,1]


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;The ordering a, b, c will render the triangle facing "you" because a to b to c is counterclockwise, but the ordering a, c, b will not.&lt;/p&gt;

&lt;h2&gt;
  
  
  Generating a field of grass
&lt;/h2&gt;

&lt;p&gt;So we now have logic to generate a single blade of grass's vertices and indices. How do we generate a whole field?&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

pub fn generate_grass(
    commands: &amp;amp;mut Commands,
    meshes: &amp;amp;mut ResMut&amp;lt;Assets&amp;lt;Mesh&amp;gt;&amp;gt;,
    materials: &amp;amp;mut ResMut&amp;lt;Assets&amp;lt;StandardMaterial&amp;gt;&amp;gt;,
) {
    let mut grass_offsets = vec![];
    let mut rng = thread_rng();
    let mut mesh = if !ENABLE_WIREFRAME { Mesh::new(PrimitiveTopology::TriangleList) } else { Mesh::new(PrimitiveTopology::LineList)};
    let mut all_verts: Vec&amp;lt;Vec3&amp;gt; = vec![];
    let mut all_indices: Vec&amp;lt;u32&amp;gt; = vec![];
    let mut blade_number = 0;
    let perlin = PerlinNoiseEntity::new();
    let height_perlin = perlin.grass_height;
    for i in 0..NUM_GRASS_X {
        let x = i as f32;
        for j in 0..NUM_GRASS_Y {
            let z = j as f32;
            let rand1 = if GRASS_OFFSET!=0.0 {rng.gen_range(-GRASS_OFFSET..GRASS_OFFSET)} else {0.0};
            let rand2 = if GRASS_OFFSET!=0.0 {rng.gen_range(-GRASS_OFFSET..GRASS_OFFSET)} else {0.0};
            let x_offset = x * GRASS_SPACING + rand1;
            let z_offset = z * GRASS_SPACING + rand2;
            let blade_height = GRASS_HEIGHT + (height_perlin.get([x_offset as f64, z_offset as f64]) as f32 * GRASS_HEIGHT_VARIATION_FACTOR);
            // let blade_height = GRASS_HEIGHT;
            let (mut verts, mut indices) = generate_single_blade_verts(x_offset, z_offset, blade_number, blade_height);
            for _ in 0..verts.len() {
                grass_offsets.push([x_offset,z_offset]);
            }
            all_verts.append(&amp;amp;mut verts);
            all_indices.append(&amp;amp;mut indices);
            blade_number += 1;
        }
    }

    generate_grass_geometry(&amp;amp;all_verts, all_indices, &amp;amp;mut mesh);

    let grass_material = StandardMaterial {
        base_color: Color::DARK_GREEN.into(),
        double_sided: false,
        perceptual_roughness: 0.1,
        diffuse_transmission: 0.5,
        reflectance: 0.0,
        cull_mode: None,
        ..default()
    };
    commands.spawn(PbrBundle {
        mesh: meshes.add(mesh),
        material: materials.add(grass_material),
        transform: Transform::from_xyz(-((NUM_GRASS_X/8) as f32), 0.0, -((NUM_GRASS_Y/8) as f32)).with_scale(Vec3::new(1.0,GRASS_SCALE_FACTOR,1.0)),
        ..default()
    })
    .insert(Grass {
        initial_vertices: all_verts,
        initial_positions: grass_offsets
    });
}


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Now, this &lt;em&gt;looks&lt;/em&gt; like a lot, but it can be broken down simply:&lt;/p&gt;

&lt;p&gt;First, this function is actually called from within the terrain generation System (EC*S*), since I intend on having the grass generate based off of the terrain. As a result, I pass a mutable reference to the &lt;code&gt;Commands&lt;/code&gt; and &lt;code&gt;Mesh&lt;/code&gt; and &lt;code&gt;StandardMaterial&lt;/code&gt; assets that the terrain System grabs in its args.  &lt;/p&gt;

&lt;h3&gt;
  
  
  Quick aside: Defining a System in Bevy
&lt;/h3&gt;

&lt;p&gt;A System in Bevy can be roughly described as logic that acts upon data. In practice it is just a function that has defined some args that it expects to receive from Bevy and gets registered with the app. In the case of terrain, it's just the &lt;code&gt;Commands&lt;/code&gt; and &lt;code&gt;Mesh&lt;/code&gt; and &lt;code&gt;StandardMaterial&lt;/code&gt; Assets. &lt;code&gt;Commands&lt;/code&gt; allows you to make changes to the game world such as spawn or despawn entities. The pointers to the meshes and materials allows you to add or modify meshes and materials in the game world. Another type of argument you can have is a &lt;code&gt;Query&amp;lt;(Components to fetch), (Filters)&amp;gt;&lt;/code&gt; argument. This allows you to get any combination of entities and even optionally filter them. You will see this being used in the logic that handles wind simulation.&lt;/p&gt;

&lt;h3&gt;
  
  
  Refocus
&lt;/h3&gt;

&lt;p&gt;Next, &lt;code&gt;grass_offsets&lt;/code&gt; defines the initial positions in (x,z) of all of the grass blades, and &lt;code&gt;all_verts&lt;/code&gt; defines the initial vertex coordinates of each of the vertices. Knowing the initial state of the grass is crucial for how we handle simulating wind (and other actions) upon the grass.&lt;/p&gt;

&lt;p&gt;I have also defined a &lt;code&gt;PerlinNoiseEntity&lt;/code&gt; that wraps all of the perlins we use. In this case I just instantiate a new one, but for real-time game updates, I have one spawned into the game world through the &lt;code&gt;PerlinPlugin&lt;/code&gt; that can be queried for to sample from instead of constantly instantiating a new one.&lt;/p&gt;

&lt;h3&gt;
  
  
  Quick aside: Plugins?
&lt;/h3&gt;

&lt;p&gt;Plugins are just a way for you to "plug" Systems into the app without having to directly add them in the main function.&lt;br&gt;
Eg. Here's my main function:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

fn main() {
    std::env::set_var("RUST_BACKTRACE", "1");

    App::new()
        .insert_resource(AmbientLight {
            brightness: 0.5,
            color: Color::AZURE,
            ..default()
        })
        .add_plugins((
            DefaultPlugins,
            WorldInspectorPlugin::new(),
            LogDiagnosticsPlugin::default(),
            FrameTimeDiagnosticsPlugin::default(),
            AtmospherePlugin,
            util::camera::CameraPlugin,
            util::lighting::LightingPlugin,
            util::perlin::PerlinPlugin,
            ent::terrain::TerrainPlugin,
            ent::grass::GrassPlugin,
            ent::player::PlayerPlugin,
            ent::enemy::EnemyPlugin,
            ent::projectiles::ProjectilePlugin,
        ))
        .register_type::&amp;lt;ent::player::Player&amp;gt;()
        .register_type::&amp;lt;ent::projectiles::BasicProjectile&amp;gt;()
        .run();
}


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;As you can see, a bunch of plugins defining game logic (as well as some for development).&lt;br&gt;
In perlin.rs, I have:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

pub struct PerlinPlugin;

impl Plugin for PerlinPlugin {
    fn build(&amp;amp;self, app: &amp;amp;mut App) {
        app.add_systems(Startup, setup_perlin);
    }
}


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Which just says that the &lt;code&gt;PerlinPlugin&lt;/code&gt; adds the system &lt;code&gt;setup_perlin&lt;/code&gt; to the Startup schedule. And here's &lt;code&gt;setup_perlin&lt;/code&gt;:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

pub fn setup_perlin(mut commands: Commands) {
    commands.spawn(
        PerlinNoiseEntity::new()
    );
}


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Which just spawns a &lt;code&gt;PerlinNoiseEntity&lt;/code&gt; (which is just a wrapper around the Perlin struct in the noise crate) that I can Query for whenever I need to sample the same perlin noise (I keep different instances of Perlin with different seeds for each use case, eg. wind, grass height, and soon, terrain).&lt;/p&gt;

&lt;h2&gt;
  
  
  Refocus
&lt;/h2&gt;

&lt;p&gt;With my outer Vectors keeping track of all of the starting positions, vertices, and indices of the grass, I use a nested for loop to generate grass for each (x,z), offsetting the position of the grass by some offset and randomizing the height. I add the single blade's generated vertices and indices to the outer vectors, and then call &lt;code&gt;generate_grass_geometry&lt;/code&gt; to populate the actual mesh. This function just sets the positions, normals, and uvs of the mesh. Finally, I define a new material &lt;code&gt;grass_material&lt;/code&gt;, and then spawn the grass mesh in. In spawning it in, I bundle it with a &lt;code&gt;Grass&lt;/code&gt; struct so I can easily query for it. Additionally, the &lt;code&gt;Grass&lt;/code&gt; struct stores the initial state of the grass vertices, so I can implement wind and other changes upon the grass.&lt;/p&gt;

&lt;p&gt;The result is not bad for two days' work!&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F41439dmhiaqtenhtibdg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F41439dmhiaqtenhtibdg.png" alt="Scene with three rectangular prisms in a field of grass"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  There's more!
&lt;/h2&gt;

&lt;p&gt;This wraps up the first blog post for this project. I already have the wind functionality implemented, so the next post on sampling perlin noise to implement wind is coming soon! If you can't wait for that, or if you want to see more code, everything I'm working on related to this project is available at this &lt;a href="https://github.com/mikeam565/first-game" rel="noopener noreferrer"&gt;Github repo&lt;/a&gt;!&lt;/p&gt;

</description>
      <category>rust</category>
      <category>gamedev</category>
      <category>bevy</category>
    </item>
  </channel>
</rss>
