From 64c15be2241ffb8e0df1a79ea4f0deb2a6f75633 Mon Sep 17 00:00:00 2001 From: hheik <4469778+hheik@users.noreply.github.com> Date: Fri, 2 Dec 2022 01:03:33 +0200 Subject: [PATCH] wip: chunk spawner --- src/game.rs | 15 ++++++ src/game/chunk.rs | 17 +++++++ src/terrain2d.rs | 49 +++++++++--------- src/terrain2d/chunk2d.rs | 106 ++++++++++++++++++++++++++++++++++----- src/terrain2d/texel2d.rs | 8 +-- src/util/vector2.rs | 6 ++- 6 files changed, 161 insertions(+), 40 deletions(-) create mode 100644 src/game/chunk.rs diff --git a/src/game.rs b/src/game.rs index b4b0226..224cadb 100644 --- a/src/game.rs +++ b/src/game.rs @@ -2,9 +2,15 @@ use bevy::prelude::*; use bevy_inspector_egui::*; use bevy_rapier2d::prelude::*; +use crate::{ + terrain2d::{Chunk2D, Terrain2D, Terrain2DPlugin}, + util::Vector2I, +}; + use self::{camera::GameCameraPlugin, kinematic::KinematicPlugin, player::PlayerPlugin}; pub mod camera; +pub mod chunk; pub mod kinematic; pub mod player; @@ -16,11 +22,20 @@ pub fn init() { .add_plugin(WorldInspectorPlugin::new()) .add_plugin(KinematicPlugin) .add_plugin(GameCameraPlugin) + .add_plugin(Terrain2DPlugin) // .add_plugin(PlayerPlugin) // .add_startup_system(setup_debug_ground) + .add_startup_system(setup_debug_terrain) .run(); } +fn setup_debug_terrain(mut terrain: ResMut) { + terrain.add_chunk(Vector2I { x: 0, y: 0 }, Chunk2D::new()); + terrain.add_chunk(Vector2I { x: 1, y: 0 }, Chunk2D::new()); + terrain.add_chunk(Vector2I { x: 0, y: 1 }, Chunk2D::new()); + terrain.add_chunk(Vector2I { x: 1, y: 1 }, Chunk2D::new()); +} + fn setup_debug_ground(mut commands: Commands) { // Static ground commands diff --git a/src/game/chunk.rs b/src/game/chunk.rs new file mode 100644 index 0000000..ee8d57f --- /dev/null +++ b/src/game/chunk.rs @@ -0,0 +1,17 @@ +use bevy::prelude::*; + +use crate::terrain2d::ChunkIndex; + +pub struct ChunkPlugin {} + +#[derive(Reflect, Component, Default)] +#[reflect(Component)] +pub struct Chunk { + pub index: ChunkIndex, +} + +#[derive(Bundle)] +pub struct ChunkBundle { + pub chunk: Chunk, + pub sprite_bundle: SpriteBundle, +} diff --git a/src/terrain2d.rs b/src/terrain2d.rs index efaeb0b..62518d5 100644 --- a/src/terrain2d.rs +++ b/src/terrain2d.rs @@ -17,8 +17,11 @@ pub struct Terrain2DPlugin; impl Plugin for Terrain2DPlugin { fn build(&self, app: &mut App) { - app.insert_resource(Terrain2D::new()) - .add_system(emit_terrain_events); + app.register_type::() + .insert_resource(Terrain2D::new()) + .add_event::() + .add_system(emit_terrain_events) + .add_system(chunk_spawner); } } @@ -45,7 +48,7 @@ pub enum TerrainEvent { #[derive(Default, Resource)] pub struct Terrain2D { - chunk_map: HashMap, + chunk_map: HashMap, events: Vec, } @@ -57,7 +60,7 @@ impl Terrain2D { } } - pub fn add_chunk(&mut self, index: ChunkIndex, chunk: Chunk) { + pub fn add_chunk(&mut self, index: ChunkIndex, chunk: Chunk2D) { self.chunk_map.insert(index, chunk); self.events.push(TerrainEvent::ChunkAdded(index)) } @@ -67,38 +70,38 @@ impl Terrain2D { self.chunk_map.remove(&index); } - pub fn chunk_iter(&self) -> Iter { + pub fn chunk_iter(&self) -> Iter { self.chunk_map.iter() } - pub fn chunk_iter_mut(&mut self) -> IterMut { + pub fn chunk_iter_mut(&mut self) -> IterMut { self.chunk_map.iter_mut() } - pub fn index_to_chunk(&self, index: &ChunkIndex) -> Option<&Chunk> { + pub fn index_to_chunk(&self, index: &ChunkIndex) -> Option<&Chunk2D> { self.chunk_map.get(index) } - pub fn index_to_chunk_mut(&mut self, index: &ChunkIndex) -> Option<&mut Chunk> { + pub fn index_to_chunk_mut(&mut self, index: &ChunkIndex) -> Option<&mut Chunk2D> { self.chunk_map.get_mut(index) } - pub fn global_to_chunk(&self, global: &Vector2I) -> Option<&Chunk> { + pub fn global_to_chunk(&self, global: &Vector2I) -> Option<&Chunk2D> { self.index_to_chunk(&global_to_chunk_index(global)) } - pub fn global_to_chunk_mut(&mut self, global: &Vector2I) -> Option<&mut Chunk> { + pub fn global_to_chunk_mut(&mut self, global: &Vector2I) -> Option<&mut Chunk2D> { self.index_to_chunk_mut(&global_to_chunk_index(global)) } - pub fn global_to_texel(&self, global: &Vector2I) -> Option { + pub fn global_to_texel(&self, global: &Vector2I) -> Option { match self.global_to_chunk(global) { Some(chunk) => chunk.get_texel(&global_to_local(global)), None => None, } } - pub fn global_to_texel_mut(&mut self, global: &Vector2I) -> Option { + pub fn global_to_texel_mut(&mut self, global: &Vector2I) -> Option { match self.global_to_chunk(global) { Some(chunk) => chunk.get_texel(&global_to_local(global)), None => None, @@ -110,7 +113,7 @@ impl Terrain2D { match self.index_to_chunk_mut(&index) { Some(chunk) => chunk.set_texel(&global_to_local(global), id), None => { - let mut chunk = Chunk::new(); + let mut chunk = Chunk2D::new(); chunk.set_texel(&global_to_local(global), id); self.add_chunk(index, chunk); } @@ -121,35 +124,35 @@ impl Terrain2D { pub fn local_to_texel_index(position: &Vector2I) -> Option { match position.x >= 0 && position.y >= 0 - && position.x < Chunk::SIZE.x - && position.y < Chunk::SIZE.y + && position.x < Chunk2D::SIZE.x + && position.y < Chunk2D::SIZE.y { - true => Some(position.y as usize * Chunk::SIZE_X + position.x as usize), + true => Some(position.y as usize * Chunk2D::SIZE_X + position.x as usize), false => None, } } pub fn texel_index_to_local(i: usize) -> Vector2I { Vector2I { - x: i as i32 % Chunk::SIZE.x, - y: i as i32 / Chunk::SIZE.y, + x: i as i32 % Chunk2D::SIZE.x, + y: i as i32 / Chunk2D::SIZE.y, } } pub fn global_to_local(position: &Vector2I) -> Vector2I { Vector2I { - x: wrapping_remainder(position.x, Chunk::SIZE.x), - y: wrapping_remainder(position.y, Chunk::SIZE.y), + x: wrapping_remainder(position.x, Chunk2D::SIZE.x), + y: wrapping_remainder(position.y, Chunk2D::SIZE.y), } } pub fn global_to_chunk_index(position: &Vector2I) -> ChunkIndex { Vector2I { - x: wrapping_quotient(position.x, Chunk::SIZE.x), - y: wrapping_quotient(position.y, Chunk::SIZE.y), + x: wrapping_quotient(position.x, Chunk2D::SIZE.x), + y: wrapping_quotient(position.y, Chunk2D::SIZE.y), } } pub fn chunk_index_to_global(chunk_pos: &ChunkIndex) -> Vector2I { - *chunk_pos * Chunk::SIZE + *chunk_pos * Chunk2D::SIZE } diff --git a/src/terrain2d/chunk2d.rs b/src/terrain2d/chunk2d.rs index d66217d..bbe5e8d 100644 --- a/src/terrain2d/chunk2d.rs +++ b/src/terrain2d/chunk2d.rs @@ -1,5 +1,18 @@ -use super::{local_to_texel_index, Texel, TexelID, NEIGHBOUR_INDEX_MAP}; +use super::{local_to_texel_index, Terrain2D, TerrainEvent, Texel2D, TexelID, NEIGHBOUR_INDEX_MAP}; use crate::util::Vector2I; +use bevy::{prelude::*, render::render_resource::Extent3d}; + +#[derive(Reflect, Component, Default)] +#[reflect(Component)] +pub struct Chunk2DIndex { + pub index: ChunkIndex, +} + +#[derive(Bundle, Default)] +pub struct ChunkBundle { + pub chunk: Chunk2DIndex, + pub sprite_bundle: SpriteBundle, +} pub type ChunkIndex = Vector2I; @@ -9,13 +22,13 @@ pub struct ChunkRect { pub max: Vector2I, } -pub struct Chunk { - pub texels: [Texel; (Self::SIZE_X * Self::SIZE_Y) as usize], +pub struct Chunk2D { + pub texels: [Texel2D; (Self::SIZE_X * Self::SIZE_Y) as usize], // TODO: handle multiple dirty rects pub dirty_rect: Option, } -impl Chunk { +impl Chunk2D { pub const SIZE_X: usize = 64; pub const SIZE_Y: usize = 64; pub const SIZE: Vector2I = Vector2I { @@ -23,15 +36,15 @@ impl Chunk { y: Self::SIZE_Y as i32, }; - pub fn new() -> Chunk { - Chunk { + pub fn new() -> Chunk2D { + Chunk2D { texels: Self::new_texel_array(), dirty_rect: None, } } - pub fn new_texel_array() -> [Texel; Self::SIZE_X * Self::SIZE_Y] { - [Texel::default(); Self::SIZE_X * Self::SIZE_Y] + pub fn new_texel_array() -> [Texel2D; Self::SIZE_X * Self::SIZE_Y] { + [Texel2D::default(); Self::SIZE_X * Self::SIZE_Y] } pub fn mark_all_dirty(&mut self) { @@ -58,11 +71,11 @@ impl Chunk { } } - pub fn get_texel(&self, position: &Vector2I) -> Option { + pub fn get_texel(&self, position: &Vector2I) -> Option { local_to_texel_index(position).map(|i| self.texels[i]) } - pub fn get_texel_option_mut(&mut self, position: &Vector2I) -> Option<&mut Texel> { + pub fn get_texel_option_mut(&mut self, position: &Vector2I) -> Option<&mut Texel2D> { local_to_texel_index(position).map(|i| &mut self.texels[i]) } @@ -72,7 +85,7 @@ impl Chunk { self.mark_dirty(position); } let update_neighbours = self.texels[i].is_empty() - != (Texel { + != (Texel2D { id, ..self.texels[i] }) @@ -80,7 +93,7 @@ impl Chunk { self.texels[i].id = id; // Update neighbour mask if update_neighbours { - for offset in Texel::NEIGHBOUR_OFFSET_VECTORS { + for offset in Texel2D::NEIGHBOUR_OFFSET_VECTORS { // Flip neighbour's bit match self.get_texel_option_mut(&(*position + offset)) { Some(mut neighbour) => { @@ -92,3 +105,72 @@ impl Chunk { } } } + +pub fn chunk_spawner( + mut commands: Commands, + mut terrain_events: EventReader, + mut images: ResMut>, + terrain: Res, + chunk_query: Query<(Entity, &Chunk2DIndex)>, +) { + for terrain_event in terrain_events.iter() { + match terrain_event { + TerrainEvent::ChunkAdded(chunk_index) => { + let mut data = Vec::with_capacity(Chunk2D::SIZE_X * Chunk2D::SIZE_Y * 4); + for _y in 0..Chunk2D::SIZE_Y { + for _x in 0..Chunk2D::SIZE_X { + data.push(0x00); + data.push(0x00); + data.push(0x00); + data.push(0x00); + } + } + let image = Image::new( + Extent3d { + width: Chunk2D::SIZE_X as u32, + height: Chunk2D::SIZE_Y as u32, + depth_or_array_layers: 1, + }, + bevy::render::render_resource::TextureDimension::D2, + data, + bevy::render::render_resource::TextureFormat::Rgba8Unorm, + ); + + images.add(image); + + let pos = Vec2::from(*chunk_index * Chunk2D::SIZE); + commands + .spawn(ChunkBundle { + chunk: Chunk2DIndex { + index: *chunk_index, + }, + sprite_bundle: SpriteBundle { + sprite: Sprite { + color: Color::rgb( + 0.25 + (chunk_index.x % 4) as f32 * 0.25, + 0.25 + (chunk_index.y % 4) as f32 * 0.25, + 0.75, + ), + custom_size: Some(Vec2::from(Chunk2D::SIZE)), + ..default() + }, + transform: Transform::from_translation(Vec3::new(pos.x, pos.y, 0.0)), + ..default() + }, + }) + .insert(Name::new(format!( + "Chunk {},{}", + chunk_index.x, chunk_index.y + ))); + } + TerrainEvent::ChunkRemoved(chunk_index) => { + for (entity, chunk) in chunk_query.iter() { + if chunk.index == *chunk_index { + commands.entity(entity).despawn_recursive(); + } + } + } + TerrainEvent::TexelsUpdated(chunk_index, rect) => {} + } + } +} diff --git a/src/terrain2d/texel2d.rs b/src/terrain2d/texel2d.rs index d5c8fc6..1de5059 100644 --- a/src/terrain2d/texel2d.rs +++ b/src/terrain2d/texel2d.rs @@ -7,7 +7,7 @@ pub use u8 as NeighbourMask; use crate::util::Vector2I; #[derive(Clone, Copy, Default)] -pub struct Texel { +pub struct Texel2D { pub id: TexelID, /// bitmask of empty/non-empty neighbours, see NEIGHBOUR_OFFSET_VECTORS for the order pub neighbour_mask: NeighbourMask, @@ -16,14 +16,14 @@ pub struct Texel { lazy_static! { pub static ref NEIGHBOUR_INDEX_MAP: HashMap = { let mut map = HashMap::new(); - for i in 0..Texel::NEIGHBOUR_OFFSET_VECTORS.len() { - map.insert(Texel::NEIGHBOUR_OFFSET_VECTORS[i], i as u8); + for i in 0..Texel2D::NEIGHBOUR_OFFSET_VECTORS.len() { + map.insert(Texel2D::NEIGHBOUR_OFFSET_VECTORS[i], i as u8); } map }; } -impl Texel { +impl Texel2D { pub const EMPTY: TexelID = 0; pub const NEIGHBOUR_OFFSET_VECTORS: [Vector2I; 4] = [ Vector2I { x: 0, y: 1 }, diff --git a/src/util/vector2.rs b/src/util/vector2.rs index 95074ec..f093f06 100644 --- a/src/util/vector2.rs +++ b/src/util/vector2.rs @@ -1,9 +1,12 @@ use core::{fmt, ops}; +use bevy::reflect::Reflect; + pub trait VectorComponent: Sized + Copy + Ord + + Reflect + fmt::Display + ops::Add + ops::Neg @@ -17,6 +20,7 @@ impl VectorComponent for T where T: Sized + Copy + Ord + + Reflect + fmt::Display + ops::Neg + ops::Add @@ -26,7 +30,7 @@ impl VectorComponent for T where { } -#[derive(PartialEq, Eq, Hash, Clone, Copy, Default, Debug)] +#[derive(PartialEq, Eq, Hash, Clone, Copy, Default, Debug, Reflect)] pub struct Vector2 { pub x: T, pub y: T,