mirror of
https://github.com/griffi-gh/kubi.git
synced 2024-11-25 16:28:42 -06:00
commit
8cd8cf35a2
4
.gitignore
vendored
4
.gitignore
vendored
|
@ -3,10 +3,6 @@
|
|||
debug/
|
||||
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
|
||||
**/*.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]
|
||||
name = "kubi"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
[workspace]
|
||||
members = ["kubi", "kubi-server", "kubi-shared", "kubi-logging"]
|
||||
resolver = "2"
|
||||
|
||||
[dependencies]
|
||||
glium = "0.32"
|
||||
image = { version = "0.24", default_features = false, features = ["png"] }
|
||||
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.release-with-debug]
|
||||
inherits = "release"
|
||||
debug = true
|
||||
|
||||
[profile.dev]
|
||||
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 log::Level;
|
||||
use std::io::Write;
|
||||
|
||||
pub use log;
|
||||
pub use env_logger;
|
||||
|
||||
#[inline]
|
||||
pub fn init() {
|
||||
let mut env = Env::default();
|
||||
if cfg!(debug_assertions) {
|
||||
env = env.filter_or("RUST_LOG", "trace");
|
||||
}
|
||||
let env = Env::default()
|
||||
.filter_or("RUST_LOG", "trace,gilrs=warn,rusty_xinput=warn");
|
||||
Builder::from_env(env)
|
||||
.format(|buf, record| {
|
||||
let mut level_style = buf.style();
|
||||
|
@ -18,6 +20,9 @@ pub fn init() {
|
|||
_ => Color::Blue
|
||||
}).set_bold(true);
|
||||
|
||||
let mut bold_style = buf.style();
|
||||
bold_style.set_bold(true);
|
||||
|
||||
let mut location_style = buf.style();
|
||||
location_style.set_bold(true);
|
||||
location_style.set_dimmed(true);
|
||||
|
@ -25,9 +30,11 @@ pub fn init() {
|
|||
let mut location_line_style = buf.style();
|
||||
location_line_style.set_dimmed(true);
|
||||
|
||||
let text = format!("{}", record.args());
|
||||
|
||||
writeln!(
|
||||
buf,
|
||||
"{} {:<50}\t{}{}{}",
|
||||
"{} {:<50}\t{}{}{}{}",
|
||||
level_style.value(match record.level() {
|
||||
Level::Error => "[e]",
|
||||
Level::Warn => "[w]",
|
||||
|
@ -35,7 +42,8 @@ pub fn init() {
|
|||
Level::Debug => "[d]",
|
||||
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_line_style.value(" :"),
|
||||
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;
|
||||
uniform vec4 color;
|
|
@ -1,4 +1,6 @@
|
|||
#version 150 core
|
||||
#version 300 es
|
||||
|
||||
precision highp float;
|
||||
|
||||
in vec3 position;
|
||||
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;
|
||||
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 vec2 v_uv;
|
||||
|
@ -9,6 +12,10 @@ uniform sampler2DArray tex;
|
|||
void main() {
|
||||
// base color from texture
|
||||
color = texture(tex, vec3(v_uv, v_tex_index));
|
||||
// discard fully transparent pixels
|
||||
if (color.w <= 0.0) {
|
||||
discard;
|
||||
}
|
||||
//basic "lighting"
|
||||
float light = abs(v_normal.x) + .8 * abs(v_normal.y) + .6 * abs(v_normal.z);
|
||||
color *= vec4(vec3(light), 1.);
|
|
@ -1,4 +1,6 @@
|
|||
#version 150 core
|
||||
#version 300 es
|
||||
|
||||
precision highp float;
|
||||
|
||||
//TODO pack this data:
|
||||
// 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!
|
||||
|
||||
use glam::{Vec3A, Vec4, Mat3A, vec3a, Vec3, vec4};
|
||||
use shipyard::{ViewMut, IntoIter, View};
|
||||
use shipyard::{ViewMut, IntoIter, View, track};
|
||||
use crate::transform::Transform;
|
||||
use super::Camera;
|
||||
|
||||
|
@ -122,9 +122,9 @@ fn intersection<const A: usize, const B: usize, const C: usize>(planes: &[Vec4;
|
|||
|
||||
pub fn update_frustum(
|
||||
mut cameras: ViewMut<Camera>,
|
||||
transforms: View<Transform>
|
||||
transforms: View<Transform, { track::All }>
|
||||
) {
|
||||
for (camera, _) in (&mut cameras, transforms.inserted_or_modified()).iter() {
|
||||
camera.frustum = Frustum::compute(camera);
|
||||
for (mut camera, _) in (&mut cameras, transforms.inserted_or_modified()).iter() {
|
||||
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 glium::glutin::{event::VirtualKeyCode, event_loop::ControlFlow};
|
||||
use crate::input::RawInputState;
|
||||
use crate::input::RawKbmInputState;
|
||||
|
||||
#[derive(Unique)]
|
||||
pub struct SetControlFlow(pub Option<ControlFlow>);
|
||||
|
||||
pub fn exit_on_esc(
|
||||
raw_inputs: UniqueView<RawInputState>,
|
||||
raw_inputs: UniqueView<RawKbmInputState>,
|
||||
mut control_flow: UniqueViewMut<SetControlFlow>
|
||||
) {
|
||||
if raw_inputs.keyboard_state.contains(&VirtualKeyCode::Escape) {
|
|
@ -3,7 +3,6 @@ use crate::rendering::Renderer;
|
|||
use glium::glutin::window::CursorGrabMode;
|
||||
|
||||
#[derive(Unique)]
|
||||
#[track(All)]
|
||||
pub struct CursorLock(pub bool);
|
||||
|
||||
pub fn update_cursor_lock_state(
|
|
@ -1,6 +1,7 @@
|
|||
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 crate::rendering::Renderer;
|
||||
|
||||
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(
|
||||
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 kubi_shared::block::Block;
|
||||
use crate::{
|
||||
world::block::Block,
|
||||
player::MainPlayer,
|
||||
transform::Transform
|
||||
};
|
||||
|
@ -21,7 +20,7 @@ pub enum PlayerActionEvent {
|
|||
}
|
||||
|
||||
pub fn generate_move_events(
|
||||
transforms: View<Transform>,
|
||||
transforms: View<Transform, { track::All }>,
|
||||
player: View<MainPlayer>,
|
||||
mut entities: EntitiesViewMut,
|
||||
mut events: ViewMut<EventComponent>,
|
|
@ -1,5 +1,5 @@
|
|||
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 crate::{transform::Transform, input::Inputs, settings::GameSettings, delta_time::DeltaTime};
|
||||
|
||||
|
@ -13,11 +13,11 @@ pub fn update_controllers() -> Workload {
|
|||
).into_workload()
|
||||
}
|
||||
|
||||
const MAX_PITCH: f32 = PI/2. - 0.025;
|
||||
const MAX_PITCH: f32 = PI/2. - 0.05;
|
||||
|
||||
fn update_look(
|
||||
controllers: View<FlyController>,
|
||||
mut transforms: ViewMut<Transform>,
|
||||
mut transforms: ViewMut<Transform, { track::All }>,
|
||||
inputs: UniqueView<Inputs>,
|
||||
settings: UniqueView<GameSettings>,
|
||||
dt: UniqueView<DeltaTime>,
|
||||
|
@ -37,12 +37,12 @@ fn update_look(
|
|||
|
||||
fn update_movement(
|
||||
controllers: View<FlyController>,
|
||||
mut transforms: ViewMut<Transform>,
|
||||
mut transforms: ViewMut<Transform, { track::All }>,
|
||||
inputs: UniqueView<Inputs>,
|
||||
dt: UniqueView<DeltaTime>,
|
||||
) {
|
||||
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() {
|
||||
let (scale, rotation, mut translation) = transform.0.to_scale_rotation_translation();
|
||||
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
|
||||
#![allow(clippy::too_many_arguments)]
|
||||
#![cfg_attr(
|
||||
all(windows, not(debug_assertions)),
|
||||
windows_subsystem = "windows"
|
||||
)]
|
||||
#![allow(clippy::too_many_arguments)] // allowed because systems often need a lot of arguments
|
||||
|
||||
use shipyard::{
|
||||
World, Workload, IntoWorkload,
|
||||
UniqueView, UniqueViewMut,
|
||||
NonSendSync
|
||||
NonSendSync, WorkloadModificator,
|
||||
SystemModificator
|
||||
};
|
||||
use glium::{
|
||||
glutin::{
|
||||
|
@ -15,13 +19,12 @@ use glium::{
|
|||
use glam::vec3;
|
||||
use std::time::Instant;
|
||||
|
||||
mod logging;
|
||||
pub use kubi_shared::transform;
|
||||
|
||||
pub(crate) mod rendering;
|
||||
pub(crate) mod world;
|
||||
pub(crate) mod player;
|
||||
pub(crate) mod prefabs;
|
||||
pub(crate) mod transform;
|
||||
pub(crate) mod settings;
|
||||
pub(crate) mod camera;
|
||||
pub(crate) mod events;
|
||||
|
@ -31,19 +34,30 @@ pub(crate) mod block_placement;
|
|||
pub(crate) mod delta_time;
|
||||
pub(crate) mod cursor_lock;
|
||||
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::{
|
||||
init_game_world,
|
||||
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 settings::load_settings;
|
||||
use camera::compute_cameras;
|
||||
use events::{
|
||||
clear_events, process_glutin_events,
|
||||
player_actions::generate_move_events
|
||||
clear_events,
|
||||
process_glutin_events,
|
||||
initial_resize_event,
|
||||
player_actions::generate_move_events,
|
||||
};
|
||||
use input::{init_input, process_inputs};
|
||||
use fly_controller::update_controllers;
|
||||
|
@ -52,49 +66,86 @@ use rendering::{
|
|||
RenderTarget,
|
||||
BackgroundColor,
|
||||
clear_background,
|
||||
primitives::init_simple_box_buffers,
|
||||
init_window_size,
|
||||
update_window_size,
|
||||
primitives::init_primitives,
|
||||
selection_box::render_selection_box,
|
||||
world::draw_world,
|
||||
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 cursor_lock::{insert_lock_state, update_cursor_lock_state, lock_cursor_now};
|
||||
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 {
|
||||
(
|
||||
initial_resize_event,
|
||||
init_window_size,
|
||||
load_settings,
|
||||
load_prefabs,
|
||||
init_simple_box_buffers,
|
||||
init_primitives,
|
||||
insert_lock_state,
|
||||
init_state,
|
||||
initialize_from_args,
|
||||
lock_cursor_now,
|
||||
init_input,
|
||||
init_game_world,
|
||||
spawn_player,
|
||||
init_gui,
|
||||
insert_control_flow_unique,
|
||||
init_delta_time,
|
||||
).into_workload()
|
||||
}
|
||||
fn update() -> Workload {
|
||||
(
|
||||
update_window_size,
|
||||
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_loaded_world_around_player,
|
||||
update_raycasts,
|
||||
block_placement_system,
|
||||
update_cursor_lock_state,
|
||||
update_block_placement,
|
||||
apply_queued_blocks,
|
||||
).into_workload().run_if(is_ingame),
|
||||
compute_cameras,
|
||||
update_gui,
|
||||
update_state,
|
||||
exit_on_esc,
|
||||
disconnect_on_exit.run_if(is_multiplayer),
|
||||
).into_workload()
|
||||
}
|
||||
fn render() -> Workload {
|
||||
(
|
||||
clear_background,
|
||||
(
|
||||
draw_world,
|
||||
draw_current_chunk_border,
|
||||
render_selection_box,
|
||||
).into_sequential_workload().run_if(is_ingame),
|
||||
render_gui,
|
||||
).into_sequential_workload()
|
||||
}
|
||||
fn after_frame_end() -> Workload {
|
||||
|
@ -103,15 +154,28 @@ fn after_frame_end() -> Workload {
|
|||
).into_workload()
|
||||
}
|
||||
|
||||
fn main() {
|
||||
logging::init();
|
||||
#[cfg(all(windows, not(debug_assertions)))]
|
||||
fn attach_console() {
|
||||
use winapi::um::wincon::{AttachConsole, ATTACH_PARENT_PROCESS};
|
||||
unsafe { AttachConsole(ATTACH_PARENT_PROCESS); }
|
||||
}
|
||||
|
||||
//Create event loop
|
||||
let event_loop = EventLoop::new();
|
||||
fn main() {
|
||||
//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
|
||||
let mut world = World::new();
|
||||
|
||||
//Create event loop
|
||||
let event_loop = EventLoop::new();
|
||||
|
||||
//Add systems and uniques, Init and load things
|
||||
world.add_unique_non_send_sync(Renderer::init(&event_loop));
|
||||
world.add_unique(BackgroundColor(vec3(0.5, 0.5, 1.)));
|
||||
|
@ -148,7 +212,7 @@ fn main() {
|
|||
last_update = now;
|
||||
}
|
||||
|
||||
//Run update workflow
|
||||
//Run update workflows
|
||||
world.run_workload(update).unwrap();
|
||||
|
||||
//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::{
|
||||
transform::Transform,
|
||||
camera::Camera,
|
||||
fly_controller::FlyController,
|
||||
world::raycast::LookingAtBlock,
|
||||
block_placement::PlayerHolding,
|
||||
};
|
||||
|
||||
#[derive(Component)]
|
||||
pub struct Player;
|
||||
pub use kubi_shared::player::Player;
|
||||
|
||||
#[derive(Component)]
|
||||
pub struct MainPlayer;
|
||||
|
@ -16,12 +15,13 @@ pub fn spawn_player (
|
|||
mut storages: AllStoragesViewMut
|
||||
) {
|
||||
log::info!("spawning player");
|
||||
storages.add_entity((
|
||||
let entity_id = storages.add_entity((
|
||||
Player,
|
||||
MainPlayer,
|
||||
Transform::default(),
|
||||
Camera::default(),
|
||||
FlyController,
|
||||
LookingAtBlock::default(),
|
||||
PlayerHolding::default(),
|
||||
));
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
use shipyard::{NonSendSync, UniqueView, Unique, AllStoragesView};
|
||||
use glium::{texture::{SrgbTexture2dArray, MipmapsOption}, Program};
|
||||
use strum::EnumIter;
|
||||
use kubi_shared::block::{Block, BlockTexture};
|
||||
use crate::rendering::Renderer;
|
||||
|
||||
mod texture;
|
||||
|
@ -13,23 +13,6 @@ pub trait AssetPaths {
|
|||
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 {
|
||||
fn file_name(self) -> &'static str {
|
||||
match self {
|
||||
|
@ -46,6 +29,9 @@ impl AssetPaths for BlockTexture {
|
|||
Self::TallGrass => "tall_grass.png",
|
||||
Self::Snow => "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);
|
||||
|
||||
#[derive(Unique)]
|
||||
pub struct BasicColoredShaderPrefab(pub Program);
|
||||
pub struct ColoredShaderPrefab(pub Program);
|
||||
|
||||
#[derive(Unique)]
|
||||
pub struct ProgressbarShaderPrefab(pub Program);
|
||||
|
||||
pub fn load_prefabs(
|
||||
storages: AllStoragesView,
|
||||
|
@ -92,7 +81,7 @@ pub fn load_prefabs(
|
|||
&renderer.display
|
||||
)
|
||||
));
|
||||
storages.add_unique_non_send_sync(BasicColoredShaderPrefab(
|
||||
storages.add_unique_non_send_sync(ColoredShaderPrefab(
|
||||
include_shader_prefab!(
|
||||
"colored",
|
||||
"../shaders/colored.vert",
|
||||
|
@ -100,4 +89,12 @@ pub fn load_prefabs(
|
|||
&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::{
|
||||
RenderTarget,
|
||||
primitives::SimpleBoxBuffers,
|
||||
primitives::cube::CubePrimitive,
|
||||
};
|
||||
|
||||
pub fn render_selection_box(
|
||||
|
@ -20,7 +20,7 @@ pub fn render_selection_box(
|
|||
camera: View<Camera>,
|
||||
mut target: NonSendSync<UniqueViewMut<RenderTarget>>,
|
||||
program: NonSendSync<UniqueView<SelBoxShaderPrefab>>,
|
||||
buffers: NonSendSync<UniqueView<SimpleBoxBuffers>>,
|
||||
buffers: NonSendSync<UniqueView<CubePrimitive>>,
|
||||
) {
|
||||
let camera = camera.iter().next().unwrap();
|
||||
let Some(lookat) = lookat.iter().next() else { return };
|
|
@ -1,5 +1,5 @@
|
|||
use glam::{Vec3, Mat4, Quat, ivec3};
|
||||
use shipyard::{NonSendSync, UniqueView, UniqueViewMut, View, IntoIter};
|
||||
use shipyard::{NonSendSync, UniqueView, UniqueViewMut, View, IntoIter, track};
|
||||
use glium::{
|
||||
implement_vertex, uniform,
|
||||
Surface, DrawParameters,
|
||||
|
@ -24,7 +24,7 @@ use crate::{
|
|||
prefabs::{
|
||||
ChunkShaderPrefab,
|
||||
BlockTexturesPrefab,
|
||||
BasicColoredShaderPrefab,
|
||||
ColoredShaderPrefab,
|
||||
},
|
||||
world::{
|
||||
ChunkStorage,
|
||||
|
@ -32,9 +32,10 @@ use crate::{
|
|||
chunk::CHUNK_SIZE,
|
||||
}, settings::GameSettings,
|
||||
};
|
||||
use super::{RenderTarget, primitives::SimpleBoxBuffers};
|
||||
use super::{RenderTarget, primitives::cube::CubePrimitive};
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
#[repr(C)]
|
||||
pub struct ChunkVertex {
|
||||
pub position: [f32; 3],
|
||||
pub normal: [f32; 3],
|
||||
|
@ -43,7 +44,6 @@ pub struct ChunkVertex {
|
|||
}
|
||||
implement_vertex!(ChunkVertex, position, normal, uv, tex_index);
|
||||
|
||||
|
||||
pub fn draw_world(
|
||||
mut target: NonSendSync<UniqueViewMut<RenderTarget>>,
|
||||
chunks: UniqueView<ChunkStorage>,
|
||||
|
@ -109,13 +109,12 @@ pub fn draw_world(
|
|||
}
|
||||
}
|
||||
|
||||
//this doesn't use culling!
|
||||
pub fn draw_current_chunk_border(
|
||||
mut target: NonSendSync<UniqueViewMut<RenderTarget>>,
|
||||
player: View<MainPlayer>,
|
||||
transforms: View<Transform>,
|
||||
buffers: NonSendSync<UniqueView<SimpleBoxBuffers>>,
|
||||
program: NonSendSync<UniqueView<BasicColoredShaderPrefab>>,
|
||||
transforms: View<Transform, { track::All }>,
|
||||
buffers: NonSendSync<UniqueView<CubePrimitive>>,
|
||||
program: NonSendSync<UniqueView<ColoredShaderPrefab>>,
|
||||
camera: View<Camera>,
|
||||
settings: UniqueView<GameSettings>,
|
||||
) {
|
|
@ -10,7 +10,7 @@ pub struct GameSettings {
|
|||
impl Default for GameSettings {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
render_distance: 5,
|
||||
render_distance: 6,
|
||||
mouse_sensitivity: 1.,
|
||||
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 anyhow::{Result, Context};
|
||||
|
||||
pub use kubi_shared::{worldgen, block::Block};
|
||||
|
||||
pub mod chunk;
|
||||
pub mod block;
|
||||
pub mod tasks;
|
||||
pub mod loading;
|
||||
pub mod mesh;
|
||||
pub mod neighbors;
|
||||
pub mod worldgen;
|
||||
pub mod raycast;
|
||||
pub mod queue;
|
||||
|
||||
use chunk::{Chunk, ChunkMesh};
|
||||
use chunk::{Chunk, ChunkMesh, CHUNK_SIZE};
|
||||
use tasks::ChunkTaskManager;
|
||||
|
||||
use self::{chunk::CHUNK_SIZE, block::Block};
|
||||
|
||||
//TODO separate world struct for render data
|
||||
// because this is not send-sync
|
||||
use queue::BlockUpdateQueue;
|
||||
|
||||
#[derive(Default, Unique)]
|
||||
#[track(Modification)]
|
||||
pub struct ChunkStorage {
|
||||
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(ChunkStorage::new());
|
||||
storages.add_unique(ChunkTaskManager::new());
|
||||
storages.add_unique(BlockUpdateQueue::new());
|
||||
}
|
|
@ -1,11 +1,8 @@
|
|||
use glam::IVec3;
|
||||
use glium::{VertexBuffer, IndexBuffer};
|
||||
use super::block::Block;
|
||||
use crate::rendering::world::ChunkVertex;
|
||||
|
||||
pub const CHUNK_SIZE: usize = 32;
|
||||
|
||||
pub type BlockData = Box<[[[Block; CHUNK_SIZE]; CHUNK_SIZE]; CHUNK_SIZE]>;
|
||||
pub use kubi_shared::chunk::{CHUNK_SIZE, BlockData};
|
||||
|
||||
pub struct ChunkData {
|
||||
pub blocks: BlockData,
|
||||
|
@ -42,6 +39,13 @@ pub enum DesiredChunkState {
|
|||
Rendered,
|
||||
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 position: IVec3,
|
||||
|
@ -49,7 +53,7 @@ pub struct Chunk {
|
|||
pub mesh_index: Option<usize>,
|
||||
pub current_state: CurrentChunkState,
|
||||
pub desired_state: DesiredChunkState,
|
||||
pub dirty: bool,
|
||||
pub mesh_dirty: bool,
|
||||
}
|
||||
impl Chunk {
|
||||
pub fn new(position: IVec3) -> Self {
|
||||
|
@ -59,7 +63,7 @@ impl Chunk {
|
|||
mesh_index: None,
|
||||
current_state: Default::default(),
|
||||
desired_state: Default::default(),
|
||||
dirty: false,
|
||||
mesh_dirty: false,
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,20 +1,26 @@
|
|||
use glam::{IVec3, ivec3};
|
||||
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::{
|
||||
player::MainPlayer,
|
||||
transform::Transform,
|
||||
settings::GameSettings,
|
||||
rendering::Renderer
|
||||
rendering::Renderer,
|
||||
state::GameState,
|
||||
networking::UdpClient,
|
||||
};
|
||||
use super::{
|
||||
ChunkStorage, ChunkMeshStorage,
|
||||
chunk::{Chunk, DesiredChunkState, CHUNK_SIZE, ChunkMesh, CurrentChunkState, ChunkData},
|
||||
tasks::{ChunkTaskManager, ChunkTaskResponse, ChunkTask},
|
||||
queue::BlockUpdateQueue
|
||||
};
|
||||
|
||||
//todo limit task starts insted
|
||||
const MAX_CHUNK_OPS: usize = 8;
|
||||
const MAX_CHUNK_OPS_INGAME: usize = 6;
|
||||
const MAX_CHUNK_OPS: usize = 32;
|
||||
|
||||
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(
|
||||
v_settings: UniqueView<GameSettings>,
|
||||
v_local_player: View<MainPlayer>,
|
||||
v_transform: View<Transform>,
|
||||
v_transform: View<Transform, { track::All }>,
|
||||
mut vm_world: UniqueViewMut<ChunkStorage>,
|
||||
) {
|
||||
//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 {
|
||||
return
|
||||
};
|
||||
|
@ -116,6 +122,7 @@ fn unload_downgrade_chunks(
|
|||
|
||||
fn start_required_tasks(
|
||||
task_manager: UniqueView<ChunkTaskManager>,
|
||||
mut udp_client: Option<UniqueViewMut<UdpClient>>,
|
||||
mut world: UniqueViewMut<ChunkStorage>,
|
||||
) {
|
||||
if !world.is_modified() {
|
||||
|
@ -128,17 +135,27 @@ fn start_required_tasks(
|
|||
match chunk.desired_state {
|
||||
DesiredChunkState::Loaded | DesiredChunkState::Rendered if chunk.current_state == CurrentChunkState::Nothing => {
|
||||
//start load task
|
||||
if let Some(client) = &mut udp_client {
|
||||
client.0.send(
|
||||
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
|
||||
let chunk = world.chunks.get_mut(&position).unwrap();
|
||||
chunk.current_state = CurrentChunkState::Loading;
|
||||
// ===========
|
||||
//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
|
||||
let Some(neighbors) = world.neighbors_all(position) else {
|
||||
continue
|
||||
|
@ -150,12 +167,12 @@ fn start_required_tasks(
|
|||
task_manager.spawn_task(ChunkTask::GenerateMesh { data, position });
|
||||
//Update chunk state
|
||||
let chunk = world.chunks.get_mut(&position).unwrap();
|
||||
if chunk.dirty {
|
||||
if chunk.mesh_dirty {
|
||||
chunk.current_state = CurrentChunkState::RecalculatingMesh;
|
||||
} else {
|
||||
chunk.current_state = CurrentChunkState::CalculatingMesh;
|
||||
}
|
||||
chunk.dirty = false;
|
||||
chunk.mesh_dirty = false;
|
||||
// ===========
|
||||
//log::trace!("Started generating mesh for chunk {position}");
|
||||
}
|
||||
|
@ -168,12 +185,14 @@ fn process_completed_tasks(
|
|||
task_manager: UniqueView<ChunkTaskManager>,
|
||||
mut world: UniqueViewMut<ChunkStorage>,
|
||||
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 {
|
||||
if let Some(res) = task_manager.receive() {
|
||||
let mut ops: usize = 0;
|
||||
while let Some(res) = task_manager.receive() {
|
||||
match res {
|
||||
ChunkTaskResponse::LoadedChunk { position, chunk_data } => {
|
||||
ChunkTaskResponse::LoadedChunk { position, chunk_data, queued } => {
|
||||
//check if chunk exists
|
||||
let Some(chunk) = world.chunks.get_mut(&position) else {
|
||||
log::warn!("blocks data discarded: chunk doesn't exist");
|
||||
|
@ -193,6 +212,15 @@ fn process_completed_tasks(
|
|||
|
||||
//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
|
||||
|
@ -223,8 +251,14 @@ fn process_completed_tasks(
|
|||
|
||||
//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 shipyard::{View, Component, ViewMut, IntoIter, UniqueView};
|
||||
use shipyard::{View, Component, ViewMut, IntoIter, UniqueView, track};
|
||||
use kubi_shared::block::Block;
|
||||
use crate::transform::Transform;
|
||||
use super::ChunkStorage;
|
||||
|
||||
use super::{ChunkStorage, block::Block};
|
||||
|
||||
const RAYCAST_STEP: f32 = 0.25;
|
||||
pub const RAYCAST_STEP: f32 = 0.25;
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct RaycastReport {
|
||||
|
@ -49,7 +49,7 @@ impl ChunkStorage {
|
|||
pub struct LookingAtBlock(pub Option<RaycastReport>);
|
||||
|
||||
pub fn update_raycasts(
|
||||
transform: View<Transform>,
|
||||
transform: View<Transform, { track::All }>,
|
||||
mut raycast: ViewMut<LookingAtBlock>,
|
||||
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()) {
|
||||
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 direction = (rotation * Vec3::NEG_Z).normalize();
|
||||
let direction = (rotation.normalize() * Vec3::NEG_Z).normalize();
|
||||
*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