Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Picking example #177

Merged
merged 3 commits into from
Aug 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,11 @@

## [Unreleased]

* Added `Hex::as_u64` and `Hex::from_u64`
* Added `Hex::as_u64` and `Hex::from_u64` (#169)
* Bumped `bevy` to 0.14.x (#173)
* Bumped bevy ecosystem dependencies (#173)
* Updated examples (#173)
* Added a 3d picking exampe (#177)

## 0.17.0

Expand Down
33 changes: 19 additions & 14 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -45,20 +45,21 @@ optional = true
[dev-dependencies.bevy]
version = "0.14"
features = [
"bevy_asset",
"bevy_winit",
"bevy_core_pipeline",
"bevy_pbr",
"bevy_render",
"bevy_sprite",
"bevy_text",
"default_font",
"png",
"x11",
"tonemapping_luts",
"bevy_gizmos",
# Faster compilation
"dynamic_linking",
"bevy_asset",
"bevy_winit",
"bevy_core_pipeline",
"bevy_pbr",
"bevy_render",
"bevy_sprite",
"bevy_text",
"default_font",
"png",
"x11",
"tonemapping_luts",
"bevy_gizmos",
"multi_threaded",
# Faster compilation
"dynamic_linking",
]
default-features = false

Expand Down Expand Up @@ -104,6 +105,10 @@ path = "examples/field_of_movement.rs"
name = "3d_columns"
path = "examples/3d_columns.rs"

[[example]]
name = "3d_picking"
path = "examples/3d_picking.rs"

[[example]]
name = "mesh_builder"
path = "examples/mesh_builder.rs"
Expand Down
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -353,6 +353,14 @@ and how to use two layouts on the same grid.
> `cargo run --example 3d_columns`

This example showcases the 3d hexagon columns procedural generation

### 3d picking

![picking](docs/3d_picking.png "3d picking example")

> `cargo run --example 3d_picking`

This example showcases how to use the camera ray to detect hovered 3d columns

### Mesh builder

Expand Down
Binary file modified docs/3d_columns.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/3d_picking.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
139 changes: 139 additions & 0 deletions examples/3d_picking.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
use bevy::{
color::palettes::css::{WHITE, YELLOW},
prelude::*,
render::{mesh::Indices, render_asset::RenderAssetUsages, render_resource::PrimitiveTopology},
utils::HashMap,
window::PrimaryWindow,
};
use hexx::{shapes, *};
use light_consts::lux;

/// World size of the hexagons (outer radius)
const HEX_SIZE: Vec2 = Vec2::splat(1.0);
/// World space height of hex columns
const COLUMN_HEIGHT: f32 = 10.0;
/// Map radius
const MAP_RADIUS: u32 = 20;

pub fn main() {
App::new()
.insert_resource(AmbientLight {
brightness: lux::OFFICE,
..default()
})
.add_plugins(DefaultPlugins)
.add_systems(Startup, (setup_camera, setup_grid))
.add_systems(Update, higlight_hovered)
.run();
}

#[derive(Debug, Resource)]
struct Map {
layout: HexLayout,
entities: HashMap<Hex, Entity>,
highlighted_material: Handle<StandardMaterial>,
default_material: Handle<StandardMaterial>,
}

/// 3D Orthogrpahic camera setup
fn setup_camera(mut commands: Commands) {
let transform = Transform::from_xyz(0.0, 60.0, 60.0).looking_at(Vec3::ZERO, Vec3::Y);
commands.spawn(Camera3dBundle {
transform,
..default()
});
let transform = Transform::from_xyz(60.0, 60.0, 00.0).looking_at(Vec3::ZERO, Vec3::Y);
commands.spawn(DirectionalLightBundle {
transform,
..default()
});
}

/// Hex grid setup
fn setup_grid(
mut commands: Commands,
mut meshes: ResMut<Assets<Mesh>>,
mut materials: ResMut<Assets<StandardMaterial>>,
) {
let layout = HexLayout {
hex_size: HEX_SIZE,
..default()
};
// materials
let default_material = materials.add(Color::Srgba(WHITE));
let highlighted_material = materials.add(Color::Srgba(YELLOW));
// mesh
let mesh = hexagonal_column(&layout);
let mesh_handle = meshes.add(mesh);

let entities = shapes::hexagon(Hex::ZERO, MAP_RADIUS)
.map(|hex| {
let pos = layout.hex_to_world_pos(hex);
let id = commands
.spawn(PbrBundle {
transform: Transform::from_xyz(pos.x, -COLUMN_HEIGHT, pos.y),
mesh: mesh_handle.clone(),
material: default_material.clone_weak(),
..default()
})
.id();
(hex, id)
})
.collect();
commands.insert_resource(Map {
layout,
entities,
highlighted_material,
default_material,
});
}

fn higlight_hovered(
mut commands: Commands,
map: Res<Map>,
mut highlighted: Local<Hex>,
cameras: Query<(&Camera, &GlobalTransform)>,
windows: Query<&Window, With<PrimaryWindow>>,
) {
let window = windows.single();
let (camera, cam_transform) = cameras.single();
let Some(ray) = window
.cursor_position()
.and_then(|p| camera.viewport_to_world(cam_transform, p))
else {
return;
};
let Some(distance) = ray.intersect_plane(Vec3::ZERO, InfinitePlane3d::new(Dir3::Y)) else {
return;
};
let point = ray.origin + ray.direction * distance;
let coord = map.layout.world_pos_to_hex(point.xz());
if coord != *highlighted {
let Some(entity) = map.entities.get(&coord).copied() else {
return;
};
commands
.entity(entity)
.insert(map.highlighted_material.clone_weak());
commands
.entity(map.entities[&*highlighted])
.insert(map.default_material.clone_weak());
*highlighted = coord;
}
}

/// Compute a bevy mesh from the layout
fn hexagonal_column(hex_layout: &HexLayout) -> Mesh {
let mesh_info = ColumnMeshBuilder::new(hex_layout, COLUMN_HEIGHT)
.without_bottom_face()
.center_aligned()
.build();
Mesh::new(
PrimitiveTopology::TriangleList,
RenderAssetUsages::RENDER_WORLD,
)
.with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, mesh_info.vertices)
.with_inserted_attribute(Mesh::ATTRIBUTE_NORMAL, mesh_info.normals)
.with_inserted_attribute(Mesh::ATTRIBUTE_UV_0, mesh_info.uvs)
.with_inserted_indices(Indices::U16(mesh_info.indices))
}
2 changes: 1 addition & 1 deletion src/algorithms/pathfinding.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ fn reconstruct_path(came_from: &HashMap<Hex, Hex>, end: Hex) -> Vec<Hex> {
/// * `start` - start node
/// * `end` - destination node
/// * `cost` - cost function taking a node pair (`a` -> `b`) and returning the
/// logical cost to go from `a` to `b`
/// logical cost to go from `a` to `b`
///
/// # Examples
///
Expand Down
6 changes: 2 additions & 4 deletions src/hex/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -698,8 +698,7 @@ impl Hex {
/// Find in which [`VertexDirection`] wedge `rhs` is relative to `self`.
///
/// > This method can be innaccurate in case of a *tie* between directions,
/// > prefer
/// using [`Self::diagonal_way_to`] instead
/// > prefer using [`Self::diagonal_way_to`] instead
pub fn main_diagonal_to(self, rhs: Self) -> VertexDirection {
self.diagonal_way_to(rhs).unwrap()
}
Expand All @@ -723,8 +722,7 @@ impl Hex {
/// Find in which [`EdgeDirection`] wedge `rhs` is relative to `self`
///
/// > This method can be innaccurate in case of a *tie* between directions,
/// > prefer
/// using [`Self::way_to`] for accuracy
/// > prefer using [`Self::way_to`] for accuracy
#[must_use]
pub fn main_direction_to(self, rhs: Self) -> EdgeDirection {
self.way_to(rhs).unwrap()
Expand Down
3 changes: 1 addition & 2 deletions src/mesh/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -108,8 +108,7 @@ impl<const VERTS: usize, const TRIS: usize> Face<VERTS, TRIS> {
///
/// * `mode` - the insetting behaviour mode
/// * `keep_inner_face` - If set to true the insetted face will be kept,
/// otherwise
/// it will be removed
/// otherwise it will be removed
#[allow(clippy::cast_possible_truncation)]
#[must_use]
pub fn inset(self, mode: InsetScaleMode, scale: f32, keep_inner_face: bool) -> MeshInfo {
Expand Down
Loading