add trans rendering and crosshair

This commit is contained in:
griffi-gh 2024-05-02 00:32:43 +02:00
parent b95be7fb2a
commit d57787059a
12 changed files with 571 additions and 446 deletions

BIN
assets/blocks/water.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 341 B

View file

@ -22,6 +22,7 @@ pub enum BlockTexture {
Cobblestone, Cobblestone,
Planks, Planks,
WaterSolid, WaterSolid,
Water,
} }
#[derive(Serialize, Deserialize, Clone, Copy, Debug, PartialEq, Eq, EnumIter, TryFromPrimitive)] #[derive(Serialize, Deserialize, Clone, Copy, Debug, PartialEq, Eq, EnumIter, TryFromPrimitive)]
@ -136,7 +137,7 @@ impl Block {
}, },
Self::Water => BlockDescriptor { Self::Water => BlockDescriptor {
name: "water", name: "water",
render: RenderType::BinaryTransparency(CubeTexture::all(BlockTexture::WaterSolid)), render: RenderType::TransBlock(CubeTexture::all(BlockTexture::Water)),
collision: CollisionType::None, collision: CollisionType::None,
raycast_collision: true, raycast_collision: true,
drops: None, drops: None,
@ -217,6 +218,7 @@ pub enum CollisionType {
pub enum RenderType { pub enum RenderType {
None, None,
SolidBlock(CubeTexture), SolidBlock(CubeTexture),
TransBlock(CubeTexture),
BinaryTransparency(CubeTexture), BinaryTransparency(CubeTexture),
CrossShape(CrossTexture), CrossShape(CrossTexture),
} }

View file

@ -1,29 +1,30 @@
#version 300 es #version 300 es
precision highp float; precision highp float;
precision lowp sampler2DArray; precision lowp sampler2DArray;
in vec3 v_normal; in vec3 v_normal;
in vec2 v_uv; in vec2 v_uv;
flat in uint v_tex_index; flat in uint v_tex_index;
out vec4 color; out vec4 color;
uniform sampler2DArray tex; uniform sampler2DArray tex;
uniform bool discard_alpha;
// vec4 alpha_drop(vec4 b, vec4 a) {
// if ((a.w < 1.) || (b.w < 1.)) { // vec4 alpha_drop(vec4 b, vec4 a) {
// return vec4(b.xyz, 0.); // if ((a.w < 1.) || (b.w < 1.)) {
// } // return vec4(b.xyz, 0.);
// return a; // }
// } // return a;
// }
void main() {
// base color from texture void main() {
color = texture(tex, vec3(v_uv, v_tex_index)); // base color from texture
// discard transparent pixels color = texture(tex, vec3(v_uv, v_tex_index));
if (color.w < 0.5) discard; // discard transparent pixels
//basic "lighting" if (color.w < (discard_alpha ? 0.01 : 0.5)) discard;
float light = abs(v_normal.x) + .8 * abs(v_normal.y) + .6 * abs(v_normal.z); //basic "lighting"
color *= vec4(vec3(light), 1.); float light = abs(v_normal.x) + .8 * abs(v_normal.y) + .6 * abs(v_normal.z);
//discard alpha color *= vec4(vec3(light), 1.);
color.w = 1.; //discard alpha
} if (discard_alpha) color.w = 1.;
}

View file

@ -15,12 +15,13 @@ use std::time::Instant;
pub(crate) use kubi_shared::transform; pub(crate) use kubi_shared::transform;
mod ui { mod ui;
pub(crate) mod loading_screen; pub(crate) use ui::{
pub(crate) mod connecting_screen; loading_screen,
pub(crate) mod chat_ui; connecting_screen,
} chat_ui,
pub(crate) use ui::{loading_screen, connecting_screen, chat_ui}; crosshair_ui,
};
pub(crate) mod rendering; pub(crate) mod rendering;
pub(crate) mod world; pub(crate) mod world;
@ -91,6 +92,7 @@ use filesystem::AssetManager;
use client_physics::{init_client_physics, update_client_physics_late}; use client_physics::{init_client_physics, update_client_physics_late};
use chat_ui::render_chat; use chat_ui::render_chat;
use chat::init_chat_manager; use chat::init_chat_manager;
use crosshair_ui::{init_crosshair_image, draw_crosshair};
/// stuff required to init the renderer and other basic systems /// stuff required to init the renderer and other basic systems
fn pre_startup() -> Workload { fn pre_startup() -> Workload {
@ -116,6 +118,7 @@ fn startup() -> Workload {
init_delta_time, init_delta_time,
init_client_physics, init_client_physics,
init_chat_manager, init_chat_manager,
init_crosshair_image,
).into_sequential_workload() ).into_sequential_workload()
} }
@ -151,6 +154,7 @@ fn update() -> Workload {
update_block_placement, update_block_placement,
apply_queued_blocks, apply_queued_blocks,
render_chat, render_chat,
draw_crosshair,
).into_sequential_workload().run_if(is_ingame), ).into_sequential_workload().run_if(is_ingame),
update_networking_late.run_if(is_multiplayer), update_networking_late.run_if(is_multiplayer),
compute_cameras, compute_cameras,

