From 3d936cc2f608c4976dd35d3f56e001e3b1bd1fe3 Mon Sep 17 00:00:00 2001 From: Martin Indra Date: Wed, 19 Oct 2022 21:54:09 +0200 Subject: [PATCH] Implement markers of selected movable entities Relates to #36. --- Cargo.lock | 1 + assets/shaders/terrain.wgsl | 50 ++++++++++++++++++++++++-- crates/controller/Cargo.toml | 1 + crates/controller/src/selection.rs | 26 ++++++++++---- crates/terrain/src/lib.rs | 17 +++++++++ crates/terrain/src/plugin.rs | 54 ++++++++++++++++++++++++++-- crates/terrain/src/shader.rs | 58 +++++++++++++++++++++++++++--- 7 files changed, 192 insertions(+), 15 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e7d4e87b8..e9809a8c8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1680,6 +1680,7 @@ dependencies = [ "de_behaviour", "de_core", "de_index", + "de_objects", "de_pathing", "de_spawner", "de_terrain", diff --git a/assets/shaders/terrain.wgsl b/assets/shaders/terrain.wgsl index 5187b66b2..33d885aec 100644 --- a/assets/shaders/terrain.wgsl +++ b/assets/shaders/terrain.wgsl @@ -10,10 +10,25 @@ // How large (in meters) is a texture. let TEXTURE_SIZE = 16.; +let SHAPE_COLOR = vec4(1., 1., 1., 0.75); +let SHAPE_THICKNESS = 0.15; + +struct Circle { + @align(16) center: vec2, + radius: f32, +}; + +struct Circles { + count: i32, + // Keep thie array lenght in sync with /crates/terrain/src/shader.rs. + @align(16) circles: array, +}; @group(1) @binding(0) -var terrain_texture: texture_2d; +var circles: Circles; @group(1) @binding(1) +var terrain_texture: texture_2d; +@group(1) @binding(2) var terrain_sampler: sampler; struct FragmentInput { @@ -22,6 +37,35 @@ struct FragmentInput { #import bevy_pbr::mesh_vertex_output }; +fn mix_colors(base: vec4, cover: vec4) -> vec4 { + let alpha = base.a * cover.a; + let rgb = base.rgb * cover.a + cover.rgb * (1. - cover.a); + return vec4(rgb, alpha); +} + +fn draw_circle( + base: vec4, + uv: vec2, + center: vec2, + radius: f32, +) -> vec4 { + let distance: f32 = distance(uv, center); + if distance <= (radius + SHAPE_THICKNESS) && radius <= distance { + return mix_colors(base, SHAPE_COLOR); + } + return base; +} + +fn draw_circles(base: vec4, uv: vec2) -> vec4 { + var output_color = base; + for ( var i: i32 = 0; i < circles.count; i++ ) { + let center: vec2 = circles.circles[i].center; + let radius = circles.circles[i].radius; + output_color = draw_circle(output_color, uv, center, radius); + } + return output_color; +} + @fragment fn fragment(in: FragmentInput) -> @location(0) vec4 { var pbr_input: PbrInput = pbr_input_new(); @@ -58,5 +102,7 @@ fn fragment(in: FragmentInput) -> @location(0) vec4 { ); pbr_input.V = calculate_view(in.world_position, pbr_input.is_orthographic); - return tone_mapping(pbr(pbr_input)); + var output_color = tone_mapping(pbr(pbr_input)); + output_color = draw_circles(output_color, in.uv); + return output_color; } diff --git a/crates/controller/Cargo.toml b/crates/controller/Cargo.toml index e6822ed69..681ecb8a2 100644 --- a/crates/controller/Cargo.toml +++ b/crates/controller/Cargo.toml @@ -13,6 +13,7 @@ categories = ["games"] [dependencies] # DE de_core = { path = "../core", version = "0.1.0-dev" } +de_objects = { path = "../objects", version = "0.1.0-dev" } de_index = { path = "../index", version = "0.1.0-dev" } de_terrain = { path = "../terrain", version = "0.1.0-dev" } de_pathing = { path = "../pathing", version = "0.1.0-dev" } diff --git a/crates/controller/src/selection.rs b/crates/controller/src/selection.rs index 6558710b7..db3f92e1b 100644 --- a/crates/controller/src/selection.rs +++ b/crates/controller/src/selection.rs @@ -1,9 +1,12 @@ use ahash::AHashSet; -use bevy::{ - ecs::system::SystemParam, - prelude::{App, Commands, Component, Entity, EventReader, Plugin, Query, With}, +use bevy::{ecs::system::SystemParam, prelude::*}; +use de_core::{ + objects::{MovableSolid, ObjectType}, + stages::GameStage, + state::GameState, }; -use de_core::{stages::GameStage, state::GameState}; +use de_objects::{IchnographyCache, ObjectCache}; +use de_terrain::CircleMarker; use iyes_loopless::prelude::*; use crate::Labels; @@ -68,7 +71,9 @@ pub(crate) enum SelectionMode { #[derive(SystemParam)] struct Selector<'w, 's> { commands: Commands<'w, 's>, + cache: Res<'w, ObjectCache>, selected: Query<'w, 's, Entity, With>, + movable: Query<'w, 's, &'static ObjectType, With>, } impl<'w, 's> Selector<'w, 's> { @@ -82,11 +87,20 @@ impl<'w, 's> Selector<'w, 's> { }; for entity in deselect { - self.commands.entity(entity).remove::(); + let mut entity_commands = self.commands.entity(entity); + entity_commands.remove::(); + if self.movable.contains(entity) { + entity_commands.remove::(); + } } for entity in select { - self.commands.entity(entity).insert(Selected); + let mut entity_commands = self.commands.entity(entity); + entity_commands.insert(Selected); + if let Ok(&object_type) = self.movable.get(entity) { + let radius = self.cache.get_ichnography(object_type).radius(); + entity_commands.insert(CircleMarker::new(radius)); + } } } } diff --git a/crates/terrain/src/lib.rs b/crates/terrain/src/lib.rs index 6e6320d99..8dc5643fa 100644 --- a/crates/terrain/src/lib.rs +++ b/crates/terrain/src/lib.rs @@ -15,3 +15,20 @@ impl PluginGroup for TerrainPluginGroup { group.add(TerrainPlugin); } } + +/// A semi-transparent circle is drawn on the terrain surface below every +/// entity with this component. +#[derive(Component)] +pub struct CircleMarker { + radius: f32, +} + +impl CircleMarker { + pub fn new(radius: f32) -> Self { + Self { radius } + } + + pub(crate) fn radius(&self) -> f32 { + self.radius + } +} diff --git a/crates/terrain/src/plugin.rs b/crates/terrain/src/plugin.rs index bcd212a77..8a9351bdb 100644 --- a/crates/terrain/src/plugin.rs +++ b/crates/terrain/src/plugin.rs @@ -1,16 +1,18 @@ +use ahash::AHashSet; use bevy::{ asset::LoadState, prelude::*, render::{ render_resource::{AddressMode, SamplerDescriptor}, texture::ImageSampler, + view::{VisibilitySystems, VisibleEntities}, }, }; -use de_core::{stages::GameStage, state::GameState}; +use de_core::{projection::ToFlat, stages::GameStage, state::GameState}; use iyes_loopless::prelude::*; use iyes_progress::prelude::*; -use crate::{shader::TerrainMaterial, terrain::Terrain}; +use crate::{shader::TerrainMaterial, terrain::Terrain, CircleMarker}; const TERRAIN_TEXTURE: &str = "textures/terrain.png"; @@ -25,7 +27,11 @@ impl Plugin for TerrainPlugin { .track_progress() .run_in_state(GameState::Loading), ) - .add_system_to_stage(GameStage::Update, init); + .add_system_to_stage(GameStage::Update, init) + .add_system_to_stage( + CoreStage::PostUpdate, + circles.after(VisibilitySystems::CheckVisibility), + ); } } @@ -84,3 +90,45 @@ fn init( }); } } + +fn circles( + mut materials: ResMut>, + camera: Query<&VisibleEntities, With>, + terrains: Query<(Entity, &ComputedVisibility, &Handle)>, + circles: Query<(Entity, &ComputedVisibility, &Transform, &CircleMarker)>, +) { + let visible = camera.get_single().map_or_else( + |_| AHashSet::new(), + |v| AHashSet::from_iter(v.iter().cloned()), + ); + + for (terrain_entity, terrain_visibility, material) in terrains.iter() { + if !terrain_visibility.is_visible_in_hierarchy() { + println!("x"); + continue; + } + if !visible.contains(&terrain_entity) { + println!("y"); + continue; + } + + let material = materials.get_mut(material).unwrap(); + material.clear(); + + for (circle_entity, circle_visibility, transform, marker) in circles.iter() { + if material.is_full() { + break; + } + if !circle_visibility.is_visible_in_hierarchy() { + // it looks like that this does not filter out anything. + println!("aa"); + continue; + } + if !visible.contains(&circle_entity) { + println!("ss"); + continue; + } + material.push(transform.translation.to_flat(), marker.radius()); + } + } +} diff --git a/crates/terrain/src/shader.rs b/crates/terrain/src/shader.rs index 1b8919213..5283a8c08 100644 --- a/crates/terrain/src/shader.rs +++ b/crates/terrain/src/shader.rs @@ -1,20 +1,41 @@ use bevy::{ prelude::{Handle, Image, Material}, reflect::TypeUuid, - render::render_resource::{AsBindGroup, ShaderRef}, + render::render_resource::{AsBindGroup, ShaderRef, ShaderType}, }; +use glam::Vec2; + +// Keep this in sync with terrain.wgsl. +const CIRCLE_CAPACITY: usize = 32; #[derive(AsBindGroup, TypeUuid, Debug, Clone)] #[uuid = "9e124e04-fdf1-4836-b82d-fa2f01fddb62"] pub struct TerrainMaterial { - #[texture(0)] - #[sampler(1)] + #[uniform(0)] + circles: Circles, + #[texture(1)] + #[sampler(2)] texture: Handle, } impl TerrainMaterial { pub(crate) fn new(texture: Handle) -> Self { - Self { texture } + Self { + circles: Circles::default(), + texture, + } + } + + pub(crate) fn is_full(&self) -> bool { + self.circles.is_full() + } + + pub(crate) fn clear(&mut self) { + self.circles.clear(); + } + + pub(crate) fn push(&mut self, center: Vec2, radius: f32) { + self.circles.push(center, radius); } } @@ -23,3 +44,32 @@ impl Material for TerrainMaterial { "shaders/terrain.wgsl".into() } } + +#[derive(ShaderType, Debug, Clone, Default)] +struct Circles { + #[size(16)] + count: i32, + circles: [Circle; CIRCLE_CAPACITY], +} + +impl Circles { + pub(crate) fn is_full(&self) -> bool { + self.count as usize >= CIRCLE_CAPACITY + } + + fn clear(&mut self) { + self.count = 0; + } + + fn push(&mut self, center: Vec2, radius: f32) { + self.circles[self.count as usize] = Circle { center, radius }; + self.count += 1; + } +} + +#[derive(ShaderType, Debug, Clone, Copy, Default)] +struct Circle { + #[align(16)] + center: Vec2, + radius: f32, +}