Skip to content

Commit

Permalink
Mesh builders UV options (#93)
Browse files Browse the repository at this point in the history
> Closes #90 

# Work done

* Added `UvOptions` for both plane and column mesh builder :
* `mesh_builder` example now uses a checker texture and showcases uv
options
  * `ColumnMeshBuilder` has uv options for cap and side faces
  • Loading branch information
ManevilleF committed Jun 16, 2023
1 parent 18e05bd commit b1002dd
Show file tree
Hide file tree
Showing 9 changed files with 246 additions and 29 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@
* `DiagonalDirection::from_flat_angle_degrees`
* Added missing `Direction::angle_degrees` method (#92)
* Added missing `DiagonalDirection::angle_degrees` method (#92)
* Added `UvOptions` for both planet and column mesh builder (#93):
* `mesh_builder` example now uses a checker texture and showcases uv options
* `ColumnMeshBuilder` has uv options for cap and side faces

## 0.6.0

Expand Down
4 changes: 2 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,11 @@ optional = true
# For lib.rs doctests and examples
[dev-dependencies.bevy]
version = "0.10"
features = ["bevy_render", "bevy_pbr", "bevy_sprite", "bevy_core_pipeline", "bevy_asset", "bevy_winit", "x11"]
features = ["bevy_render", "bevy_pbr", "bevy_sprite", "bevy_core_pipeline", "bevy_asset", "bevy_winit", "x11", "png"]
default-features = false

[dev-dependencies.criterion]
version = "0.4"
version = "0.5"
features = ["html_reports"]

[dev-dependencies]
Expand Down
Binary file added assets/uv_checker.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/mesh_builder.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
70 changes: 60 additions & 10 deletions examples/mesh_builder.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use bevy::{
input::mouse::MouseMotion,
pbr::wireframe::{Wireframe, WireframePlugin},
prelude::*,
render::{mesh::Indices, render_resource::PrimitiveTopology},
Expand All @@ -13,6 +14,13 @@ struct HexInfo {
pub mesh_handle: Handle<Mesh>,
}

#[derive(Debug, Reflect)]
struct UVParams {
pub uv_offset: Vec2,
pub uv_scale_factor: Vec2,
pub uv_flip: BVec2,
}

#[derive(Debug, Resource, Reflect, InspectorOptions)]
#[reflect(Resource, InspectorOptions)]
struct BuilderParams {
Expand All @@ -22,6 +30,8 @@ struct BuilderParams {
pub subdivisions: usize,
pub top_face: bool,
pub bottom_face: bool,
pub sides_uvs: UVParams,
pub caps_uvs: UVParams,
}

pub fn main() {
Expand All @@ -47,7 +57,9 @@ fn setup(
params: Res<BuilderParams>,
mut meshes: ResMut<Assets<Mesh>>,
mut materials: ResMut<Assets<StandardMaterial>>,
asset_server: Res<AssetServer>,
) {
let texture = asset_server.load("uv_checker.png");
let transform = Transform::from_xyz(0.0, 0.0, 20.0).looking_at(Vec3::ZERO, Vec3::Y);
commands.spawn(Camera3dBundle {
transform,
Expand All @@ -63,7 +75,7 @@ fn setup(
.with_offset(Vec3::NEG_Y * params.height / 2.0)
.build();
let mesh_handle = meshes.add(compute_mesh(mesh));
let material = materials.add(Color::CYAN.into());
let material = materials.add(texture.into());
let mesh_entity = commands
.spawn((
PbrBundle {
Expand All @@ -81,13 +93,20 @@ fn setup(
});
}

fn animate(info: Res<HexInfo>, mut transforms: Query<&mut Transform>, time: Res<Time>) {
let delta_time = time.delta_seconds() / 2.0;
let mut transform = transforms.get_mut(info.mesh_entity).unwrap();
transform.rotate_x(delta_time);
transform.rotate_y(delta_time);
transform.rotate_local_y(delta_time);
transform.rotate_z(delta_time);
fn animate(
info: Res<HexInfo>,
mut transforms: Query<&mut Transform>,
mut motion_evr: EventReader<MouseMotion>,
buttons: Res<Input<MouseButton>>,
time: Res<Time>,
) {
if buttons.pressed(MouseButton::Left) {
for event in motion_evr.iter() {
let mut transform = transforms.get_mut(info.mesh_entity).unwrap();
transform.rotate_y(event.delta.x * time.delta_seconds());
transform.rotate_x(event.delta.y * time.delta_seconds());
}
}
}

fn update_mesh(params: Res<BuilderParams>, info: Res<HexInfo>, mut meshes: ResMut<Assets<Mesh>>) {
Expand All @@ -96,15 +115,27 @@ fn update_mesh(params: Res<BuilderParams>, info: Res<HexInfo>, mut meshes: ResMu
}
let mut new_mesh = ColumnMeshBuilder::new(&info.layout, params.height)
.with_subdivisions(params.subdivisions)
.with_offset(Vec3::NEG_Y * params.height / 2.0);
.with_offset(Vec3::NEG_Y * params.height / 2.0)
.with_caps_uv_options(UVOptions {
scale_factor: params.caps_uvs.uv_scale_factor,
flip_u: params.caps_uvs.uv_flip.x,
flip_v: params.caps_uvs.uv_flip.y,
offset: params.caps_uvs.uv_offset,
})
.with_sides_uv_options(UVOptions {
scale_factor: params.sides_uvs.uv_scale_factor,
flip_u: params.sides_uvs.uv_flip.x,
flip_v: params.sides_uvs.uv_flip.y,
offset: params.sides_uvs.uv_offset,
});
if !params.top_face {
new_mesh = new_mesh.without_top_face();
}
if !params.bottom_face {
new_mesh = new_mesh.without_bottom_face();
}
let new_mesh = compute_mesh(new_mesh.build());
println!("Mesh has {} vertices", new_mesh.count_vertices());
// println!("Mesh has {} vertices", new_mesh.count_vertices());
let mesh = meshes.get_mut(&info.mesh_handle).unwrap();
*mesh = new_mesh;
}
Expand All @@ -126,6 +157,25 @@ impl Default for BuilderParams {
subdivisions: 3,
top_face: true,
bottom_face: true,
sides_uvs: UVParams {
uv_scale_factor: Vec2::new(1.0, 0.3),
..default()
},
caps_uvs: UVParams {
uv_scale_factor: Vec2::splat(0.5),
uv_offset: Vec2::splat(0.5),
..default()
},
}
}
}

impl Default for UVParams {
fn default() -> Self {
Self {
uv_offset: Vec2::default(),
uv_flip: BVec2::default(),
uv_scale_factor: Vec2::ONE,
}
}
}
43 changes: 39 additions & 4 deletions src/mesh/column_builder.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use glam::{Quat, Vec3};

use super::{MeshInfo, BASE_FACING};
use crate::{Hex, HexLayout};
use crate::{Hex, HexLayout, PlaneMeshBuilder, UVOptions};

/// Builder struct to customize hex column mesh generation.
///
Expand Down Expand Up @@ -45,6 +45,10 @@ pub struct ColumnMeshBuilder<'l> {
pub top_face: bool,
/// Should the bottom hexagonal face be present
pub bottom_face: bool,
/// UV mapping options for the column sides
pub sides_uv_options: UVOptions,
/// UV mapping options for top and bottom faces
pub caps_uv_options: UVOptions,
}

impl<'l> ColumnMeshBuilder<'l> {
Expand All @@ -60,6 +64,8 @@ impl<'l> ColumnMeshBuilder<'l> {
offset: None,
top_face: true,
bottom_face: true,
sides_uv_options: UVOptions::quad_default(),
caps_uv_options: UVOptions::cap_default(),
}
}

Expand All @@ -70,6 +76,7 @@ impl<'l> ColumnMeshBuilder<'l> {
/// It might be more optimal to generate only one mesh at [`Hex::ZERO`] and offset it later
/// than have one mesh per hex position
#[must_use]
#[inline]
pub const fn at(mut self, pos: Hex) -> Self {
self.pos = pos;
self
Expand All @@ -78,6 +85,7 @@ impl<'l> ColumnMeshBuilder<'l> {
/// Specify a custom *facing* direction for the mesh, by default the column is vertical (facing
/// up)
#[must_use]
#[inline]
pub const fn facing(mut self, facing: Vec3) -> Self {
self.facing = Some(facing);
self
Expand All @@ -100,38 +108,64 @@ impl<'l> ColumnMeshBuilder<'l> {
/// .build();
/// ```
#[must_use]
#[inline]
pub const fn with_offset(mut self, offset: Vec3) -> Self {
self.offset = Some(offset);
self
}

/// Defines the column side quads amount
#[must_use]
#[inline]
pub const fn with_subdivisions(mut self, subdivisions: usize) -> Self {
self.subdivisions = Some(subdivisions);
self
}

/// The mesh will not include a *bottom* hexagon face
#[must_use]
#[inline]
pub const fn without_bottom_face(mut self) -> Self {
self.bottom_face = false;
self
}

/// The mesh will not include a *top* hexagon face
#[must_use]
#[inline]
pub const fn without_top_face(mut self) -> Self {
self.top_face = false;
self
}

#[must_use]
#[inline]
/// Specify custom uv options for the top/bottom caps triangles
///
/// Note:
/// this won't have any effect if `top_cap` and `bottom_cap` are disabled
pub const fn with_caps_uv_options(mut self, uv_options: UVOptions) -> Self {
self.caps_uv_options = uv_options;
self
}

#[must_use]
#[inline]
/// Specify custom uv options for the side triangles
pub const fn with_sides_uv_options(mut self, uv_options: UVOptions) -> Self {
self.sides_uv_options = uv_options;
self
}

#[must_use]
#[allow(clippy::cast_precision_loss)]
#[allow(clippy::many_single_char_names)]
/// Comsumes the builder to return the computed mesh data
pub fn build(self) -> MeshInfo {
let plane = MeshInfo::hexagonal_plane(self.layout, self.pos);
let cap_mesh = PlaneMeshBuilder::new(self.layout)
.at(self.pos)
.with_uv_options(self.caps_uv_options)
.build();
let mut mesh = MeshInfo::default();
// Column sides
let subidivisions = self.subdivisions.unwrap_or(0).max(1);
Expand All @@ -149,12 +183,13 @@ impl<'l> ColumnMeshBuilder<'l> {
mesh.merge_with(quad);
}
}
self.sides_uv_options.alter_uvs(&mut mesh.uvs);
if self.top_face {
mesh.merge_with(plane.clone().with_offset(Vec3::Y * self.height));
mesh.merge_with(cap_mesh.clone().with_offset(Vec3::Y * self.height));
}
if self.bottom_face {
let rotation = Quat::from_rotation_arc(BASE_FACING, -BASE_FACING);
let bottom_face = plane.rotated(rotation);
let bottom_face = cap_mesh.rotated(rotation);
mesh.merge_with(bottom_face);
}
if let Some(offset) = self.offset {
Expand Down
22 changes: 11 additions & 11 deletions src/mesh/mod.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
mod column_builder;
mod plane_builder;
mod uv_mapping;

pub use column_builder::ColumnMeshBuilder;
pub use plane_builder::PlaneMeshBuilder;
pub use uv_mapping::UVOptions;

use glam::{Quat, Vec2, Vec3};

use crate::{Hex, HexLayout};

pub(crate) const BASE_FACING: Vec3 = Vec3::Y;
pub(crate) const UV_DELTA: Vec2 = Vec2::splat(0.5);

#[derive(Debug, Clone, Default)]
/// Mesh information. The `const LEN` attribute ensures that there is the same number of vertices, normals and uvs
Expand Down Expand Up @@ -90,16 +91,21 @@ impl MeshInfo {

/// Computes mesh data for an hexagonal plane facing `Vec3::Y`
///
/// Prefer using [`PlaneMeshBuilder`] for additional customization
/// # Note
///
/// Prefer using [`PlaneMeshBuilder`] for additional customization like:
/// * UV options
/// * Offsets
/// * rotation
/// * etc
#[must_use]
pub fn hexagonal_plane(layout: &HexLayout, hex: Hex) -> Self {
let center = layout.hex_to_world_pos(hex);
let center = Vec3::new(center.x, 0., center.y);
let corners = layout.hex_corners(hex);
let corners_arr = corners.map(|p| Vec3::new(p.x, 0., p.y));
Self {
vertices: vec![
center,
Vec3::new(center.x, 0., center.y),
corners_arr[0],
corners_arr[1],
corners_arr[2],
Expand All @@ -108,13 +114,7 @@ impl MeshInfo {
corners_arr[5],
],
uvs: vec![
UV_DELTA,
corners[0] + UV_DELTA,
corners[1] + UV_DELTA,
corners[2] + UV_DELTA,
corners[3] + UV_DELTA,
corners[4] + UV_DELTA,
corners[5] + UV_DELTA,
center, corners[0], corners[1], corners[2], corners[3], corners[4], corners[5],
],
normals: [Vec3::Y; 7].to_vec(),
indices: vec![
Expand Down
15 changes: 13 additions & 2 deletions src/mesh/plane_builder.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use super::{MeshInfo, BASE_FACING};
use crate::{Hex, HexLayout};
use crate::{Hex, HexLayout, UVOptions};
use glam::{Quat, Vec3};

/// Builder struct to customize hex plane mesh generation.
Expand All @@ -18,6 +18,8 @@ pub struct PlaneMeshBuilder<'l> {
///
/// By default the mesh is *facing* up (**Y** axis)
pub facing: Option<Vec3>,
/// UV mapping options
pub uv_options: UVOptions,
}

impl<'l> PlaneMeshBuilder<'l> {
Expand All @@ -29,6 +31,7 @@ impl<'l> PlaneMeshBuilder<'l> {
pos: Hex::ZERO,
facing: None,
offset: None,
uv_options: UVOptions::cap_default(),
}
}

Expand All @@ -52,13 +55,20 @@ impl<'l> PlaneMeshBuilder<'l> {
self
}

/// Specify a cusom offset for the whole mesh
/// Specify a custom offset for the whole mesh
#[must_use]
pub const fn with_offset(mut self, offset: Vec3) -> Self {
self.offset = Some(offset);
self
}

/// Specify custom UV mapping options
#[must_use]
pub const fn with_uv_options(mut self, uv_options: UVOptions) -> Self {
self.uv_options = uv_options;
self
}

/// Comsumes the builder to return the computed mesh data
#[must_use]
pub fn build(self) -> MeshInfo {
Expand All @@ -71,6 +81,7 @@ impl<'l> PlaneMeshBuilder<'l> {
let rotation = Quat::from_rotation_arc(BASE_FACING, facing);
mesh = mesh.rotated(rotation);
}
self.uv_options.alter_uvs(&mut mesh.uvs);
mesh
}
}

0 comments on commit b1002dd

Please sign in to comment.