mirror of
https://github.com/griffi-gh/kubi.git
synced 2024-11-26 00:38:44 -06:00
commit
8cd8cf35a2
4
.gitignore
vendored
4
.gitignore
vendored
|
@ -3,10 +3,6 @@
|
||||||
debug/
|
debug/
|
||||||
target/
|
target/
|
||||||
|
|
||||||
# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
|
|
||||||
# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
|
|
||||||
Cargo.lock
|
|
||||||
|
|
||||||
# These are backup files generated by rustfmt
|
# These are backup files generated by rustfmt
|
||||||
**/*.rs.bk
|
**/*.rs.bk
|
||||||
|
|
||||||
|
|
9
.vscode/settings.json
vendored
Normal file
9
.vscode/settings.json
vendored
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
{
|
||||||
|
"editor.tabSize": 2,
|
||||||
|
"rust-analyzer.diagnostics.disabled": [
|
||||||
|
//rust-analyzer issue #14269,
|
||||||
|
"unresolved-method",
|
||||||
|
"unresolved-import",
|
||||||
|
"unresolved-field"
|
||||||
|
]
|
||||||
|
}
|
2317
Cargo.lock
generated
Normal file
2317
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load diff
25
Cargo.toml
25
Cargo.toml
|
@ -1,23 +1,10 @@
|
||||||
[package]
|
[workspace]
|
||||||
name = "kubi"
|
members = ["kubi", "kubi-server", "kubi-shared", "kubi-logging"]
|
||||||
version = "0.1.0"
|
resolver = "2"
|
||||||
edition = "2021"
|
|
||||||
|
|
||||||
[dependencies]
|
[profile.release-with-debug]
|
||||||
glium = "0.32"
|
inherits = "release"
|
||||||
image = { version = "0.24", default_features = false, features = ["png"] }
|
debug = true
|
||||||
log = "0.4"
|
|
||||||
env_logger = "0.10"
|
|
||||||
strum = { version = "0.24", features = ["derive"] }
|
|
||||||
glam = { version = "0.22", features = ["debug-glam-assert", "mint", "fast-math"] }
|
|
||||||
hashbrown = "0.13"
|
|
||||||
rayon = "1.6"
|
|
||||||
shipyard = { version = "0.6", features = ["thread_local"] }
|
|
||||||
nohash-hasher = "0.2.0"
|
|
||||||
anyhow = "1.0"
|
|
||||||
flume = "0.10"
|
|
||||||
bracket-noise = "0.8"
|
|
||||||
#rkyv = "0.7"
|
|
||||||
|
|
||||||
[profile.dev]
|
[profile.dev]
|
||||||
opt-level = 1
|
opt-level = 1
|
||||||
|
|
7
Server.toml
Normal file
7
Server.toml
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
[server]
|
||||||
|
address = "0.0.0.0:12345"
|
||||||
|
max_clients = 254
|
||||||
|
timeout_ms = 10000
|
||||||
|
|
||||||
|
[world]
|
||||||
|
seed = 0xfeb_face_dead_cafe
|
BIN
assets/blocks/cobblestone.png
Normal file
BIN
assets/blocks/cobblestone.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 578 B |
BIN
assets/blocks/planks.png
Normal file
BIN
assets/blocks/planks.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 247 B |
BIN
assets/blocks/solid_water.png
Normal file
BIN
assets/blocks/solid_water.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 210 B |
8
kubi-logging/Cargo.toml
Normal file
8
kubi-logging/Cargo.toml
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
[package]
|
||||||
|
name = "kubi-logging"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
log = "0.4"
|
||||||
|
env_logger = "0.10"
|
|
@ -1,14 +1,16 @@
|
||||||
//! Custom env_logger options and styling
|
/// Custom env_logger options and styling
|
||||||
|
|
||||||
use env_logger::{fmt::Color, Builder, Env};
|
use env_logger::{fmt::Color, Builder, Env};
|
||||||
use log::Level;
|
use log::Level;
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
|
|
||||||
|
pub use log;
|
||||||
|
pub use env_logger;
|
||||||
|
|
||||||
|
#[inline]
|
||||||
pub fn init() {
|
pub fn init() {
|
||||||
let mut env = Env::default();
|
let env = Env::default()
|
||||||
if cfg!(debug_assertions) {
|
.filter_or("RUST_LOG", "trace,gilrs=warn,rusty_xinput=warn");
|
||||||
env = env.filter_or("RUST_LOG", "trace");
|
|
||||||
}
|
|
||||||
Builder::from_env(env)
|
Builder::from_env(env)
|
||||||
.format(|buf, record| {
|
.format(|buf, record| {
|
||||||
let mut level_style = buf.style();
|
let mut level_style = buf.style();
|
||||||
|
@ -18,6 +20,9 @@ pub fn init() {
|
||||||
_ => Color::Blue
|
_ => Color::Blue
|
||||||
}).set_bold(true);
|
}).set_bold(true);
|
||||||
|
|
||||||
|
let mut bold_style = buf.style();
|
||||||
|
bold_style.set_bold(true);
|
||||||
|
|
||||||
let mut location_style = buf.style();
|
let mut location_style = buf.style();
|
||||||
location_style.set_bold(true);
|
location_style.set_bold(true);
|
||||||
location_style.set_dimmed(true);
|
location_style.set_dimmed(true);
|
||||||
|
@ -25,9 +30,11 @@ pub fn init() {
|
||||||
let mut location_line_style = buf.style();
|
let mut location_line_style = buf.style();
|
||||||
location_line_style.set_dimmed(true);
|
location_line_style.set_dimmed(true);
|
||||||
|
|
||||||
|
let text = format!("{}", record.args());
|
||||||
|
|
||||||
writeln!(
|
writeln!(
|
||||||
buf,
|
buf,
|
||||||
"{} {:<50}\t{}{}{}",
|
"{} {:<50}\t{}{}{}{}",
|
||||||
level_style.value(match record.level() {
|
level_style.value(match record.level() {
|
||||||
Level::Error => "[e]",
|
Level::Error => "[e]",
|
||||||
Level::Warn => "[w]",
|
Level::Warn => "[w]",
|
||||||
|
@ -35,7 +42,8 @@ pub fn init() {
|
||||||
Level::Debug => "[d]",
|
Level::Debug => "[d]",
|
||||||
Level::Trace => "[t]",
|
Level::Trace => "[t]",
|
||||||
}),
|
}),
|
||||||
format!("{}", record.args()),
|
text,
|
||||||
|
bold_style.value((text.len() > 50).then_some("\n ╰─ ").unwrap_or_default()),
|
||||||
location_style.value(record.target()),
|
location_style.value(record.target()),
|
||||||
location_line_style.value(" :"),
|
location_line_style.value(" :"),
|
||||||
location_line_style.value(record.line().unwrap_or(0))
|
location_line_style.value(record.line().unwrap_or(0))
|
26
kubi-server/Cargo.toml
Normal file
26
kubi-server/Cargo.toml
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
[package]
|
||||||
|
name = "kubi-server"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
publish = false
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
kubi-shared = { path = "../kubi-shared" }
|
||||||
|
kubi-logging = { path = "../kubi-logging" }
|
||||||
|
log = "*"
|
||||||
|
shipyard = { git = "https://github.com/leudz/shipyard", rev = "eb189f66", features = ["thread_local"] }
|
||||||
|
serde = { version = "1.0", default-features = false, features = ["alloc", "derive"] }
|
||||||
|
toml = "0.7"
|
||||||
|
glam = { version = "0.23", features = ["debug-glam-assert", "fast-math"] }
|
||||||
|
hashbrown = "0.13"
|
||||||
|
nohash-hasher = "0.2.0"
|
||||||
|
anyhow = "1.0"
|
||||||
|
rayon = "1.6"
|
||||||
|
flume = "0.10"
|
||||||
|
rand = "0.8"
|
||||||
|
uflow = "0.7"
|
||||||
|
postcard = { version = "1.0", features = ["alloc"] }
|
||||||
|
|
||||||
|
[features]
|
||||||
|
default = []
|
||||||
|
nightly = ["rand/nightly", "rand/simd_support", "serde/unstable", "glam/core-simd", "kubi-shared/nightly"]
|
86
kubi-server/src/auth.rs
Normal file
86
kubi-server/src/auth.rs
Normal file
|
@ -0,0 +1,86 @@
|
||||||
|
use shipyard::{UniqueView, NonSendSync};
|
||||||
|
use uflow::{server::Event as ServerEvent, SendMode};
|
||||||
|
use kubi_shared::networking::messages::{
|
||||||
|
ClientToServerMessage,
|
||||||
|
ServerToClientMessage,
|
||||||
|
InitData,
|
||||||
|
C_CLIENT_HELLO
|
||||||
|
};
|
||||||
|
use crate::{
|
||||||
|
server::{ServerEvents, UdpServer, IsMessageOfType},
|
||||||
|
config::ConfigTable
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn authenticate_players(
|
||||||
|
server: NonSendSync<UniqueView<UdpServer>>,
|
||||||
|
events: UniqueView<ServerEvents>,
|
||||||
|
config: UniqueView<ConfigTable>
|
||||||
|
) {
|
||||||
|
for event in &events.0 {
|
||||||
|
// if let ServerEvent::MessageReceived {
|
||||||
|
// from,
|
||||||
|
// message: ClientToServerMessage::ClientHello {
|
||||||
|
// username,
|
||||||
|
// password
|
||||||
|
// }
|
||||||
|
// } = event {
|
||||||
|
|
||||||
|
let ServerEvent::Receive(client_addr, data) = event else{
|
||||||
|
continue
|
||||||
|
};
|
||||||
|
let Some(client) = server.0.client(client_addr) else {
|
||||||
|
log::error!("Client doesn't exist");
|
||||||
|
continue
|
||||||
|
};
|
||||||
|
if !event.is_message_of_type::<C_CLIENT_HELLO>() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
let Ok(parsed_message) = postcard::from_bytes(data) else {
|
||||||
|
log::error!("Malformed message");
|
||||||
|
continue
|
||||||
|
};
|
||||||
|
let ClientToServerMessage::ClientHello { username, password } = parsed_message else {
|
||||||
|
unreachable!()
|
||||||
|
};
|
||||||
|
|
||||||
|
log::info!("ClientHello; username={} password={:?}", username, password);
|
||||||
|
|
||||||
|
// Handle password auth
|
||||||
|
if let Some(server_password) = &config.server.password {
|
||||||
|
if let Some(user_password) = &password {
|
||||||
|
if server_password != user_password {
|
||||||
|
let res = postcard::to_allocvec(&ServerToClientMessage::ServerFuckOff {
|
||||||
|
reason: "Passwords don't match".into()
|
||||||
|
}).unwrap().into_boxed_slice();
|
||||||
|
client.borrow_mut().send(
|
||||||
|
res, 0, SendMode::Reliable
|
||||||
|
);
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let res = postcard::to_allocvec(&ServerToClientMessage::ServerFuckOff {
|
||||||
|
reason: "This server is password protected".into()
|
||||||
|
}).unwrap().into_boxed_slice();
|
||||||
|
client.borrow_mut().send(
|
||||||
|
res, 0, SendMode::Reliable
|
||||||
|
);
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//Spawn the user
|
||||||
|
//TODO Spawn the user on server side
|
||||||
|
|
||||||
|
//Approve the user
|
||||||
|
let res = postcard::to_allocvec(&ServerToClientMessage::ServerHello {
|
||||||
|
init: InitData {
|
||||||
|
users: vec![] //TODO create init data
|
||||||
|
}
|
||||||
|
}).unwrap().into_boxed_slice();
|
||||||
|
client.borrow_mut().send(
|
||||||
|
res, 0, SendMode::Reliable
|
||||||
|
);
|
||||||
|
|
||||||
|
log::info!("{username} joined the game!")
|
||||||
|
}
|
||||||
|
}
|
19
kubi-server/src/client.rs
Normal file
19
kubi-server/src/client.rs
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
use shipyard::{Component, EntityId};
|
||||||
|
use hashbrown::HashMap;
|
||||||
|
use nohash_hasher::BuildNoHashHasher;
|
||||||
|
use kubi_shared::networking::client::ClientId;
|
||||||
|
|
||||||
|
#[derive(Component)]
|
||||||
|
pub struct Client(ClientId);
|
||||||
|
|
||||||
|
pub struct ClientMap(HashMap<ClientId, EntityId, BuildNoHashHasher<ClientId>>);
|
||||||
|
impl ClientMap {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self(HashMap::with_hasher(BuildNoHashHasher::default()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Default for ClientMap {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new()
|
||||||
|
}
|
||||||
|
}
|
31
kubi-server/src/config.rs
Normal file
31
kubi-server/src/config.rs
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
use shipyard::{AllStoragesView, Unique};
|
||||||
|
use serde::{Serialize, Deserialize};
|
||||||
|
use std::{fs, net::SocketAddr};
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
pub struct ConfigTableServer {
|
||||||
|
pub address: SocketAddr,
|
||||||
|
pub max_clients: usize,
|
||||||
|
pub timeout_ms: u64,
|
||||||
|
pub password: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
pub struct ConfigTableWorld {
|
||||||
|
pub seed: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Unique, Serialize, Deserialize)]
|
||||||
|
pub struct ConfigTable {
|
||||||
|
pub server: ConfigTableServer,
|
||||||
|
pub world: ConfigTableWorld,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn read_config(
|
||||||
|
storages: AllStoragesView,
|
||||||
|
) {
|
||||||
|
log::info!("Reading config...");
|
||||||
|
let config_str = fs::read_to_string("Server.toml").expect("No config file found");
|
||||||
|
let config: ConfigTable = toml::from_str(&config_str).expect("Invalid configuration file");
|
||||||
|
storages.add_unique(config);
|
||||||
|
}
|
46
kubi-server/src/main.rs
Normal file
46
kubi-server/src/main.rs
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
use shipyard::{World, Workload, IntoWorkload};
|
||||||
|
use std::{thread, time::Duration};
|
||||||
|
|
||||||
|
pub(crate) mod util;
|
||||||
|
pub(crate) mod config;
|
||||||
|
pub(crate) mod server;
|
||||||
|
pub(crate) mod client;
|
||||||
|
//pub(crate) mod world;
|
||||||
|
pub(crate) mod auth;
|
||||||
|
|
||||||
|
use config::read_config;
|
||||||
|
use server::{bind_server, update_server, log_server_errors};
|
||||||
|
use auth::authenticate_players;
|
||||||
|
//use world::{update_world, init_world};
|
||||||
|
|
||||||
|
fn initialize() -> Workload {
|
||||||
|
(
|
||||||
|
read_config,
|
||||||
|
bind_server,
|
||||||
|
//init_world,
|
||||||
|
).into_workload()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update() -> Workload {
|
||||||
|
(
|
||||||
|
update_server,
|
||||||
|
(
|
||||||
|
log_server_errors,
|
||||||
|
authenticate_players,
|
||||||
|
//update_world,
|
||||||
|
).into_workload()
|
||||||
|
).into_sequential_workload()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
kubi_logging::init();
|
||||||
|
let world = World::new();
|
||||||
|
world.add_workload(initialize);
|
||||||
|
world.add_workload(update);
|
||||||
|
world.run_workload(initialize).unwrap();
|
||||||
|
log::info!("The server is now running");
|
||||||
|
loop {
|
||||||
|
world.run_workload(update).unwrap();
|
||||||
|
thread::sleep(Duration::from_millis(16));
|
||||||
|
}
|
||||||
|
}
|
62
kubi-server/src/server.rs
Normal file
62
kubi-server/src/server.rs
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
use shipyard::{AllStoragesView, Unique, UniqueView, UniqueViewMut, NonSendSync};
|
||||||
|
use uflow::{server::{Server, Event as ServerEvent, Config as ServerConfig}, EndpointConfig};
|
||||||
|
use crate::config::ConfigTable;
|
||||||
|
|
||||||
|
#[derive(Unique)]
|
||||||
|
#[repr(transparent)]
|
||||||
|
pub struct UdpServer(pub Server);
|
||||||
|
|
||||||
|
#[derive(Unique, Default)]
|
||||||
|
pub struct ServerEvents(pub Vec<ServerEvent>);
|
||||||
|
|
||||||
|
pub trait IsMessageOfType {
|
||||||
|
///Checks if postcard-encoded message has a type
|
||||||
|
fn is_message_of_type<const T: u8>(&self) -> bool;
|
||||||
|
}
|
||||||
|
impl IsMessageOfType for ServerEvent {
|
||||||
|
fn is_message_of_type<const T: u8>(&self) -> bool {
|
||||||
|
let ServerEvent::Receive(_, data) = &self else { return false };
|
||||||
|
if data.len() == 0 { return false }
|
||||||
|
data[0] == T
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn bind_server(
|
||||||
|
storages: AllStoragesView,
|
||||||
|
) {
|
||||||
|
log::info!("Creating server...");
|
||||||
|
let config = storages.borrow::<UniqueView<ConfigTable>>().unwrap();
|
||||||
|
let server = Server::bind(
|
||||||
|
config.server.address,
|
||||||
|
ServerConfig {
|
||||||
|
max_total_connections: config.server.max_clients,
|
||||||
|
max_active_connections: config.server.max_clients,
|
||||||
|
enable_handshake_errors: true,
|
||||||
|
endpoint_config: EndpointConfig {
|
||||||
|
active_timeout_ms: config.server.timeout_ms,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
).expect("Failed to create the server");
|
||||||
|
storages.add_unique_non_send_sync(UdpServer(server));
|
||||||
|
storages.add_unique(ServerEvents::default());
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update_server(
|
||||||
|
mut server: NonSendSync<UniqueViewMut<UdpServer>>,
|
||||||
|
mut events: UniqueViewMut<ServerEvents>,
|
||||||
|
) {
|
||||||
|
events.0.clear();
|
||||||
|
events.0.extend(server.0.step());
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn log_server_errors(
|
||||||
|
events: UniqueView<ServerEvents>,
|
||||||
|
) {
|
||||||
|
for event in &events.0 {
|
||||||
|
if let ServerEvent::Error(addr, error) = event {
|
||||||
|
log::error!("Server error addr: {addr} error: {error:?}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
3
kubi-server/src/util.rs
Normal file
3
kubi-server/src/util.rs
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
pub fn log_error(error: anyhow::Error) {
|
||||||
|
log::error!("{}", error);
|
||||||
|
}
|
119
kubi-server/src/world.rs
Normal file
119
kubi-server/src/world.rs
Normal file
|
@ -0,0 +1,119 @@
|
||||||
|
use shipyard::{Unique, UniqueView, UniqueViewMut, Workload, IntoWorkload, AllStoragesView};
|
||||||
|
use glam::IVec3;
|
||||||
|
use hashbrown::HashMap;
|
||||||
|
use kubi_shared::networking::messages::{ClientToServerMessage, ServerToClientMessage};
|
||||||
|
use crate::{
|
||||||
|
server::{UdpServer, ServerEvents},
|
||||||
|
config::ConfigTable,
|
||||||
|
util::log_error,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub mod chunk;
|
||||||
|
pub mod tasks;
|
||||||
|
|
||||||
|
use chunk::Chunk;
|
||||||
|
|
||||||
|
use self::{tasks::{ChunkTaskManager, ChunkTask, ChunkTaskResponse, init_chunk_task_manager}, chunk::ChunkState};
|
||||||
|
|
||||||
|
#[derive(Unique, Default)]
|
||||||
|
pub struct ChunkManager {
|
||||||
|
pub chunks: HashMap<IVec3, Chunk>
|
||||||
|
}
|
||||||
|
impl ChunkManager {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn process_chunk_requests(
|
||||||
|
mut server: UniqueViewMut<UdpServer>,
|
||||||
|
events: UniqueView<ServerEvents>,
|
||||||
|
mut chunk_manager: UniqueViewMut<ChunkManager>,
|
||||||
|
task_manager: UniqueView<ChunkTaskManager>,
|
||||||
|
config: UniqueView<ConfigTable>
|
||||||
|
) {
|
||||||
|
for event in &events.0 {
|
||||||
|
if let ServerEvent::MessageReceived {
|
||||||
|
from: client_id,
|
||||||
|
message: ClientToServerMessage::ChunkSubRequest {
|
||||||
|
chunk: chunk_position
|
||||||
|
}
|
||||||
|
} = event {
|
||||||
|
let chunk_position = IVec3::from_array(*chunk_position);
|
||||||
|
if let Some(chunk) = chunk_manager.chunks.get_mut(&chunk_position) {
|
||||||
|
chunk.subscriptions.insert(*client_id);
|
||||||
|
//TODO Start task here if status is "Nothing"
|
||||||
|
if let Some(blocks) = &chunk.blocks {
|
||||||
|
server.0.send_message(*client_id, kubi_shared::networking::messages::ServerToClientMessage::ChunkResponse {
|
||||||
|
chunk: chunk_position.to_array(),
|
||||||
|
data: blocks.clone(),
|
||||||
|
queued: Vec::with_capacity(0)
|
||||||
|
}).map_err(log_error).ok();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let mut chunk = Chunk::new(chunk_position);
|
||||||
|
chunk.state = ChunkState::Loading;
|
||||||
|
chunk.subscriptions.insert(*client_id);
|
||||||
|
chunk_manager.chunks.insert(chunk_position, chunk);
|
||||||
|
task_manager.spawn_task(ChunkTask::LoadChunk {
|
||||||
|
position: chunk_position,
|
||||||
|
seed: config.world.seed,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn process_finished_tasks(
|
||||||
|
mut server: UniqueViewMut<UdpServer>,
|
||||||
|
task_manager: UniqueView<ChunkTaskManager>,
|
||||||
|
mut chunk_manager: UniqueViewMut<ChunkManager>,
|
||||||
|
) {
|
||||||
|
let mut limit: usize = 8;
|
||||||
|
while let Some(res) = task_manager.receive() {
|
||||||
|
let ChunkTaskResponse::ChunkLoaded { chunk_position, blocks, queue } = res;
|
||||||
|
let Some(chunk) = chunk_manager.chunks.get_mut(&chunk_position) else {
|
||||||
|
log::warn!("Chunk discarded: Doesn't exist");
|
||||||
|
continue
|
||||||
|
};
|
||||||
|
if chunk.state != ChunkState::Loading {
|
||||||
|
log::warn!("Chunk discarded: Not Loading");
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
chunk.state = ChunkState::Loaded;
|
||||||
|
chunk.blocks = Some(blocks.clone());
|
||||||
|
for &subscriber in &chunk.subscriptions {
|
||||||
|
server.0.send_message(subscriber, ServerToClientMessage::ChunkResponse {
|
||||||
|
chunk: chunk_position.to_array(),
|
||||||
|
data: blocks.clone(),
|
||||||
|
queued: queue
|
||||||
|
}).map_err(log_error).ok();
|
||||||
|
}
|
||||||
|
log::debug!("Chunk {chunk_position} loaded, {} subs", chunk.subscriptions.len());
|
||||||
|
//HACK: Implement proper flow control/reliable transport in kubi-udp
|
||||||
|
limit -= 1;
|
||||||
|
if limit == 0 {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn init_chunk_manager(
|
||||||
|
storages: AllStoragesView
|
||||||
|
) {
|
||||||
|
storages.add_unique(ChunkManager::new());
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn init_world() -> Workload {
|
||||||
|
(
|
||||||
|
init_chunk_manager,
|
||||||
|
init_chunk_task_manager,
|
||||||
|
).into_workload()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update_world() -> Workload {
|
||||||
|
(
|
||||||
|
process_chunk_requests,
|
||||||
|
process_finished_tasks,
|
||||||
|
).into_workload()
|
||||||
|
}
|
31
kubi-server/src/world/chunk.rs
Normal file
31
kubi-server/src/world/chunk.rs
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
use glam::IVec3;
|
||||||
|
use hashbrown::HashSet;
|
||||||
|
use nohash_hasher::BuildNoHashHasher;
|
||||||
|
use kubi_shared::{
|
||||||
|
chunk::BlockData,
|
||||||
|
networking::client::ClientId
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||||
|
pub enum ChunkState {
|
||||||
|
Nothing,
|
||||||
|
Loading,
|
||||||
|
Loaded,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Chunk {
|
||||||
|
pub position: IVec3,
|
||||||
|
pub state: ChunkState,
|
||||||
|
pub blocks: Option<BlockData>,
|
||||||
|
pub subscriptions: HashSet<ClientId, BuildNoHashHasher<ClientId>>,
|
||||||
|
}
|
||||||
|
impl Chunk {
|
||||||
|
pub fn new(position: IVec3) -> Self {
|
||||||
|
Self {
|
||||||
|
position,
|
||||||
|
state: ChunkState::Nothing,
|
||||||
|
blocks: None,
|
||||||
|
subscriptions: HashSet::with_hasher(BuildNoHashHasher::default()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
59
kubi-server/src/world/tasks.rs
Normal file
59
kubi-server/src/world/tasks.rs
Normal file
|
@ -0,0 +1,59 @@
|
||||||
|
use shipyard::{Unique, AllStoragesView};
|
||||||
|
use flume::{unbounded, Sender, Receiver};
|
||||||
|
use glam::IVec3;
|
||||||
|
use rayon::{ThreadPool, ThreadPoolBuilder};
|
||||||
|
use anyhow::Result;
|
||||||
|
use kubi_shared::{
|
||||||
|
chunk::BlockData,
|
||||||
|
worldgen::generate_world,
|
||||||
|
queue::QueuedBlock,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub enum ChunkTask {
|
||||||
|
LoadChunk {
|
||||||
|
position: IVec3,
|
||||||
|
seed: u64,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum ChunkTaskResponse {
|
||||||
|
ChunkLoaded {
|
||||||
|
chunk_position: IVec3,
|
||||||
|
blocks: BlockData,
|
||||||
|
queue: Vec<QueuedBlock>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Unique)]
|
||||||
|
pub struct ChunkTaskManager {
|
||||||
|
channel: (Sender<ChunkTaskResponse>, Receiver<ChunkTaskResponse>),
|
||||||
|
pool: ThreadPool,
|
||||||
|
}
|
||||||
|
impl ChunkTaskManager {
|
||||||
|
pub fn new() -> Result<Self> {
|
||||||
|
Ok(Self {
|
||||||
|
channel: unbounded(),
|
||||||
|
pool: ThreadPoolBuilder::new().build()?
|
||||||
|
})
|
||||||
|
}
|
||||||
|
pub fn spawn_task(&self, task: ChunkTask) {
|
||||||
|
let sender = self.channel.0.clone();
|
||||||
|
self.pool.spawn(move || {
|
||||||
|
sender.send(match task {
|
||||||
|
ChunkTask::LoadChunk { position: chunk_position, seed } => {
|
||||||
|
let (blocks, queue) = generate_world(chunk_position, seed);
|
||||||
|
ChunkTaskResponse::ChunkLoaded { chunk_position, blocks, queue }
|
||||||
|
}
|
||||||
|
}).unwrap()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
pub fn receive(&self) -> Option<ChunkTaskResponse> {
|
||||||
|
self.channel.1.try_recv().ok()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn init_chunk_task_manager(
|
||||||
|
storages: AllStoragesView
|
||||||
|
) {
|
||||||
|
storages.add_unique(ChunkTaskManager::new().expect("ChunkTaskManager Init failed"));
|
||||||
|
}
|
22
kubi-shared/Cargo.toml
Normal file
22
kubi-shared/Cargo.toml
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
[package]
|
||||||
|
name = "kubi-shared"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
publish = false
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
glam = { version = "0.23", features = ["debug-glam-assert", "fast-math", "serde"] }
|
||||||
|
shipyard = { git = "https://github.com/leudz/shipyard", rev = "eb189f66", default-features = false, features = ["std"] }
|
||||||
|
strum = { version = "0.24", features = ["derive"] }
|
||||||
|
postcard = { version = "1.0", features = ["alloc"] }
|
||||||
|
serde = { version = "1.0", default-features = false, features = ["alloc", "derive"] }
|
||||||
|
anyhow = "1.0"
|
||||||
|
bracket-noise = "0.8"
|
||||||
|
rand = { version = "0.8", default_features = false, features = ["std", "min_const_gen"] }
|
||||||
|
rand_xoshiro = "0.6"
|
||||||
|
|
||||||
|
[features]
|
||||||
|
default = []
|
||||||
|
nightly = ["rand/nightly", "rand/simd_support", "serde/unstable", "glam/core-simd"]
|
211
kubi-shared/src/block.rs
Normal file
211
kubi-shared/src/block.rs
Normal file
|
@ -0,0 +1,211 @@
|
||||||
|
use serde::{Serialize, Deserialize};
|
||||||
|
use strum::EnumIter;
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Clone, Copy, Debug, EnumIter)]
|
||||||
|
#[repr(u8)]
|
||||||
|
pub enum BlockTexture {
|
||||||
|
Stone,
|
||||||
|
Dirt,
|
||||||
|
GrassTop,
|
||||||
|
GrassSide,
|
||||||
|
Sand,
|
||||||
|
Bedrock,
|
||||||
|
Wood,
|
||||||
|
WoodTop,
|
||||||
|
Leaf,
|
||||||
|
Torch,
|
||||||
|
TallGrass,
|
||||||
|
Snow,
|
||||||
|
GrassSideSnow,
|
||||||
|
Cobblestone,
|
||||||
|
Planks,
|
||||||
|
WaterSolid,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Clone, Copy, Debug, PartialEq, Eq, EnumIter)]
|
||||||
|
#[repr(u8)]
|
||||||
|
pub enum Block {
|
||||||
|
Air,
|
||||||
|
Marker,
|
||||||
|
Stone,
|
||||||
|
Dirt,
|
||||||
|
Grass,
|
||||||
|
Sand,
|
||||||
|
Cobblestone,
|
||||||
|
TallGrass,
|
||||||
|
Planks,
|
||||||
|
Torch,
|
||||||
|
Wood,
|
||||||
|
Leaf,
|
||||||
|
Water,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Block {
|
||||||
|
#[inline]
|
||||||
|
pub const fn descriptor(self) -> BlockDescriptor {
|
||||||
|
match self {
|
||||||
|
Self::Air => BlockDescriptor {
|
||||||
|
name: "air",
|
||||||
|
render: RenderType::None,
|
||||||
|
collision: CollisionType::None,
|
||||||
|
raycast_collision: false,
|
||||||
|
},
|
||||||
|
Self::Marker => BlockDescriptor {
|
||||||
|
name: "marker",
|
||||||
|
render: RenderType::None,
|
||||||
|
collision: CollisionType::None,
|
||||||
|
raycast_collision: false,
|
||||||
|
},
|
||||||
|
Self::Stone => BlockDescriptor {
|
||||||
|
name: "stone",
|
||||||
|
render: RenderType::SolidBlock(CubeTexture::all(BlockTexture::Stone)),
|
||||||
|
collision: CollisionType::Solid,
|
||||||
|
raycast_collision: true,
|
||||||
|
},
|
||||||
|
Self::Dirt => BlockDescriptor {
|
||||||
|
name: "dirt",
|
||||||
|
render: RenderType::SolidBlock(CubeTexture::all(BlockTexture::Dirt)),
|
||||||
|
collision: CollisionType::Solid,
|
||||||
|
raycast_collision: true,
|
||||||
|
},
|
||||||
|
Self::Grass => BlockDescriptor {
|
||||||
|
name: "grass",
|
||||||
|
render: RenderType::SolidBlock(CubeTexture::top_sides_bottom(
|
||||||
|
BlockTexture::GrassTop,
|
||||||
|
BlockTexture::GrassSide,
|
||||||
|
BlockTexture::Dirt
|
||||||
|
)),
|
||||||
|
collision: CollisionType::Solid,
|
||||||
|
raycast_collision: true,
|
||||||
|
},
|
||||||
|
Self::Sand => BlockDescriptor {
|
||||||
|
name: "sand",
|
||||||
|
render: RenderType::SolidBlock(CubeTexture::all(BlockTexture::Sand)),
|
||||||
|
collision: CollisionType::Solid,
|
||||||
|
raycast_collision: true,
|
||||||
|
},
|
||||||
|
Self::Cobblestone => BlockDescriptor {
|
||||||
|
name: "cobblestone",
|
||||||
|
render: RenderType::SolidBlock(CubeTexture::all(BlockTexture::Cobblestone)),
|
||||||
|
collision: CollisionType::Solid,
|
||||||
|
raycast_collision: true,
|
||||||
|
},
|
||||||
|
Self::TallGrass => BlockDescriptor {
|
||||||
|
name: "tall grass",
|
||||||
|
render: RenderType::CrossShape(CrossTexture::all(BlockTexture::TallGrass)),
|
||||||
|
collision: CollisionType::None,
|
||||||
|
raycast_collision: true,
|
||||||
|
},
|
||||||
|
Self::Planks => BlockDescriptor {
|
||||||
|
name: "planks",
|
||||||
|
render: RenderType::SolidBlock(CubeTexture::all(BlockTexture::Planks)),
|
||||||
|
collision: CollisionType::Solid,
|
||||||
|
raycast_collision: true,
|
||||||
|
},
|
||||||
|
Self::Torch => BlockDescriptor {
|
||||||
|
name: "torch",
|
||||||
|
render: RenderType::CrossShape(CrossTexture::all(BlockTexture::Torch)),
|
||||||
|
collision: CollisionType::None,
|
||||||
|
raycast_collision: true,
|
||||||
|
},
|
||||||
|
Self::Wood => BlockDescriptor {
|
||||||
|
name: "leaf",
|
||||||
|
render: RenderType::SolidBlock(CubeTexture::horizontal_vertical(BlockTexture::Wood, BlockTexture::WoodTop)),
|
||||||
|
collision: CollisionType::Solid,
|
||||||
|
raycast_collision: true,
|
||||||
|
},
|
||||||
|
Self::Leaf => BlockDescriptor {
|
||||||
|
name: "leaf",
|
||||||
|
render: RenderType::BinaryTransparency(CubeTexture::all(BlockTexture::Leaf)),
|
||||||
|
collision: CollisionType::Solid,
|
||||||
|
raycast_collision: true,
|
||||||
|
},
|
||||||
|
Self::Water => BlockDescriptor {
|
||||||
|
name: "water",
|
||||||
|
render: RenderType::BinaryTransparency(CubeTexture::all(BlockTexture::WaterSolid)),
|
||||||
|
collision: CollisionType::None,
|
||||||
|
raycast_collision: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
pub struct BlockDescriptor {
|
||||||
|
pub name: &'static str,
|
||||||
|
pub render: RenderType,
|
||||||
|
pub collision: CollisionType,
|
||||||
|
pub raycast_collision: bool,
|
||||||
|
}
|
||||||
|
// impl BlockDescriptor {
|
||||||
|
// pub fn of(block: Block) -> Self {
|
||||||
|
// block.descriptor()
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
pub struct CubeTexture {
|
||||||
|
pub top: BlockTexture,
|
||||||
|
pub bottom: BlockTexture,
|
||||||
|
pub left: BlockTexture,
|
||||||
|
pub right: BlockTexture,
|
||||||
|
pub front: BlockTexture,
|
||||||
|
pub back: BlockTexture,
|
||||||
|
}
|
||||||
|
impl CubeTexture {
|
||||||
|
pub const fn top_sides_bottom(top: BlockTexture, sides: BlockTexture, bottom: BlockTexture) -> Self {
|
||||||
|
Self {
|
||||||
|
top,
|
||||||
|
bottom,
|
||||||
|
left: sides,
|
||||||
|
right: sides,
|
||||||
|
front: sides,
|
||||||
|
back: sides,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub const fn horizontal_vertical(horizontal: BlockTexture, vertical: BlockTexture) -> Self {
|
||||||
|
Self::top_sides_bottom(vertical, horizontal, vertical)
|
||||||
|
}
|
||||||
|
pub const fn all(texture: BlockTexture) -> Self {
|
||||||
|
Self::horizontal_vertical(texture, texture)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
pub struct CrossTextureSides {
|
||||||
|
pub front: BlockTexture,
|
||||||
|
pub back: BlockTexture
|
||||||
|
}
|
||||||
|
impl CrossTextureSides {
|
||||||
|
pub const fn all(texture: BlockTexture) -> Self {
|
||||||
|
Self {
|
||||||
|
front: texture,
|
||||||
|
back: texture
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
pub struct CrossTexture(pub CrossTextureSides, pub CrossTextureSides);
|
||||||
|
impl CrossTexture {
|
||||||
|
pub const fn all(texture: BlockTexture) -> Self {
|
||||||
|
Self(
|
||||||
|
CrossTextureSides::all(texture),
|
||||||
|
CrossTextureSides::all(texture)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||||
|
pub enum CollisionType {
|
||||||
|
None,
|
||||||
|
Solid,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
pub enum RenderType {
|
||||||
|
None,
|
||||||
|
SolidBlock(CubeTexture),
|
||||||
|
BinaryTransparency(CubeTexture),
|
||||||
|
CrossShape(CrossTexture),
|
||||||
|
}
|
4
kubi-shared/src/chunk.rs
Normal file
4
kubi-shared/src/chunk.rs
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
use crate::block::Block;
|
||||||
|
|
||||||
|
pub const CHUNK_SIZE: usize = 32;
|
||||||
|
pub type BlockData = Box<[[[Block; CHUNK_SIZE]; CHUNK_SIZE]; CHUNK_SIZE]>;
|
18
kubi-shared/src/entity.rs
Normal file
18
kubi-shared/src/entity.rs
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
use shipyard::Component;
|
||||||
|
|
||||||
|
#[derive(Component)]
|
||||||
|
pub struct Entity;
|
||||||
|
|
||||||
|
#[derive(Component)]
|
||||||
|
pub struct Health {
|
||||||
|
pub current: u8,
|
||||||
|
pub max: u8,
|
||||||
|
}
|
||||||
|
impl Health {
|
||||||
|
fn new(health: u8) -> Self {
|
||||||
|
Self {
|
||||||
|
current: health,
|
||||||
|
max: health
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
8
kubi-shared/src/lib.rs
Normal file
8
kubi-shared/src/lib.rs
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
pub mod block;
|
||||||
|
pub mod networking;
|
||||||
|
pub mod worldgen;
|
||||||
|
pub mod chunk;
|
||||||
|
pub mod transform;
|
||||||
|
pub mod entity;
|
||||||
|
pub mod player;
|
||||||
|
pub mod queue;
|
3
kubi-shared/src/networking.rs
Normal file
3
kubi-shared/src/networking.rs
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
pub mod messages;
|
||||||
|
pub mod state;
|
||||||
|
pub mod client;
|
3
kubi-shared/src/networking/client.rs
Normal file
3
kubi-shared/src/networking/client.rs
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
pub type ClientId = u16;
|
||||||
|
pub type ClientKey = u16;
|
||||||
|
|
72
kubi-shared/src/networking/messages.rs
Normal file
72
kubi-shared/src/networking/messages.rs
Normal file
|
@ -0,0 +1,72 @@
|
||||||
|
use std::num::NonZeroUsize;
|
||||||
|
use serde::{Serialize, Deserialize};
|
||||||
|
use crate::{chunk::BlockData, queue::QueuedBlock};
|
||||||
|
|
||||||
|
pub type IVec3Arr = [i32; 3];
|
||||||
|
pub type Vec3Arr = [f32; 3];
|
||||||
|
pub type QuatArr = [f32; 3];
|
||||||
|
|
||||||
|
pub const PROTOCOL_ID: u16 = 2;
|
||||||
|
|
||||||
|
pub const C_CLIENT_HELLO: u8 = 0;
|
||||||
|
pub const C_POSITION_CHANGED: u8 = 1;
|
||||||
|
pub const C_CHUNK_SUB_REQUEST: u8 = 2;
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Clone)]
|
||||||
|
#[repr(u8)]
|
||||||
|
pub enum ClientToServerMessage {
|
||||||
|
ClientHello {
|
||||||
|
username: String,
|
||||||
|
password: Option<String>,
|
||||||
|
} = C_CLIENT_HELLO,
|
||||||
|
PositionChanged {
|
||||||
|
position: Vec3Arr,
|
||||||
|
velocity: Vec3Arr,
|
||||||
|
direction: QuatArr,
|
||||||
|
} = C_POSITION_CHANGED,
|
||||||
|
ChunkSubRequest {
|
||||||
|
chunk: IVec3Arr,
|
||||||
|
} = C_CHUNK_SUB_REQUEST,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const S_SERVER_HELLO: u8 = 0;
|
||||||
|
pub const S_SERVER_FUCK_OFF: u8 = 1;
|
||||||
|
pub const S_PLAYER_POSITION_CHANGED: u8 = 2;
|
||||||
|
pub const S_CHUNK_RESPONSE: u8 = 3;
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Clone)]
|
||||||
|
#[repr(u8)]
|
||||||
|
pub enum ServerToClientMessage {
|
||||||
|
ServerHello {
|
||||||
|
init: InitData
|
||||||
|
} = S_SERVER_HELLO,
|
||||||
|
ServerFuckOff {
|
||||||
|
reason: String,
|
||||||
|
} = S_SERVER_FUCK_OFF,
|
||||||
|
PlayerPositionChanged {
|
||||||
|
client_id: u8,
|
||||||
|
position: Vec3Arr,
|
||||||
|
direction: QuatArr,
|
||||||
|
} = S_PLAYER_POSITION_CHANGED,
|
||||||
|
ChunkResponse {
|
||||||
|
chunk: IVec3Arr,
|
||||||
|
data: BlockData,
|
||||||
|
queued: Vec<QueuedBlock>,
|
||||||
|
} = S_CHUNK_RESPONSE,
|
||||||
|
}
|
||||||
|
|
||||||
|
//---
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Clone)]
|
||||||
|
pub struct UserInitData {
|
||||||
|
pub client_id: NonZeroUsize, //maybe use the proper type instead
|
||||||
|
pub username: String,
|
||||||
|
pub position: Vec3Arr,
|
||||||
|
pub velocity: Vec3Arr,
|
||||||
|
pub direction: QuatArr,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Clone)]
|
||||||
|
pub struct InitData {
|
||||||
|
pub users: Vec<UserInitData>
|
||||||
|
}
|
15
kubi-shared/src/networking/state.rs
Normal file
15
kubi-shared/src/networking/state.rs
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
use shipyard::{Unique, Component};
|
||||||
|
|
||||||
|
// disconnected => connect => join => load => ingame
|
||||||
|
#[derive(Unique, Component, PartialEq, Eq, Clone, Copy, Debug)]
|
||||||
|
#[repr(u8)]
|
||||||
|
pub enum ClientJoinState {
|
||||||
|
/// Not connected yet
|
||||||
|
Disconnected,
|
||||||
|
/// Client has connected to the game, but hasn't authenticated yet
|
||||||
|
Connected,
|
||||||
|
/// Client has joined the game, but hasn't loaded the world yet
|
||||||
|
Joined,
|
||||||
|
/// Client is currently ingame
|
||||||
|
InGame,
|
||||||
|
}
|
4
kubi-shared/src/player.rs
Normal file
4
kubi-shared/src/player.rs
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
use shipyard::Component;
|
||||||
|
|
||||||
|
#[derive(Component)]
|
||||||
|
pub struct Player;
|
11
kubi-shared/src/queue.rs
Normal file
11
kubi-shared/src/queue.rs
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
use glam::IVec3;
|
||||||
|
use serde::{Serialize, Deserialize};
|
||||||
|
use crate::block::Block;
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Clone, Copy, Debug)]
|
||||||
|
pub struct QueuedBlock {
|
||||||
|
pub position: IVec3,
|
||||||
|
pub block_type: Block,
|
||||||
|
/// Only replace air blocks
|
||||||
|
pub soft: bool,
|
||||||
|
}
|
8
kubi-shared/src/transform.rs
Normal file
8
kubi-shared/src/transform.rs
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
use shipyard::Component;
|
||||||
|
use glam::{Mat4, Mat3};
|
||||||
|
|
||||||
|
#[derive(Component, Clone, Copy, Debug, Default)]
|
||||||
|
pub struct Transform(pub Mat4);
|
||||||
|
|
||||||
|
#[derive(Component, Clone, Copy, Debug, Default)]
|
||||||
|
pub struct Transform2d(pub Mat3);
|
333
kubi-shared/src/worldgen.rs
Normal file
333
kubi-shared/src/worldgen.rs
Normal file
|
@ -0,0 +1,333 @@
|
||||||
|
use bracket_noise::prelude::*;
|
||||||
|
use rand::prelude::*;
|
||||||
|
use glam::{IVec3, ivec3, Vec3Swizzles, IVec2};
|
||||||
|
use rand_xoshiro::Xoshiro256StarStar;
|
||||||
|
use crate::{
|
||||||
|
chunk::{BlockData, CHUNK_SIZE},
|
||||||
|
block::Block,
|
||||||
|
queue::QueuedBlock,
|
||||||
|
};
|
||||||
|
|
||||||
|
fn mountain_ramp(mut x: f32) -> f32 {
|
||||||
|
x = x * 2.0;
|
||||||
|
if x < 0.4 {
|
||||||
|
0.5 * x
|
||||||
|
} else if x < 0.55 {
|
||||||
|
4. * (x - 0.4) + 0.2
|
||||||
|
} else {
|
||||||
|
0.4444 * (x - 0.55) + 0.8
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn local_height(height: i32, chunk_position: IVec3) -> usize {
|
||||||
|
let offset = chunk_position * CHUNK_SIZE as i32;
|
||||||
|
(height - offset.y).clamp(0, CHUNK_SIZE as i32) as usize
|
||||||
|
}
|
||||||
|
|
||||||
|
fn local_y_position(height: i32, chunk_position: IVec3) -> Option<usize> {
|
||||||
|
let offset = chunk_position * CHUNK_SIZE as i32;
|
||||||
|
let position = height - offset.y;
|
||||||
|
(0..CHUNK_SIZE as i32).contains(&position).then_some(position as usize)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
pub fn generate_world(chunk_position: IVec3, seed: u64) -> (BlockData, Vec<QueuedBlock>) {
|
||||||
|
let offset = chunk_position * CHUNK_SIZE as i32;
|
||||||
|
let mut blocks = Box::new([[[Block::Air; CHUNK_SIZE]; CHUNK_SIZE]; CHUNK_SIZE]);
|
||||||
|
let mut queue = Vec::with_capacity(0);
|
||||||
|
|
||||||
|
let mut smart_place = |blocks: &mut BlockData, position: IVec3, block: Block| {
|
||||||
|
if position.to_array().iter().any(|&x| !(0..CHUNK_SIZE).contains(&(x as usize))) {
|
||||||
|
let event_pos = offset + position;
|
||||||
|
queue.retain(|block: &QueuedBlock| {
|
||||||
|
block.position != event_pos
|
||||||
|
});
|
||||||
|
queue.push(QueuedBlock {
|
||||||
|
position: event_pos,
|
||||||
|
block_type: block,
|
||||||
|
soft: true
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
blocks[position.x as usize][position.y as usize][position.z as usize] = block;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut height_noise = FastNoise::seeded(seed);
|
||||||
|
height_noise.set_fractal_type(FractalType::FBM);
|
||||||
|
height_noise.set_fractal_octaves(4);
|
||||||
|
height_noise.set_frequency(0.003);
|
||||||
|
|
||||||
|
let mut elevation_noise = FastNoise::seeded(seed.rotate_left(1));
|
||||||
|
elevation_noise.set_fractal_type(FractalType::FBM);
|
||||||
|
elevation_noise.set_fractal_octaves(1);
|
||||||
|
elevation_noise.set_frequency(0.001);
|
||||||
|
|
||||||
|
let mut cave_noise_a = FastNoise::seeded(seed.rotate_left(2));
|
||||||
|
cave_noise_a.set_fractal_type(FractalType::FBM);
|
||||||
|
cave_noise_a.set_fractal_octaves(2);
|
||||||
|
cave_noise_a.set_frequency(0.01);
|
||||||
|
|
||||||
|
let mut cave_noise_b = FastNoise::seeded(seed.rotate_left(3));
|
||||||
|
cave_noise_b.set_fractal_type(FractalType::FBM);
|
||||||
|
cave_noise_b.set_fractal_octaves(3);
|
||||||
|
cave_noise_b.set_frequency(0.015);
|
||||||
|
|
||||||
|
let mut cave_noise_holes = FastNoise::seeded(seed.rotate_left(4));
|
||||||
|
cave_noise_holes.set_fractal_type(FractalType::FBM);
|
||||||
|
cave_noise_holes.set_fractal_octaves(2);
|
||||||
|
cave_noise_holes.set_frequency(0.005);
|
||||||
|
|
||||||
|
let mut ravine_nose_line = FastNoise::seeded(seed.rotate_left(5));
|
||||||
|
ravine_nose_line.set_fractal_type(FractalType::Billow);
|
||||||
|
ravine_nose_line.set_fractal_octaves(2);
|
||||||
|
ravine_nose_line.set_frequency(0.005);
|
||||||
|
|
||||||
|
let mut ravine_noise_location = FastNoise::seeded(seed.rotate_left(6));
|
||||||
|
ravine_noise_location.set_fractal_type(FractalType::FBM);
|
||||||
|
ravine_noise_location.set_fractal_octaves(1);
|
||||||
|
ravine_noise_location.set_frequency(0.005);
|
||||||
|
|
||||||
|
let mut river_noise = FastNoise::seeded(seed.rotate_left(7));
|
||||||
|
river_noise.set_fractal_type(FractalType::Billow);
|
||||||
|
river_noise.set_fractal_octaves(2);
|
||||||
|
river_noise.set_frequency(0.5 * 0.005);
|
||||||
|
|
||||||
|
let mut rng = Xoshiro256StarStar::seed_from_u64(
|
||||||
|
seed
|
||||||
|
^ ((chunk_position.x as u32 as u64) << 0)
|
||||||
|
^ ((chunk_position.z as u32 as u64) << 32)
|
||||||
|
);
|
||||||
|
let rng_map_a: [[f32; CHUNK_SIZE]; CHUNK_SIZE] = rng.gen();
|
||||||
|
let rng_map_b: [[f32; CHUNK_SIZE]; CHUNK_SIZE] = rng.gen();
|
||||||
|
|
||||||
|
//Generate height map
|
||||||
|
let mut within_heightmap = false;
|
||||||
|
let mut deco_heightmap = [[None; CHUNK_SIZE]; CHUNK_SIZE];
|
||||||
|
|
||||||
|
for x in 0..CHUNK_SIZE {
|
||||||
|
for z in 0..CHUNK_SIZE {
|
||||||
|
let (noise_x, noise_y) = ((offset.x + x as i32) as f32, (offset.z + z as i32) as f32);
|
||||||
|
//sample noises (that are needed right now)
|
||||||
|
let raw_heightmap_value = height_noise.get_noise(noise_x, noise_y);
|
||||||
|
let raw_elevation_value = elevation_noise.get_noise(noise_x, noise_y);
|
||||||
|
let raw_ravine_location_value = ravine_noise_location.get_noise(noise_x, noise_y);
|
||||||
|
//compute height
|
||||||
|
let mut is_surface = true;
|
||||||
|
let mut river_fill_height = None;
|
||||||
|
let height = {
|
||||||
|
let local_elevation = raw_elevation_value.powi(4).sqrt();
|
||||||
|
let mut height = (mountain_ramp(raw_heightmap_value) * local_elevation * 100.) as i32;
|
||||||
|
//Flatten valleys
|
||||||
|
if height < 0 {
|
||||||
|
height /= 2;
|
||||||
|
}
|
||||||
|
//Generate rivers
|
||||||
|
{
|
||||||
|
let river_width = (height as f32 / -5.).clamp(0.5, 1.);
|
||||||
|
let river_value = river_noise.get_noise(noise_x, noise_y);
|
||||||
|
if ((-0.00625 * river_width)..(0.00625 * river_width)).contains(&(river_value.powi(2))) {
|
||||||
|
is_surface = false;
|
||||||
|
river_fill_height = Some(height - 1);
|
||||||
|
//river_fill_height = Some(-3);
|
||||||
|
height -= (river_width * 15. * ((0.00625 * river_width) - river_value.powi(2)) * (1. / (0.00625 * river_width))).round() as i32;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//Generate ravines
|
||||||
|
if height < 0 {
|
||||||
|
if raw_ravine_location_value > 0.4 {
|
||||||
|
let raw_ravine_value = ravine_nose_line.get_noise(noise_x, noise_y);
|
||||||
|
if (-0.0125..0.0125).contains(&(raw_ravine_value.powi(2))) {
|
||||||
|
is_surface = false;
|
||||||
|
height -= (100. * (0.0125 - raw_ravine_value.powi(2)) * (1. / 0.0125)).round() as i32;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
height
|
||||||
|
};
|
||||||
|
//add to heightmap
|
||||||
|
if is_surface {
|
||||||
|
deco_heightmap[x as usize][z as usize] = Some(height);
|
||||||
|
//place dirt
|
||||||
|
for y in 0..local_height(height, chunk_position) {
|
||||||
|
blocks[x][y][z] = Block::Dirt;
|
||||||
|
within_heightmap = true;
|
||||||
|
}
|
||||||
|
//place stone
|
||||||
|
for y in 0..local_height(height - 5 - (raw_heightmap_value * 5.) as i32, chunk_position) {
|
||||||
|
blocks[x][y][z] = Block::Stone;
|
||||||
|
within_heightmap = true;
|
||||||
|
}
|
||||||
|
//place grass
|
||||||
|
if let Some(y) = local_y_position(height, chunk_position) {
|
||||||
|
blocks[x][y][z] = Block::Grass;
|
||||||
|
within_heightmap = true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if let Some(river_fill_height) = river_fill_height {
|
||||||
|
//Place water
|
||||||
|
for y in 0..local_height(river_fill_height, chunk_position) {
|
||||||
|
blocks[x][y][z] = Block::Water;
|
||||||
|
within_heightmap = true;
|
||||||
|
}
|
||||||
|
//Place stone
|
||||||
|
for y in 0..local_height(height - 1, chunk_position) {
|
||||||
|
blocks[x][y][z] = Block::Stone;
|
||||||
|
within_heightmap = true;
|
||||||
|
}
|
||||||
|
//Place dirt
|
||||||
|
if let Some(y) = local_y_position(height, chunk_position) {
|
||||||
|
blocks[x][y][z] = Block::Dirt;
|
||||||
|
within_heightmap = true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
//Place stone
|
||||||
|
for y in 0..local_height(height, chunk_position) {
|
||||||
|
blocks[x][y][z] = Block::Stone;
|
||||||
|
within_heightmap = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//Carve out caves
|
||||||
|
if within_heightmap {
|
||||||
|
for z in 0..CHUNK_SIZE {
|
||||||
|
for y in 0..CHUNK_SIZE {
|
||||||
|
for x in 0..CHUNK_SIZE {
|
||||||
|
if blocks[x][y][z] != Block::Stone { continue }
|
||||||
|
|
||||||
|
let cave_size = ((offset.y + y as i32) as f32 / -100.).clamp(0., 1.);
|
||||||
|
let inv_cave_size = 1. - cave_size;
|
||||||
|
if cave_size < 0.1 { continue }
|
||||||
|
|
||||||
|
let position = ivec3(x as i32, y as i32, z as i32) + offset;
|
||||||
|
|
||||||
|
let is_cave = || {
|
||||||
|
let raw_cavemap_value_a = cave_noise_a.get_noise3d(position.x as f32, position.y as f32, position.z as f32);
|
||||||
|
let raw_cavemap_value_b = cave_noise_b.get_noise3d(position.x as f32, position.y as f32, position.z as f32);
|
||||||
|
((cave_size * -0.15)..=(cave_size * 0.15)).contains(&raw_cavemap_value_a) &&
|
||||||
|
((cave_size * -0.15)..=(cave_size * 0.15)).contains(&raw_cavemap_value_b)
|
||||||
|
};
|
||||||
|
let is_hole_cave = || {
|
||||||
|
let raw_cavemap_value_holes = cave_noise_holes.get_noise3d(position.x as f32, position.y as f32, position.z as f32);
|
||||||
|
((0.9 + (0.1 * inv_cave_size))..=1.0).contains(&raw_cavemap_value_holes.abs())
|
||||||
|
};
|
||||||
|
|
||||||
|
if is_cave() || is_hole_cave() {
|
||||||
|
blocks[x][y][z] = Block::Air;
|
||||||
|
if deco_heightmap[x][z] == Some(y as i32 + offset.y) {
|
||||||
|
deco_heightmap[x][z] = None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//Add decorations
|
||||||
|
for x in 0..CHUNK_SIZE {
|
||||||
|
for z in 0..CHUNK_SIZE {
|
||||||
|
//get height
|
||||||
|
let Some(height) = deco_heightmap[x][z] else { continue };
|
||||||
|
//check for air
|
||||||
|
// if blocks[x][local_y][z] == Block::Air {
|
||||||
|
// continue
|
||||||
|
// }
|
||||||
|
//place tall grass
|
||||||
|
if rng_map_a[x][z] < 0.03 {
|
||||||
|
if let Some(y) = local_y_position(height + 1, chunk_position) {
|
||||||
|
blocks[x][y][z] = Block::TallGrass;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//place trees!
|
||||||
|
if rng_map_a[x][z] < 0.001 {
|
||||||
|
//Place wood (no smart_place needed here!)
|
||||||
|
let tree_height = 4 + (rng_map_b[x][z] * 3.).round() as i32;
|
||||||
|
for tree_y in 0..tree_height {
|
||||||
|
if let Some(y) = local_y_position(height + 1 + tree_y, chunk_position) {
|
||||||
|
blocks[x][y][z] = Block::Wood;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let tree_height = 4 + (rng_map_b[x][z] * 3.).round() as i32;
|
||||||
|
|
||||||
|
//Place leaf blocks
|
||||||
|
if let Some(y) = local_y_position(height + 1, chunk_position) {
|
||||||
|
let tree_pos = ivec3(x as i32, y as i32, z as i32);
|
||||||
|
// Place wood (smart_place)
|
||||||
|
// for tree_y in 0..tree_height {
|
||||||
|
// smart_place(&mut blocks, tree_pos + tree_y * IVec3::Y, Block::Wood);
|
||||||
|
// }
|
||||||
|
// Part that wraps around the tree
|
||||||
|
{
|
||||||
|
let tree_leaf_height = tree_height - 3;
|
||||||
|
let leaf_width = 2;
|
||||||
|
for tree_y in tree_leaf_height..tree_height {
|
||||||
|
for tree_x in (-leaf_width)..=leaf_width {
|
||||||
|
for tree_z in (-leaf_width)..=leaf_width {
|
||||||
|
let tree_offset = ivec3(tree_x, tree_y, tree_z);
|
||||||
|
if tree_offset.xz() == IVec2::ZERO { continue }
|
||||||
|
smart_place(&mut blocks, tree_pos + tree_offset, Block::Leaf);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//part above the tree
|
||||||
|
{
|
||||||
|
let leaf_above_height = 2;
|
||||||
|
let leaf_width = 1;
|
||||||
|
for tree_y in tree_height..(tree_height + leaf_above_height) {
|
||||||
|
for tree_x in (-leaf_width)..=leaf_width {
|
||||||
|
for tree_z in (-leaf_width)..=leaf_width {
|
||||||
|
let tree_offset = ivec3(tree_x, tree_y, tree_z);
|
||||||
|
smart_place(&mut blocks, tree_pos + tree_offset, Block::Leaf);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
(blocks, queue)
|
||||||
|
|
||||||
|
// let mut cave_noise = FastNoise::seeded(seed);
|
||||||
|
// cave_noise.set_fractal_type(FractalType::FBM);
|
||||||
|
// cave_noise.set_frequency(0.1);
|
||||||
|
|
||||||
|
// let mut dirt_noise = FastNoise::seeded(seed.rotate_left(1));
|
||||||
|
// dirt_noise.set_fractal_type(FractalType::FBM);
|
||||||
|
// dirt_noise.set_frequency(0.1);
|
||||||
|
|
||||||
|
//
|
||||||
|
|
||||||
|
// if chunk_position.y >= 0 {
|
||||||
|
// if chunk_position.y == 0 {
|
||||||
|
// for x in 0..CHUNK_SIZE {
|
||||||
|
// for z in 0..CHUNK_SIZE {
|
||||||
|
// blocks[x][0][z] = Block::Dirt;
|
||||||
|
// blocks[x][1][z] = Block::Grass;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// } else {
|
||||||
|
// for x in 0..CHUNK_SIZE {
|
||||||
|
// for y in 0..CHUNK_SIZE {
|
||||||
|
// for z in 0..CHUNK_SIZE {
|
||||||
|
// let position = ivec3(x as i32, y as i32, z as i32) + offset;
|
||||||
|
// let v_cave_noise = cave_noise.get_noise3d(position.x as f32, position.y as f32, position.z as f32) * (-position.y as f32 - 10.0).clamp(0., 1.);
|
||||||
|
// let v_dirt_noise = dirt_noise.get_noise3d(position.x as f32, position.y as f32, position.z as f32) * (-position.y as f32).clamp(0., 1.);
|
||||||
|
// if v_cave_noise > 0.5 {
|
||||||
|
// blocks[x][y][z] = Block::Stone;
|
||||||
|
// } else if v_dirt_noise > 0.5 {
|
||||||
|
// blocks[x][y][z] = Block::Dirt;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// blocks
|
||||||
|
|
||||||
|
}
|
31
kubi/Cargo.toml
Normal file
31
kubi/Cargo.toml
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
[package]
|
||||||
|
name = "kubi"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
publish = false
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
kubi-shared = { path = "../kubi-shared" }
|
||||||
|
kubi-logging = { path = "../kubi-logging" }
|
||||||
|
log = "*"
|
||||||
|
glium = "0.32"
|
||||||
|
glam = { version = "0.23", features = ["debug-glam-assert", "fast-math"] }
|
||||||
|
image = { version = "0.24", default_features = false, features = ["png"] }
|
||||||
|
strum = { version = "0.24", features = ["derive"] }
|
||||||
|
hashbrown = "0.13"
|
||||||
|
rayon = "1.6"
|
||||||
|
shipyard = { git = "https://github.com/leudz/shipyard", rev = "eb189f66", default-features = false, features = ["std", "proc", "thread_local"] }
|
||||||
|
nohash-hasher = "0.2.0"
|
||||||
|
anyhow = "1.0"
|
||||||
|
flume = "0.10"
|
||||||
|
gilrs = { version = "0.10", default_features = false, features = ["xinput"] }
|
||||||
|
uflow = "0.7"
|
||||||
|
postcard = { version = "1.0", features = ["alloc"] }
|
||||||
|
|
||||||
|
[target.'cfg(target_os = "windows")'.dependencies]
|
||||||
|
winapi = { version = "0.3" }
|
||||||
|
|
||||||
|
[features]
|
||||||
|
default = []
|
||||||
|
parallel = ["shipyard/parallel"]
|
||||||
|
nightly = ["glam/core-simd", "kubi-shared/nightly"]
|
|
@ -1,4 +1,6 @@
|
||||||
#version 150 core
|
#version 300 es
|
||||||
|
|
||||||
|
precision highp float;
|
||||||
|
|
||||||
out vec4 out_color;
|
out vec4 out_color;
|
||||||
uniform vec4 color;
|
uniform vec4 color;
|
|
@ -1,4 +1,6 @@
|
||||||
#version 150 core
|
#version 300 es
|
||||||
|
|
||||||
|
precision highp float;
|
||||||
|
|
||||||
in vec3 position;
|
in vec3 position;
|
||||||
uniform mat4 model;
|
uniform mat4 model;
|
17
kubi/shaders/gui/progressbar.frag
Normal file
17
kubi/shaders/gui/progressbar.frag
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
#version 300 es
|
||||||
|
|
||||||
|
precision highp float;
|
||||||
|
|
||||||
|
in vec2 v_uv;
|
||||||
|
out vec4 out_color;
|
||||||
|
uniform float progress;
|
||||||
|
uniform vec4 color;
|
||||||
|
uniform vec4 bg_color;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
if (v_uv.x <= progress) {
|
||||||
|
out_color = color;
|
||||||
|
} else {
|
||||||
|
out_color = bg_color;
|
||||||
|
}
|
||||||
|
}
|
12
kubi/shaders/gui/progressbar.vert
Normal file
12
kubi/shaders/gui/progressbar.vert
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
#version 300 es
|
||||||
|
|
||||||
|
in vec2 position;
|
||||||
|
out vec2 v_uv;
|
||||||
|
uniform mat4 ui_view;
|
||||||
|
uniform mat3 transform;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
v_uv = position;
|
||||||
|
vec2 transformed = (transform * vec3(position, 1.)).xy;
|
||||||
|
gl_Position = ui_view * vec4(transformed, 0., 1.);
|
||||||
|
}
|
11
kubi/shaders/selection_box.frag
Normal file
11
kubi/shaders/selection_box.frag
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
#version 300 es
|
||||||
|
|
||||||
|
precision highp float;
|
||||||
|
|
||||||
|
out vec4 color;
|
||||||
|
uniform vec4 u_color;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
color = u_color;
|
||||||
|
// color -= vec4(0, 0, 0, 0.1 * sin(gl_FragCoord.x) * cos(gl_FragCoord.y));
|
||||||
|
}
|
|
@ -1,4 +1,6 @@
|
||||||
#version 150 core
|
#version 300 es
|
||||||
|
|
||||||
|
precision highp float;
|
||||||
|
|
||||||
in vec3 position;
|
in vec3 position;
|
||||||
uniform ivec3 u_position;
|
uniform ivec3 u_position;
|
|
@ -1,4 +1,7 @@
|
||||||
#version 150 core
|
#version 300 es
|
||||||
|
|
||||||
|
precision highp float;
|
||||||
|
precision lowp sampler2DArray;
|
||||||
|
|
||||||
in vec3 v_normal;
|
in vec3 v_normal;
|
||||||
in vec2 v_uv;
|
in vec2 v_uv;
|
||||||
|
@ -9,6 +12,10 @@ uniform sampler2DArray tex;
|
||||||
void main() {
|
void main() {
|
||||||
// base color from texture
|
// base color from texture
|
||||||
color = texture(tex, vec3(v_uv, v_tex_index));
|
color = texture(tex, vec3(v_uv, v_tex_index));
|
||||||
|
// discard fully transparent pixels
|
||||||
|
if (color.w <= 0.0) {
|
||||||
|
discard;
|
||||||
|
}
|
||||||
//basic "lighting"
|
//basic "lighting"
|
||||||
float light = abs(v_normal.x) + .8 * abs(v_normal.y) + .6 * abs(v_normal.z);
|
float light = abs(v_normal.x) + .8 * abs(v_normal.y) + .6 * abs(v_normal.z);
|
||||||
color *= vec4(vec3(light), 1.);
|
color *= vec4(vec3(light), 1.);
|
|
@ -1,4 +1,6 @@
|
||||||
#version 150 core
|
#version 300 es
|
||||||
|
|
||||||
|
precision highp float;
|
||||||
|
|
||||||
//TODO pack this data:
|
//TODO pack this data:
|
||||||
// uint position_normal_uv
|
// uint position_normal_uv
|
94
kubi/src/block_placement.rs
Normal file
94
kubi/src/block_placement.rs
Normal file
|
@ -0,0 +1,94 @@
|
||||||
|
use shipyard::{UniqueViewMut, UniqueView, View, IntoIter, ViewMut, EntitiesViewMut, Component, Workload, IntoWorkload};
|
||||||
|
use glium::glutin::event::VirtualKeyCode;
|
||||||
|
use kubi_shared::{
|
||||||
|
block::Block,
|
||||||
|
queue::QueuedBlock,
|
||||||
|
};
|
||||||
|
use crate::{
|
||||||
|
player::MainPlayer,
|
||||||
|
world::{raycast::{LookingAtBlock, RAYCAST_STEP}, queue::BlockUpdateQueue},
|
||||||
|
input::{Inputs, PrevInputs, RawKbmInputState},
|
||||||
|
events::{EventComponent, player_actions::PlayerActionEvent},
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Component)]
|
||||||
|
pub struct PlayerHolding(pub Block);
|
||||||
|
impl Default for PlayerHolding {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self(Block::Cobblestone)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const BLOCK_KEY_MAP: &[(VirtualKeyCode, Block)] = &[
|
||||||
|
(VirtualKeyCode::Key1, Block::Cobblestone),
|
||||||
|
(VirtualKeyCode::Key2, Block::Planks),
|
||||||
|
(VirtualKeyCode::Key3, Block::Dirt),
|
||||||
|
(VirtualKeyCode::Key4, Block::Grass),
|
||||||
|
(VirtualKeyCode::Key5, Block::Sand),
|
||||||
|
(VirtualKeyCode::Key6, Block::Stone),
|
||||||
|
(VirtualKeyCode::Key7, Block::Torch),
|
||||||
|
(VirtualKeyCode::Key8, Block::Leaf),
|
||||||
|
];
|
||||||
|
|
||||||
|
fn pick_block_with_number_keys(
|
||||||
|
main_player: View<MainPlayer>,
|
||||||
|
mut holding: ViewMut<PlayerHolding>,
|
||||||
|
input: UniqueView<RawKbmInputState>,
|
||||||
|
) {
|
||||||
|
let Some((_, mut holding)) = (&main_player, &mut holding).iter().next() else { return };
|
||||||
|
for &(key, block) in BLOCK_KEY_MAP {
|
||||||
|
if input.keyboard_state.contains(&key) {
|
||||||
|
holding.0 = block;
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn block_placement_system(
|
||||||
|
main_player: View<MainPlayer>,
|
||||||
|
holding: View<PlayerHolding>,
|
||||||
|
raycast: View<LookingAtBlock>,
|
||||||
|
input: UniqueView<Inputs>,
|
||||||
|
prev_input: UniqueView<PrevInputs>,
|
||||||
|
mut block_event_queue: UniqueViewMut<BlockUpdateQueue>,
|
||||||
|
mut entities: EntitiesViewMut,
|
||||||
|
mut events: ViewMut<EventComponent>,
|
||||||
|
mut player_events: ViewMut<PlayerActionEvent>,
|
||||||
|
) {
|
||||||
|
let action_place = input.action_b && !prev_input.0.action_b;
|
||||||
|
let action_break = input.action_a && !prev_input.0.action_a;
|
||||||
|
if action_place ^ action_break {
|
||||||
|
//get components
|
||||||
|
let Some((_, ray, block)) = (&main_player, &raycast, &holding).iter().next() else { return };
|
||||||
|
let Some(ray) = ray.0 else { return };
|
||||||
|
//get coord and block type
|
||||||
|
let (place_position, place_block) = if action_place {
|
||||||
|
if block.0 == Block::Air { return }
|
||||||
|
let position = (ray.position - ray.direction * (RAYCAST_STEP + 0.001)).floor().as_ivec3();
|
||||||
|
(position, block.0)
|
||||||
|
} else {
|
||||||
|
(ray.block_position, Block::Air)
|
||||||
|
};
|
||||||
|
//queue place
|
||||||
|
block_event_queue.push(QueuedBlock {
|
||||||
|
position: place_position,
|
||||||
|
block_type: place_block,
|
||||||
|
soft: place_block != Block::Air,
|
||||||
|
});
|
||||||
|
//send event
|
||||||
|
entities.add_entity(
|
||||||
|
(&mut events, &mut player_events),
|
||||||
|
(EventComponent, PlayerActionEvent::UpdatedBlock {
|
||||||
|
position: place_position,
|
||||||
|
block: place_block,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update_block_placement() -> Workload {
|
||||||
|
(
|
||||||
|
pick_block_with_number_keys,
|
||||||
|
block_placement_system
|
||||||
|
).into_workload()
|
||||||
|
}
|
|
@ -8,7 +8,7 @@
|
||||||
// three layers of stolen code, yay!
|
// three layers of stolen code, yay!
|
||||||
|
|
||||||
use glam::{Vec3A, Vec4, Mat3A, vec3a, Vec3, vec4};
|
use glam::{Vec3A, Vec4, Mat3A, vec3a, Vec3, vec4};
|
||||||
use shipyard::{ViewMut, IntoIter, View};
|
use shipyard::{ViewMut, IntoIter, View, track};
|
||||||
use crate::transform::Transform;
|
use crate::transform::Transform;
|
||||||
use super::Camera;
|
use super::Camera;
|
||||||
|
|
||||||
|
@ -122,9 +122,9 @@ fn intersection<const A: usize, const B: usize, const C: usize>(planes: &[Vec4;
|
||||||
|
|
||||||
pub fn update_frustum(
|
pub fn update_frustum(
|
||||||
mut cameras: ViewMut<Camera>,
|
mut cameras: ViewMut<Camera>,
|
||||||
transforms: View<Transform>
|
transforms: View<Transform, { track::All }>
|
||||||
) {
|
) {
|
||||||
for (camera, _) in (&mut cameras, transforms.inserted_or_modified()).iter() {
|
for (mut camera, _) in (&mut cameras, transforms.inserted_or_modified()).iter() {
|
||||||
camera.frustum = Frustum::compute(camera);
|
camera.frustum = Frustum::compute(&camera);
|
||||||
}
|
}
|
||||||
}
|
}
|
48
kubi/src/camera/matrices.rs
Normal file
48
kubi/src/camera/matrices.rs
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
use glam::{Vec3, Mat4};
|
||||||
|
use shipyard::{ViewMut, View, IntoIter, Workload, IntoWorkload, track, UniqueView, SystemModificator};
|
||||||
|
use crate::{transform::Transform, rendering::WindowSize, events::WindowResizedEvent};
|
||||||
|
use super::Camera;
|
||||||
|
|
||||||
|
//maybe parallelize these two?
|
||||||
|
|
||||||
|
fn update_view_matrix(
|
||||||
|
mut vm_camera: ViewMut<Camera>,
|
||||||
|
v_transform: View<Transform, { track::All }>
|
||||||
|
) {
|
||||||
|
for (mut camera, transform) in (&mut vm_camera, v_transform.inserted_or_modified()).iter() {
|
||||||
|
let (_, rotation, translation) = transform.0.to_scale_rotation_translation();
|
||||||
|
let direction = (rotation.normalize() * Vec3::NEG_Z).normalize();
|
||||||
|
camera.view_matrix = Mat4::look_to_rh(translation, direction, camera.up);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_perspective_matrix(
|
||||||
|
mut vm_camera: ViewMut<Camera>,
|
||||||
|
size: UniqueView<WindowSize>,
|
||||||
|
) {
|
||||||
|
for mut camera in (&mut vm_camera).iter() {
|
||||||
|
camera.perspective_matrix = Mat4::perspective_rh_gl(
|
||||||
|
camera.fov,
|
||||||
|
size.0.x as f32 / size.0.y as f32,
|
||||||
|
camera.z_near,
|
||||||
|
camera.z_far,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn need_perspective_calc(
|
||||||
|
v_camera: View<Camera>,
|
||||||
|
resize_event: View<WindowResizedEvent>,
|
||||||
|
) -> bool {
|
||||||
|
(resize_event.len() > 0) ||
|
||||||
|
(v_camera.iter().any(|camera| {
|
||||||
|
camera.perspective_matrix == Mat4::default()
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update_matrices() -> Workload {
|
||||||
|
(
|
||||||
|
update_view_matrix,
|
||||||
|
update_perspective_matrix.run_if(need_perspective_calc),
|
||||||
|
).into_workload()
|
||||||
|
}
|
12
kubi/src/color.rs
Normal file
12
kubi/src/color.rs
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
use glam::{Vec4, vec4};
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn color_rgba(r: u8, g: u8, b: u8, a: u8) -> Vec4 {
|
||||||
|
vec4(r as f32 / 255., g as f32 / 255., b as f32 / 255., a as f32 / 255.)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn color_hex(c: u32) -> Vec4 {
|
||||||
|
let c = c.to_be_bytes();
|
||||||
|
color_rgba(c[0], c[1], c[2], c[3])
|
||||||
|
}
|
12
kubi/src/connecting_screen.rs
Normal file
12
kubi/src/connecting_screen.rs
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
use kubi_shared::networking::state::ClientJoinState;
|
||||||
|
use shipyard::{UniqueViewMut, UniqueView};
|
||||||
|
use crate::state::{NextState, GameState};
|
||||||
|
|
||||||
|
pub fn switch_to_loading_if_connected(
|
||||||
|
mut next_state: UniqueViewMut<NextState>,
|
||||||
|
client_state: UniqueView<ClientJoinState>,
|
||||||
|
) {
|
||||||
|
if *client_state == ClientJoinState::Joined {
|
||||||
|
next_state.0 = Some(GameState::LoadingWorld);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,12 +1,12 @@
|
||||||
use shipyard::{UniqueView, UniqueViewMut, Unique, AllStoragesView};
|
use shipyard::{UniqueView, UniqueViewMut, Unique, AllStoragesView};
|
||||||
use glium::glutin::{event::VirtualKeyCode, event_loop::ControlFlow};
|
use glium::glutin::{event::VirtualKeyCode, event_loop::ControlFlow};
|
||||||
use crate::input::RawInputState;
|
use crate::input::RawKbmInputState;
|
||||||
|
|
||||||
#[derive(Unique)]
|
#[derive(Unique)]
|
||||||
pub struct SetControlFlow(pub Option<ControlFlow>);
|
pub struct SetControlFlow(pub Option<ControlFlow>);
|
||||||
|
|
||||||
pub fn exit_on_esc(
|
pub fn exit_on_esc(
|
||||||
raw_inputs: UniqueView<RawInputState>,
|
raw_inputs: UniqueView<RawKbmInputState>,
|
||||||
mut control_flow: UniqueViewMut<SetControlFlow>
|
mut control_flow: UniqueViewMut<SetControlFlow>
|
||||||
) {
|
) {
|
||||||
if raw_inputs.keyboard_state.contains(&VirtualKeyCode::Escape) {
|
if raw_inputs.keyboard_state.contains(&VirtualKeyCode::Escape) {
|
|
@ -3,7 +3,6 @@ use crate::rendering::Renderer;
|
||||||
use glium::glutin::window::CursorGrabMode;
|
use glium::glutin::window::CursorGrabMode;
|
||||||
|
|
||||||
#[derive(Unique)]
|
#[derive(Unique)]
|
||||||
#[track(All)]
|
|
||||||
pub struct CursorLock(pub bool);
|
pub struct CursorLock(pub bool);
|
||||||
|
|
||||||
pub fn update_cursor_lock_state(
|
pub fn update_cursor_lock_state(
|
|
@ -1,6 +1,7 @@
|
||||||
use glam::UVec2;
|
use glam::UVec2;
|
||||||
use shipyard::{World, Component, AllStoragesViewMut, SparseSet};
|
use shipyard::{World, Component, AllStoragesViewMut, SparseSet, NonSendSync, UniqueView};
|
||||||
use glium::glutin::event::{Event, DeviceEvent, DeviceId, WindowEvent};
|
use glium::glutin::event::{Event, DeviceEvent, DeviceId, WindowEvent};
|
||||||
|
use crate::rendering::Renderer;
|
||||||
|
|
||||||
pub mod player_actions;
|
pub mod player_actions;
|
||||||
|
|
||||||
|
@ -50,6 +51,19 @@ pub fn process_glutin_events(world: &mut World, event: &Event<'_, ()>) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn initial_resize_event(
|
||||||
|
mut storages: AllStoragesViewMut,
|
||||||
|
) {
|
||||||
|
let (w, h) = {
|
||||||
|
let renderer = storages.borrow::<NonSendSync<UniqueView<Renderer>>>().unwrap();
|
||||||
|
renderer.display.get_framebuffer_dimensions()
|
||||||
|
};
|
||||||
|
storages.add_entity((
|
||||||
|
EventComponent,
|
||||||
|
WindowResizedEvent(UVec2::new(w, h))
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
pub fn clear_events(
|
pub fn clear_events(
|
||||||
mut all_storages: AllStoragesViewMut,
|
mut all_storages: AllStoragesViewMut,
|
||||||
) {
|
) {
|
|
@ -1,8 +1,7 @@
|
||||||
use shipyard::{Component, View, ViewMut, EntitiesViewMut, IntoIter};
|
use shipyard::{Component, View, ViewMut, EntitiesViewMut, IntoIter, track};
|
||||||
use glam::{IVec3, Quat, Vec3};
|
use glam::{IVec3, Quat, Vec3};
|
||||||
|
use kubi_shared::block::Block;
|
||||||
use crate::{
|
use crate::{
|
||||||
world::block::Block,
|
|
||||||
player::MainPlayer,
|
player::MainPlayer,
|
||||||
transform::Transform
|
transform::Transform
|
||||||
};
|
};
|
||||||
|
@ -21,7 +20,7 @@ pub enum PlayerActionEvent {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn generate_move_events(
|
pub fn generate_move_events(
|
||||||
transforms: View<Transform>,
|
transforms: View<Transform, { track::All }>,
|
||||||
player: View<MainPlayer>,
|
player: View<MainPlayer>,
|
||||||
mut entities: EntitiesViewMut,
|
mut entities: EntitiesViewMut,
|
||||||
mut events: ViewMut<EventComponent>,
|
mut events: ViewMut<EventComponent>,
|
|
@ -1,5 +1,5 @@
|
||||||
use glam::{Vec3, Mat4, Quat, EulerRot, Vec2};
|
use glam::{Vec3, Mat4, Quat, EulerRot, Vec2};
|
||||||
use shipyard::{Component, View, ViewMut, IntoIter, UniqueView, Workload, IntoWorkload};
|
use shipyard::{Component, View, ViewMut, IntoIter, UniqueView, Workload, IntoWorkload, track};
|
||||||
use std::f32::consts::PI;
|
use std::f32::consts::PI;
|
||||||
use crate::{transform::Transform, input::Inputs, settings::GameSettings, delta_time::DeltaTime};
|
use crate::{transform::Transform, input::Inputs, settings::GameSettings, delta_time::DeltaTime};
|
||||||
|
|
||||||
|
@ -13,11 +13,11 @@ pub fn update_controllers() -> Workload {
|
||||||
).into_workload()
|
).into_workload()
|
||||||
}
|
}
|
||||||
|
|
||||||
const MAX_PITCH: f32 = PI/2. - 0.025;
|
const MAX_PITCH: f32 = PI/2. - 0.05;
|
||||||
|
|
||||||
fn update_look(
|
fn update_look(
|
||||||
controllers: View<FlyController>,
|
controllers: View<FlyController>,
|
||||||
mut transforms: ViewMut<Transform>,
|
mut transforms: ViewMut<Transform, { track::All }>,
|
||||||
inputs: UniqueView<Inputs>,
|
inputs: UniqueView<Inputs>,
|
||||||
settings: UniqueView<GameSettings>,
|
settings: UniqueView<GameSettings>,
|
||||||
dt: UniqueView<DeltaTime>,
|
dt: UniqueView<DeltaTime>,
|
||||||
|
@ -37,12 +37,12 @@ fn update_look(
|
||||||
|
|
||||||
fn update_movement(
|
fn update_movement(
|
||||||
controllers: View<FlyController>,
|
controllers: View<FlyController>,
|
||||||
mut transforms: ViewMut<Transform>,
|
mut transforms: ViewMut<Transform, { track::All }>,
|
||||||
inputs: UniqueView<Inputs>,
|
inputs: UniqueView<Inputs>,
|
||||||
dt: UniqueView<DeltaTime>,
|
dt: UniqueView<DeltaTime>,
|
||||||
) {
|
) {
|
||||||
if inputs.movement == Vec2::ZERO { return }
|
if inputs.movement == Vec2::ZERO { return }
|
||||||
let movement = inputs.movement.normalize_or_zero() * 30. * dt.0.as_secs_f32();
|
let movement = inputs.movement * 30. * dt.0.as_secs_f32();
|
||||||
for (_, mut transform) in (&controllers, &mut transforms).iter() {
|
for (_, mut transform) in (&controllers, &mut transforms).iter() {
|
||||||
let (scale, rotation, mut translation) = transform.0.to_scale_rotation_translation();
|
let (scale, rotation, mut translation) = transform.0.to_scale_rotation_translation();
|
||||||
translation += (rotation * Vec3::NEG_Z).normalize() * movement.y;
|
translation += (rotation * Vec3::NEG_Z).normalize() * movement.y;
|
78
kubi/src/gui.rs
Normal file
78
kubi/src/gui.rs
Normal file
|
@ -0,0 +1,78 @@
|
||||||
|
use shipyard::{Component, Unique, Workload, IntoWorkload, AllStoragesView, View, UniqueViewMut, IntoIter};
|
||||||
|
use glam::{Vec4, Mat4};
|
||||||
|
use crate::{color::color_hex, events::WindowResizedEvent};
|
||||||
|
|
||||||
|
pub mod text_widget;
|
||||||
|
pub mod progressbar;
|
||||||
|
|
||||||
|
use progressbar::render_progressbars;
|
||||||
|
|
||||||
|
//TODO compute gui scale on window resize
|
||||||
|
#[derive(Unique, Clone, Copy, Debug, Default)]
|
||||||
|
pub struct GuiView(pub Mat4);
|
||||||
|
|
||||||
|
#[derive(Component, Clone, Copy, Debug, Default)]
|
||||||
|
pub struct GuiComponent;
|
||||||
|
|
||||||
|
#[derive(Component, Clone, Copy, Debug)]
|
||||||
|
pub struct PrimaryColor(pub Vec4);
|
||||||
|
impl Default for PrimaryColor {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self(color_hex(0x156cddff))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Component, Clone, Copy, Debug)]
|
||||||
|
pub struct SecondaryColor(pub Vec4);
|
||||||
|
impl Default for SecondaryColor {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self(color_hex(0xc9d5e4ff))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_gui_view(
|
||||||
|
mut view: UniqueViewMut<GuiView>,
|
||||||
|
resize: View<WindowResizedEvent>,
|
||||||
|
) {
|
||||||
|
let Some(&size) = resize.iter().next() else {
|
||||||
|
return
|
||||||
|
};
|
||||||
|
let [w, h] = size.0.to_array();
|
||||||
|
view.0 = Mat4::orthographic_rh_gl(0.0, w as f32, h as f32, 0.0, -1.0, 1.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn init_gui(
|
||||||
|
storages: AllStoragesView
|
||||||
|
) {
|
||||||
|
storages.add_unique(GuiView::default());
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update_gui() -> Workload {
|
||||||
|
(
|
||||||
|
update_gui_view
|
||||||
|
).into_workload()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn render_gui() -> Workload {
|
||||||
|
(
|
||||||
|
render_progressbars
|
||||||
|
).into_workload()
|
||||||
|
}
|
||||||
|
|
||||||
|
// pub fn gui_testing(
|
||||||
|
// mut storages: AllStoragesViewMut,
|
||||||
|
// ) {
|
||||||
|
// storages.add_entity((
|
||||||
|
// GuiComponent,
|
||||||
|
// Transform2d(Mat3::from_scale_angle_translation(
|
||||||
|
// vec2(1920., 16.),
|
||||||
|
// 0.,
|
||||||
|
// vec2(0., 0.)
|
||||||
|
// )),
|
||||||
|
// ProgressbarComponent {
|
||||||
|
// progress: 0.33
|
||||||
|
// },
|
||||||
|
// PrimaryColor::default(),
|
||||||
|
// SecondaryColor::default(),
|
||||||
|
// ));
|
||||||
|
// }
|
46
kubi/src/gui/progressbar.rs
Normal file
46
kubi/src/gui/progressbar.rs
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
use shipyard::{UniqueView, UniqueViewMut, NonSendSync, View, Component, IntoIter, IntoWithId, Get, track};
|
||||||
|
use glium::{Surface, uniform, DrawParameters};
|
||||||
|
use crate::{
|
||||||
|
prefabs::ProgressbarShaderPrefab,
|
||||||
|
rendering::{
|
||||||
|
RenderTarget,
|
||||||
|
primitives::rect::RectPrimitive
|
||||||
|
},
|
||||||
|
transform::Transform2d,
|
||||||
|
};
|
||||||
|
use super::{GuiComponent, PrimaryColor, SecondaryColor, GuiView};
|
||||||
|
|
||||||
|
#[derive(Component, Debug, Clone, Copy, Default)]
|
||||||
|
pub struct ProgressbarComponent {
|
||||||
|
pub progress: f32
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn render_progressbars(
|
||||||
|
mut target: NonSendSync<UniqueViewMut<RenderTarget>>,
|
||||||
|
rect: NonSendSync<UniqueView<RectPrimitive>>,
|
||||||
|
program: NonSendSync<UniqueView<ProgressbarShaderPrefab>>,
|
||||||
|
view: UniqueView<GuiView>,
|
||||||
|
components: View<GuiComponent>,
|
||||||
|
transforms: View<Transform2d, { track::All }>,
|
||||||
|
progressbars: View<ProgressbarComponent>,
|
||||||
|
primary: View<PrimaryColor>,
|
||||||
|
secondary: View<SecondaryColor>,
|
||||||
|
) {
|
||||||
|
for (eid, (_, transform, progress)) in (&components, &transforms, &progressbars).iter().with_id() {
|
||||||
|
let primary_color = primary.get(eid).copied().unwrap_or_default();
|
||||||
|
let secondary_color = secondary.get(eid).copied().unwrap_or_default();
|
||||||
|
target.0.draw(
|
||||||
|
&rect.0,
|
||||||
|
&rect.1,
|
||||||
|
&program.0,
|
||||||
|
&uniform! {
|
||||||
|
transform: transform.0.to_cols_array_2d(),
|
||||||
|
ui_view: view.0.to_cols_array_2d(),
|
||||||
|
progress: progress.progress,
|
||||||
|
color: primary_color.0.to_array(),
|
||||||
|
bg_color: secondary_color.0.to_array(),
|
||||||
|
},
|
||||||
|
&DrawParameters::default()
|
||||||
|
).unwrap();
|
||||||
|
}
|
||||||
|
}
|
1
kubi/src/gui/text_widget.rs
Normal file
1
kubi/src/gui/text_widget.rs
Normal file
|
@ -0,0 +1 @@
|
||||||
|
//TODO text widget
|
21
kubi/src/init.rs
Normal file
21
kubi/src/init.rs
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
use shipyard::{AllStoragesView, UniqueViewMut};
|
||||||
|
use std::{env, net::SocketAddr};
|
||||||
|
use crate::{
|
||||||
|
networking::{GameType, ServerAddress},
|
||||||
|
state::{GameState, NextState}
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn initialize_from_args(
|
||||||
|
all_storages: AllStoragesView,
|
||||||
|
) {
|
||||||
|
let args: Vec<String> = env::args().collect();
|
||||||
|
if args.len() > 1 {
|
||||||
|
let address = args[1].parse::<SocketAddr>().expect("invalid address");
|
||||||
|
all_storages.add_unique(GameType::Muliplayer);
|
||||||
|
all_storages.add_unique(ServerAddress(address));
|
||||||
|
all_storages.borrow::<UniqueViewMut<NextState>>().unwrap().0 = Some(GameState::Connecting);
|
||||||
|
} else {
|
||||||
|
all_storages.add_unique(GameType::Singleplayer);
|
||||||
|
all_storages.borrow::<UniqueViewMut<NextState>>().unwrap().0 = Some(GameState::LoadingWorld);
|
||||||
|
}
|
||||||
|
}
|
138
kubi/src/input.rs
Normal file
138
kubi/src/input.rs
Normal file
|
@ -0,0 +1,138 @@
|
||||||
|
use gilrs::{Gilrs, GamepadId, Button, Event, Axis};
|
||||||
|
use glam::{Vec2, DVec2, vec2};
|
||||||
|
use glium::glutin::event::{DeviceEvent, VirtualKeyCode, ElementState};
|
||||||
|
use hashbrown::HashSet;
|
||||||
|
use nohash_hasher::BuildNoHashHasher;
|
||||||
|
use shipyard::{AllStoragesView, Unique, View, IntoIter, UniqueViewMut, Workload, IntoWorkload, UniqueView, NonSendSync};
|
||||||
|
use crate::events::InputDeviceEvent;
|
||||||
|
|
||||||
|
#[derive(Unique, Clone, Copy, Default, Debug)]
|
||||||
|
pub struct Inputs {
|
||||||
|
pub movement: Vec2,
|
||||||
|
pub look: Vec2,
|
||||||
|
pub action_a: bool,
|
||||||
|
pub action_b: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Unique, Clone, Copy, Default, Debug)]
|
||||||
|
pub struct PrevInputs(pub Inputs);
|
||||||
|
|
||||||
|
#[derive(Unique, Clone, Default, Debug)]
|
||||||
|
pub struct RawKbmInputState {
|
||||||
|
pub keyboard_state: HashSet<VirtualKeyCode, BuildNoHashHasher<u32>>,
|
||||||
|
pub button_state: [bool; 32],
|
||||||
|
pub mouse_delta: DVec2
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Unique)]
|
||||||
|
pub struct GilrsWrapper(Gilrs);
|
||||||
|
|
||||||
|
#[derive(Unique, Default, Clone, Copy)]
|
||||||
|
pub struct ActiveGamepad(Option<GamepadId>);
|
||||||
|
|
||||||
|
//maybe we should manage gamepad state ourselves just like keyboard?
|
||||||
|
//at least for the sake of consitency
|
||||||
|
|
||||||
|
fn process_events(
|
||||||
|
device_events: View<InputDeviceEvent>,
|
||||||
|
mut input_state: UniqueViewMut<RawKbmInputState>,
|
||||||
|
) {
|
||||||
|
input_state.mouse_delta = DVec2::ZERO;
|
||||||
|
for event in device_events.iter() {
|
||||||
|
match event.event {
|
||||||
|
DeviceEvent::MouseMotion { delta } => {
|
||||||
|
input_state.mouse_delta = DVec2::from(delta);
|
||||||
|
},
|
||||||
|
DeviceEvent::Key(input) => {
|
||||||
|
if let Some(keycode) = input.virtual_keycode {
|
||||||
|
match input.state {
|
||||||
|
ElementState::Pressed => input_state.keyboard_state.insert(keycode),
|
||||||
|
ElementState::Released => input_state.keyboard_state.remove(&keycode),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
},
|
||||||
|
DeviceEvent::Button { button, state } => {
|
||||||
|
if button < 32 {
|
||||||
|
input_state.button_state[button as usize] = matches!(state, ElementState::Pressed);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
_ => ()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn process_gilrs_events(
|
||||||
|
mut gilrs: NonSendSync<UniqueViewMut<GilrsWrapper>>,
|
||||||
|
mut active_gamepad: UniqueViewMut<ActiveGamepad>
|
||||||
|
) {
|
||||||
|
while let Some(Event { id, event: _, time: _ }) = gilrs.0.next_event() {
|
||||||
|
active_gamepad.0 = Some(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn input_start(
|
||||||
|
mut inputs: UniqueViewMut<Inputs>,
|
||||||
|
mut prev_inputs: UniqueViewMut<PrevInputs>,
|
||||||
|
) {
|
||||||
|
prev_inputs.0 = *inputs;
|
||||||
|
*inputs = Inputs::default();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_input_state (
|
||||||
|
raw_inputs: UniqueView<RawKbmInputState>,
|
||||||
|
mut inputs: UniqueViewMut<Inputs>,
|
||||||
|
) {
|
||||||
|
inputs.movement += Vec2::new(
|
||||||
|
raw_inputs.keyboard_state.contains(&VirtualKeyCode::D) as u32 as f32 -
|
||||||
|
raw_inputs.keyboard_state.contains(&VirtualKeyCode::A) as u32 as f32,
|
||||||
|
raw_inputs.keyboard_state.contains(&VirtualKeyCode::W) as u32 as f32 -
|
||||||
|
raw_inputs.keyboard_state.contains(&VirtualKeyCode::S) as u32 as f32
|
||||||
|
);
|
||||||
|
inputs.look += raw_inputs.mouse_delta.as_vec2();
|
||||||
|
inputs.action_a |= raw_inputs.button_state[1];
|
||||||
|
inputs.action_b |= raw_inputs.button_state[3];
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_input_state_gamepad (
|
||||||
|
gilrs: NonSendSync<UniqueView<GilrsWrapper>>,
|
||||||
|
active_gamepad: UniqueView<ActiveGamepad>,
|
||||||
|
mut inputs: UniqueViewMut<Inputs>,
|
||||||
|
) {
|
||||||
|
if let Some(gamepad) = active_gamepad.0.map(|id| gilrs.0.gamepad(id)) {
|
||||||
|
let left_stick = vec2(gamepad.value(Axis::LeftStickX), gamepad.value(Axis::LeftStickY));
|
||||||
|
let right_stick = vec2(gamepad.value(Axis::RightStickX), -gamepad.value(Axis::RightStickY));
|
||||||
|
inputs.movement += left_stick;
|
||||||
|
inputs.look += right_stick;
|
||||||
|
inputs.action_a |= gamepad.is_pressed(Button::South);
|
||||||
|
inputs.action_b |= gamepad.is_pressed(Button::East);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn input_end(
|
||||||
|
mut inputs: UniqueViewMut<Inputs>,
|
||||||
|
) {
|
||||||
|
if inputs.movement.length() >= 1. {
|
||||||
|
inputs.movement = inputs.movement.normalize();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn init_input (
|
||||||
|
storages: AllStoragesView
|
||||||
|
) {
|
||||||
|
storages.add_unique_non_send_sync(GilrsWrapper(Gilrs::new().expect("Failed to initialize Gilrs")));
|
||||||
|
storages.add_unique(ActiveGamepad::default());
|
||||||
|
storages.add_unique(Inputs::default());
|
||||||
|
storages.add_unique(PrevInputs::default());
|
||||||
|
storages.add_unique(RawKbmInputState::default());
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn process_inputs() -> Workload {
|
||||||
|
(
|
||||||
|
process_events,
|
||||||
|
process_gilrs_events,
|
||||||
|
input_start,
|
||||||
|
update_input_state,
|
||||||
|
update_input_state_gamepad,
|
||||||
|
input_end,
|
||||||
|
).into_workload()
|
||||||
|
}
|
104
kubi/src/loading_screen.rs
Normal file
104
kubi/src/loading_screen.rs
Normal file
|
@ -0,0 +1,104 @@
|
||||||
|
use shipyard::{UniqueView, UniqueViewMut, Workload, IntoWorkload, EntityId, Unique, AllStoragesViewMut, ViewMut, Get, SystemModificator, track};
|
||||||
|
use glium::glutin::event::VirtualKeyCode;
|
||||||
|
use glam::{Mat3, vec2};
|
||||||
|
use crate::{
|
||||||
|
world::ChunkStorage,
|
||||||
|
state::{GameState, NextState, is_changing_state},
|
||||||
|
transform::Transform2d,
|
||||||
|
gui::{
|
||||||
|
GuiComponent,
|
||||||
|
progressbar::ProgressbarComponent
|
||||||
|
},
|
||||||
|
rendering::{WindowSize, if_resized},
|
||||||
|
input::RawKbmInputState,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Unique, Clone, Copy)]
|
||||||
|
struct ProgressbarId(EntityId);
|
||||||
|
|
||||||
|
fn spawn_loading_screen(
|
||||||
|
mut storages: AllStoragesViewMut,
|
||||||
|
) {
|
||||||
|
let size = *storages.borrow::<UniqueView<WindowSize>>().unwrap();
|
||||||
|
let entity = storages.add_entity((
|
||||||
|
GuiComponent,
|
||||||
|
Transform2d(Mat3::from_scale_angle_translation(
|
||||||
|
vec2(size.0.x as f32, 16.),
|
||||||
|
0.,
|
||||||
|
vec2(0., 0.)
|
||||||
|
)),
|
||||||
|
ProgressbarComponent {
|
||||||
|
progress: 0.33
|
||||||
|
},
|
||||||
|
));
|
||||||
|
storages.add_unique(ProgressbarId(entity));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn resize_progress_bar(
|
||||||
|
size: UniqueView<WindowSize>,
|
||||||
|
bar: UniqueView<ProgressbarId>,
|
||||||
|
mut transforms: ViewMut<Transform2d, { track::All }>
|
||||||
|
) {
|
||||||
|
let mut trans = (&mut transforms).get(bar.0).unwrap();
|
||||||
|
trans.0.x_axis.x = size.0.x as f32;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_progress_bar_progress (
|
||||||
|
world: UniqueView<ChunkStorage>,
|
||||||
|
mut bar: ViewMut<ProgressbarComponent>,
|
||||||
|
eid: UniqueView<ProgressbarId>,
|
||||||
|
) {
|
||||||
|
let mut bar = (&mut bar).get(eid.0).unwrap();
|
||||||
|
let loaded = world.chunks.iter().fold(0, |acc, (&_, chunk)| {
|
||||||
|
acc + chunk.desired_state.matches_current(chunk.current_state) as usize
|
||||||
|
});
|
||||||
|
let total = world.chunks.len();
|
||||||
|
let progress = loaded as f32 / total as f32;
|
||||||
|
bar.progress = progress;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn switch_to_ingame_if_loaded(
|
||||||
|
world: UniqueView<ChunkStorage>,
|
||||||
|
mut state: UniqueViewMut<NextState>
|
||||||
|
) {
|
||||||
|
if world.chunks.is_empty() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if world.chunks.iter().all(|(_, chunk)| {
|
||||||
|
chunk.desired_state.matches_current(chunk.current_state)
|
||||||
|
}) {
|
||||||
|
log::info!("Finished loading chunks");
|
||||||
|
state.0 = Some(GameState::InGame);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn override_loading(
|
||||||
|
kbm_state: UniqueView<RawKbmInputState>,
|
||||||
|
mut state: UniqueViewMut<NextState>
|
||||||
|
) {
|
||||||
|
if kbm_state.keyboard_state.contains(&VirtualKeyCode::F) {
|
||||||
|
state.0 = Some(GameState::InGame);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn despawn_loading_screen_if_switching_state(
|
||||||
|
mut storages: AllStoragesViewMut,
|
||||||
|
) {
|
||||||
|
let state = storages.borrow::<UniqueView<NextState>>().unwrap().0.unwrap();
|
||||||
|
if state != GameState::LoadingWorld {
|
||||||
|
let progress_bar = storages.borrow::<UniqueView<ProgressbarId>>().unwrap().0;
|
||||||
|
storages.delete_entity(progress_bar);
|
||||||
|
storages.remove_unique::<ProgressbarId>().unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update_loading_screen() -> Workload {
|
||||||
|
(
|
||||||
|
spawn_loading_screen.run_if_missing_unique::<ProgressbarId>(),
|
||||||
|
resize_progress_bar.run_if(if_resized),
|
||||||
|
update_progress_bar_progress,
|
||||||
|
override_loading,
|
||||||
|
switch_to_ingame_if_loaded,
|
||||||
|
despawn_loading_screen_if_switching_state.run_if(is_changing_state),
|
||||||
|
).into_workload()
|
||||||
|
}
|
|
@ -1,10 +1,14 @@
|
||||||
// allowed because systems often need a lot of arguments
|
#![cfg_attr(
|
||||||
#![allow(clippy::too_many_arguments)]
|
all(windows, not(debug_assertions)),
|
||||||
|
windows_subsystem = "windows"
|
||||||
|
)]
|
||||||
|
#![allow(clippy::too_many_arguments)] // allowed because systems often need a lot of arguments
|
||||||
|
|
||||||
use shipyard::{
|
use shipyard::{
|
||||||
World, Workload, IntoWorkload,
|
World, Workload, IntoWorkload,
|
||||||
UniqueView, UniqueViewMut,
|
UniqueView, UniqueViewMut,
|
||||||
NonSendSync
|
NonSendSync, WorkloadModificator,
|
||||||
|
SystemModificator
|
||||||
};
|
};
|
||||||
use glium::{
|
use glium::{
|
||||||
glutin::{
|
glutin::{
|
||||||
|
@ -15,13 +19,12 @@ use glium::{
|
||||||
use glam::vec3;
|
use glam::vec3;
|
||||||
use std::time::Instant;
|
use std::time::Instant;
|
||||||
|
|
||||||
mod logging;
|
pub use kubi_shared::transform;
|
||||||
|
|
||||||
pub(crate) mod rendering;
|
pub(crate) mod rendering;
|
||||||
pub(crate) mod world;
|
pub(crate) mod world;
|
||||||
pub(crate) mod player;
|
pub(crate) mod player;
|
||||||
pub(crate) mod prefabs;
|
pub(crate) mod prefabs;
|
||||||
pub(crate) mod transform;
|
|
||||||
pub(crate) mod settings;
|
pub(crate) mod settings;
|
||||||
pub(crate) mod camera;
|
pub(crate) mod camera;
|
||||||
pub(crate) mod events;
|
pub(crate) mod events;
|
||||||
|
@ -31,19 +34,30 @@ pub(crate) mod block_placement;
|
||||||
pub(crate) mod delta_time;
|
pub(crate) mod delta_time;
|
||||||
pub(crate) mod cursor_lock;
|
pub(crate) mod cursor_lock;
|
||||||
pub(crate) mod control_flow;
|
pub(crate) mod control_flow;
|
||||||
|
pub(crate) mod state;
|
||||||
|
pub(crate) mod gui;
|
||||||
|
pub(crate) mod networking;
|
||||||
|
pub(crate) mod init;
|
||||||
|
pub(crate) mod color;
|
||||||
|
pub(crate) mod loading_screen;
|
||||||
|
pub(crate) mod connecting_screen;
|
||||||
|
|
||||||
use world::{
|
use world::{
|
||||||
init_game_world,
|
init_game_world,
|
||||||
loading::update_loaded_world_around_player,
|
loading::update_loaded_world_around_player,
|
||||||
raycast::update_raycasts
|
raycast::update_raycasts,
|
||||||
|
queue::apply_queued_blocks,
|
||||||
|
tasks::{inject_network_responses_into_manager_queue, ChunkTaskManager}, ChunkStorage
|
||||||
};
|
};
|
||||||
use player::spawn_player;
|
use player::{spawn_player, MainPlayer};
|
||||||
use prefabs::load_prefabs;
|
use prefabs::load_prefabs;
|
||||||
use settings::load_settings;
|
use settings::load_settings;
|
||||||
use camera::compute_cameras;
|
use camera::compute_cameras;
|
||||||
use events::{
|
use events::{
|
||||||
clear_events, process_glutin_events,
|
clear_events,
|
||||||
player_actions::generate_move_events
|
process_glutin_events,
|
||||||
|
initial_resize_event,
|
||||||
|
player_actions::generate_move_events,
|
||||||
};
|
};
|
||||||
use input::{init_input, process_inputs};
|
use input::{init_input, process_inputs};
|
||||||
use fly_controller::update_controllers;
|
use fly_controller::update_controllers;
|
||||||
|
@ -52,49 +66,86 @@ use rendering::{
|
||||||
RenderTarget,
|
RenderTarget,
|
||||||
BackgroundColor,
|
BackgroundColor,
|
||||||
clear_background,
|
clear_background,
|
||||||
primitives::init_simple_box_buffers,
|
init_window_size,
|
||||||
|
update_window_size,
|
||||||
|
primitives::init_primitives,
|
||||||
selection_box::render_selection_box,
|
selection_box::render_selection_box,
|
||||||
world::draw_world,
|
world::draw_world,
|
||||||
world::draw_current_chunk_border,
|
world::draw_current_chunk_border,
|
||||||
};
|
};
|
||||||
use block_placement::block_placement_system;
|
use block_placement::update_block_placement;
|
||||||
use delta_time::{DeltaTime, init_delta_time};
|
use delta_time::{DeltaTime, init_delta_time};
|
||||||
use cursor_lock::{insert_lock_state, update_cursor_lock_state, lock_cursor_now};
|
use cursor_lock::{insert_lock_state, update_cursor_lock_state, lock_cursor_now};
|
||||||
use control_flow::{exit_on_esc, insert_control_flow_unique, SetControlFlow};
|
use control_flow::{exit_on_esc, insert_control_flow_unique, SetControlFlow};
|
||||||
|
use state::{is_ingame, is_ingame_or_loading, is_loading, init_state, update_state, is_connecting};
|
||||||
|
use networking::{update_networking, is_multiplayer, disconnect_on_exit};
|
||||||
|
use init::initialize_from_args;
|
||||||
|
use gui::{render_gui, init_gui, update_gui};
|
||||||
|
use loading_screen::update_loading_screen;
|
||||||
|
use connecting_screen::switch_to_loading_if_connected;
|
||||||
|
|
||||||
fn startup() -> Workload {
|
fn startup() -> Workload {
|
||||||
(
|
(
|
||||||
|
initial_resize_event,
|
||||||
|
init_window_size,
|
||||||
load_settings,
|
load_settings,
|
||||||
load_prefabs,
|
load_prefabs,
|
||||||
init_simple_box_buffers,
|
init_primitives,
|
||||||
insert_lock_state,
|
insert_lock_state,
|
||||||
|
init_state,
|
||||||
|
initialize_from_args,
|
||||||
lock_cursor_now,
|
lock_cursor_now,
|
||||||
init_input,
|
init_input,
|
||||||
init_game_world,
|
init_gui,
|
||||||
spawn_player,
|
|
||||||
insert_control_flow_unique,
|
insert_control_flow_unique,
|
||||||
init_delta_time,
|
init_delta_time,
|
||||||
).into_workload()
|
).into_workload()
|
||||||
}
|
}
|
||||||
fn update() -> Workload {
|
fn update() -> Workload {
|
||||||
(
|
(
|
||||||
process_inputs,
|
update_window_size,
|
||||||
update_controllers,
|
|
||||||
generate_move_events,
|
|
||||||
update_loaded_world_around_player,
|
|
||||||
update_raycasts,
|
|
||||||
block_placement_system,
|
|
||||||
update_cursor_lock_state,
|
update_cursor_lock_state,
|
||||||
|
process_inputs,
|
||||||
|
(
|
||||||
|
init_game_world.run_if_missing_unique::<ChunkTaskManager>(),
|
||||||
|
spawn_player.run_if_storage_empty::<MainPlayer>(),
|
||||||
|
).into_sequential_workload().run_if(is_ingame_or_loading).tag("game_init"),
|
||||||
|
(
|
||||||
|
update_networking,
|
||||||
|
inject_network_responses_into_manager_queue.run_if(is_ingame_or_loading).skip_if_missing_unique::<ChunkTaskManager>(),
|
||||||
|
).into_sequential_workload().run_if(is_multiplayer).tag("networking").after_all("game_init"),
|
||||||
|
(
|
||||||
|
switch_to_loading_if_connected
|
||||||
|
).into_workload().run_if(is_connecting).after_all("networking"),
|
||||||
|
(
|
||||||
|
update_loading_screen,
|
||||||
|
).into_workload().run_if(is_loading).after_all("game_init"),
|
||||||
|
(
|
||||||
|
update_loaded_world_around_player,
|
||||||
|
).into_workload().run_if(is_ingame_or_loading).after_all("game_init"),
|
||||||
|
(
|
||||||
|
update_controllers,
|
||||||
|
generate_move_events,
|
||||||
|
update_raycasts,
|
||||||
|
update_block_placement,
|
||||||
|
apply_queued_blocks,
|
||||||
|
).into_workload().run_if(is_ingame),
|
||||||
compute_cameras,
|
compute_cameras,
|
||||||
|
update_gui,
|
||||||
|
update_state,
|
||||||
exit_on_esc,
|
exit_on_esc,
|
||||||
|
disconnect_on_exit.run_if(is_multiplayer),
|
||||||
).into_workload()
|
).into_workload()
|
||||||
}
|
}
|
||||||
fn render() -> Workload {
|
fn render() -> Workload {
|
||||||
(
|
(
|
||||||
clear_background,
|
clear_background,
|
||||||
draw_world,
|
(
|
||||||
draw_current_chunk_border,
|
draw_world,
|
||||||
render_selection_box,
|
draw_current_chunk_border,
|
||||||
|
render_selection_box,
|
||||||
|
).into_sequential_workload().run_if(is_ingame),
|
||||||
|
render_gui,
|
||||||
).into_sequential_workload()
|
).into_sequential_workload()
|
||||||
}
|
}
|
||||||
fn after_frame_end() -> Workload {
|
fn after_frame_end() -> Workload {
|
||||||
|
@ -103,15 +154,28 @@ fn after_frame_end() -> Workload {
|
||||||
).into_workload()
|
).into_workload()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() {
|
#[cfg(all(windows, not(debug_assertions)))]
|
||||||
logging::init();
|
fn attach_console() {
|
||||||
|
use winapi::um::wincon::{AttachConsole, ATTACH_PARENT_PROCESS};
|
||||||
|
unsafe { AttachConsole(ATTACH_PARENT_PROCESS); }
|
||||||
|
}
|
||||||
|
|
||||||
//Create event loop
|
fn main() {
|
||||||
let event_loop = EventLoop::new();
|
//Attach console on release builds on windows
|
||||||
|
#[cfg(all(windows, not(debug_assertions)))] attach_console();
|
||||||
|
|
||||||
|
//Print version
|
||||||
|
println!("{:─^54}", format!("[ ▄▀ Kubi client v. {} ]", env!("CARGO_PKG_VERSION")));
|
||||||
|
|
||||||
|
//Init env_logger
|
||||||
|
kubi_logging::init();
|
||||||
|
|
||||||
//Create a shipyard world
|
//Create a shipyard world
|
||||||
let mut world = World::new();
|
let mut world = World::new();
|
||||||
|
|
||||||
|
//Create event loop
|
||||||
|
let event_loop = EventLoop::new();
|
||||||
|
|
||||||
//Add systems and uniques, Init and load things
|
//Add systems and uniques, Init and load things
|
||||||
world.add_unique_non_send_sync(Renderer::init(&event_loop));
|
world.add_unique_non_send_sync(Renderer::init(&event_loop));
|
||||||
world.add_unique(BackgroundColor(vec3(0.5, 0.5, 1.)));
|
world.add_unique(BackgroundColor(vec3(0.5, 0.5, 1.)));
|
||||||
|
@ -148,7 +212,7 @@ fn main() {
|
||||||
last_update = now;
|
last_update = now;
|
||||||
}
|
}
|
||||||
|
|
||||||
//Run update workflow
|
//Run update workflows
|
||||||
world.run_workload(update).unwrap();
|
world.run_workload(update).unwrap();
|
||||||
|
|
||||||
//Start rendering (maybe use custom views for this?)
|
//Start rendering (maybe use custom views for this?)
|
169
kubi/src/networking.rs
Normal file
169
kubi/src/networking.rs
Normal file
|
@ -0,0 +1,169 @@
|
||||||
|
use shipyard::{Unique, AllStoragesView, UniqueView, UniqueViewMut, Workload, IntoWorkload, EntitiesViewMut, Component, ViewMut, SystemModificator, View, IntoIter, WorkloadModificator};
|
||||||
|
use glium::glutin::event_loop::ControlFlow;
|
||||||
|
use std::net::SocketAddr;
|
||||||
|
use uflow::client::{Client, Config as ClientConfig, Event as ClientEvent};
|
||||||
|
use kubi_shared::networking::{
|
||||||
|
messages::{ClientToServerMessage, ServerToClientMessage, S_SERVER_HELLO},
|
||||||
|
state::ClientJoinState
|
||||||
|
};
|
||||||
|
use crate::{events::EventComponent, control_flow::SetControlFlow};
|
||||||
|
|
||||||
|
#[derive(Unique, Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub enum GameType {
|
||||||
|
Singleplayer,
|
||||||
|
Muliplayer
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Unique, Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub struct ServerAddress(pub SocketAddr);
|
||||||
|
|
||||||
|
#[derive(Unique)]
|
||||||
|
pub struct UdpClient(pub Client);
|
||||||
|
|
||||||
|
#[derive(Component)]
|
||||||
|
pub struct NetworkEvent(pub ClientEvent);
|
||||||
|
|
||||||
|
impl NetworkEvent {
|
||||||
|
///Checks if postcard-encoded message has a type
|
||||||
|
pub fn is_message_of_type<const T: u8>(&self) -> bool {
|
||||||
|
let ClientEvent::Receive(data) = &self.0 else { return false };
|
||||||
|
if data.len() == 0 { return false }
|
||||||
|
data[0] == T
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Component)]
|
||||||
|
pub struct NetworkMessageEvent(pub ServerToClientMessage);
|
||||||
|
|
||||||
|
fn connect_client(
|
||||||
|
storages: AllStoragesView
|
||||||
|
) {
|
||||||
|
log::info!("Creating client");
|
||||||
|
let address = storages.borrow::<UniqueView<ServerAddress>>().unwrap();
|
||||||
|
let client = Client::connect(address.0, ClientConfig::default()).expect("Client connection failed");
|
||||||
|
storages.add_unique(UdpClient(client));
|
||||||
|
storages.add_unique(ClientJoinState::Disconnected);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn poll_client(
|
||||||
|
mut client: UniqueViewMut<UdpClient>,
|
||||||
|
mut entities: EntitiesViewMut,
|
||||||
|
mut events: ViewMut<EventComponent>,
|
||||||
|
mut network_events: ViewMut<NetworkEvent>,
|
||||||
|
) {
|
||||||
|
entities.bulk_add_entity((
|
||||||
|
&mut events,
|
||||||
|
&mut network_events,
|
||||||
|
), client.0.step().map(|event| {
|
||||||
|
(EventComponent, NetworkEvent(event))
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_client_join_state_to_connected(
|
||||||
|
mut join_state: UniqueViewMut<ClientJoinState>
|
||||||
|
) {
|
||||||
|
log::info!("Setting ClientJoinState");
|
||||||
|
*join_state = ClientJoinState::Connected;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn say_hello(
|
||||||
|
mut client: UniqueViewMut<UdpClient>,
|
||||||
|
) {
|
||||||
|
log::info!("Authenticating");
|
||||||
|
client.0.send(
|
||||||
|
postcard::to_allocvec(
|
||||||
|
&ClientToServerMessage::ClientHello {
|
||||||
|
username: "Sbeve".into(),
|
||||||
|
password: None
|
||||||
|
}
|
||||||
|
).unwrap().into_boxed_slice(),
|
||||||
|
0,
|
||||||
|
uflow::SendMode::Reliable
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn check_server_hello_response(
|
||||||
|
network_events: View<NetworkEvent>,
|
||||||
|
mut join_state: UniqueViewMut<ClientJoinState>
|
||||||
|
) {
|
||||||
|
for event in network_events.iter() {
|
||||||
|
let ClientEvent::Receive(data) = &event.0 else {
|
||||||
|
continue
|
||||||
|
};
|
||||||
|
if !event.is_message_of_type::<S_SERVER_HELLO>() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
let Ok(parsed_message) = postcard::from_bytes(data) else {
|
||||||
|
log::error!("Malformed message");
|
||||||
|
continue
|
||||||
|
};
|
||||||
|
let ServerToClientMessage::ServerHello { init } = parsed_message else {
|
||||||
|
unreachable!()
|
||||||
|
};
|
||||||
|
//TODO handle init data
|
||||||
|
*join_state = ClientJoinState::Joined;
|
||||||
|
log::info!("Joined the server!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update_networking() -> Workload {
|
||||||
|
(
|
||||||
|
connect_client.run_if_missing_unique::<UdpClient>(),
|
||||||
|
poll_client,
|
||||||
|
(
|
||||||
|
set_client_join_state_to_connected,
|
||||||
|
say_hello,
|
||||||
|
).into_workload().run_if(if_just_connected),
|
||||||
|
(
|
||||||
|
check_server_hello_response,
|
||||||
|
).into_workload().run_if(is_join_state::<{ClientJoinState::Connected as u8}>)
|
||||||
|
).into_sequential_workload() //HACK Weird issues with shipyard removed
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn disconnect_on_exit(
|
||||||
|
control_flow: UniqueView<SetControlFlow>,
|
||||||
|
mut client: UniqueViewMut<UdpClient>,
|
||||||
|
) {
|
||||||
|
if let Some(ControlFlow::ExitWithCode(_)) = control_flow.0 {
|
||||||
|
if client.0.is_active() {
|
||||||
|
client.0.flush();
|
||||||
|
client.0.disconnect();
|
||||||
|
while client.0.is_active() { client.0.step().for_each(|_|()); }
|
||||||
|
log::info!("Client disconnected");
|
||||||
|
} else {
|
||||||
|
log::info!("Client inactive")
|
||||||
|
}
|
||||||
|
// if let Err(error) = client.0. {
|
||||||
|
// log::error!("failed to disconnect: {}", error);
|
||||||
|
// } else {
|
||||||
|
// log::info!("Client disconnected");
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// conditions
|
||||||
|
|
||||||
|
fn if_just_connected(
|
||||||
|
network_events: View<NetworkEvent>,
|
||||||
|
) -> bool {
|
||||||
|
network_events.iter().any(|event| matches!(&event.0, ClientEvent::Connect))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_join_state<const STATE: u8>(
|
||||||
|
join_state: UniqueView<ClientJoinState>
|
||||||
|
) -> bool {
|
||||||
|
(*join_state as u8) == STATE
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_multiplayer(
|
||||||
|
game_type: UniqueView<GameType>
|
||||||
|
) -> bool {
|
||||||
|
*game_type == GameType::Muliplayer
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_singleplayer(
|
||||||
|
game_type: UniqueView<GameType>
|
||||||
|
) -> bool {
|
||||||
|
*game_type == GameType::Singleplayer
|
||||||
|
}
|
|
@ -1,13 +1,12 @@
|
||||||
use shipyard::{Component, AllStoragesViewMut};
|
use shipyard::{Component, AllStoragesViewMut, View, IntoIter};
|
||||||
use crate::{
|
use crate::{
|
||||||
transform::Transform,
|
transform::Transform,
|
||||||
camera::Camera,
|
camera::Camera,
|
||||||
fly_controller::FlyController,
|
fly_controller::FlyController,
|
||||||
world::raycast::LookingAtBlock,
|
world::raycast::LookingAtBlock,
|
||||||
|
block_placement::PlayerHolding,
|
||||||
};
|
};
|
||||||
|
pub use kubi_shared::player::Player;
|
||||||
#[derive(Component)]
|
|
||||||
pub struct Player;
|
|
||||||
|
|
||||||
#[derive(Component)]
|
#[derive(Component)]
|
||||||
pub struct MainPlayer;
|
pub struct MainPlayer;
|
||||||
|
@ -16,12 +15,13 @@ pub fn spawn_player (
|
||||||
mut storages: AllStoragesViewMut
|
mut storages: AllStoragesViewMut
|
||||||
) {
|
) {
|
||||||
log::info!("spawning player");
|
log::info!("spawning player");
|
||||||
storages.add_entity((
|
let entity_id = storages.add_entity((
|
||||||
Player,
|
Player,
|
||||||
MainPlayer,
|
MainPlayer,
|
||||||
Transform::default(),
|
Transform::default(),
|
||||||
Camera::default(),
|
Camera::default(),
|
||||||
FlyController,
|
FlyController,
|
||||||
LookingAtBlock::default(),
|
LookingAtBlock::default(),
|
||||||
|
PlayerHolding::default(),
|
||||||
));
|
));
|
||||||
}
|
}
|
|
@ -1,6 +1,6 @@
|
||||||
use shipyard::{NonSendSync, UniqueView, Unique, AllStoragesView};
|
use shipyard::{NonSendSync, UniqueView, Unique, AllStoragesView};
|
||||||
use glium::{texture::{SrgbTexture2dArray, MipmapsOption}, Program};
|
use glium::{texture::{SrgbTexture2dArray, MipmapsOption}, Program};
|
||||||
use strum::EnumIter;
|
use kubi_shared::block::{Block, BlockTexture};
|
||||||
use crate::rendering::Renderer;
|
use crate::rendering::Renderer;
|
||||||
|
|
||||||
mod texture;
|
mod texture;
|
||||||
|
@ -13,23 +13,6 @@ pub trait AssetPaths {
|
||||||
fn file_name(self) -> &'static str;
|
fn file_name(self) -> &'static str;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, EnumIter)]
|
|
||||||
#[repr(u8)]
|
|
||||||
pub enum BlockTexture {
|
|
||||||
Stone = 0,
|
|
||||||
Dirt = 1,
|
|
||||||
GrassTop = 2,
|
|
||||||
GrassSide = 3,
|
|
||||||
Sand = 4,
|
|
||||||
Bedrock = 5,
|
|
||||||
Wood = 6,
|
|
||||||
WoodTop = 7,
|
|
||||||
Leaf = 8,
|
|
||||||
Torch = 9,
|
|
||||||
TallGrass = 10,
|
|
||||||
Snow = 11,
|
|
||||||
GrassSideSnow = 12,
|
|
||||||
}
|
|
||||||
impl AssetPaths for BlockTexture {
|
impl AssetPaths for BlockTexture {
|
||||||
fn file_name(self) -> &'static str {
|
fn file_name(self) -> &'static str {
|
||||||
match self {
|
match self {
|
||||||
|
@ -46,6 +29,9 @@ impl AssetPaths for BlockTexture {
|
||||||
Self::TallGrass => "tall_grass.png",
|
Self::TallGrass => "tall_grass.png",
|
||||||
Self::Snow => "snow.png",
|
Self::Snow => "snow.png",
|
||||||
Self::GrassSideSnow => "grass_side_snow.png",
|
Self::GrassSideSnow => "grass_side_snow.png",
|
||||||
|
Self::Cobblestone => "cobblestone.png",
|
||||||
|
Self::Planks => "planks.png",
|
||||||
|
Self::WaterSolid => "solid_water.png",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -60,7 +46,10 @@ pub struct ChunkShaderPrefab(pub Program);
|
||||||
pub struct SelBoxShaderPrefab(pub Program);
|
pub struct SelBoxShaderPrefab(pub Program);
|
||||||
|
|
||||||
#[derive(Unique)]
|
#[derive(Unique)]
|
||||||
pub struct BasicColoredShaderPrefab(pub Program);
|
pub struct ColoredShaderPrefab(pub Program);
|
||||||
|
|
||||||
|
#[derive(Unique)]
|
||||||
|
pub struct ProgressbarShaderPrefab(pub Program);
|
||||||
|
|
||||||
pub fn load_prefabs(
|
pub fn load_prefabs(
|
||||||
storages: AllStoragesView,
|
storages: AllStoragesView,
|
||||||
|
@ -92,7 +81,7 @@ pub fn load_prefabs(
|
||||||
&renderer.display
|
&renderer.display
|
||||||
)
|
)
|
||||||
));
|
));
|
||||||
storages.add_unique_non_send_sync(BasicColoredShaderPrefab(
|
storages.add_unique_non_send_sync(ColoredShaderPrefab(
|
||||||
include_shader_prefab!(
|
include_shader_prefab!(
|
||||||
"colored",
|
"colored",
|
||||||
"../shaders/colored.vert",
|
"../shaders/colored.vert",
|
||||||
|
@ -100,4 +89,12 @@ pub fn load_prefabs(
|
||||||
&renderer.display
|
&renderer.display
|
||||||
)
|
)
|
||||||
));
|
));
|
||||||
|
storages.add_unique_non_send_sync(ProgressbarShaderPrefab(
|
||||||
|
include_shader_prefab!(
|
||||||
|
"gui/progressbar",
|
||||||
|
"../shaders/gui/progressbar.vert",
|
||||||
|
"../shaders/gui/progressbar.frag",
|
||||||
|
&renderer.display
|
||||||
|
)
|
||||||
|
));
|
||||||
}
|
}
|
79
kubi/src/rendering.rs
Normal file
79
kubi/src/rendering.rs
Normal file
|
@ -0,0 +1,79 @@
|
||||||
|
use shipyard::{Unique, NonSendSync, UniqueView, UniqueViewMut, View, IntoIter, AllStoragesView};
|
||||||
|
use glium::{
|
||||||
|
Display, Surface,
|
||||||
|
Version, Api,
|
||||||
|
glutin::{
|
||||||
|
event_loop::EventLoop,
|
||||||
|
window::WindowBuilder,
|
||||||
|
ContextBuilder, GlProfile
|
||||||
|
},
|
||||||
|
};
|
||||||
|
use glam::{Vec3, UVec2};
|
||||||
|
use crate::events::WindowResizedEvent;
|
||||||
|
|
||||||
|
pub mod primitives;
|
||||||
|
pub mod world;
|
||||||
|
pub mod selection_box;
|
||||||
|
|
||||||
|
#[derive(Unique)]
|
||||||
|
pub struct RenderTarget(pub glium::Frame);
|
||||||
|
|
||||||
|
#[derive(Unique)]
|
||||||
|
pub struct BackgroundColor(pub Vec3);
|
||||||
|
|
||||||
|
#[derive(Unique, Clone, Copy)]
|
||||||
|
pub struct WindowSize(pub UVec2);
|
||||||
|
|
||||||
|
#[derive(Unique)]
|
||||||
|
pub struct Renderer {
|
||||||
|
pub display: Display
|
||||||
|
}
|
||||||
|
impl Renderer {
|
||||||
|
pub fn init(event_loop: &EventLoop<()>) -> Self {
|
||||||
|
log::info!("initializing display");
|
||||||
|
let wb = WindowBuilder::new()
|
||||||
|
.with_title("uwu")
|
||||||
|
.with_maximized(true);
|
||||||
|
let cb = ContextBuilder::new()
|
||||||
|
.with_depth_buffer(24)
|
||||||
|
.with_gl_profile(GlProfile::Core);
|
||||||
|
let display = Display::new(wb, cb, event_loop)
|
||||||
|
.expect("Failed to create a glium Display");
|
||||||
|
log::info!("Renderer: {}", display.get_opengl_renderer_string());
|
||||||
|
log::info!("OpenGL {}", display.get_opengl_version_string());
|
||||||
|
log::info!("Supports GLES {:?}", display.get_supported_glsl_version());
|
||||||
|
assert!(display.is_glsl_version_supported(&Version(Api::GlEs, 3, 0)), "GLES 3.0 is not supported");
|
||||||
|
Self { display }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn clear_background(
|
||||||
|
mut target: NonSendSync<UniqueViewMut<RenderTarget>>,
|
||||||
|
color: UniqueView<BackgroundColor>,
|
||||||
|
) {
|
||||||
|
target.0.clear_color_srgb_and_depth((color.0.x, color.0.y, color.0.z, 1.), 1.);
|
||||||
|
}
|
||||||
|
|
||||||
|
//not sure if this belongs here
|
||||||
|
|
||||||
|
pub fn init_window_size(
|
||||||
|
storages: AllStoragesView,
|
||||||
|
) {
|
||||||
|
let size = storages.borrow::<View<WindowResizedEvent>>().unwrap().iter().next().unwrap().0;
|
||||||
|
storages.add_unique(WindowSize(size))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update_window_size(
|
||||||
|
mut win_size: UniqueViewMut<WindowSize>,
|
||||||
|
resize: View<WindowResizedEvent>,
|
||||||
|
) {
|
||||||
|
if let Some(resize) = resize.iter().next() {
|
||||||
|
win_size.0 = resize.0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn if_resized (
|
||||||
|
resize: View<WindowResizedEvent>,
|
||||||
|
) -> bool {
|
||||||
|
resize.len() > 0
|
||||||
|
}
|
27
kubi/src/rendering/primitives.rs
Normal file
27
kubi/src/rendering/primitives.rs
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
use shipyard::{Workload, IntoWorkload};
|
||||||
|
use glium::implement_vertex;
|
||||||
|
|
||||||
|
pub mod cube;
|
||||||
|
pub mod rect;
|
||||||
|
|
||||||
|
use cube::init_cube_primitive;
|
||||||
|
use rect::init_rect_primitive;
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Default)]
|
||||||
|
pub struct PositionOnlyVertex {
|
||||||
|
pub position: [f32; 3],
|
||||||
|
}
|
||||||
|
implement_vertex!(PositionOnlyVertex, position);
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Default)]
|
||||||
|
pub struct PositionOnlyVertex2d {
|
||||||
|
pub position: [f32; 2],
|
||||||
|
}
|
||||||
|
implement_vertex!(PositionOnlyVertex2d, position);
|
||||||
|
|
||||||
|
pub fn init_primitives() -> Workload {
|
||||||
|
(
|
||||||
|
init_cube_primitive,
|
||||||
|
init_rect_primitive,
|
||||||
|
).into_workload()
|
||||||
|
}
|
56
kubi/src/rendering/primitives/cube.rs
Normal file
56
kubi/src/rendering/primitives/cube.rs
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
use shipyard::{AllStoragesView, NonSendSync, UniqueView, Unique};
|
||||||
|
use glium::{VertexBuffer, IndexBuffer, index::PrimitiveType};
|
||||||
|
use crate::rendering::Renderer;
|
||||||
|
use super::PositionOnlyVertex;
|
||||||
|
|
||||||
|
#[derive(Unique)]
|
||||||
|
pub struct CubePrimitive(pub VertexBuffer<PositionOnlyVertex>, pub IndexBuffer<u16>);
|
||||||
|
|
||||||
|
const CUBE_VERTICES: &[PositionOnlyVertex] = &[
|
||||||
|
// front
|
||||||
|
PositionOnlyVertex { position: [0.0, 0.0, 1.0] },
|
||||||
|
PositionOnlyVertex { position: [1.0, 0.0, 1.0] },
|
||||||
|
PositionOnlyVertex { position: [1.0, 1.0, 1.0] },
|
||||||
|
PositionOnlyVertex { position: [0.0, 1.0, 1.0] },
|
||||||
|
// back
|
||||||
|
PositionOnlyVertex { position: [0.0, 0.0, 0.0] },
|
||||||
|
PositionOnlyVertex { position: [1.0, 0.0, 0.0] },
|
||||||
|
PositionOnlyVertex { position: [1.0, 1.0, 0.0] },
|
||||||
|
PositionOnlyVertex { position: [0.0, 1.0, 0.0] },
|
||||||
|
];
|
||||||
|
const CUBE_INDICES: &[u16] = &[
|
||||||
|
// front
|
||||||
|
0, 1, 2,
|
||||||
|
2, 3, 0,
|
||||||
|
// right
|
||||||
|
1, 5, 6,
|
||||||
|
6, 2, 1,
|
||||||
|
// back
|
||||||
|
7, 6, 5,
|
||||||
|
5, 4, 7,
|
||||||
|
// left
|
||||||
|
4, 0, 3,
|
||||||
|
3, 7, 4,
|
||||||
|
// bottom
|
||||||
|
4, 5, 1,
|
||||||
|
1, 0, 4,
|
||||||
|
// top
|
||||||
|
3, 2, 6,
|
||||||
|
6, 7, 3
|
||||||
|
];
|
||||||
|
|
||||||
|
pub(super) fn init_cube_primitive(
|
||||||
|
storages: AllStoragesView,
|
||||||
|
display: NonSendSync<UniqueView<Renderer>>
|
||||||
|
) {
|
||||||
|
let vert = VertexBuffer::new(
|
||||||
|
&display.display,
|
||||||
|
CUBE_VERTICES
|
||||||
|
).unwrap();
|
||||||
|
let index = IndexBuffer::new(
|
||||||
|
&display.display,
|
||||||
|
PrimitiveType::TrianglesList,
|
||||||
|
CUBE_INDICES
|
||||||
|
).unwrap();
|
||||||
|
storages.add_unique_non_send_sync(CubePrimitive(vert, index));
|
||||||
|
}
|
31
kubi/src/rendering/primitives/rect.rs
Normal file
31
kubi/src/rendering/primitives/rect.rs
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
use shipyard::{Unique, AllStoragesView, NonSendSync, UniqueView};
|
||||||
|
use glium::{VertexBuffer, IndexBuffer, index::PrimitiveType};
|
||||||
|
use crate::rendering::Renderer;
|
||||||
|
use super::PositionOnlyVertex2d;
|
||||||
|
|
||||||
|
#[derive(Unique)]
|
||||||
|
pub struct RectPrimitive(pub VertexBuffer<PositionOnlyVertex2d>, pub IndexBuffer<u16>);
|
||||||
|
|
||||||
|
const RECT_VERTEX: &[PositionOnlyVertex2d] = &[
|
||||||
|
PositionOnlyVertex2d { position: [0., 0.] },
|
||||||
|
PositionOnlyVertex2d { position: [1., 0.] },
|
||||||
|
PositionOnlyVertex2d { position: [0., 1.] },
|
||||||
|
PositionOnlyVertex2d { position: [1., 1.] },
|
||||||
|
];
|
||||||
|
const RECT_INDEX: &[u16] = &[0, 1, 2, 1, 3, 2];
|
||||||
|
|
||||||
|
pub(super) fn init_rect_primitive(
|
||||||
|
storages: AllStoragesView,
|
||||||
|
display: NonSendSync<UniqueView<Renderer>>
|
||||||
|
) {
|
||||||
|
let vert = VertexBuffer::new(
|
||||||
|
&display.display,
|
||||||
|
RECT_VERTEX
|
||||||
|
).unwrap();
|
||||||
|
let index = IndexBuffer::new(
|
||||||
|
&display.display,
|
||||||
|
PrimitiveType::TrianglesList,
|
||||||
|
RECT_INDEX
|
||||||
|
).unwrap();
|
||||||
|
storages.add_unique_non_send_sync(RectPrimitive(vert, index));
|
||||||
|
}
|
|
@ -12,7 +12,7 @@ use crate::{
|
||||||
};
|
};
|
||||||
use super::{
|
use super::{
|
||||||
RenderTarget,
|
RenderTarget,
|
||||||
primitives::SimpleBoxBuffers,
|
primitives::cube::CubePrimitive,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn render_selection_box(
|
pub fn render_selection_box(
|
||||||
|
@ -20,7 +20,7 @@ pub fn render_selection_box(
|
||||||
camera: View<Camera>,
|
camera: View<Camera>,
|
||||||
mut target: NonSendSync<UniqueViewMut<RenderTarget>>,
|
mut target: NonSendSync<UniqueViewMut<RenderTarget>>,
|
||||||
program: NonSendSync<UniqueView<SelBoxShaderPrefab>>,
|
program: NonSendSync<UniqueView<SelBoxShaderPrefab>>,
|
||||||
buffers: NonSendSync<UniqueView<SimpleBoxBuffers>>,
|
buffers: NonSendSync<UniqueView<CubePrimitive>>,
|
||||||
) {
|
) {
|
||||||
let camera = camera.iter().next().unwrap();
|
let camera = camera.iter().next().unwrap();
|
||||||
let Some(lookat) = lookat.iter().next() else { return };
|
let Some(lookat) = lookat.iter().next() else { return };
|
|
@ -1,5 +1,5 @@
|
||||||
use glam::{Vec3, Mat4, Quat, ivec3};
|
use glam::{Vec3, Mat4, Quat, ivec3};
|
||||||
use shipyard::{NonSendSync, UniqueView, UniqueViewMut, View, IntoIter};
|
use shipyard::{NonSendSync, UniqueView, UniqueViewMut, View, IntoIter, track};
|
||||||
use glium::{
|
use glium::{
|
||||||
implement_vertex, uniform,
|
implement_vertex, uniform,
|
||||||
Surface, DrawParameters,
|
Surface, DrawParameters,
|
||||||
|
@ -24,7 +24,7 @@ use crate::{
|
||||||
prefabs::{
|
prefabs::{
|
||||||
ChunkShaderPrefab,
|
ChunkShaderPrefab,
|
||||||
BlockTexturesPrefab,
|
BlockTexturesPrefab,
|
||||||
BasicColoredShaderPrefab,
|
ColoredShaderPrefab,
|
||||||
},
|
},
|
||||||
world::{
|
world::{
|
||||||
ChunkStorage,
|
ChunkStorage,
|
||||||
|
@ -32,9 +32,10 @@ use crate::{
|
||||||
chunk::CHUNK_SIZE,
|
chunk::CHUNK_SIZE,
|
||||||
}, settings::GameSettings,
|
}, settings::GameSettings,
|
||||||
};
|
};
|
||||||
use super::{RenderTarget, primitives::SimpleBoxBuffers};
|
use super::{RenderTarget, primitives::cube::CubePrimitive};
|
||||||
|
|
||||||
#[derive(Clone, Copy)]
|
#[derive(Clone, Copy)]
|
||||||
|
#[repr(C)]
|
||||||
pub struct ChunkVertex {
|
pub struct ChunkVertex {
|
||||||
pub position: [f32; 3],
|
pub position: [f32; 3],
|
||||||
pub normal: [f32; 3],
|
pub normal: [f32; 3],
|
||||||
|
@ -43,7 +44,6 @@ pub struct ChunkVertex {
|
||||||
}
|
}
|
||||||
implement_vertex!(ChunkVertex, position, normal, uv, tex_index);
|
implement_vertex!(ChunkVertex, position, normal, uv, tex_index);
|
||||||
|
|
||||||
|
|
||||||
pub fn draw_world(
|
pub fn draw_world(
|
||||||
mut target: NonSendSync<UniqueViewMut<RenderTarget>>,
|
mut target: NonSendSync<UniqueViewMut<RenderTarget>>,
|
||||||
chunks: UniqueView<ChunkStorage>,
|
chunks: UniqueView<ChunkStorage>,
|
||||||
|
@ -109,13 +109,12 @@ pub fn draw_world(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//this doesn't use culling!
|
|
||||||
pub fn draw_current_chunk_border(
|
pub fn draw_current_chunk_border(
|
||||||
mut target: NonSendSync<UniqueViewMut<RenderTarget>>,
|
mut target: NonSendSync<UniqueViewMut<RenderTarget>>,
|
||||||
player: View<MainPlayer>,
|
player: View<MainPlayer>,
|
||||||
transforms: View<Transform>,
|
transforms: View<Transform, { track::All }>,
|
||||||
buffers: NonSendSync<UniqueView<SimpleBoxBuffers>>,
|
buffers: NonSendSync<UniqueView<CubePrimitive>>,
|
||||||
program: NonSendSync<UniqueView<BasicColoredShaderPrefab>>,
|
program: NonSendSync<UniqueView<ColoredShaderPrefab>>,
|
||||||
camera: View<Camera>,
|
camera: View<Camera>,
|
||||||
settings: UniqueView<GameSettings>,
|
settings: UniqueView<GameSettings>,
|
||||||
) {
|
) {
|
|
@ -10,7 +10,7 @@ pub struct GameSettings {
|
||||||
impl Default for GameSettings {
|
impl Default for GameSettings {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
render_distance: 5,
|
render_distance: 6,
|
||||||
mouse_sensitivity: 1.,
|
mouse_sensitivity: 1.,
|
||||||
debug_draw_current_chunk_border: cfg!(debug_assertions),
|
debug_draw_current_chunk_border: cfg!(debug_assertions),
|
||||||
}
|
}
|
58
kubi/src/state.rs
Normal file
58
kubi/src/state.rs
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
use shipyard::{Unique, UniqueView, UniqueViewMut, AllStoragesView};
|
||||||
|
use std::mem::take;
|
||||||
|
|
||||||
|
#[derive(Unique, PartialEq, Eq, Default, Clone, Copy)]
|
||||||
|
pub enum GameState {
|
||||||
|
#[default]
|
||||||
|
Initial,
|
||||||
|
Connecting,
|
||||||
|
LoadingWorld,
|
||||||
|
InGame
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Unique, PartialEq, Eq, Default, Clone, Copy)]
|
||||||
|
pub struct NextState(pub Option<GameState>);
|
||||||
|
|
||||||
|
pub fn init_state(
|
||||||
|
all_storages: AllStoragesView,
|
||||||
|
) {
|
||||||
|
all_storages.add_unique(GameState::default());
|
||||||
|
all_storages.add_unique(NextState::default());
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update_state(
|
||||||
|
mut state: UniqueViewMut<GameState>,
|
||||||
|
mut next: UniqueViewMut<NextState>,
|
||||||
|
) {
|
||||||
|
*state = take(&mut next.0).unwrap_or(*state);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_changing_state(
|
||||||
|
state: UniqueView<NextState>
|
||||||
|
) -> bool {
|
||||||
|
state.0.is_some()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_connecting(
|
||||||
|
state: UniqueView<GameState>
|
||||||
|
) -> bool {
|
||||||
|
*state == GameState::Connecting
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_ingame(
|
||||||
|
state: UniqueView<GameState>
|
||||||
|
) -> bool {
|
||||||
|
*state == GameState::InGame
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_loading(
|
||||||
|
state: UniqueView<GameState>
|
||||||
|
) -> bool {
|
||||||
|
matches!(*state, GameState::LoadingWorld)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_ingame_or_loading(
|
||||||
|
state: UniqueView<GameState>
|
||||||
|
) -> bool {
|
||||||
|
matches!(*state, GameState::InGame | GameState::LoadingWorld)
|
||||||
|
}
|
|
@ -4,25 +4,21 @@ use glam::IVec3;
|
||||||
use hashbrown::HashMap;
|
use hashbrown::HashMap;
|
||||||
use anyhow::{Result, Context};
|
use anyhow::{Result, Context};
|
||||||
|
|
||||||
|
pub use kubi_shared::{worldgen, block::Block};
|
||||||
|
|
||||||
pub mod chunk;
|
pub mod chunk;
|
||||||
pub mod block;
|
|
||||||
pub mod tasks;
|
pub mod tasks;
|
||||||
pub mod loading;
|
pub mod loading;
|
||||||
pub mod mesh;
|
pub mod mesh;
|
||||||
pub mod neighbors;
|
pub mod neighbors;
|
||||||
pub mod worldgen;
|
|
||||||
pub mod raycast;
|
pub mod raycast;
|
||||||
|
pub mod queue;
|
||||||
|
|
||||||
use chunk::{Chunk, ChunkMesh};
|
use chunk::{Chunk, ChunkMesh, CHUNK_SIZE};
|
||||||
use tasks::ChunkTaskManager;
|
use tasks::ChunkTaskManager;
|
||||||
|
use queue::BlockUpdateQueue;
|
||||||
use self::{chunk::CHUNK_SIZE, block::Block};
|
|
||||||
|
|
||||||
//TODO separate world struct for render data
|
|
||||||
// because this is not send-sync
|
|
||||||
|
|
||||||
#[derive(Default, Unique)]
|
#[derive(Default, Unique)]
|
||||||
#[track(Modification)]
|
|
||||||
pub struct ChunkStorage {
|
pub struct ChunkStorage {
|
||||||
pub chunks: HashMap<IVec3, Chunk>
|
pub chunks: HashMap<IVec3, Chunk>
|
||||||
}
|
}
|
||||||
|
@ -109,4 +105,5 @@ pub fn init_game_world(
|
||||||
storages.add_unique_non_send_sync(ChunkMeshStorage::new());
|
storages.add_unique_non_send_sync(ChunkMeshStorage::new());
|
||||||
storages.add_unique(ChunkStorage::new());
|
storages.add_unique(ChunkStorage::new());
|
||||||
storages.add_unique(ChunkTaskManager::new());
|
storages.add_unique(ChunkTaskManager::new());
|
||||||
|
storages.add_unique(BlockUpdateQueue::new());
|
||||||
}
|
}
|
|
@ -1,11 +1,8 @@
|
||||||
use glam::IVec3;
|
use glam::IVec3;
|
||||||
use glium::{VertexBuffer, IndexBuffer};
|
use glium::{VertexBuffer, IndexBuffer};
|
||||||
use super::block::Block;
|
|
||||||
use crate::rendering::world::ChunkVertex;
|
use crate::rendering::world::ChunkVertex;
|
||||||
|
|
||||||
pub const CHUNK_SIZE: usize = 32;
|
pub use kubi_shared::chunk::{CHUNK_SIZE, BlockData};
|
||||||
|
|
||||||
pub type BlockData = Box<[[[Block; CHUNK_SIZE]; CHUNK_SIZE]; CHUNK_SIZE]>;
|
|
||||||
|
|
||||||
pub struct ChunkData {
|
pub struct ChunkData {
|
||||||
pub blocks: BlockData,
|
pub blocks: BlockData,
|
||||||
|
@ -42,6 +39,13 @@ pub enum DesiredChunkState {
|
||||||
Rendered,
|
Rendered,
|
||||||
ToUnload,
|
ToUnload,
|
||||||
}
|
}
|
||||||
|
impl DesiredChunkState {
|
||||||
|
pub fn matches_current(self, current: CurrentChunkState) -> bool {
|
||||||
|
(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 struct Chunk {
|
||||||
pub position: IVec3,
|
pub position: IVec3,
|
||||||
|
@ -49,7 +53,7 @@ pub struct Chunk {
|
||||||
pub mesh_index: Option<usize>,
|
pub mesh_index: Option<usize>,
|
||||||
pub current_state: CurrentChunkState,
|
pub current_state: CurrentChunkState,
|
||||||
pub desired_state: DesiredChunkState,
|
pub desired_state: DesiredChunkState,
|
||||||
pub dirty: bool,
|
pub mesh_dirty: bool,
|
||||||
}
|
}
|
||||||
impl Chunk {
|
impl Chunk {
|
||||||
pub fn new(position: IVec3) -> Self {
|
pub fn new(position: IVec3) -> Self {
|
||||||
|
@ -59,7 +63,7 @@ impl Chunk {
|
||||||
mesh_index: None,
|
mesh_index: None,
|
||||||
current_state: Default::default(),
|
current_state: Default::default(),
|
||||||
desired_state: Default::default(),
|
desired_state: Default::default(),
|
||||||
dirty: false,
|
mesh_dirty: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,20 +1,26 @@
|
||||||
use glam::{IVec3, ivec3};
|
use glam::{IVec3, ivec3};
|
||||||
use glium::{VertexBuffer, IndexBuffer, index::PrimitiveType};
|
use glium::{VertexBuffer, IndexBuffer, index::PrimitiveType};
|
||||||
use shipyard::{View, UniqueView, UniqueViewMut, IntoIter, Workload, IntoWorkload, NonSendSync};
|
use kubi_shared::networking::messages::ClientToServerMessage;
|
||||||
|
use shipyard::{View, UniqueView, UniqueViewMut, IntoIter, Workload, IntoWorkload, NonSendSync, track};
|
||||||
|
use kubi_shared::queue::QueuedBlock;
|
||||||
|
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,
|
||||||
|
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
|
||||||
};
|
};
|
||||||
|
|
||||||
//todo limit task starts insted
|
const MAX_CHUNK_OPS_INGAME: usize = 6;
|
||||||
const MAX_CHUNK_OPS: usize = 8;
|
const MAX_CHUNK_OPS: usize = 32;
|
||||||
|
|
||||||
pub fn update_loaded_world_around_player() -> Workload {
|
pub fn update_loaded_world_around_player() -> Workload {
|
||||||
(
|
(
|
||||||
|
@ -28,11 +34,11 @@ pub fn update_loaded_world_around_player() -> 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>,
|
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 chnk 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
|
||||||
};
|
};
|
||||||
|
@ -116,6 +122,7 @@ fn unload_downgrade_chunks(
|
||||||
|
|
||||||
fn start_required_tasks(
|
fn start_required_tasks(
|
||||||
task_manager: UniqueView<ChunkTaskManager>,
|
task_manager: UniqueView<ChunkTaskManager>,
|
||||||
|
mut udp_client: Option<UniqueViewMut<UdpClient>>,
|
||||||
mut world: UniqueViewMut<ChunkStorage>,
|
mut world: UniqueViewMut<ChunkStorage>,
|
||||||
) {
|
) {
|
||||||
if !world.is_modified() {
|
if !world.is_modified() {
|
||||||
|
@ -128,17 +135,27 @@ fn start_required_tasks(
|
||||||
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
|
||||||
task_manager.spawn_task(ChunkTask::LoadChunk {
|
if let Some(client) = &mut udp_client {
|
||||||
seed: 0xbeef_face_dead_cafe,
|
client.0.send(
|
||||||
position
|
postcard::to_allocvec(&ClientToServerMessage::ChunkSubRequest {
|
||||||
});
|
chunk: position.to_array()
|
||||||
|
}).unwrap().into_boxed_slice(),
|
||||||
|
0,
|
||||||
|
SendMode::Reliable
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
task_manager.spawn_task(ChunkTask::LoadChunk {
|
||||||
|
seed: 0xbeef_face_dead_cafe,
|
||||||
|
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.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
|
||||||
|
@ -150,12 +167,12 @@ fn start_required_tasks(
|
||||||
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.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.dirty = false;
|
chunk.mesh_dirty = false;
|
||||||
// ===========
|
// ===========
|
||||||
//log::trace!("Started generating mesh for chunk {position}");
|
//log::trace!("Started generating mesh for chunk {position}");
|
||||||
}
|
}
|
||||||
|
@ -168,63 +185,80 @@ 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>,
|
||||||
|
mut queue: UniqueViewMut<BlockUpdateQueue>,
|
||||||
) {
|
) {
|
||||||
for _ in 0..MAX_CHUNK_OPS {
|
let mut ops: usize = 0;
|
||||||
if let Some(res) = task_manager.receive() {
|
while let Some(res) = task_manager.receive() {
|
||||||
match res {
|
match res {
|
||||||
ChunkTaskResponse::LoadedChunk { position, chunk_data } => {
|
ChunkTaskResponse::LoadedChunk { position, chunk_data, 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
|
|
||||||
chunk.block_data = Some(ChunkData {
|
|
||||||
blocks: chunk_data
|
|
||||||
});
|
|
||||||
|
|
||||||
//update chunk state
|
|
||||||
chunk.current_state = CurrentChunkState::Loaded;
|
|
||||||
},
|
|
||||||
ChunkTaskResponse::GeneratedMesh { position, vertices, indexes } => {
|
|
||||||
//check if chunk exists
|
|
||||||
let Some(chunk) = world.chunks.get_mut(&position) else {
|
|
||||||
log::warn!("mesh discarded: chunk doesn't exist");
|
|
||||||
return
|
|
||||||
};
|
|
||||||
|
|
||||||
//check if chunk still wants it
|
|
||||||
if chunk.desired_state != DesiredChunkState::Rendered {
|
|
||||||
log::warn!("mesh discarded: state undesirable: {:?}", chunk.desired_state);
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
//apply the mesh
|
|
||||||
let vertex_buffer = VertexBuffer::new(&renderer.display, &vertices).unwrap();
|
|
||||||
let index_buffer = IndexBuffer::new(&renderer.display, PrimitiveType::TrianglesList, &indexes).unwrap();
|
|
||||||
let mesh = ChunkMesh {
|
|
||||||
vertex_buffer,
|
|
||||||
index_buffer,
|
|
||||||
};
|
|
||||||
if let Some(index) = chunk.mesh_index {
|
|
||||||
meshes.update(index, mesh).expect("Mesh update failed");
|
|
||||||
} else {
|
|
||||||
let mesh_index = meshes.insert(mesh);
|
|
||||||
chunk.mesh_index = Some(mesh_index);
|
|
||||||
}
|
|
||||||
|
|
||||||
//update chunk state
|
|
||||||
chunk.current_state = CurrentChunkState::Rendered;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//set the block data
|
||||||
|
chunk.block_data = Some(ChunkData {
|
||||||
|
blocks: chunk_data
|
||||||
|
});
|
||||||
|
|
||||||
|
//update chunk state
|
||||||
|
chunk.current_state = CurrentChunkState::Loaded;
|
||||||
|
|
||||||
|
//push queued blocks
|
||||||
|
//TODO use extend
|
||||||
|
for item in queued {
|
||||||
|
queue.push(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
//increase ops counter
|
||||||
|
ops += 1;
|
||||||
|
},
|
||||||
|
ChunkTaskResponse::GeneratedMesh { position, vertices, indexes } => {
|
||||||
|
//check if chunk exists
|
||||||
|
let Some(chunk) = world.chunks.get_mut(&position) else {
|
||||||
|
log::warn!("mesh discarded: chunk doesn't exist");
|
||||||
|
return
|
||||||
|
};
|
||||||
|
|
||||||
|
//check if chunk still wants it
|
||||||
|
if chunk.desired_state != DesiredChunkState::Rendered {
|
||||||
|
log::warn!("mesh discarded: state undesirable: {:?}", chunk.desired_state);
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
//apply the mesh
|
||||||
|
let vertex_buffer = VertexBuffer::new(&renderer.display, &vertices).unwrap();
|
||||||
|
let index_buffer = IndexBuffer::new(&renderer.display, PrimitiveType::TrianglesList, &indexes).unwrap();
|
||||||
|
let mesh = ChunkMesh {
|
||||||
|
vertex_buffer,
|
||||||
|
index_buffer,
|
||||||
|
};
|
||||||
|
if let Some(index) = chunk.mesh_index {
|
||||||
|
meshes.update(index, mesh).expect("Mesh update failed");
|
||||||
|
} else {
|
||||||
|
let mesh_index = meshes.insert(mesh);
|
||||||
|
chunk.mesh_index = Some(mesh_index);
|
||||||
|
}
|
||||||
|
|
||||||
|
//update chunk state
|
||||||
|
chunk.current_state = CurrentChunkState::Rendered;
|
||||||
|
|
||||||
|
//increase ops counter
|
||||||
|
ops += 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if ops >= match *state {
|
||||||
|
GameState::InGame => MAX_CHUNK_OPS_INGAME,
|
||||||
|
_ => MAX_CHUNK_OPS,
|
||||||
|
} { break }
|
||||||
}
|
}
|
||||||
}
|
}
|
92
kubi/src/world/mesh.rs
Normal file
92
kubi/src/world/mesh.rs
Normal file
|
@ -0,0 +1,92 @@
|
||||||
|
use glam::{IVec3, ivec3};
|
||||||
|
use strum::IntoEnumIterator;
|
||||||
|
use kubi_shared::block::{Block, RenderType};
|
||||||
|
use crate::world::chunk::CHUNK_SIZE;
|
||||||
|
use crate::rendering::world::ChunkVertex;
|
||||||
|
|
||||||
|
pub mod data;
|
||||||
|
mod builder;
|
||||||
|
|
||||||
|
use data::MeshGenData;
|
||||||
|
use builder::{MeshBuilder, CubeFace, DiagonalFace};
|
||||||
|
|
||||||
|
pub fn generate_mesh(data: MeshGenData) -> (Vec<ChunkVertex>, Vec<u32>) {
|
||||||
|
let get_block = |pos: IVec3| -> Block {
|
||||||
|
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]
|
||||||
|
} else if pos.x >= CHUNK_SIZE as i32 {
|
||||||
|
data.block_data_pos_x[pos.x as usize - CHUNK_SIZE][pos.y as usize][pos.z as usize]
|
||||||
|
} else if pos.y < 0 {
|
||||||
|
data.block_data_neg_y[pos.x as usize][(CHUNK_SIZE as i32 + pos.y) as usize][pos.z as usize]
|
||||||
|
} else if pos.y >= CHUNK_SIZE as i32 {
|
||||||
|
data.block_data_pos_y[pos.x as usize][pos.y as usize - CHUNK_SIZE][pos.z as usize]
|
||||||
|
} else if pos.z < 0 {
|
||||||
|
data.block_data_neg_z[pos.x as usize][pos.y as usize][(CHUNK_SIZE as i32 + pos.z) as usize]
|
||||||
|
} else if pos.z >= CHUNK_SIZE as i32 {
|
||||||
|
data.block_data_pos_z[pos.x as usize][pos.y as usize][pos.z as usize - CHUNK_SIZE]
|
||||||
|
} else {
|
||||||
|
data.block_data[pos.x as usize][pos.y as usize][pos.z as usize]
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut builder = MeshBuilder::new();
|
||||||
|
|
||||||
|
for x in 0..CHUNK_SIZE as i32 {
|
||||||
|
for y in 0..CHUNK_SIZE as i32 {
|
||||||
|
for z in 0..CHUNK_SIZE as i32 {
|
||||||
|
let coord = ivec3(x, y, z);
|
||||||
|
let block = get_block(coord);
|
||||||
|
let descriptor = block.descriptor();
|
||||||
|
match descriptor.render {
|
||||||
|
RenderType::None => continue,
|
||||||
|
RenderType::SolidBlock(textures) | RenderType::BinaryTransparency(textures) => {
|
||||||
|
for face in CubeFace::iter() {
|
||||||
|
let facing_direction = face.normal();
|
||||||
|
let facing_coord = coord + facing_direction;
|
||||||
|
let facing_block = get_block(facing_coord);
|
||||||
|
let facing_descriptor = facing_block.descriptor();
|
||||||
|
let face_obstructed = match descriptor.render {
|
||||||
|
RenderType::SolidBlock(_) => matches!(facing_descriptor.render, RenderType::SolidBlock(_)),
|
||||||
|
RenderType::BinaryTransparency(_) => {
|
||||||
|
match facing_descriptor.render {
|
||||||
|
RenderType::SolidBlock(_) => true,
|
||||||
|
RenderType::BinaryTransparency(_) => block == facing_block,
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
_ => unreachable!(),
|
||||||
|
};
|
||||||
|
if !face_obstructed {
|
||||||
|
let face_texture = match face {
|
||||||
|
CubeFace::Top => textures.top,
|
||||||
|
CubeFace::Front => textures.front,
|
||||||
|
CubeFace::Left => textures.left,
|
||||||
|
CubeFace::Right => textures.right,
|
||||||
|
CubeFace::Back => textures.back,
|
||||||
|
CubeFace::Bottom => textures.bottom,
|
||||||
|
};
|
||||||
|
builder.add_face(face, coord, face_texture as u8);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
RenderType::CrossShape(textures) => {
|
||||||
|
builder.add_diagonal_face(
|
||||||
|
coord,
|
||||||
|
DiagonalFace::LeftZ,
|
||||||
|
textures.0.front as u8,
|
||||||
|
textures.0.back as u8
|
||||||
|
);
|
||||||
|
builder.add_diagonal_face(
|
||||||
|
coord,
|
||||||
|
DiagonalFace::RigthZ,
|
||||||
|
textures.1.front as u8,
|
||||||
|
textures.1.back as u8
|
||||||
|
);
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
builder.finish()
|
||||||
|
}
|
177
kubi/src/world/mesh/builder.rs
Normal file
177
kubi/src/world/mesh/builder.rs
Normal file
|
@ -0,0 +1,177 @@
|
||||||
|
use strum::EnumIter;
|
||||||
|
use glam::{Vec3, vec3, IVec3, ivec3};
|
||||||
|
use crate::rendering::world::ChunkVertex;
|
||||||
|
|
||||||
|
const INV_SQRT_2: f32 = 0.70710678118655; // 1 / 2.sqrt()
|
||||||
|
|
||||||
|
#[repr(usize)]
|
||||||
|
#[derive(Clone, Copy, Debug, EnumIter)]
|
||||||
|
pub enum CubeFace {
|
||||||
|
Top = 0,
|
||||||
|
Front = 4,
|
||||||
|
Left = 2,
|
||||||
|
Right = 3,
|
||||||
|
Back = 1,
|
||||||
|
Bottom = 5,
|
||||||
|
}
|
||||||
|
impl CubeFace {
|
||||||
|
pub const fn normal(self) -> IVec3 {
|
||||||
|
CUBE_FACE_NORMALS_IVEC3[self as usize]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const CUBE_FACE_VERTICES: [[Vec3; 4]; 6] = [
|
||||||
|
[vec3(0., 1., 0.), vec3(0., 1., 1.), vec3(1., 1., 0.), vec3(1., 1., 1.)],
|
||||||
|
[vec3(0., 0., 0.), vec3(0., 1., 0.), vec3(1., 0., 0.), vec3(1., 1., 0.)],
|
||||||
|
[vec3(0., 0., 1.), vec3(0., 1., 1.), vec3(0., 0., 0.), vec3(0., 1., 0.)],
|
||||||
|
[vec3(1., 0., 0.), vec3(1., 1., 0.), vec3(1., 0., 1.), vec3(1., 1., 1.)],
|
||||||
|
[vec3(1., 0., 1.), vec3(1., 1., 1.), vec3(0., 0., 1.), vec3(0., 1., 1.)],
|
||||||
|
[vec3(0., 0., 1.), vec3(0., 0., 0.), vec3(1., 0., 1.), vec3(1., 0., 0.)],
|
||||||
|
];
|
||||||
|
const CUBE_FACE_NORMALS_IVEC3: [IVec3; 6] = [
|
||||||
|
ivec3( 0, 1, 0),
|
||||||
|
ivec3( 0, 0, -1),
|
||||||
|
ivec3(-1, 0, 0),
|
||||||
|
ivec3( 1, 0, 0),
|
||||||
|
ivec3( 0, 0, 1),
|
||||||
|
ivec3( 0, -1, 0)
|
||||||
|
];
|
||||||
|
const CUBE_FACE_NORMALS: [Vec3; 6] = [
|
||||||
|
vec3(0., 1., 0.),
|
||||||
|
vec3(0., 0., -1.),
|
||||||
|
vec3(-1.,0., 0.),
|
||||||
|
vec3(1., 0., 0.),
|
||||||
|
vec3(0., 0., 1.),
|
||||||
|
vec3(0., -1.,0.)
|
||||||
|
];
|
||||||
|
const CUBE_FACE_INDICES: [u32; 6] = [0, 1, 2, 2, 1, 3];
|
||||||
|
|
||||||
|
#[repr(usize)]
|
||||||
|
pub enum DiagonalFace {
|
||||||
|
RigthZ = 0,
|
||||||
|
LeftZ = 1,
|
||||||
|
}
|
||||||
|
const CROSS_FACES: [[Vec3; 4]; 2] = [
|
||||||
|
[
|
||||||
|
vec3(0., 0., 0.),
|
||||||
|
vec3(0., 1., 0.),
|
||||||
|
vec3(1., 0., 1.),
|
||||||
|
vec3(1., 1., 1.),
|
||||||
|
],
|
||||||
|
[
|
||||||
|
vec3(0., 0., 1.),
|
||||||
|
vec3(0., 1., 1.),
|
||||||
|
vec3(1., 0., 0.),
|
||||||
|
vec3(1., 1., 0.),
|
||||||
|
]
|
||||||
|
];
|
||||||
|
const CROSS_FACE_NORMALS: [Vec3; 2] = [
|
||||||
|
vec3(-INV_SQRT_2, 0., INV_SQRT_2),
|
||||||
|
vec3(INV_SQRT_2, 0., INV_SQRT_2),
|
||||||
|
];
|
||||||
|
const CROSS_FACE_NORMALS_BACK: [Vec3; 2] = [
|
||||||
|
vec3(INV_SQRT_2, 0., -INV_SQRT_2),
|
||||||
|
vec3(-INV_SQRT_2, 0., -INV_SQRT_2),
|
||||||
|
];
|
||||||
|
const CROSS_FACE_INDICES: [u32; 12] = [
|
||||||
|
0, 1, 2, 2, 1, 3, //Front side
|
||||||
|
6, 5, 4, 7, 5, 6, //Back side
|
||||||
|
];
|
||||||
|
|
||||||
|
|
||||||
|
const UV_COORDS: [[f32; 2]; 4] = [
|
||||||
|
[0., 0.],
|
||||||
|
[0., 1.],
|
||||||
|
[1., 0.],
|
||||||
|
[1., 1.],
|
||||||
|
];
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct MeshBuilder {
|
||||||
|
vertex_buffer: Vec<ChunkVertex>,
|
||||||
|
index_buffer: Vec<u32>,
|
||||||
|
idx_counter: u32,
|
||||||
|
}
|
||||||
|
impl MeshBuilder {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self::default()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_face(&mut self, face: CubeFace, coord: IVec3, texture: u8) {
|
||||||
|
let coord = coord.as_vec3();
|
||||||
|
let face_index = face as usize;
|
||||||
|
|
||||||
|
//Push vertices
|
||||||
|
let norm = CUBE_FACE_NORMALS[face_index];
|
||||||
|
let vert = CUBE_FACE_VERTICES[face_index];
|
||||||
|
self.vertex_buffer.reserve(4);
|
||||||
|
for i in 0..4 {
|
||||||
|
self.vertex_buffer.push(ChunkVertex {
|
||||||
|
position: (coord + vert[i]).to_array(),
|
||||||
|
normal: norm.to_array(),
|
||||||
|
uv: UV_COORDS[i],
|
||||||
|
tex_index: texture
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
//Push indices
|
||||||
|
self.index_buffer.extend_from_slice(&CUBE_FACE_INDICES.map(|x| x + self.idx_counter));
|
||||||
|
|
||||||
|
//Increment idx counter
|
||||||
|
self.idx_counter += 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_diagonal_face(&mut self, coord: IVec3, face_type: DiagonalFace, front_texture: u8, back_texture: u8) {
|
||||||
|
//Push vertices
|
||||||
|
let face_type = face_type as usize;
|
||||||
|
let vertices = CROSS_FACES[face_type];
|
||||||
|
let normal_front = CROSS_FACE_NORMALS[face_type].to_array();
|
||||||
|
let normal_back = CROSS_FACE_NORMALS_BACK[face_type].to_array();
|
||||||
|
self.vertex_buffer.reserve(8);
|
||||||
|
for i in 0..4 { //push front vertices
|
||||||
|
self.vertex_buffer.push(ChunkVertex {
|
||||||
|
position: (coord.as_vec3() + vertices[i]).to_array(),
|
||||||
|
normal: normal_front,
|
||||||
|
uv: UV_COORDS[i],
|
||||||
|
tex_index: front_texture
|
||||||
|
})
|
||||||
|
}
|
||||||
|
for i in 0..4 { //push back vertices
|
||||||
|
self.vertex_buffer.push(ChunkVertex {
|
||||||
|
position: (coord.as_vec3() + vertices[i]).to_array(),
|
||||||
|
normal: normal_back,
|
||||||
|
uv: UV_COORDS[i],
|
||||||
|
tex_index: back_texture
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
//Push indices
|
||||||
|
self.index_buffer.extend_from_slice(&CROSS_FACE_INDICES.map(|x| x + self.idx_counter));
|
||||||
|
|
||||||
|
//Increment idx counter
|
||||||
|
self.idx_counter += 8;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_model(&mut self, position: Vec3, vertices: &[ChunkVertex], indices: Option<&[u32]>) {
|
||||||
|
//push vertices
|
||||||
|
self.vertex_buffer.extend(vertices.iter().map(|vertex| {
|
||||||
|
let mut vertex = *vertex;
|
||||||
|
vertex.position[0] += position.x;
|
||||||
|
vertex.position[0] += position.y;
|
||||||
|
vertex.position[0] += position.z;
|
||||||
|
vertex
|
||||||
|
}));
|
||||||
|
//push indices
|
||||||
|
if let Some(indices) = indices {
|
||||||
|
self.index_buffer.extend(indices.iter().map(|x| x + self.idx_counter));
|
||||||
|
} else {
|
||||||
|
self.index_buffer.extend(0..(self.vertex_buffer.len() as u32));
|
||||||
|
}
|
||||||
|
//increment idx counter
|
||||||
|
self.idx_counter += vertices.len() as u32;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn finish(self) -> (Vec<ChunkVertex>, Vec<u32>) {
|
||||||
|
(self.vertex_buffer, self.index_buffer)
|
||||||
|
}
|
||||||
|
}
|
57
kubi/src/world/queue.rs
Normal file
57
kubi/src/world/queue.rs
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
use glam::{IVec3, ivec3};
|
||||||
|
use kubi_shared::{block::Block, chunk::CHUNK_SIZE, queue::QueuedBlock};
|
||||||
|
use shipyard::{UniqueViewMut, Unique};
|
||||||
|
use super::ChunkStorage;
|
||||||
|
|
||||||
|
#[derive(Unique, Default, Clone)]
|
||||||
|
pub struct BlockUpdateQueue {
|
||||||
|
queue: Vec<QueuedBlock>
|
||||||
|
}
|
||||||
|
impl BlockUpdateQueue {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self::default()
|
||||||
|
}
|
||||||
|
pub fn push(&mut self, event: QueuedBlock) {
|
||||||
|
self.queue.push(event)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn apply_queued_blocks(
|
||||||
|
mut queue: UniqueViewMut<BlockUpdateQueue>,
|
||||||
|
mut world: UniqueViewMut<ChunkStorage>
|
||||||
|
) {
|
||||||
|
//maybe i need to check for desired/current state here before marking as dirty?
|
||||||
|
queue.queue.retain(|&event| {
|
||||||
|
if let Some(block) = world.get_block_mut(event.position) {
|
||||||
|
if event.soft && *block != Block::Air {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
*block = event.block_type;
|
||||||
|
//mark chunk as dirty
|
||||||
|
let (chunk_pos, block_pos) = ChunkStorage::to_chunk_coords(event.position);
|
||||||
|
let chunk = world.chunks.get_mut(&chunk_pos).expect("This error should never happen, if it does then something is super fucked up and the whole project needs to be burnt down.");
|
||||||
|
chunk.mesh_dirty = true;
|
||||||
|
//If block pos is close to the border, some neighbors may be dirty!
|
||||||
|
const DIRECTIONS: [IVec3; 6] = [
|
||||||
|
ivec3(1, 0, 0),
|
||||||
|
ivec3(-1, 0, 0),
|
||||||
|
ivec3(0, 1, 0),
|
||||||
|
ivec3(0, -1, 0),
|
||||||
|
ivec3(0, 0, 1),
|
||||||
|
ivec3(0, 0, -1),
|
||||||
|
];
|
||||||
|
for direction in DIRECTIONS {
|
||||||
|
let outside_chunk = |x| !(0..CHUNK_SIZE as i32).contains(x);
|
||||||
|
let chunk_dirty = (block_pos + direction).to_array().iter().any(outside_chunk);
|
||||||
|
if chunk_dirty {
|
||||||
|
let dir_chunk_pos = chunk_pos + direction;
|
||||||
|
if let Some(dir_chunk) = world.chunks.get_mut(&dir_chunk_pos) {
|
||||||
|
dir_chunk.mesh_dirty = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
true
|
||||||
|
});
|
||||||
|
}
|
|
@ -1,10 +1,10 @@
|
||||||
use glam::{Vec3, IVec3};
|
use glam::{Vec3, IVec3};
|
||||||
use shipyard::{View, Component, ViewMut, IntoIter, UniqueView};
|
use shipyard::{View, Component, ViewMut, IntoIter, UniqueView, track};
|
||||||
|
use kubi_shared::block::Block;
|
||||||
use crate::transform::Transform;
|
use crate::transform::Transform;
|
||||||
|
use super::ChunkStorage;
|
||||||
|
|
||||||
use super::{ChunkStorage, block::Block};
|
pub const RAYCAST_STEP: f32 = 0.25;
|
||||||
|
|
||||||
const RAYCAST_STEP: f32 = 0.25;
|
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug)]
|
#[derive(Clone, Copy, Debug)]
|
||||||
pub struct RaycastReport {
|
pub struct RaycastReport {
|
||||||
|
@ -49,7 +49,7 @@ impl ChunkStorage {
|
||||||
pub struct LookingAtBlock(pub Option<RaycastReport>);
|
pub struct LookingAtBlock(pub Option<RaycastReport>);
|
||||||
|
|
||||||
pub fn update_raycasts(
|
pub fn update_raycasts(
|
||||||
transform: View<Transform>,
|
transform: View<Transform, { track::All }>,
|
||||||
mut raycast: ViewMut<LookingAtBlock>,
|
mut raycast: ViewMut<LookingAtBlock>,
|
||||||
world: UniqueView<ChunkStorage>,
|
world: UniqueView<ChunkStorage>,
|
||||||
) {
|
) {
|
||||||
|
@ -57,9 +57,9 @@ pub fn update_raycasts(
|
||||||
if !(world.is_inserted_or_modified() || (transform.inserted_or_modified(), &raycast).iter().next().is_some()) {
|
if !(world.is_inserted_or_modified() || (transform.inserted_or_modified(), &raycast).iter().next().is_some()) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
for (transform, report) in (&transform, &mut raycast).iter() {
|
for (transform, mut report) in (&transform, &mut raycast).iter() {
|
||||||
let (_, rotation, position) = transform.0.to_scale_rotation_translation();
|
let (_, rotation, position) = transform.0.to_scale_rotation_translation();
|
||||||
let direction = (rotation * Vec3::NEG_Z).normalize();
|
let direction = (rotation.normalize() * Vec3::NEG_Z).normalize();
|
||||||
*report = LookingAtBlock(world.raycast(position, direction, Some(30.)));
|
*report = LookingAtBlock(world.raycast(position, direction, Some(30.)));
|
||||||
}
|
}
|
||||||
}
|
}
|
106
kubi/src/world/tasks.rs
Normal file
106
kubi/src/world/tasks.rs
Normal file
|
@ -0,0 +1,106 @@
|
||||||
|
use flume::{Sender, Receiver};
|
||||||
|
use glam::IVec3;
|
||||||
|
use kubi_shared::{
|
||||||
|
networking::messages::{S_CHUNK_RESPONSE, ServerToClientMessage},
|
||||||
|
queue::QueuedBlock
|
||||||
|
};
|
||||||
|
use shipyard::{Unique, UniqueView, View, IntoIter};
|
||||||
|
use rayon::{ThreadPool, ThreadPoolBuilder};
|
||||||
|
use uflow::client::Event as ClientEvent;
|
||||||
|
use super::{
|
||||||
|
chunk::BlockData,
|
||||||
|
mesh::{generate_mesh, data::MeshGenData},
|
||||||
|
worldgen::generate_world,
|
||||||
|
};
|
||||||
|
use crate::{
|
||||||
|
rendering::world::ChunkVertex,
|
||||||
|
networking::NetworkEvent,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub enum ChunkTask {
|
||||||
|
LoadChunk {
|
||||||
|
seed: u64,
|
||||||
|
position: IVec3
|
||||||
|
},
|
||||||
|
GenerateMesh {
|
||||||
|
position: IVec3,
|
||||||
|
data: MeshGenData
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub enum ChunkTaskResponse {
|
||||||
|
LoadedChunk {
|
||||||
|
position: IVec3,
|
||||||
|
chunk_data: BlockData,
|
||||||
|
queued: Vec<QueuedBlock>
|
||||||
|
},
|
||||||
|
GeneratedMesh {
|
||||||
|
position: IVec3,
|
||||||
|
vertices: Vec<ChunkVertex>,
|
||||||
|
indexes: Vec<u32>
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Unique)]
|
||||||
|
pub struct ChunkTaskManager {
|
||||||
|
channel: (Sender<ChunkTaskResponse>, Receiver<ChunkTaskResponse>),
|
||||||
|
pool: ThreadPool,
|
||||||
|
}
|
||||||
|
impl ChunkTaskManager {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
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
|
||||||
|
self.channel.0.send(response).unwrap()
|
||||||
|
}
|
||||||
|
pub fn spawn_task(&self, task: ChunkTask) {
|
||||||
|
let sender = self.channel.0.clone();
|
||||||
|
self.pool.spawn(move || {
|
||||||
|
let _ = sender.send(match task {
|
||||||
|
ChunkTask::GenerateMesh { position, data } => {
|
||||||
|
let (vertices, indexes) = generate_mesh(data);
|
||||||
|
ChunkTaskResponse::GeneratedMesh { position, vertices, indexes }
|
||||||
|
},
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//TODO get rid of this, this is awfulll
|
||||||
|
pub fn inject_network_responses_into_manager_queue(
|
||||||
|
manager: UniqueView<ChunkTaskManager>,
|
||||||
|
events: View<NetworkEvent>
|
||||||
|
) {
|
||||||
|
for event in events.iter() {
|
||||||
|
if event.is_message_of_type::<S_CHUNK_RESPONSE>() {
|
||||||
|
let NetworkEvent(ClientEvent::Receive(data)) = &event else { unreachable!() };
|
||||||
|
let ServerToClientMessage::ChunkResponse {
|
||||||
|
chunk, data, queued
|
||||||
|
} = postcard::from_bytes(data).expect("Chunk decode failed") else { unreachable!() };
|
||||||
|
manager.add_sussy_response(ChunkTaskResponse::LoadedChunk {
|
||||||
|
position: IVec3::from_array(chunk),
|
||||||
|
chunk_data: data,
|
||||||
|
queued
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// if let ClientEvent::MessageReceived(ServerToClientMessage::ChunkResponse { &chunk, data, queued }) = &event.0 {
|
||||||
|
// let position = IVec3::from_array(chunk);
|
||||||
|
// manager.add_sussy_response(ChunkTaskResponse::LoadedChunk {
|
||||||
|
// position,
|
||||||
|
// chunk_data: data.clone(),
|
||||||
|
// queued
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,9 +0,0 @@
|
||||||
#version 150 core
|
|
||||||
|
|
||||||
out vec4 color;
|
|
||||||
uniform vec4 u_color;
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
color = u_color;
|
|
||||||
color -= vec4(0, 0, 0, 0.1 * sin(gl_FragCoord.x) * cos(gl_FragCoord.y));
|
|
||||||
}
|
|
|
@ -1,48 +0,0 @@
|
||||||
use shipyard::{UniqueViewMut, UniqueView, View, IntoIter, ViewMut, EntitiesViewMut};
|
|
||||||
use crate::{
|
|
||||||
player::MainPlayer,
|
|
||||||
world::{raycast::LookingAtBlock, ChunkStorage, block::Block},
|
|
||||||
input::{Inputs, PrevInputs},
|
|
||||||
events::{EventComponent, player_actions::PlayerActionEvent},
|
|
||||||
};
|
|
||||||
|
|
||||||
pub fn block_placement_system(
|
|
||||||
main_player: View<MainPlayer>,
|
|
||||||
raycast: View<LookingAtBlock>,
|
|
||||||
input: UniqueView<Inputs>,
|
|
||||||
prev_input: UniqueView<PrevInputs>,
|
|
||||||
mut world: UniqueViewMut<ChunkStorage>,
|
|
||||||
mut entities: EntitiesViewMut,
|
|
||||||
mut events: ViewMut<EventComponent>,
|
|
||||||
mut player_events: ViewMut<PlayerActionEvent>,
|
|
||||||
) {
|
|
||||||
let action_place = input.action_b && !prev_input.0.action_b;
|
|
||||||
let action_break = input.action_a && !prev_input.0.action_a;
|
|
||||||
if action_place ^ action_break {
|
|
||||||
//get raycast info
|
|
||||||
let Some(ray) = (&main_player, &raycast).iter().next().unwrap().1/**/.0 else { return };
|
|
||||||
//update block
|
|
||||||
let (place_position, place_block) = if action_place {
|
|
||||||
let position = (ray.position - ray.direction * 0.5).floor().as_ivec3();
|
|
||||||
let Some(block) = world.get_block_mut(position) else { return };
|
|
||||||
*block = Block::Dirt;
|
|
||||||
(position, *block)
|
|
||||||
} else {
|
|
||||||
let Some(block) = world.get_block_mut(ray.block_position) else { return };
|
|
||||||
*block = Block::Air;
|
|
||||||
(ray.block_position, *block)
|
|
||||||
};
|
|
||||||
//mark chunk as dirty
|
|
||||||
let (chunk_pos, _) = ChunkStorage::to_chunk_coords(place_position);
|
|
||||||
let chunk = world.chunks.get_mut(&chunk_pos).unwrap();
|
|
||||||
chunk.dirty = true;
|
|
||||||
//send event
|
|
||||||
entities.add_entity(
|
|
||||||
(&mut events, &mut player_events),
|
|
||||||
(EventComponent, PlayerActionEvent::UpdatedBlock {
|
|
||||||
position: place_position,
|
|
||||||
block: place_block,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,42 +0,0 @@
|
||||||
use glam::{Vec3, Mat4};
|
|
||||||
use shipyard::{ViewMut, View, IntoIter, Workload, IntoWorkload};
|
|
||||||
use crate::{transform::Transform, events::WindowResizedEvent};
|
|
||||||
use super::Camera;
|
|
||||||
|
|
||||||
//maybe parallelize these two?
|
|
||||||
|
|
||||||
fn update_view_matrix(
|
|
||||||
mut vm_camera: ViewMut<Camera>,
|
|
||||||
v_transform: View<Transform>
|
|
||||||
) {
|
|
||||||
for (camera, transform) in (&mut vm_camera, v_transform.inserted_or_modified()).iter() {
|
|
||||||
let (_, rotation, translation) = transform.0.to_scale_rotation_translation();
|
|
||||||
let direction = rotation * Vec3::NEG_Z;
|
|
||||||
camera.view_matrix = Mat4::look_to_rh(translation, direction, camera.up);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn update_perspective_matrix(
|
|
||||||
mut vm_camera: ViewMut<Camera>,
|
|
||||||
resize: View<WindowResizedEvent>,
|
|
||||||
) {
|
|
||||||
//TODO update on launch
|
|
||||||
let Some(&size) = resize.iter().next() else {
|
|
||||||
return
|
|
||||||
};
|
|
||||||
for camera in (&mut vm_camera).iter() {
|
|
||||||
camera.perspective_matrix = Mat4::perspective_rh_gl(
|
|
||||||
camera.fov,
|
|
||||||
size.0.x as f32 / size.0.y as f32,
|
|
||||||
camera.z_near,
|
|
||||||
camera.z_far,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn update_matrices() -> Workload {
|
|
||||||
(
|
|
||||||
update_view_matrix,
|
|
||||||
update_perspective_matrix,
|
|
||||||
).into_workload()
|
|
||||||
}
|
|
84
src/input.rs
84
src/input.rs
|
@ -1,84 +0,0 @@
|
||||||
use glam::{Vec2, DVec2};
|
|
||||||
use glium::glutin::event::{DeviceEvent, VirtualKeyCode, ElementState};
|
|
||||||
use hashbrown::HashSet;
|
|
||||||
use nohash_hasher::BuildNoHashHasher;
|
|
||||||
use shipyard::{AllStoragesView, Unique, View, IntoIter, UniqueViewMut, Workload, IntoWorkload, UniqueView};
|
|
||||||
use crate::events::InputDeviceEvent;
|
|
||||||
|
|
||||||
#[derive(Unique, Clone, Copy, Default, Debug)]
|
|
||||||
pub struct Inputs {
|
|
||||||
pub movement: Vec2,
|
|
||||||
pub look: Vec2,
|
|
||||||
pub action_a: bool,
|
|
||||||
pub action_b: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Unique, Clone, Copy, Default, Debug)]
|
|
||||||
pub struct PrevInputs(pub Inputs);
|
|
||||||
|
|
||||||
#[derive(Unique, Clone, Default, Debug)]
|
|
||||||
pub struct RawInputState {
|
|
||||||
pub keyboard_state: HashSet<VirtualKeyCode, BuildNoHashHasher<u32>>,
|
|
||||||
pub button_state: [bool; 32],
|
|
||||||
pub mouse_delta: DVec2
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn process_events(
|
|
||||||
device_events: View<InputDeviceEvent>,
|
|
||||||
mut input_state: UniqueViewMut<RawInputState>,
|
|
||||||
) {
|
|
||||||
input_state.mouse_delta = DVec2::ZERO;
|
|
||||||
for event in device_events.iter() {
|
|
||||||
match event.event {
|
|
||||||
DeviceEvent::MouseMotion { delta } => {
|
|
||||||
input_state.mouse_delta = DVec2::from(delta);
|
|
||||||
},
|
|
||||||
DeviceEvent::Key(input) => {
|
|
||||||
if let Some(keycode) = input.virtual_keycode {
|
|
||||||
match input.state {
|
|
||||||
ElementState::Pressed => input_state.keyboard_state.insert(keycode),
|
|
||||||
ElementState::Released => input_state.keyboard_state.remove(&keycode),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
},
|
|
||||||
DeviceEvent::Button { button, state } => {
|
|
||||||
if button < 32 {
|
|
||||||
input_state.button_state[button as usize] = matches!(state, ElementState::Pressed);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
_ => ()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn update_input_states (
|
|
||||||
raw_inputs: UniqueView<RawInputState>,
|
|
||||||
mut inputs: UniqueViewMut<Inputs>,
|
|
||||||
mut prev_inputs: UniqueViewMut<PrevInputs>,
|
|
||||||
) {
|
|
||||||
prev_inputs.0 = *inputs;
|
|
||||||
inputs.movement = Vec2::new(
|
|
||||||
raw_inputs.keyboard_state.contains(&VirtualKeyCode::D) as u32 as f32 -
|
|
||||||
raw_inputs.keyboard_state.contains(&VirtualKeyCode::A) as u32 as f32,
|
|
||||||
raw_inputs.keyboard_state.contains(&VirtualKeyCode::W) as u32 as f32 -
|
|
||||||
raw_inputs.keyboard_state.contains(&VirtualKeyCode::S) as u32 as f32
|
|
||||||
).normalize_or_zero();
|
|
||||||
inputs.look = raw_inputs.mouse_delta.as_vec2();
|
|
||||||
inputs.action_a = raw_inputs.button_state[1];
|
|
||||||
inputs.action_b = raw_inputs.button_state[3];
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn init_input (
|
|
||||||
storages: AllStoragesView
|
|
||||||
) {
|
|
||||||
storages.add_unique(Inputs::default());
|
|
||||||
storages.add_unique(PrevInputs::default());
|
|
||||||
storages.add_unique(RawInputState::default());
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn process_inputs() -> Workload {
|
|
||||||
(
|
|
||||||
process_events,
|
|
||||||
update_input_states
|
|
||||||
).into_workload()
|
|
||||||
}
|
|
|
@ -1,46 +0,0 @@
|
||||||
use shipyard::{Unique, NonSendSync, UniqueView, UniqueViewMut};
|
|
||||||
use glium::{
|
|
||||||
Display, Surface,
|
|
||||||
glutin::{
|
|
||||||
event_loop::EventLoop,
|
|
||||||
window::WindowBuilder,
|
|
||||||
ContextBuilder, GlProfile
|
|
||||||
},
|
|
||||||
};
|
|
||||||
use glam::Vec3;
|
|
||||||
|
|
||||||
pub mod primitives;
|
|
||||||
pub mod world;
|
|
||||||
pub mod selection_box;
|
|
||||||
|
|
||||||
#[derive(Unique)]
|
|
||||||
pub struct RenderTarget(pub glium::Frame);
|
|
||||||
|
|
||||||
#[derive(Unique)]
|
|
||||||
pub struct BackgroundColor(pub Vec3);
|
|
||||||
|
|
||||||
#[derive(Unique)]
|
|
||||||
pub struct Renderer {
|
|
||||||
pub display: Display
|
|
||||||
}
|
|
||||||
impl Renderer {
|
|
||||||
pub fn init(event_loop: &EventLoop<()>) -> Self {
|
|
||||||
log::info!("initializing display");
|
|
||||||
let wb = WindowBuilder::new()
|
|
||||||
.with_title("uwu")
|
|
||||||
.with_maximized(true);
|
|
||||||
let cb = ContextBuilder::new()
|
|
||||||
.with_depth_buffer(24)
|
|
||||||
.with_gl_profile(GlProfile::Core);
|
|
||||||
let display = Display::new(wb, cb, event_loop)
|
|
||||||
.expect("Failed to create a glium Display");
|
|
||||||
Self { display }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn clear_background(
|
|
||||||
mut target: NonSendSync<UniqueViewMut<RenderTarget>>,
|
|
||||||
color: UniqueView<BackgroundColor>,
|
|
||||||
) {
|
|
||||||
target.0.clear_color_srgb_and_depth((color.0.x, color.0.y, color.0.z, 1.), 1.);
|
|
||||||
}
|
|
|
@ -1,80 +0,0 @@
|
||||||
use glium::{implement_vertex, VertexBuffer, IndexBuffer, index::PrimitiveType};
|
|
||||||
use shipyard::{NonSendSync, UniqueView, AllStoragesView, Unique};
|
|
||||||
use super::Renderer;
|
|
||||||
|
|
||||||
pub const CUBE_VERTICES: &[f32] = &[
|
|
||||||
// front
|
|
||||||
0.0, 0.0, 1.0,
|
|
||||||
1.0, 0.0, 1.0,
|
|
||||||
1.0, 1.0, 1.0,
|
|
||||||
0.0, 1.0, 1.0,
|
|
||||||
// back
|
|
||||||
0.0, 0.0, 0.0,
|
|
||||||
1.0, 0.0, 0.0,
|
|
||||||
1.0, 1.0, 0.0,
|
|
||||||
0.0, 1.0, 0.0
|
|
||||||
];
|
|
||||||
pub const CUBE_INDICES: &[u16] = &[
|
|
||||||
// front
|
|
||||||
0, 1, 2,
|
|
||||||
2, 3, 0,
|
|
||||||
// right
|
|
||||||
1, 5, 6,
|
|
||||||
6, 2, 1,
|
|
||||||
// back
|
|
||||||
7, 6, 5,
|
|
||||||
5, 4, 7,
|
|
||||||
// left
|
|
||||||
4, 0, 3,
|
|
||||||
3, 7, 4,
|
|
||||||
// bottom
|
|
||||||
4, 5, 1,
|
|
||||||
1, 0, 4,
|
|
||||||
// top
|
|
||||||
3, 2, 6,
|
|
||||||
6, 7, 3
|
|
||||||
];
|
|
||||||
|
|
||||||
#[derive(Clone, Copy, Default)]
|
|
||||||
pub struct PositionOnlyVertex {
|
|
||||||
pub position: [f32; 3],
|
|
||||||
}
|
|
||||||
implement_vertex!(PositionOnlyVertex, position);
|
|
||||||
|
|
||||||
const fn box_vertices() -> [PositionOnlyVertex; CUBE_VERTICES.len() / 3] {
|
|
||||||
let mut arr = [PositionOnlyVertex { position: [0., 0., 0.] }; CUBE_VERTICES.len() / 3];
|
|
||||||
let mut ptr = 0;
|
|
||||||
loop {
|
|
||||||
arr[ptr] = PositionOnlyVertex {
|
|
||||||
position: [
|
|
||||||
CUBE_VERTICES[ptr * 3],
|
|
||||||
CUBE_VERTICES[(ptr * 3) + 1],
|
|
||||||
CUBE_VERTICES[(ptr * 3) + 2]
|
|
||||||
]
|
|
||||||
};
|
|
||||||
ptr += 1;
|
|
||||||
if ptr >= CUBE_VERTICES.len() / 3 {
|
|
||||||
return arr
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const BOX_VERTICES: &[PositionOnlyVertex] = &box_vertices();
|
|
||||||
|
|
||||||
#[derive(Unique)]
|
|
||||||
pub struct SimpleBoxBuffers(pub VertexBuffer<PositionOnlyVertex>, pub IndexBuffer<u16>);
|
|
||||||
|
|
||||||
pub fn init_simple_box_buffers(
|
|
||||||
storages: AllStoragesView,
|
|
||||||
display: NonSendSync<UniqueView<Renderer>>
|
|
||||||
) {
|
|
||||||
let vert = VertexBuffer::new(
|
|
||||||
&display.display,
|
|
||||||
BOX_VERTICES
|
|
||||||
).unwrap();
|
|
||||||
let index = IndexBuffer::new(
|
|
||||||
&display.display,
|
|
||||||
PrimitiveType::TrianglesList,
|
|
||||||
CUBE_INDICES
|
|
||||||
).unwrap();
|
|
||||||
storages.add_unique_non_send_sync(SimpleBoxBuffers(vert, index));
|
|
||||||
}
|
|
|
@ -1,6 +0,0 @@
|
||||||
use shipyard::Component;
|
|
||||||
use glam::Mat4;
|
|
||||||
|
|
||||||
#[derive(Component, Clone, Copy, Debug, Default)]
|
|
||||||
#[track(All)]
|
|
||||||
pub struct Transform(pub Mat4);
|
|
|
@ -1,105 +0,0 @@
|
||||||
use strum::EnumIter;
|
|
||||||
use crate::prefabs::BlockTexture;
|
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, EnumIter)]
|
|
||||||
#[repr(u8)]
|
|
||||||
pub enum Block {
|
|
||||||
Air,
|
|
||||||
Stone,
|
|
||||||
Dirt,
|
|
||||||
Grass,
|
|
||||||
Sand,
|
|
||||||
}
|
|
||||||
impl Block {
|
|
||||||
pub const fn descriptor(self) -> BlockDescriptor {
|
|
||||||
match self {
|
|
||||||
Self::Air => BlockDescriptor {
|
|
||||||
name: "air",
|
|
||||||
render: RenderType::None,
|
|
||||||
collision: CollisionType::None,
|
|
||||||
raycast_collision: false,
|
|
||||||
},
|
|
||||||
Self::Stone => BlockDescriptor {
|
|
||||||
name: "stone",
|
|
||||||
render: RenderType::SolidBlock(CubeTexture::all(BlockTexture::Stone)),
|
|
||||||
collision: CollisionType::Solid,
|
|
||||||
raycast_collision: true,
|
|
||||||
},
|
|
||||||
Self::Dirt => BlockDescriptor {
|
|
||||||
name: "dirt",
|
|
||||||
render: RenderType::SolidBlock(CubeTexture::all(BlockTexture::Dirt)),
|
|
||||||
collision: CollisionType::Solid,
|
|
||||||
raycast_collision: true,
|
|
||||||
},
|
|
||||||
Self::Grass => BlockDescriptor {
|
|
||||||
name: "grass",
|
|
||||||
render: RenderType::SolidBlock(CubeTexture::top_sides_bottom(
|
|
||||||
BlockTexture::GrassTop,
|
|
||||||
BlockTexture::GrassSide,
|
|
||||||
BlockTexture::Dirt
|
|
||||||
)),
|
|
||||||
collision: CollisionType::Solid,
|
|
||||||
raycast_collision: true,
|
|
||||||
},
|
|
||||||
Self::Sand => BlockDescriptor {
|
|
||||||
name: "sand",
|
|
||||||
render: RenderType::SolidBlock(CubeTexture::all(BlockTexture::Sand)),
|
|
||||||
collision: CollisionType::Solid,
|
|
||||||
raycast_collision: true,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug)]
|
|
||||||
pub struct BlockDescriptor {
|
|
||||||
pub name: &'static str,
|
|
||||||
pub render: RenderType,
|
|
||||||
pub collision: CollisionType,
|
|
||||||
pub raycast_collision: bool,
|
|
||||||
}
|
|
||||||
// impl BlockDescriptor {
|
|
||||||
// pub fn of(block: Block) -> Self {
|
|
||||||
// block.descriptor()
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug)]
|
|
||||||
pub struct CubeTexture {
|
|
||||||
pub top: BlockTexture,
|
|
||||||
pub bottom: BlockTexture,
|
|
||||||
pub left: BlockTexture,
|
|
||||||
pub right: BlockTexture,
|
|
||||||
pub front: BlockTexture,
|
|
||||||
pub back: BlockTexture,
|
|
||||||
}
|
|
||||||
impl CubeTexture {
|
|
||||||
pub const fn top_sides_bottom(top: BlockTexture, sides: BlockTexture, bottom: BlockTexture) -> Self {
|
|
||||||
Self {
|
|
||||||
top,
|
|
||||||
bottom,
|
|
||||||
left: sides,
|
|
||||||
right: sides,
|
|
||||||
front: sides,
|
|
||||||
back: sides,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub const fn horizontal_vertical(horizontal: BlockTexture, vertical: BlockTexture) -> Self {
|
|
||||||
Self::top_sides_bottom(vertical, horizontal, vertical)
|
|
||||||
}
|
|
||||||
pub const fn all(texture: BlockTexture) -> Self {
|
|
||||||
Self::horizontal_vertical(texture, texture)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
|
||||||
pub enum CollisionType {
|
|
||||||
None,
|
|
||||||
Solid,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug)]
|
|
||||||
pub enum RenderType {
|
|
||||||
None,
|
|
||||||
SolidBlock(CubeTexture)
|
|
||||||
}
|
|
|
@ -1,138 +0,0 @@
|
||||||
use strum::{EnumIter, IntoEnumIterator};
|
|
||||||
use glam::{Vec3A, vec3a, IVec3, ivec3};
|
|
||||||
use std::mem::discriminant;
|
|
||||||
use super::{chunk::CHUNK_SIZE, block::{Block, RenderType}};
|
|
||||||
use crate::rendering::world::ChunkVertex;
|
|
||||||
|
|
||||||
pub mod data;
|
|
||||||
use data::MeshGenData;
|
|
||||||
|
|
||||||
#[repr(usize)]
|
|
||||||
#[derive(Clone, Copy, Debug, EnumIter)]
|
|
||||||
pub enum CubeFace {
|
|
||||||
Top = 0,
|
|
||||||
Front = 1,
|
|
||||||
Left = 2,
|
|
||||||
Right = 3,
|
|
||||||
Back = 4,
|
|
||||||
Bottom = 5,
|
|
||||||
}
|
|
||||||
const CUBE_FACE_VERTICES: [[Vec3A; 4]; 6] = [
|
|
||||||
[vec3a(0., 1., 0.), vec3a(0., 1., 1.), vec3a(1., 1., 0.), vec3a(1., 1., 1.)],
|
|
||||||
[vec3a(0., 0., 0.), vec3a(0., 1., 0.), vec3a(1., 0., 0.), vec3a(1., 1., 0.)],
|
|
||||||
[vec3a(0., 0., 1.), vec3a(0., 1., 1.), vec3a(0., 0., 0.), vec3a(0., 1., 0.)],
|
|
||||||
[vec3a(1., 0., 0.), vec3a(1., 1., 0.), vec3a(1., 0., 1.), vec3a(1., 1., 1.)],
|
|
||||||
[vec3a(1., 0., 1.), vec3a(1., 1., 1.), vec3a(0., 0., 1.), vec3a(0., 1., 1.)],
|
|
||||||
[vec3a(0., 0., 1.), vec3a(0., 0., 0.), vec3a(1., 0., 1.), vec3a(1., 0., 0.)],
|
|
||||||
];
|
|
||||||
const CUBE_FACE_NORMALS: [Vec3A; 6] = [
|
|
||||||
vec3a(0., 1., 0.),
|
|
||||||
vec3a(0., 0., -1.),
|
|
||||||
vec3a(-1.,0., 0.),
|
|
||||||
vec3a(1., 0., 0.),
|
|
||||||
vec3a(0., 0., 1.),
|
|
||||||
vec3a(0., -1.,0.)
|
|
||||||
];
|
|
||||||
const CUBE_FACE_INDICES: [u32; 6] = [0, 1, 2, 2, 1, 3];
|
|
||||||
const UV_COORDS: [[f32; 2]; 4] = [
|
|
||||||
[0., 0.],
|
|
||||||
[0., 1.],
|
|
||||||
[1., 0.],
|
|
||||||
[1., 1.],
|
|
||||||
];
|
|
||||||
|
|
||||||
#[derive(Default)]
|
|
||||||
struct MeshBuilder {
|
|
||||||
vertex_buffer: Vec<ChunkVertex>,
|
|
||||||
index_buffer: Vec<u32>,
|
|
||||||
idx_counter: u32,
|
|
||||||
}
|
|
||||||
impl MeshBuilder {
|
|
||||||
pub fn new() -> Self {
|
|
||||||
Self::default()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn add_face(&mut self, face: CubeFace, coord: IVec3, texture: u8) {
|
|
||||||
let coord = coord.as_vec3a();
|
|
||||||
let face_index = face as usize;
|
|
||||||
|
|
||||||
//Push vertexes
|
|
||||||
let norm = CUBE_FACE_NORMALS[face_index];
|
|
||||||
let vert = CUBE_FACE_VERTICES[face_index];
|
|
||||||
self.vertex_buffer.reserve(4);
|
|
||||||
for i in 0..4 {
|
|
||||||
self.vertex_buffer.push(ChunkVertex {
|
|
||||||
position: (coord + vert[i]).to_array(),
|
|
||||||
normal: norm.to_array(),
|
|
||||||
uv: UV_COORDS[i],
|
|
||||||
tex_index: texture
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
//Push indices
|
|
||||||
self.index_buffer.extend_from_slice(&CUBE_FACE_INDICES.map(|x| x + self.idx_counter));
|
|
||||||
self.idx_counter += 4;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn finish(self) -> (Vec<ChunkVertex>, Vec<u32>) {
|
|
||||||
(self.vertex_buffer, self.index_buffer)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn generate_mesh(data: MeshGenData) -> (Vec<ChunkVertex>, Vec<u32>) {
|
|
||||||
let get_block = |pos: IVec3| -> Block {
|
|
||||||
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]
|
|
||||||
} else if pos.x >= CHUNK_SIZE as i32 {
|
|
||||||
data.block_data_pos_x[pos.x as usize - CHUNK_SIZE][pos.y as usize][pos.z as usize]
|
|
||||||
} else if pos.y < 0 {
|
|
||||||
data.block_data_neg_y[pos.x as usize][(CHUNK_SIZE as i32 + pos.y) as usize][pos.z as usize]
|
|
||||||
} else if pos.y >= CHUNK_SIZE as i32 {
|
|
||||||
data.block_data_pos_y[pos.x as usize][pos.y as usize - CHUNK_SIZE][pos.z as usize]
|
|
||||||
} else if pos.z < 0 {
|
|
||||||
data.block_data_neg_z[pos.x as usize][pos.y as usize][(CHUNK_SIZE as i32 + pos.z) as usize]
|
|
||||||
} else if pos.z >= CHUNK_SIZE as i32 {
|
|
||||||
data.block_data_pos_z[pos.x as usize][pos.y as usize][pos.z as usize - CHUNK_SIZE]
|
|
||||||
} else {
|
|
||||||
data.block_data[pos.x as usize][pos.y as usize][pos.z as usize]
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut builder = MeshBuilder::new();
|
|
||||||
|
|
||||||
for x in 0..CHUNK_SIZE {
|
|
||||||
for y in 0..CHUNK_SIZE {
|
|
||||||
for z in 0..CHUNK_SIZE {
|
|
||||||
let coord = ivec3(x as i32, y as i32, z as i32);
|
|
||||||
let block = get_block(coord);
|
|
||||||
let descriptor = block.descriptor();
|
|
||||||
if matches!(descriptor.render, RenderType::None) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
for face in CubeFace::iter() {
|
|
||||||
let facing = CUBE_FACE_NORMALS[face as usize].as_ivec3();
|
|
||||||
let facing_coord = coord + facing;
|
|
||||||
let show = discriminant(&get_block(facing_coord).descriptor().render) != discriminant(&descriptor.render);
|
|
||||||
if show {
|
|
||||||
match descriptor.render {
|
|
||||||
RenderType::SolidBlock(textures) => {
|
|
||||||
let face_texture = match face {
|
|
||||||
CubeFace::Top => textures.top,
|
|
||||||
CubeFace::Front => textures.front,
|
|
||||||
CubeFace::Left => textures.left,
|
|
||||||
CubeFace::Right => textures.right,
|
|
||||||
CubeFace::Back => textures.back,
|
|
||||||
CubeFace::Bottom => textures.bottom,
|
|
||||||
};
|
|
||||||
builder.add_face(face, coord, face_texture as u8);
|
|
||||||
},
|
|
||||||
_ => unimplemented!()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
builder.finish()
|
|
||||||
}
|
|
|
@ -1,64 +0,0 @@
|
||||||
use flume::{Sender, Receiver};
|
|
||||||
use glam::IVec3;
|
|
||||||
use shipyard::Unique;
|
|
||||||
use rayon::{ThreadPool, ThreadPoolBuilder};
|
|
||||||
use super::{
|
|
||||||
chunk::BlockData,
|
|
||||||
mesh::{generate_mesh, data::MeshGenData},
|
|
||||||
worldgen::generate_world,
|
|
||||||
};
|
|
||||||
use crate::rendering::world::ChunkVertex;
|
|
||||||
|
|
||||||
pub enum ChunkTask {
|
|
||||||
LoadChunk {
|
|
||||||
seed: u64,
|
|
||||||
position: IVec3
|
|
||||||
},
|
|
||||||
GenerateMesh {
|
|
||||||
position: IVec3,
|
|
||||||
data: MeshGenData
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub enum ChunkTaskResponse {
|
|
||||||
LoadedChunk {
|
|
||||||
position: IVec3,
|
|
||||||
chunk_data: BlockData,
|
|
||||||
},
|
|
||||||
GeneratedMesh {
|
|
||||||
position: IVec3,
|
|
||||||
vertices: Vec<ChunkVertex>,
|
|
||||||
indexes: Vec<u32>
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Unique)]
|
|
||||||
pub struct ChunkTaskManager {
|
|
||||||
channel: (Sender<ChunkTaskResponse>, Receiver<ChunkTaskResponse>),
|
|
||||||
pool: ThreadPool,
|
|
||||||
}
|
|
||||||
impl ChunkTaskManager {
|
|
||||||
pub fn new() -> Self {
|
|
||||||
Self {
|
|
||||||
channel: flume::unbounded::<ChunkTaskResponse>(), //maybe put a bound or even bound(0)?
|
|
||||||
pool: ThreadPoolBuilder::new().num_threads(4).build().unwrap()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub fn spawn_task(&self, task: ChunkTask) {
|
|
||||||
let sender = self.channel.0.clone();
|
|
||||||
self.pool.spawn(move || {
|
|
||||||
let _ = sender.send(match task {
|
|
||||||
ChunkTask::GenerateMesh { position, data } => {
|
|
||||||
let (vertices, indexes) = generate_mesh(data);
|
|
||||||
ChunkTaskResponse::GeneratedMesh { position, vertices, indexes }
|
|
||||||
},
|
|
||||||
ChunkTask::LoadChunk { position, seed } => {
|
|
||||||
let chunk_data = generate_world(position, seed);
|
|
||||||
ChunkTaskResponse::LoadedChunk { position, chunk_data }
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
pub fn receive(&self) -> Option<ChunkTaskResponse> {
|
|
||||||
self.channel.1.try_recv().ok()
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,49 +0,0 @@
|
||||||
use glam::{IVec3, ivec3};
|
|
||||||
use bracket_noise::prelude::*;
|
|
||||||
use super::{
|
|
||||||
chunk::{BlockData, CHUNK_SIZE},
|
|
||||||
block::Block
|
|
||||||
};
|
|
||||||
|
|
||||||
pub fn generate_world(chunk_position: IVec3, seed: u64) -> BlockData {
|
|
||||||
let offset = chunk_position * CHUNK_SIZE as i32;
|
|
||||||
|
|
||||||
let mut cave_noise = FastNoise::seeded(seed);
|
|
||||||
cave_noise.set_fractal_type(FractalType::FBM);
|
|
||||||
cave_noise.set_frequency(0.1);
|
|
||||||
|
|
||||||
let mut dirt_noise = FastNoise::seeded(seed.rotate_left(1));
|
|
||||||
dirt_noise.set_fractal_type(FractalType::FBM);
|
|
||||||
dirt_noise.set_frequency(0.1);
|
|
||||||
|
|
||||||
let mut blocks = Box::new([[[Block::Air; CHUNK_SIZE]; CHUNK_SIZE]; CHUNK_SIZE]);
|
|
||||||
|
|
||||||
if chunk_position.y >= 0 {
|
|
||||||
if chunk_position.y == 0 {
|
|
||||||
for x in 0..CHUNK_SIZE {
|
|
||||||
for z in 0..CHUNK_SIZE {
|
|
||||||
blocks[x][0][z] = Block::Dirt;
|
|
||||||
blocks[x][1][z] = Block::Grass;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
for x in 0..CHUNK_SIZE {
|
|
||||||
for y in 0..CHUNK_SIZE {
|
|
||||||
for z in 0..CHUNK_SIZE {
|
|
||||||
let position = ivec3(x as i32, y as i32, z as i32) + offset;
|
|
||||||
let v_cave_noise = cave_noise.get_noise3d(position.x as f32, position.y as f32, position.z as f32) * (-position.y as f32 - 10.0).clamp(0., 1.);
|
|
||||||
let v_dirt_noise = dirt_noise.get_noise3d(position.x as f32, position.y as f32, position.z as f32) * (-position.y as f32).clamp(0., 1.);
|
|
||||||
if v_cave_noise > 0.5 {
|
|
||||||
blocks[x][y][z] = Block::Stone;
|
|
||||||
} else if v_dirt_noise > 0.5 {
|
|
||||||
blocks[x][y][z] = Block::Dirt;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
blocks
|
|
||||||
}
|
|
Loading…
Reference in a new issue