We left off last article rendering a plane with a nice grid on it. It should look something like this.
Grid Box
This is starting to look pretty good. Our shader antialises the lines very smoothly over our UV coordinates and we are able to easily draw grids. Let’s make it even nicer though, let’s render a box instead of a plane! In order to do this, we need to write some code to build a mesh for a box with the proper UV coordinates.
pubstructGridBox{size: Vec3,subdivisions: UVec3,}implFrom<GridBox>forMesh{fnfrom(value: GridBox)-> Self{letx_vertex_count=value.subdivisions.x+2;lety_vertex_count=value.subdivisions.y+2;letz_vertex_count=value.subdivisions.z+2;letnum_vertices=(((z_vertex_count*x_vertex_count)+(z_vertex_count*y_vertex_count)+(x_vertex_count*y_vertex_count))*2)asusize;letnum_indices=((((z_vertex_count-1)*(x_vertex_count-1))+((z_vertex_count-1)*(y_vertex_count-1))+((x_vertex_count-1)*(y_vertex_count-1)))*6*2)asusize;letx_up=Vec3::X.to_array();letx_down=(Vec3::X*-1.0).to_array();lety_up=Vec3::Y.to_array();lety_down=(Vec3::Y*-1.0).to_array();letz_up=Vec3::Z.to_array();letz_down=(Vec3::Z*-1.0).to_array();letmutpositions: Vec<[f32;3]>=Vec::with_capacity(num_vertices);letmutnormals: Vec<[f32;3]>=Vec::with_capacity(num_vertices);letmutuvs: Vec<[f32;2]>=Vec::with_capacity(num_vertices);letmutindices: Vec<u32>=Vec::with_capacity(num_indices);letmutindex_offset: u32=0;// Front Mesh
forzin0..z_vertex_count{forxin0..x_vertex_count{lettx=xasf32/(x_vertex_count-1)asf32;letty=1.0asf32;lettz=zasf32/(z_vertex_count-1)asf32;letux=(x%2)asf32;letuz=(z%2)asf32;positions.push([(-0.5+tx)*value.size.x,(-0.5+ty)*value.size.y,(-0.5+tz)*value.size.z,]);normals.push(y_up);uvs.push([ux,uz]);}}// Front Indices
forzin0..z_vertex_count-1{forxin0..x_vertex_count-1{letquad=z*x_vertex_count+x;indices.push(quad+x_vertex_count+1);indices.push(quad+1);indices.push(quad+x_vertex_count);indices.push(quad);indices.push(quad+x_vertex_count);indices.push(quad+1);}}// Back Mesh
index_offset=positions.len()asu32;forzin0..z_vertex_count{forxin0..x_vertex_count{lettx=xasf32/(x_vertex_count-1)asf32;letty=0.0asf32;lettz=zasf32/(z_vertex_count-1)asf32;letux=(x%2)asf32;letuz=(z%2)asf32;positions.push([(-0.5+tx)*value.size.x,(-0.5+ty)*value.size.y,(-0.5+tz)*value.size.z,]);normals.push(y_down);uvs.push([ux,uz]);}}// Back Indices
forzin0..z_vertex_count-1{forxin0..x_vertex_count-1{letquad=index_offset+z*x_vertex_count+x;indices.push(quad+1);indices.push(quad+x_vertex_count+1);indices.push(quad+x_vertex_count);indices.push(quad+x_vertex_count);indices.push(quad);indices.push(quad+1);}}// Top Mesh
index_offset=positions.len()asu32;foryin0..y_vertex_count{forxin0..x_vertex_count{lettx=xasf32/(x_vertex_count-1)asf32;letty=yasf32/(y_vertex_count-1)asf32;lettz=1.0asf32;letux=(x%2)asf32;letuy=(y%2)asf32;positions.push([(-0.5+tx)*value.size.x,(-0.5+ty)*value.size.y,(-0.5+tz)*value.size.z,]);normals.push(z_up);uvs.push([ux,uy]);}}// Top Indices
foryin0..y_vertex_count-1{forxin0..x_vertex_count-1{letquad=index_offset+y*x_vertex_count+x;indices.push(quad+1);indices.push(quad+x_vertex_count+1);indices.push(quad+x_vertex_count);indices.push(quad+x_vertex_count);indices.push(quad);indices.push(quad+1);}}// Bottom Mesh
index_offset=positions.len()asu32;foryin0..y_vertex_count{forxin0..x_vertex_count{lettx=xasf32/(x_vertex_count-1)asf32;letty=yasf32/(y_vertex_count-1)asf32;lettz=0.0asf32;letux=(x%2)asf32;letuy=(y%2)asf32;positions.push([(-0.5+tx)*value.size.x,(-0.5+ty)*value.size.y,(-0.5+tz)*value.size.z,]);normals.push(z_down);uvs.push([ux,uy]);}}// Bottom Indices
foryin0..y_vertex_count-1{forxin0..x_vertex_count-1{letquad=index_offset+y*x_vertex_count+x;indices.push(quad+x_vertex_count+1);indices.push(quad+1);indices.push(quad+x_vertex_count);indices.push(quad+x_vertex_count);indices.push(quad+1);indices.push(quad);}}// Right Mesh
index_offset=positions.len()asu32;foryin0..y_vertex_count{forzin0..z_vertex_count{lettx=1.0asf32;letty=yasf32/(y_vertex_count-1)asf32;lettz=zasf32/(z_vertex_count-1)asf32;letuz=(z%2)asf32;letuy=(y%2)asf32;positions.push([(-0.5+tx)*value.size.x,(-0.5+ty)*value.size.y,(-0.5+tz)*value.size.z,]);normals.push(x_up);uvs.push([uz,uy]);}}// Right Indices
foryin0..y_vertex_count-1{forzin0..z_vertex_count-1{letquad=index_offset+y*x_vertex_count+z;indices.push(quad+z_vertex_count+1);indices.push(quad+1);indices.push(quad+z_vertex_count);indices.push(quad+z_vertex_count);indices.push(quad+1);indices.push(quad);}}// Left Mesh
index_offset=positions.len()asu32;foryin0..y_vertex_count{forzin0..z_vertex_count{lettx=0.0asf32;letty=yasf32/(y_vertex_count-1)asf32;lettz=zasf32/(z_vertex_count-1)asf32;letuz=(z%2)asf32;letuy=(y%2)asf32;positions.push([(-0.5+tx)*value.size.x,(-0.5+ty)*value.size.y,(-0.5+tz)*value.size.z,]);normals.push(x_down);uvs.push([uz,uy]);}}// Left Indices
foryin0..y_vertex_count-1{forzin0..z_vertex_count-1{letquad=index_offset+y*x_vertex_count+z;indices.push(quad+1);indices.push(quad+z_vertex_count+1);indices.push(quad+z_vertex_count);indices.push(quad+1);indices.push(quad+z_vertex_count);indices.push(quad);}}Mesh::new(PrimitiveTopology::TriangleList).with_indices(Some(Indices::U32(indices))).with_inserted_attribute(Mesh::ATTRIBUTE_POSITION,positions).with_inserted_attribute(Mesh::ATTRIBUTE_NORMAL,normals).with_inserted_attribute(Mesh::ATTRIBUTE_UV_0,uvs)}}
Update Render Scene
Now that we have a nice way to build our meshes, let’s update the scene and see what it looks like.
implTextGem{...fnstartup(mutcommands: Commands,mutmeshes: ResMut<Assets<Mesh>>,mutgrid_materials: ResMut<Assets<ExtendedMaterial<StandardMaterial,GridMaterial>>>,){...// Replace Graph Paper with a box
// Grid Box
commands.spawn(MaterialMeshBundle{mesh: meshes.add(GridBox{size: Vec3::new(300.0,30.0,300.0),subdivisions: UVec3::new(10,0,10),}.into(),),material: grid_materials.add(ExtendedMaterial{base: Color::BLUE.into(),extension: GridMaterial{color: Color::ORANGE,subdivisions: UVec2::new(0,0),line_widths: Vec2::new(0.01,0.01),},}),..Default::default()});}...}
Run our application with cargo run and we should see a 3d shape that looks like this.
Large Grid Box
Let’s make a larger grid box. Unless we are making games that use a small grid like Into the Breach, we’ll probably want a fairly large grid to play on. For instance, RimWorld map sizes range from 200x200 to 300x300. Let’s create a 200x200 grid and see how it looks.
Run our application with cargo run and we now see a much larger grid box.
Next Up…
This is great! We can now render grids of any size for our games. We still need a camera to move around our field of view and we’ll also need to render text to the grid to represent our game objects. In the next post, we’ll create a camera that is capable of zooming around and looking at our grids.
Full code for this post can be found here: TextGem Post 3.