View file

@ -34,6 +34,7 @@ impl AssetPaths for BlockTexture {
Self::Cobblestone => "cobblestone.png", Self::Cobblestone => "cobblestone.png",
Self::Planks => "planks.png", Self::Planks => "planks.png",
Self::WaterSolid => "solid_water.png", Self::WaterSolid => "solid_water.png",
Self::Water => "water.png",
} }
} }
} }

View file

@ -54,7 +54,7 @@ pub fn draw_world(
settings: UniqueView<GameSettings> settings: UniqueView<GameSettings>
) { ) {
let camera = camera.iter().next().expect("No cameras in the scene"); let camera = camera.iter().next().expect("No cameras in the scene");
let draw_parameters = DrawParameters { let mut draw_parameters = DrawParameters {
depth: Depth { depth: Depth {
test: DepthTest::IfLess, test: DepthTest::IfLess,
write: true, write: true,
@ -75,6 +75,8 @@ pub fn draw_world(
let view = camera.view_matrix.to_cols_array_2d(); let view = camera.view_matrix.to_cols_array_2d();
let perspective = camera.perspective_matrix.to_cols_array_2d(); let perspective = camera.perspective_matrix.to_cols_array_2d();
let mut enqueue_trans = Vec::new();
for (&position, chunk) in &chunks.chunks { for (&position, chunk) in &chunks.chunks {
if let Some(key) = chunk.mesh_index { if let Some(key) = chunk.mesh_index {
let mesh = meshes.get(key).expect("Mesh index pointing to nothing"); let mesh = meshes.get(key).expect("Mesh index pointing to nothing");
@ -107,8 +109,31 @@ pub fn draw_world(
}, },
&draw_parameters &draw_parameters
).unwrap(); ).unwrap();
if mesh.trans_index_buffer.len() > 0 {
enqueue_trans.push((chunk, mesh));
}
} }
} }
draw_parameters.blend = Blend::alpha_blending();
draw_parameters.backface_culling = BackfaceCullingMode::CullingDisabled;
for (chunk, mesh) in enqueue_trans {
let world_position = chunk.position.as_vec3() * CHUNK_SIZE as f32;
target.0.draw(
&mesh.trans_vertex_buffer,
&mesh.trans_index_buffer,
&program.0,
&uniform! {
position_offset: world_position.to_array(),
view: view,
perspective: perspective,
tex: texture_sampler,
},
&draw_parameters
).unwrap();
}
} }
pub fn draw_current_chunk_border( pub fn draw_current_chunk_border(

4
kubi/src/ui.rs Normal file
View file

@ -0,0 +1,4 @@
pub(crate) mod loading_screen;
pub(crate) mod connecting_screen;
pub(crate) mod chat_ui;
pub(crate) mod crosshair_ui;

View file

@ -0,0 +1,60 @@
use std::f32::consts::PI;
use glam::uvec2;
use hui::{
draw::{ImageHandle, TextureFormat},
element::{container::Container, image::Image, transformer::ElementTransformExt, UiElementExt},
layout::Alignment,
size
};
use shipyard::{AllStoragesViewMut, IntoIter, NonSendSync, Unique, UniqueView, UniqueViewMut, View};
use crate::{hui_integration::UiState, player::MainPlayer, rendering::WindowSize, world::raycast::LookingAtBlock};
const CROSSHAIR_SIZE: usize = 9;
const CROSSHAIR: &[u8] = &[
0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0x00,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0x00,
];
#[derive(Unique)]
pub struct CrosshairImage(ImageHandle);
pub fn init_crosshair_image(storages: AllStoragesViewMut) {
let mut ui = storages.borrow::<NonSendSync<UniqueViewMut<UiState>>>().unwrap();
let image = ui.hui.add_image(TextureFormat::Grayscale, CROSSHAIR, CROSSHAIR_SIZE);
storages.add_unique(CrosshairImage(image));
}
pub fn draw_crosshair(
mut ui: NonSendSync<UniqueViewMut<UiState>>,
crosshair: UniqueView<CrosshairImage>,
size: UniqueView<WindowSize>,
player: View<MainPlayer>,
raycast: View<LookingAtBlock>,
) {
let mut cursor_active = false;
if let Some((_, raycast)) = (&player, &raycast).iter().next() {
cursor_active = raycast.0.is_some();
}
Container::default()
.with_size(size!(100%))
.with_align(Alignment::Center)
.with_children(|ui| {
Image::new(crosshair.0)
.with_color((1., 1., 1., 0.5))
.with_size(size!(18, 18))
.transform()
.scale(glam::Vec2::splat(if cursor_active { 1. } else { 0.66 }))
.rotate(if cursor_active { 0. } else { PI / 4. })
.add_child(ui);
})
.add_root(&mut ui.hui, uvec2(size.0.x & !1, size.0.y & !1).as_vec2());
}

View file

@ -1,69 +1,71 @@
use glam::IVec3; use glam::IVec3;
use glium::{VertexBuffer, IndexBuffer}; use glium::{VertexBuffer, IndexBuffer};
use crate::rendering::world::ChunkVertex; use crate::rendering::world::ChunkVertex;
pub use kubi_shared::chunk::{CHUNK_SIZE, BlockData}; pub use kubi_shared::chunk::{CHUNK_SIZE, BlockData};
pub struct ChunkData { pub struct ChunkData {
pub blocks: BlockData, pub blocks: BlockData,
//pub has_renderable_blocks: bool, //pub has_renderable_blocks: bool,
} }
impl ChunkData { impl ChunkData {
// pub fn update_metadata(&mut self) { // pub fn update_metadata(&mut self) {
// todo!() // todo!()
// } // }
} }
pub struct ChunkMesh { pub struct ChunkMesh {
pub vertex_buffer: VertexBuffer<ChunkVertex>, pub vertex_buffer: VertexBuffer<ChunkVertex>,
pub index_buffer: IndexBuffer<u32>, pub index_buffer: IndexBuffer<u32>,
} pub trans_vertex_buffer: VertexBuffer<ChunkVertex>,
pub trans_index_buffer: IndexBuffer<u32>,
#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)] }
pub enum CurrentChunkState {
#[default] #[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
Nothing, pub enum CurrentChunkState {
Loading, #[default]
Loaded, Nothing,
CalculatingMesh, Loading,
Rendered, Loaded,
RecalculatingMesh, CalculatingMesh,
Unloading, Rendered,
} RecalculatingMesh,
Unloading,
#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)] }
pub enum DesiredChunkState {
#[default] #[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
Nothing, pub enum DesiredChunkState {
Loaded, #[default]
Rendered, Nothing,
ToUnload, Loaded,
} Rendered,
impl DesiredChunkState { ToUnload,
pub fn matches_current(self, current: CurrentChunkState) -> bool { }
(matches!(self, DesiredChunkState::Nothing) && matches!(current, CurrentChunkState::Nothing)) || impl DesiredChunkState {
(matches!(self, DesiredChunkState::Loaded) && matches!(current, CurrentChunkState::Loaded)) || pub fn matches_current(self, current: CurrentChunkState) -> bool {
(matches!(self, DesiredChunkState::Rendered) && matches!(current, CurrentChunkState::Rendered)) (matches!(self, DesiredChunkState::Nothing) && matches!(current, CurrentChunkState::Nothing)) ||
} (matches!(self, DesiredChunkState::Loaded) && matches!(current, CurrentChunkState::Loaded)) ||
} (matches!(self, DesiredChunkState::Rendered) && matches!(current, CurrentChunkState::Rendered))
}
pub struct Chunk { }
pub position: IVec3,
pub block_data: Option<ChunkData>, pub struct Chunk {
pub mesh_index: Option<usize>, pub position: IVec3,
pub current_state: CurrentChunkState, pub block_data: Option<ChunkData>,
pub desired_state: DesiredChunkState, pub mesh_index: Option<usize>,
pub mesh_dirty: bool, pub current_state: CurrentChunkState,
} pub desired_state: DesiredChunkState,
impl Chunk { pub mesh_dirty: bool,
pub fn new(position: IVec3) -> Self { }
Self { impl Chunk {
position, pub fn new(position: IVec3) -> Self {
block_data: None, Self {
mesh_index: None, position,
current_state: Default::default(), block_data: None,
desired_state: Default::default(), mesh_index: None,
mesh_dirty: false, current_state: Default::default(),
} desired_state: Default::default(),
} mesh_dirty: false,
} }
}
}

View file

@ -1,262 +1,267 @@
use glam::{IVec3, ivec3}; use glam::{IVec3, ivec3};
use glium::{VertexBuffer, IndexBuffer, index::PrimitiveType}; use glium::{VertexBuffer, IndexBuffer, index::PrimitiveType};
use kubi_shared::networking::messages::ClientToServerMessage; use kubi_shared::networking::messages::ClientToServerMessage;
use shipyard::{View, UniqueView, UniqueViewMut, IntoIter, Workload, IntoWorkload, NonSendSync, track}; use shipyard::{View, UniqueView, UniqueViewMut, IntoIter, Workload, IntoWorkload, NonSendSync, track};
use uflow::SendMode; use uflow::SendMode;
use crate::{ use crate::{
player::MainPlayer, player::MainPlayer,
transform::Transform, transform::Transform,
settings::GameSettings, settings::GameSettings,
rendering::Renderer, rendering::Renderer,
state::GameState, state::GameState,
networking::UdpClient, networking::UdpClient,
}; };
use super::{ use super::{
ChunkStorage, ChunkMeshStorage, ChunkStorage, ChunkMeshStorage,
chunk::{Chunk, DesiredChunkState, CHUNK_SIZE, ChunkMesh, CurrentChunkState, ChunkData}, chunk::{Chunk, DesiredChunkState, CHUNK_SIZE, ChunkMesh, CurrentChunkState, ChunkData},
tasks::{ChunkTaskManager, ChunkTaskResponse, ChunkTask}, tasks::{ChunkTaskManager, ChunkTaskResponse, ChunkTask},
queue::BlockUpdateQueue queue::BlockUpdateQueue
}; };
const MAX_CHUNK_OPS_INGAME: usize = 6; const MAX_CHUNK_OPS_INGAME: usize = 6;
const MAX_CHUNK_OPS: usize = 32; const MAX_CHUNK_OPS: usize = 32;
pub fn update_loaded_world_around_player() -> Workload { pub fn update_loaded_world_around_player() -> Workload {
( (
update_chunks_if_player_moved, update_chunks_if_player_moved,
unload_downgrade_chunks, unload_downgrade_chunks,
start_required_tasks, start_required_tasks,
process_completed_tasks, process_completed_tasks,
).into_sequential_workload() ).into_sequential_workload()
} }
pub fn update_chunks_if_player_moved( pub fn update_chunks_if_player_moved(
v_settings: UniqueView<GameSettings>, v_settings: UniqueView<GameSettings>,
v_local_player: View<MainPlayer>, v_local_player: View<MainPlayer>,
v_transform: View<Transform, track::All>, v_transform: View<Transform, track::All>,
mut vm_world: UniqueViewMut<ChunkStorage>, mut vm_world: UniqueViewMut<ChunkStorage>,
) { ) {
//Check if the player actually moved //Check if the player actually moved
//TODO fix this also triggers on rotation, only activate when the player crosses the chunk border //TODO fix this also triggers on rotation, only activate when the player crosses the chunk border
let Some((_, transform)) = (&v_local_player, v_transform.inserted_or_modified()).iter().next() else { let Some((_, transform)) = (&v_local_player, v_transform.inserted_or_modified()).iter().next() else {
return return
}; };
//Read game settings //Read game settings
let load_distance = (v_settings.render_distance + 1) as i32; let load_distance = (v_settings.render_distance + 1) as i32;
//If it did, get it's position and current chunk //If it did, get it's position and current chunk
let player_position = transform.0.to_scale_rotation_translation().2; let player_position = transform.0.to_scale_rotation_translation().2;
let player_position_ivec3 = player_position.as_ivec3(); let player_position_ivec3 = player_position.as_ivec3();
let player_at_chunk = ivec3( let player_at_chunk = ivec3(
player_position_ivec3.x.div_euclid(CHUNK_SIZE as i32), player_position_ivec3.x.div_euclid(CHUNK_SIZE as i32),
player_position_ivec3.y.div_euclid(CHUNK_SIZE as i32), player_position_ivec3.y.div_euclid(CHUNK_SIZE as i32),
player_position_ivec3.z.div_euclid(CHUNK_SIZE as i32), player_position_ivec3.z.div_euclid(CHUNK_SIZE as i32),
); );
//Then, mark *ALL* chunks with ToUnload //Then, mark *ALL* chunks with ToUnload
for (_, chunk) in &mut vm_world.chunks { for (_, chunk) in &mut vm_world.chunks {
chunk.desired_state = DesiredChunkState::ToUnload; chunk.desired_state = DesiredChunkState::ToUnload;
} }
//Then mark chunks that are near to the player //Then mark chunks that are near to the player
for x in -load_distance..=load_distance { for x in -load_distance..=load_distance {
for y in -load_distance..=load_distance { for y in -load_distance..=load_distance {
for z in -load_distance..=load_distance { for z in -load_distance..=load_distance {
let chunk_pos_offset = ivec3(x, y, z); let chunk_pos_offset = ivec3(x, y, z);
let chunk_pos = player_at_chunk + chunk_pos_offset; let chunk_pos = player_at_chunk + chunk_pos_offset;
let is_border = { let is_border = {
chunk_pos_offset.x.abs() == load_distance || chunk_pos_offset.x.abs() == load_distance ||
chunk_pos_offset.y.abs() == load_distance || chunk_pos_offset.y.abs() == load_distance ||
chunk_pos_offset.z.abs() == load_distance chunk_pos_offset.z.abs() == load_distance
}; };
//If chunk doesn't exist create it //If chunk doesn't exist create it
let chunk = match vm_world.chunks.get_mut(&chunk_pos) { let chunk = match vm_world.chunks.get_mut(&chunk_pos) {
Some(chunk) => chunk, Some(chunk) => chunk,
None => { None => {
let chunk = Chunk::new(chunk_pos); let chunk = Chunk::new(chunk_pos);
vm_world.chunks.insert_unique_unchecked(chunk_pos, chunk); vm_world.chunks.insert_unique_unchecked(chunk_pos, chunk);
vm_world.chunks.get_mut(&chunk_pos).unwrap() vm_world.chunks.get_mut(&chunk_pos).unwrap()
} }
}; };
let desired = match is_border { let desired = match is_border {
true => DesiredChunkState::Loaded, true => DesiredChunkState::Loaded,
false => DesiredChunkState::Rendered, false => DesiredChunkState::Rendered,
}; };
chunk.desired_state = desired; chunk.desired_state = desired;
} }
} }
} }
} }
fn unload_downgrade_chunks( fn unload_downgrade_chunks(
mut vm_world: UniqueViewMut<ChunkStorage>, mut vm_world: UniqueViewMut<ChunkStorage>,
mut vm_meshes: NonSendSync<UniqueViewMut<ChunkMeshStorage>> mut vm_meshes: NonSendSync<UniqueViewMut<ChunkMeshStorage>>
) { ) {
if !vm_world.is_modified() { if !vm_world.is_modified() {
return return
} }
//TODO refactor this //TODO refactor this
//TODO unsubscibe if in multiplayer //TODO unsubscibe if in multiplayer
vm_world.chunks.retain(|_, chunk| { vm_world.chunks.retain(|_, chunk| {
if chunk.desired_state == DesiredChunkState::ToUnload { if chunk.desired_state == DesiredChunkState::ToUnload {
if let Some(mesh_index) = chunk.mesh_index { if let Some(mesh_index) = chunk.mesh_index {
vm_meshes.remove(mesh_index).unwrap(); vm_meshes.remove(mesh_index).unwrap();
} }
false false
} else { } else {
match chunk.desired_state { match chunk.desired_state {
DesiredChunkState::Loaded if matches!(chunk.current_state, CurrentChunkState::Rendered | CurrentChunkState::CalculatingMesh | CurrentChunkState::RecalculatingMesh) => { DesiredChunkState::Loaded if matches!(chunk.current_state, CurrentChunkState::Rendered | CurrentChunkState::CalculatingMesh | CurrentChunkState::RecalculatingMesh) => {
if let Some(mesh_index) = chunk.mesh_index { if let Some(mesh_index) = chunk.mesh_index {
vm_meshes.remove(mesh_index).unwrap(); vm_meshes.remove(mesh_index).unwrap();
} }
chunk.mesh_index = None; chunk.mesh_index = None;
chunk.current_state = CurrentChunkState::Loaded; chunk.current_state = CurrentChunkState::Loaded;
}, },
_ => (), _ => (),
} }
true true
} }
}) })
} }
fn start_required_tasks( fn start_required_tasks(
task_manager: UniqueView<ChunkTaskManager>, task_manager: UniqueView<ChunkTaskManager>,
mut udp_client: Option<UniqueViewMut<UdpClient>>, mut udp_client: Option<UniqueViewMut<UdpClient>>,
mut world: UniqueViewMut<ChunkStorage>, mut world: UniqueViewMut<ChunkStorage>,
) { ) {
if !world.is_modified() { if !world.is_modified() {
return return
} }
//HACK: cant iterate over chunks.keys() or chunk directly! //HACK: cant iterate over chunks.keys() or chunk directly!
let hashmap_keys: Vec<IVec3> = world.chunks.keys().copied().collect(); let hashmap_keys: Vec<IVec3> = world.chunks.keys().copied().collect();
for position in hashmap_keys { for position in hashmap_keys {
let chunk = world.chunks.get(&position).unwrap(); let chunk = world.chunks.get(&position).unwrap();
match chunk.desired_state { match chunk.desired_state {
DesiredChunkState::Loaded | DesiredChunkState::Rendered if chunk.current_state == CurrentChunkState::Nothing => { DesiredChunkState::Loaded | DesiredChunkState::Rendered if chunk.current_state == CurrentChunkState::Nothing => {
//start load task //start load task
if let Some(client) = &mut udp_client { if let Some(client) = &mut udp_client {
client.0.send( client.0.send(
postcard::to_allocvec(&ClientToServerMessage::ChunkSubRequest { postcard::to_allocvec(&ClientToServerMessage::ChunkSubRequest {
chunk: position, chunk: position,
}).unwrap().into_boxed_slice(), }).unwrap().into_boxed_slice(),
0, 0,
SendMode::Reliable SendMode::Reliable
); );
} else { } else {
task_manager.spawn_task(ChunkTask::LoadChunk { task_manager.spawn_task(ChunkTask::LoadChunk {
seed: 0xbeef_face_dead_cafe, seed: 0xbeef_face_dead_cafe,
position position
}); });
} }
//Update chunk state //Update chunk state
let chunk = world.chunks.get_mut(&position).unwrap(); let chunk = world.chunks.get_mut(&position).unwrap();
chunk.current_state = CurrentChunkState::Loading; chunk.current_state = CurrentChunkState::Loading;
// =========== // ===========
//log::trace!("Started loading chunk {position}"); //log::trace!("Started loading chunk {position}");
}, },
DesiredChunkState::Rendered if (chunk.current_state == CurrentChunkState::Loaded || chunk.mesh_dirty) => { DesiredChunkState::Rendered if (chunk.current_state == CurrentChunkState::Loaded || chunk.mesh_dirty) => {
//get needed data //get needed data
let Some(neighbors) = world.neighbors_all(position) else { let Some(neighbors) = world.neighbors_all(position) else {
continue continue
}; };
let Some(data) = neighbors.mesh_data() else { let Some(data) = neighbors.mesh_data() else {
continue continue
}; };
//spawn task //spawn task
task_manager.spawn_task(ChunkTask::GenerateMesh { data, position }); task_manager.spawn_task(ChunkTask::GenerateMesh { data, position });
//Update chunk state //Update chunk state
let chunk = world.chunks.get_mut(&position).unwrap(); let chunk = world.chunks.get_mut(&position).unwrap();
if chunk.mesh_dirty { if chunk.mesh_dirty {
chunk.current_state = CurrentChunkState::RecalculatingMesh; chunk.current_state = CurrentChunkState::RecalculatingMesh;
} else { } else {
chunk.current_state = CurrentChunkState::CalculatingMesh; chunk.current_state = CurrentChunkState::CalculatingMesh;
} }
chunk.mesh_dirty = false; chunk.mesh_dirty = false;
// =========== // ===========
//log::trace!("Started generating mesh for chunk {position}"); //log::trace!("Started generating mesh for chunk {position}");
} }
_ => () _ => ()
} }
} }
} }
fn process_completed_tasks( fn process_completed_tasks(
task_manager: UniqueView<ChunkTaskManager>, task_manager: UniqueView<ChunkTaskManager>,
mut world: UniqueViewMut<ChunkStorage>, mut world: UniqueViewMut<ChunkStorage>,
mut meshes: NonSendSync<UniqueViewMut<ChunkMeshStorage>>, mut meshes: NonSendSync<UniqueViewMut<ChunkMeshStorage>>,
renderer: NonSendSync<UniqueView<Renderer>>, renderer: NonSendSync<UniqueView<Renderer>>,
state: UniqueView<GameState>, state: UniqueView<GameState>,
mut queue: UniqueViewMut<BlockUpdateQueue>, mut queue: UniqueViewMut<BlockUpdateQueue>,
) { ) {
let mut ops: usize = 0; let mut ops: usize = 0;
while let Some(res) = task_manager.receive() { while let Some(res) = task_manager.receive() {
match res { match res {
ChunkTaskResponse::LoadedChunk { position, chunk_data, mut queued } => { ChunkTaskResponse::LoadedChunk { position, chunk_data, mut queued } => {
//check if chunk exists //check if chunk exists
let Some(chunk) = world.chunks.get_mut(&position) else { let Some(chunk) = world.chunks.get_mut(&position) else {
log::warn!("blocks data discarded: chunk doesn't exist"); log::warn!("blocks data discarded: chunk doesn't exist");
return return
}; };
//check if chunk still wants it //check if chunk still wants it
if !matches!(chunk.desired_state, DesiredChunkState::Loaded | DesiredChunkState::Rendered) { if !matches!(chunk.desired_state, DesiredChunkState::Loaded | DesiredChunkState::Rendered) {
log::warn!("block data discarded: state undesirable: {:?}", chunk.desired_state); log::warn!("block data discarded: state undesirable: {:?}", chunk.desired_state);
return return
} }
//set the block data //set the block data
chunk.block_data = Some(ChunkData { chunk.block_data = Some(ChunkData {
blocks: chunk_data blocks: chunk_data
}); });
//update chunk state //update chunk state
chunk.current_state = CurrentChunkState::Loaded; chunk.current_state = CurrentChunkState::Loaded;
//push queued blocks //push queued blocks
queue.0.append(&mut queued); queue.0.append(&mut queued);
drop(queued); //`queued` is empty after `append` drop(queued); //`queued` is empty after `append`
//increase ops counter //increase ops counter
ops += 1; ops += 1;
}, },
ChunkTaskResponse::GeneratedMesh { position, vertices, indexes } => { ChunkTaskResponse::GeneratedMesh {
//check if chunk exists position,
let Some(chunk) = world.chunks.get_mut(&position) else { vertices, indices,
log::warn!("mesh discarded: chunk doesn't exist"); trans_vertices, trans_indices,
return } => {
}; //check if chunk exists
let Some(chunk) = world.chunks.get_mut(&position) else {
//check if chunk still wants it log::warn!("mesh discarded: chunk doesn't exist");
if chunk.desired_state != DesiredChunkState::Rendered { return
log::warn!("mesh discarded: state undesirable: {:?}", chunk.desired_state); };
return
} //check if chunk still wants it
if chunk.desired_state != DesiredChunkState::Rendered {
//apply the mesh log::warn!("mesh discarded: state undesirable: {:?}", chunk.desired_state);
let vertex_buffer = VertexBuffer::immutable(&renderer.display, &vertices).unwrap(); return
let index_buffer = IndexBuffer::immutable(&renderer.display, PrimitiveType::TrianglesList, &indexes).unwrap(); }
let mesh = ChunkMesh {
vertex_buffer, //apply the mesh
index_buffer, //TODO: Skip if mesh is empty? (i.e. set to None)
}; let mesh = ChunkMesh {
if let Some(index) = chunk.mesh_index { vertex_buffer: VertexBuffer::immutable(&renderer.display, &vertices).unwrap(),
meshes.update(index, mesh).expect("Mesh update failed"); index_buffer: IndexBuffer::immutable(&renderer.display, PrimitiveType::TrianglesList, &indices).unwrap(),
} else { trans_vertex_buffer: VertexBuffer::immutable(&renderer.display, &trans_vertices).unwrap(),
let mesh_index = meshes.insert(mesh); trans_index_buffer: IndexBuffer::immutable(&renderer.display, PrimitiveType::TrianglesList, &trans_indices).unwrap(),
chunk.mesh_index = Some(mesh_index); };
} if let Some(index) = chunk.mesh_index {
meshes.update(index, mesh).expect("Mesh update failed");
//update chunk state } else {
chunk.current_state = CurrentChunkState::Rendered; let mesh_index = meshes.insert(mesh);
chunk.mesh_index = Some(mesh_index);
//increase ops counter }
ops += 1;
} //update chunk state
} chunk.current_state = CurrentChunkState::Rendered;
if ops >= match *state {
GameState::InGame => MAX_CHUNK_OPS_INGAME, //increase ops counter
_ => MAX_CHUNK_OPS, ops += 1;
} { break } }
} }
} if ops >= match *state {
GameState::InGame => MAX_CHUNK_OPS_INGAME,
_ => MAX_CHUNK_OPS,
} { break }
}
}

View file

@ -1,6 +1,6 @@
use glam::{IVec3, ivec3}; use glam::{IVec3, ivec3};
use strum::IntoEnumIterator; use strum::IntoEnumIterator;
use kubi_shared::block::{Block, RenderType}; use kubi_shared::block::{Block, BlockTexture, RenderType};
use crate::world::chunk::CHUNK_SIZE; use crate::world::chunk::CHUNK_SIZE;
use crate::rendering::world::ChunkVertex; use crate::rendering::world::ChunkVertex;
@ -10,7 +10,10 @@ mod builder;
use data::MeshGenData; use data::MeshGenData;
use builder::{MeshBuilder, CubeFace, DiagonalFace}; use builder::{MeshBuilder, CubeFace, DiagonalFace};
pub fn generate_mesh(data: MeshGenData) -> (Vec<ChunkVertex>, Vec<u32>) { pub fn generate_mesh(data: MeshGenData) -> (
(Vec<ChunkVertex>, Vec<u32>),
(Vec<ChunkVertex>, Vec<u32>),
) {
let get_block = |pos: IVec3| -> Block { let get_block = |pos: IVec3| -> Block {
if pos.x < 0 { if pos.x < 0 {
data.block_data_neg_x[(CHUNK_SIZE as i32 + pos.x) as usize][pos.y as usize][pos.z as usize] data.block_data_neg_x[(CHUNK_SIZE as i32 + pos.x) as usize][pos.y as usize][pos.z as usize]
@ -30,6 +33,7 @@ pub fn generate_mesh(data: MeshGenData) -> (Vec<ChunkVertex>, Vec<u32>) {
}; };
let mut builder = MeshBuilder::new(); let mut builder = MeshBuilder::new();
let mut trans_builder = MeshBuilder::new();
for x in 0..CHUNK_SIZE as i32 { for x in 0..CHUNK_SIZE as i32 {
for y in 0..CHUNK_SIZE as i32 { for y in 0..CHUNK_SIZE as i32 {
@ -39,7 +43,9 @@ pub fn generate_mesh(data: MeshGenData) -> (Vec<ChunkVertex>, Vec<u32>) {
let descriptor = block.descriptor(); let descriptor = block.descriptor();
match descriptor.render { match descriptor.render {
RenderType::None => continue, RenderType::None => continue,
RenderType::SolidBlock(textures) | RenderType::BinaryTransparency(textures) => { RenderType::SolidBlock(textures) |
RenderType::BinaryTransparency(textures) |
RenderType::TransBlock(textures) => {
for face in CubeFace::iter() { for face in CubeFace::iter() {
let facing_direction = face.normal(); let facing_direction = face.normal();
let facing_coord = coord + facing_direction; let facing_coord = coord + facing_direction;
@ -47,10 +53,12 @@ pub fn generate_mesh(data: MeshGenData) -> (Vec<ChunkVertex>, Vec<u32>) {
let facing_descriptor = facing_block.descriptor(); let facing_descriptor = facing_block.descriptor();
let face_obstructed = match descriptor.render { let face_obstructed = match descriptor.render {
RenderType::SolidBlock(_) => matches!(facing_descriptor.render, RenderType::SolidBlock(_)), RenderType::SolidBlock(_) => matches!(facing_descriptor.render, RenderType::SolidBlock(_)),
RenderType::BinaryTransparency(_) => { RenderType::BinaryTransparency(_) |
RenderType::TransBlock(_) => {
match facing_descriptor.render { match facing_descriptor.render {
RenderType::SolidBlock(_) => true, RenderType::SolidBlock(_) => true,
RenderType::BinaryTransparency(_) => block == facing_block, RenderType::BinaryTransparency(_) |
RenderType::TransBlock(_) => block == facing_block,
_ => false, _ => false,
} }
}, },
@ -65,7 +73,11 @@ pub fn generate_mesh(data: MeshGenData) -> (Vec<ChunkVertex>, Vec<u32>) {
CubeFace::Back => textures.back, CubeFace::Back => textures.back,
CubeFace::Bottom => textures.bottom, CubeFace::Bottom => textures.bottom,
}; };
builder.add_face(face, coord, face_texture as u8); let cur_builder = match descriptor.render {
RenderType::TransBlock(_) => &mut trans_builder,
_ => &mut builder,
};
cur_builder.add_face(face, coord, face_texture as u8);
} }
} }
}, },
@ -88,5 +100,5 @@ pub fn generate_mesh(data: MeshGenData) -> (Vec<ChunkVertex>, Vec<u32>) {
} }
} }
builder.finish() (builder.finish(), trans_builder.finish())
} }

View file

@ -1,71 +1,80 @@
use flume::{Sender, Receiver}; use flume::{Sender, Receiver};
use glam::IVec3; use glam::IVec3;
use kubi_shared::queue::QueuedBlock; use kubi_shared::queue::QueuedBlock;
use shipyard::Unique; use shipyard::Unique;
use rayon::{ThreadPool, ThreadPoolBuilder}; use rayon::{ThreadPool, ThreadPoolBuilder};
use super::{ use super::{
chunk::BlockData, chunk::BlockData,
mesh::{generate_mesh, data::MeshGenData}, mesh::{generate_mesh, data::MeshGenData},
worldgen::generate_world, worldgen::generate_world,
}; };
use crate::rendering::world::ChunkVertex; use crate::rendering::world::ChunkVertex;
pub enum ChunkTask { pub enum ChunkTask {
LoadChunk { LoadChunk {
seed: u64, seed: u64,
position: IVec3 position: IVec3
}, },
GenerateMesh { GenerateMesh {
position: IVec3, position: IVec3,
data: MeshGenData data: MeshGenData
} }
} }
pub enum ChunkTaskResponse { pub enum ChunkTaskResponse {
LoadedChunk { LoadedChunk {
position: IVec3, position: IVec3,
chunk_data: BlockData, chunk_data: BlockData,
queued: Vec<QueuedBlock> queued: Vec<QueuedBlock>
}, },
GeneratedMesh { GeneratedMesh {
position: IVec3, position: IVec3,
vertices: Vec<ChunkVertex>, vertices: Vec<ChunkVertex>,
indexes: Vec<u32> indices: Vec<u32>,
}, trans_vertices: Vec<ChunkVertex>,
} trans_indices: Vec<u32>,
},
#[derive(Unique)] }
pub struct ChunkTaskManager {
channel: (Sender<ChunkTaskResponse>, Receiver<ChunkTaskResponse>), #[derive(Unique)]
pool: ThreadPool, pub struct ChunkTaskManager {
} channel: (Sender<ChunkTaskResponse>, Receiver<ChunkTaskResponse>),
impl ChunkTaskManager { pool: ThreadPool,
pub fn new() -> Self { }
Self { impl ChunkTaskManager {
channel: flume::unbounded::<ChunkTaskResponse>(), //maybe put a bound or even bound(0)? pub fn new() -> Self {
pool: ThreadPoolBuilder::new().num_threads(4).build().unwrap() Self {
} channel: flume::unbounded::<ChunkTaskResponse>(), //maybe put a bound or even bound(0)?
} pool: ThreadPoolBuilder::new().num_threads(4).build().unwrap()
pub fn add_sussy_response(&self, response: ChunkTaskResponse) { }
// this WILL get stuck if the channel is bounded }
// don't make the channel bounded ever pub fn add_sussy_response(&self, response: ChunkTaskResponse) {
self.channel.0.send(response).unwrap() // this WILL get stuck if the channel is bounded
} // don't make the channel bounded ever
pub fn spawn_task(&self, task: ChunkTask) { self.channel.0.send(response).unwrap()
let sender = self.channel.0.clone(); }
self.pool.spawn(move || { pub fn spawn_task(&self, task: ChunkTask) {
let _ = sender.send(match task { let sender = self.channel.0.clone();
ChunkTask::GenerateMesh { position, data } => { self.pool.spawn(move || {
let (vertices, indexes) = generate_mesh(data); let _ = sender.send(match task {
ChunkTaskResponse::GeneratedMesh { position, vertices, indexes } ChunkTask::GenerateMesh { position, data } => {
}, let (
ChunkTask::LoadChunk { position, seed } => { (vertices, indices),
let (chunk_data, queued) = generate_world(position, seed); (trans_vertices, trans_indices),
ChunkTaskResponse::LoadedChunk { position, chunk_data, queued } ) = generate_mesh(data);
} ChunkTaskResponse::GeneratedMesh {
}); position,
}); vertices, indices,
} trans_vertices, trans_indices,
pub fn receive(&self) -> Option<ChunkTaskResponse> { }
self.channel.1.try_recv().ok() },
} ChunkTask::LoadChunk { position, seed } => {
} let (chunk_data, queued) = generate_world(position, seed);
ChunkTaskResponse::LoadedChunk { position, chunk_data, queued }
}
});
});
}
pub fn receive(&self) -> Option<ChunkTaskResponse> {
self.channel.1.try_recv().ok()
}
}