Better asset loading
This commit is contained in:
Generated
+7
@@ -610,6 +610,7 @@ dependencies = [
|
|||||||
"bytemuck",
|
"bytemuck",
|
||||||
"env_logger",
|
"env_logger",
|
||||||
"glam",
|
"glam",
|
||||||
|
"glob",
|
||||||
"log",
|
"log",
|
||||||
"noise",
|
"noise",
|
||||||
"png",
|
"png",
|
||||||
@@ -665,6 +666,12 @@ dependencies = [
|
|||||||
"bytemuck",
|
"bytemuck",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "glob"
|
||||||
|
version = "0.3.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "half"
|
name = "half"
|
||||||
version = "2.6.0"
|
version = "2.6.0"
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ bitflags = "2.9.0"
|
|||||||
bytemuck = { version = "1.22.0", features = ["derive"] }
|
bytemuck = { version = "1.22.0", features = ["derive"] }
|
||||||
env_logger = "0.11.8"
|
env_logger = "0.11.8"
|
||||||
glam = { version = "0.30.2", features = ["bytemuck"] }
|
glam = { version = "0.30.2", features = ["bytemuck"] }
|
||||||
|
glob = "0.3.2"
|
||||||
log = "0.4.27"
|
log = "0.4.27"
|
||||||
noise = "0.9.0"
|
noise = "0.9.0"
|
||||||
png = "0.17.16"
|
png = "0.17.16"
|
||||||
|
|||||||
@@ -42,6 +42,9 @@ pub enum Error {
|
|||||||
AllocateImage(#[from] AllocateImageError),
|
AllocateImage(#[from] AllocateImageError),
|
||||||
#[error("GPU memory allocation error: {0}")]
|
#[error("GPU memory allocation error: {0}")]
|
||||||
MemoryAllocator(#[from] MemoryAllocatorError),
|
MemoryAllocator(#[from] MemoryAllocatorError),
|
||||||
|
|
||||||
|
#[error("Missing asset/directory: {0}")]
|
||||||
|
AssetMissing(String),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<Box<ValidationError>> for Error {
|
impl From<Box<ValidationError>> for Error {
|
||||||
|
|||||||
+41
-7
@@ -1,9 +1,14 @@
|
|||||||
#![feature(duration_constants)]
|
#![feature(duration_constants)]
|
||||||
|
#![allow(clippy::new_without_default)]
|
||||||
|
|
||||||
use std::{process::ExitCode, sync::Arc};
|
use std::{process::ExitCode, sync::Arc, time::Instant};
|
||||||
|
|
||||||
use error::Error;
|
use error::Error;
|
||||||
use render::Renderer;
|
use glam::Vec3;
|
||||||
|
use render::{
|
||||||
|
asset::{AssetLoader, TextureLoadStage},
|
||||||
|
Renderer,
|
||||||
|
};
|
||||||
use winit::{
|
use winit::{
|
||||||
application::ApplicationHandler,
|
application::ApplicationHandler,
|
||||||
dpi::PhysicalSize,
|
dpi::PhysicalSize,
|
||||||
@@ -11,26 +16,32 @@ use winit::{
|
|||||||
event_loop::{ActiveEventLoop, EventLoop},
|
event_loop::{ActiveEventLoop, EventLoop},
|
||||||
window::{Window, WindowId},
|
window::{Window, WindowId},
|
||||||
};
|
};
|
||||||
|
use world::{Chunk, ChunkGenerator, NoiseChunkGenerator, World};
|
||||||
|
|
||||||
pub mod error;
|
pub mod error;
|
||||||
pub mod render;
|
pub mod render;
|
||||||
pub mod world;
|
pub mod world;
|
||||||
|
|
||||||
#[derive(Default)]
|
|
||||||
pub struct Engine {
|
pub struct Engine {
|
||||||
window: Option<Arc<Window>>,
|
window: Option<Arc<Window>>,
|
||||||
renderer: Option<Renderer>,
|
renderer: Option<Renderer>,
|
||||||
|
assets: Option<AssetLoader<TextureLoadStage>>,
|
||||||
|
world: Option<World>,
|
||||||
|
camera_position: Vec3,
|
||||||
|
generator: Box<dyn ChunkGenerator>,
|
||||||
|
start: Instant,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Engine {
|
impl Engine {
|
||||||
pub fn ensure_renderer(&mut self, event_loop: &ActiveEventLoop) -> Result<(), Error> {
|
pub fn ensure_renderer(&mut self, event_loop: &ActiveEventLoop) -> Result<(), Error> {
|
||||||
if self.window.is_none() {
|
if self.window.is_none() {
|
||||||
|
let assets = self.assets.take().expect("asset loader");
|
||||||
let window_attributes = Window::default_attributes()
|
let window_attributes = Window::default_attributes()
|
||||||
.with_resizable(false)
|
.with_resizable(false)
|
||||||
.with_inner_size(PhysicalSize::new(800, 600));
|
.with_inner_size(PhysicalSize::new(800, 600));
|
||||||
|
|
||||||
let window = Arc::new(event_loop.create_window(window_attributes)?);
|
let window = Arc::new(event_loop.create_window(window_attributes)?);
|
||||||
let render = Renderer::from_window(window.clone())?;
|
let render = Renderer::from_window(window.clone(), assets)?;
|
||||||
|
|
||||||
self.window = Some(window);
|
self.window = Some(window);
|
||||||
self.renderer = Some(render);
|
self.renderer = Some(render);
|
||||||
@@ -88,8 +99,19 @@ impl ApplicationHandler for Engine {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
WindowEvent::RedrawRequested => {
|
WindowEvent::RedrawRequested => {
|
||||||
if let Some(render) = self.renderer.as_mut() {
|
if let (Some(render), Some(world)) = (self.renderer.as_mut(), self.world.as_mut()) {
|
||||||
if let Err(error) = render.render() {
|
let delta = self.start.elapsed();
|
||||||
|
let dt = delta.as_secs_f64();
|
||||||
|
|
||||||
|
self.camera_position = Vec3::new(
|
||||||
|
(dt.cos() * 32.0) as f32 + (Chunk::SIZE / 2) as f32,
|
||||||
|
48.0,
|
||||||
|
(dt.sin() * 32.0) as f32 + (Chunk::SIZE / 2) as f32,
|
||||||
|
);
|
||||||
|
|
||||||
|
world.update_with_camera(self.camera_position, &mut *self.generator);
|
||||||
|
|
||||||
|
if let Err(error) = render.render(self.camera_position, world) {
|
||||||
log::error!("Render error: {error}");
|
log::error!("Render error: {error}");
|
||||||
event_loop.exit();
|
event_loop.exit();
|
||||||
}
|
}
|
||||||
@@ -124,8 +146,20 @@ impl ApplicationHandler for Engine {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn run() -> Result<(), Error> {
|
fn run() -> Result<(), Error> {
|
||||||
|
let loader = AssetLoader::new();
|
||||||
|
let (loader, block_registry) = loader.load_block_definitions()?;
|
||||||
|
let world = World::new(block_registry);
|
||||||
|
|
||||||
let event_loop = EventLoop::new()?;
|
let event_loop = EventLoop::new()?;
|
||||||
let mut engine = Engine::default();
|
let mut engine = Engine {
|
||||||
|
window: None,
|
||||||
|
renderer: None,
|
||||||
|
assets: Some(loader),
|
||||||
|
world: Some(world),
|
||||||
|
camera_position: Vec3::ZERO,
|
||||||
|
generator: Box::new(NoiseChunkGenerator::from_seed(1234)),
|
||||||
|
start: Instant::now(),
|
||||||
|
};
|
||||||
|
|
||||||
event_loop.run_app(&mut engine)?;
|
event_loop.run_app(&mut engine)?;
|
||||||
|
|
||||||
|
|||||||
+105
-159
@@ -1,178 +1,124 @@
|
|||||||
use std::{collections::HashMap, fs::File, path::PathBuf, sync::Arc};
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use vulkano::{device::Queue, image::view::ImageView};
|
|
||||||
|
|
||||||
use crate::error::Error;
|
use crate::render::asset::texture::FALLBACK_TEXTURE_ID;
|
||||||
|
|
||||||
use super::{
|
use super::model::CubeTextures;
|
||||||
model::{BlockModelRegistry, CubeTextures},
|
|
||||||
texture::TextureRegistryBuilder,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
pub struct BlockDefinition {
|
pub struct InputBlockDefinition {
|
||||||
pub model: BlockModelDefinition,
|
pub model: BlockModelDefinition,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct BlockDefinition {
|
||||||
|
pub model: SidedModelDefinition,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
pub struct SidedModelDefinition {
|
||||||
|
pub front: String,
|
||||||
|
pub left: String,
|
||||||
|
pub back: String,
|
||||||
|
pub right: String,
|
||||||
|
pub bottom: String,
|
||||||
|
pub top: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
pub struct GrassLikeModelDefinition {
|
||||||
|
pub side: String,
|
||||||
|
pub top: String,
|
||||||
|
pub bottom: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
#[serde(rename_all = "snake_case")]
|
||||||
|
pub enum BlockModelDefinition {
|
||||||
|
Sided(SidedModelDefinition),
|
||||||
|
GrassLike(GrassLikeModelDefinition),
|
||||||
|
Simple(String),
|
||||||
|
}
|
||||||
|
|
||||||
pub struct BlockRegistry {
|
pub struct BlockRegistry {
|
||||||
name_to_id: HashMap<String, u8>,
|
name_to_id: HashMap<String, u8>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
|
||||||
#[serde(rename_all = "snake_case")]
|
|
||||||
pub enum BlockModelDefinition {
|
|
||||||
Simple(String),
|
|
||||||
Sided {
|
|
||||||
front: String,
|
|
||||||
left: String,
|
|
||||||
back: String,
|
|
||||||
right: String,
|
|
||||||
bottom: String,
|
|
||||||
top: String,
|
|
||||||
},
|
|
||||||
GrassLike {
|
|
||||||
side: String,
|
|
||||||
top: String,
|
|
||||||
bottom: String,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct BlockRegistryBuilder {
|
|
||||||
texture_registry_builder: TextureRegistryBuilder,
|
|
||||||
definitions: HashMap<u8, CubeTextures>,
|
|
||||||
name_to_id: HashMap<String, u8>,
|
|
||||||
last_block_id: u8,
|
|
||||||
}
|
|
||||||
|
|
||||||
struct BlockSides<'a> {
|
|
||||||
front: &'a str,
|
|
||||||
left: &'a str,
|
|
||||||
back: &'a str,
|
|
||||||
right: &'a str,
|
|
||||||
bottom: &'a str,
|
|
||||||
top: &'a str,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl BlockModelDefinition {
|
|
||||||
fn sides(&self) -> BlockSides {
|
|
||||||
match self {
|
|
||||||
Self::Simple(texture) => BlockSides {
|
|
||||||
front: texture,
|
|
||||||
left: texture,
|
|
||||||
back: texture,
|
|
||||||
right: texture,
|
|
||||||
bottom: texture,
|
|
||||||
top: texture,
|
|
||||||
},
|
|
||||||
Self::Sided {
|
|
||||||
front,
|
|
||||||
left,
|
|
||||||
back,
|
|
||||||
right,
|
|
||||||
bottom,
|
|
||||||
top,
|
|
||||||
} => BlockSides {
|
|
||||||
front,
|
|
||||||
left,
|
|
||||||
back,
|
|
||||||
right,
|
|
||||||
bottom,
|
|
||||||
top,
|
|
||||||
},
|
|
||||||
Self::GrassLike { side, top, bottom } => BlockSides {
|
|
||||||
front: side,
|
|
||||||
left: side,
|
|
||||||
back: side,
|
|
||||||
right: side,
|
|
||||||
bottom,
|
|
||||||
top,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl BlockRegistryBuilder {
|
|
||||||
pub fn new(texture_registry_builder: TextureRegistryBuilder) -> Self {
|
|
||||||
Self {
|
|
||||||
texture_registry_builder,
|
|
||||||
definitions: HashMap::new(),
|
|
||||||
name_to_id: HashMap::new(),
|
|
||||||
last_block_id: 1,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn load(&mut self, name: &str) -> Result<(), Error> {
|
|
||||||
let path = PathBuf::from("assets/blocks").join(format!("{name}.json"));
|
|
||||||
let file = File::open(path)?;
|
|
||||||
let definition = serde_json::from_reader(file).unwrap();
|
|
||||||
|
|
||||||
self.add(name, definition);
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn add(&mut self, name: &str, definition: BlockDefinition) {
|
|
||||||
let id = self.last_block_id;
|
|
||||||
|
|
||||||
let sides = definition.model.sides();
|
|
||||||
let front = self
|
|
||||||
.texture_registry_builder
|
|
||||||
.add_texture_or_fallback(sides.front);
|
|
||||||
let left = self
|
|
||||||
.texture_registry_builder
|
|
||||||
.add_texture_or_fallback(sides.left);
|
|
||||||
let back = self
|
|
||||||
.texture_registry_builder
|
|
||||||
.add_texture_or_fallback(sides.back);
|
|
||||||
let right = self
|
|
||||||
.texture_registry_builder
|
|
||||||
.add_texture_or_fallback(sides.right);
|
|
||||||
let bottom = self
|
|
||||||
.texture_registry_builder
|
|
||||||
.add_texture_or_fallback(sides.bottom);
|
|
||||||
let top = self
|
|
||||||
.texture_registry_builder
|
|
||||||
.add_texture_or_fallback(sides.top);
|
|
||||||
|
|
||||||
self.definitions.insert(
|
|
||||||
id,
|
|
||||||
CubeTextures {
|
|
||||||
front,
|
|
||||||
left,
|
|
||||||
back,
|
|
||||||
right,
|
|
||||||
bottom,
|
|
||||||
top,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
self.name_to_id.insert(name.into(), id);
|
|
||||||
log::info!("{id} -> {name}");
|
|
||||||
|
|
||||||
self.last_block_id += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn build(
|
|
||||||
self,
|
|
||||||
queue: Arc<Queue>,
|
|
||||||
) -> Result<(BlockModelRegistry, BlockRegistry, Arc<ImageView>), Error> {
|
|
||||||
let image_view = self.texture_registry_builder.upload(queue)?;
|
|
||||||
|
|
||||||
let block_model_registry = BlockModelRegistry {
|
|
||||||
id_to_model: self.definitions,
|
|
||||||
fallback: CubeTextures::single(0),
|
|
||||||
};
|
|
||||||
let block_registry = BlockRegistry {
|
|
||||||
name_to_id: self.name_to_id,
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok((block_model_registry, block_registry, image_view))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl BlockRegistry {
|
impl BlockRegistry {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
name_to_id: HashMap::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add(&mut self, name: String, id: u8) {
|
||||||
|
self.name_to_id.insert(name, id);
|
||||||
|
}
|
||||||
|
|
||||||
pub fn get(&self, name: &str) -> Option<u8> {
|
pub fn get(&self, name: &str) -> Option<u8> {
|
||||||
self.name_to_id.get(name).copied()
|
self.name_to_id.get(name).copied()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl BlockModelDefinition {
|
||||||
|
pub fn to_sided(self) -> SidedModelDefinition {
|
||||||
|
match self {
|
||||||
|
Self::Simple(texture) => SidedModelDefinition {
|
||||||
|
front: texture.clone(),
|
||||||
|
left: texture.clone(),
|
||||||
|
back: texture.clone(),
|
||||||
|
right: texture.clone(),
|
||||||
|
bottom: texture.clone(),
|
||||||
|
top: texture,
|
||||||
|
},
|
||||||
|
Self::Sided(sided) => sided,
|
||||||
|
Self::GrassLike(grass) => SidedModelDefinition {
|
||||||
|
front: grass.side.clone(),
|
||||||
|
left: grass.side.clone(),
|
||||||
|
back: grass.side.clone(),
|
||||||
|
right: grass.side,
|
||||||
|
bottom: grass.bottom,
|
||||||
|
top: grass.top,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SidedModelDefinition {
|
||||||
|
pub fn resolve(&self, texture_map: &HashMap<String, u32>) -> CubeTextures {
|
||||||
|
let front = texture_map
|
||||||
|
.get(&self.front)
|
||||||
|
.copied()
|
||||||
|
.unwrap_or(FALLBACK_TEXTURE_ID);
|
||||||
|
let left = texture_map
|
||||||
|
.get(&self.left)
|
||||||
|
.copied()
|
||||||
|
.unwrap_or(FALLBACK_TEXTURE_ID);
|
||||||
|
let back = texture_map
|
||||||
|
.get(&self.back)
|
||||||
|
.copied()
|
||||||
|
.unwrap_or(FALLBACK_TEXTURE_ID);
|
||||||
|
let right = texture_map
|
||||||
|
.get(&self.right)
|
||||||
|
.copied()
|
||||||
|
.unwrap_or(FALLBACK_TEXTURE_ID);
|
||||||
|
let bottom = texture_map
|
||||||
|
.get(&self.bottom)
|
||||||
|
.copied()
|
||||||
|
.unwrap_or(FALLBACK_TEXTURE_ID);
|
||||||
|
let top = texture_map
|
||||||
|
.get(&self.top)
|
||||||
|
.copied()
|
||||||
|
.unwrap_or(FALLBACK_TEXTURE_ID);
|
||||||
|
|
||||||
|
CubeTextures {
|
||||||
|
front,
|
||||||
|
left,
|
||||||
|
back,
|
||||||
|
right,
|
||||||
|
bottom,
|
||||||
|
top,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,122 @@
|
|||||||
|
use std::{collections::HashMap, fs::File, path::Path, sync::Arc};
|
||||||
|
|
||||||
|
use block::{BlockDefinition, BlockRegistry, InputBlockDefinition};
|
||||||
|
use model::BlockModelRegistry;
|
||||||
|
use texture::TextureDependencies;
|
||||||
|
use vulkano::{device::Queue, image::view::ImageView};
|
||||||
|
|
||||||
|
use crate::error::Error;
|
||||||
|
|
||||||
|
use super::util::Allocators;
|
||||||
|
|
||||||
pub mod block;
|
pub mod block;
|
||||||
pub mod model;
|
pub mod model;
|
||||||
pub mod texture;
|
pub mod texture;
|
||||||
|
|
||||||
|
#[derive(Debug, Default)]
|
||||||
|
pub struct BlockDefinitionStage {
|
||||||
|
definitions: HashMap<String, InputBlockDefinition>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct TextureLoadStage {
|
||||||
|
dependencies: TextureDependencies,
|
||||||
|
definitions: HashMap<u8, BlockDefinition>,
|
||||||
|
}
|
||||||
|
pub struct RuntimeStage;
|
||||||
|
|
||||||
|
pub struct AssetLoader<S> {
|
||||||
|
stage: S,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AssetLoader<BlockDefinitionStage> {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
stage: Default::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn load_block_definitions(
|
||||||
|
mut self,
|
||||||
|
) -> Result<(AssetLoader<TextureLoadStage>, BlockRegistry), Error> {
|
||||||
|
const PATH: &str = "assets/blocks";
|
||||||
|
for entry in
|
||||||
|
glob::glob(&format!("{PATH}/*.json")).map_err(|_| Error::AssetMissing(PATH.into()))?
|
||||||
|
{
|
||||||
|
let Ok(entry) = entry else { continue };
|
||||||
|
let Some(name) = entry.file_stem().and_then(|e| e.to_str()) else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Err(error) = self.load_definition(name, &entry) {
|
||||||
|
log::error!("block:{name} load error: {error}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut block_registry = BlockRegistry::new();
|
||||||
|
let mut definitions = HashMap::new();
|
||||||
|
let mut id = 1;
|
||||||
|
for (name, definition) in self.stage.definitions.into_iter() {
|
||||||
|
let sided_model = definition.model.to_sided();
|
||||||
|
|
||||||
|
block_registry.add(name, id);
|
||||||
|
definitions.insert(id, BlockDefinition { model: sided_model });
|
||||||
|
|
||||||
|
id += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
let texture_dependencies =
|
||||||
|
TextureDependencies::from_block_definitions(definitions.values());
|
||||||
|
|
||||||
|
Ok((
|
||||||
|
AssetLoader {
|
||||||
|
stage: TextureLoadStage {
|
||||||
|
dependencies: texture_dependencies,
|
||||||
|
definitions,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
block_registry,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn load_definition<P: AsRef<Path>>(&mut self, name: &str, path: P) -> Result<(), Error> {
|
||||||
|
if self.stage.definitions.contains_key(name) {
|
||||||
|
log::warn!("Ignoring duplicate block:{name}");
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
let file = File::open(path)?;
|
||||||
|
let definition = serde_json::from_reader(file).unwrap();
|
||||||
|
self.stage.definitions.insert(name.into(), definition);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AssetLoader<TextureLoadStage> {
|
||||||
|
pub fn load_textures(
|
||||||
|
self,
|
||||||
|
allocators: Arc<Allocators>,
|
||||||
|
queue: Arc<Queue>,
|
||||||
|
) -> Result<
|
||||||
|
(
|
||||||
|
AssetLoader<RuntimeStage>,
|
||||||
|
BlockModelRegistry,
|
||||||
|
Arc<ImageView>,
|
||||||
|
),
|
||||||
|
Error,
|
||||||
|
> {
|
||||||
|
let (texture_map, texture_view) = self.stage.dependencies.load(allocators, queue)?;
|
||||||
|
|
||||||
|
let mut block_model_registry = BlockModelRegistry::new();
|
||||||
|
for (id, block) in self.stage.definitions {
|
||||||
|
block_model_registry.add(id, block.model.resolve(&texture_map));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok((
|
||||||
|
AssetLoader {
|
||||||
|
stage: RuntimeStage,
|
||||||
|
},
|
||||||
|
block_model_registry,
|
||||||
|
texture_view,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use super::texture::FALLBACK_TEXTURE_ID;
|
||||||
|
|
||||||
pub struct BlockModelRegistry {
|
pub struct BlockModelRegistry {
|
||||||
pub id_to_model: HashMap<u8, CubeTextures>,
|
pub id_to_model: HashMap<u8, CubeTextures>,
|
||||||
pub fallback: CubeTextures,
|
pub fallback: CubeTextures,
|
||||||
@@ -28,6 +30,17 @@ impl CubeTextures {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl BlockModelRegistry {
|
impl BlockModelRegistry {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
id_to_model: HashMap::new(),
|
||||||
|
fallback: CubeTextures::single(FALLBACK_TEXTURE_ID),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add(&mut self, id: u8, model: CubeTextures) {
|
||||||
|
self.id_to_model.insert(id, model);
|
||||||
|
}
|
||||||
|
|
||||||
pub fn get_or_fallback(&self, id: u8) -> &CubeTextures {
|
pub fn get_or_fallback(&self, id: u8) -> &CubeTextures {
|
||||||
self.id_to_model.get(&id).unwrap_or(&self.fallback)
|
self.id_to_model.get(&id).unwrap_or(&self.fallback)
|
||||||
}
|
}
|
||||||
|
|||||||
+78
-53
@@ -1,5 +1,5 @@
|
|||||||
use std::{
|
use std::{
|
||||||
collections::{hash_map::Entry, HashMap},
|
collections::{HashMap, HashSet},
|
||||||
fs::File,
|
fs::File,
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
sync::Arc,
|
sync::Arc,
|
||||||
@@ -18,6 +18,10 @@ use vulkano::{
|
|||||||
|
|
||||||
use crate::{error::Error, render::util::Allocators};
|
use crate::{error::Error, render::util::Allocators};
|
||||||
|
|
||||||
|
use super::block::{BlockDefinition, SidedModelDefinition};
|
||||||
|
|
||||||
|
pub const FALLBACK_TEXTURE_ID: u32 = 0;
|
||||||
|
|
||||||
pub trait TextureReader {
|
pub trait TextureReader {
|
||||||
fn format(&self) -> Format;
|
fn format(&self) -> Format;
|
||||||
fn width(&self) -> u32;
|
fn width(&self) -> u32;
|
||||||
@@ -34,64 +38,14 @@ pub struct TextureArrayBuilder {
|
|||||||
readers: Vec<Box<dyn TextureReader>>,
|
readers: Vec<Box<dyn TextureReader>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct TextureRegistryBuilder {
|
pub struct TextureDependencies {
|
||||||
inner: TextureArrayBuilder,
|
set: HashSet<String>,
|
||||||
map: HashMap<String, u32>,
|
|
||||||
last_texture_id: u32,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct PngTextureReader {
|
pub struct PngTextureReader {
|
||||||
reader: png::Reader<File>,
|
reader: png::Reader<File>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TextureRegistryBuilder {
|
|
||||||
pub fn new(
|
|
||||||
device: Arc<Device>,
|
|
||||||
allocators: Arc<Allocators>,
|
|
||||||
width: u32,
|
|
||||||
height: u32,
|
|
||||||
) -> Result<Self, Error> {
|
|
||||||
let inner = TextureArrayBuilder::new(device, allocators, width, height);
|
|
||||||
let mut this = Self {
|
|
||||||
inner,
|
|
||||||
last_texture_id: 0,
|
|
||||||
map: HashMap::new(),
|
|
||||||
};
|
|
||||||
|
|
||||||
this.add_texture("fallback")?;
|
|
||||||
|
|
||||||
Ok(this)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn add_texture(&mut self, name: &str) -> Result<u32, Error> {
|
|
||||||
match self.map.entry(name.into()) {
|
|
||||||
Entry::Occupied(entry) => Ok(*entry.get()),
|
|
||||||
Entry::Vacant(entry) => {
|
|
||||||
let id = self.last_texture_id;
|
|
||||||
let path = PathBuf::from("assets/textures/blocks").join(format!("{name}.png"));
|
|
||||||
|
|
||||||
self.inner.push(Box::new(PngTextureReader::open(&path)?));
|
|
||||||
log::info!("{path:?} -> {id}");
|
|
||||||
self.last_texture_id += 1;
|
|
||||||
|
|
||||||
entry.insert(id);
|
|
||||||
|
|
||||||
Ok(id)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn add_texture_or_fallback(&mut self, name: &str) -> u32 {
|
|
||||||
self.add_texture(name)
|
|
||||||
.inspect_err(|e| log::error!("Failed to load texture {name}: {e}"))
|
|
||||||
.unwrap_or(0)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn upload(self, queue: Arc<Queue>) -> Result<Arc<ImageView>, Error> {
|
|
||||||
self.inner.upload(queue)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PngTextureReader {
|
impl PngTextureReader {
|
||||||
pub fn open<P: AsRef<Path>>(path: P) -> Result<Self, Error> {
|
pub fn open<P: AsRef<Path>>(path: P) -> Result<Self, Error> {
|
||||||
let file = File::open(path)?;
|
let file = File::open(path)?;
|
||||||
@@ -214,3 +168,74 @@ impl TextureArrayBuilder {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl TextureDependencies {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
set: HashSet::from_iter(["fallback".into()]),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn from_block_definitions<'a, I: IntoIterator<Item = &'a BlockDefinition>>(
|
||||||
|
iter: I,
|
||||||
|
) -> Self {
|
||||||
|
let mut this = Self::new();
|
||||||
|
for def in iter {
|
||||||
|
this.from_definition(&def.model);
|
||||||
|
}
|
||||||
|
this
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add(&mut self, name: &str) {
|
||||||
|
self.set.insert(name.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn from_definition(&mut self, definition: &SidedModelDefinition) {
|
||||||
|
self.add(&definition.front);
|
||||||
|
self.add(&definition.left);
|
||||||
|
self.add(&definition.back);
|
||||||
|
self.add(&definition.right);
|
||||||
|
self.add(&definition.bottom);
|
||||||
|
self.add(&definition.top);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn load(
|
||||||
|
self,
|
||||||
|
allocators: Arc<Allocators>,
|
||||||
|
queue: Arc<Queue>,
|
||||||
|
) -> Result<(HashMap<String, u32>, Arc<ImageView>), Error> {
|
||||||
|
const TEXTURE_PATH: &str = "assets/textures/blocks";
|
||||||
|
let base = PathBuf::from(TEXTURE_PATH);
|
||||||
|
|
||||||
|
let device = queue.device().clone();
|
||||||
|
let mut builder = TextureArrayBuilder::new(device, allocators, 32, 32);
|
||||||
|
|
||||||
|
// Add fallback texture
|
||||||
|
builder.push(Box::new(PngTextureReader::open(base.join("fallback.png"))?));
|
||||||
|
|
||||||
|
let mut id = 1;
|
||||||
|
let mut map = HashMap::new();
|
||||||
|
for name in self.set {
|
||||||
|
if name == "fallback" {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let reader = match PngTextureReader::open(base.join(format!("{name}.png"))) {
|
||||||
|
Ok(reader) => reader,
|
||||||
|
Err(error) => {
|
||||||
|
log::error!("Texture \"{name}\" load error: {error}");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
builder.push(Box::new(reader));
|
||||||
|
log::info!("{name} -> {id}");
|
||||||
|
map.insert(name, id);
|
||||||
|
id += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
let texture_view = builder.upload(queue)?;
|
||||||
|
|
||||||
|
Ok((map, texture_view))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
+3
-2
@@ -90,7 +90,7 @@ impl ChunkMesh {
|
|||||||
let (bx, bz) = coords.block_coords(x, z);
|
let (bx, bz) = coords.block_coords(x, z);
|
||||||
|
|
||||||
let textures = block_model_registry.get_or_fallback(id);
|
let textures = block_model_registry.get_or_fallback(id);
|
||||||
builder.add_cube(bx, y as i32, bz, nq, &textures);
|
builder.add_cube(bx, y as i32, bz, nq, textures);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -208,7 +208,8 @@ impl MeshBuilder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn upload(self, allocators: &Arc<Allocators>) -> Result<Subbuffer<[InputVertex]>, Error> {
|
pub fn upload(self, allocators: &Arc<Allocators>) -> Result<Subbuffer<[InputVertex]>, Error> {
|
||||||
log::info!("Upload {} vertices", self.vertices.len());
|
log::debug!("Upload {} vertices", self.vertices.len());
|
||||||
|
|
||||||
let buffer = Buffer::from_iter(
|
let buffer = Buffer::from_iter(
|
||||||
allocators.memory.clone(),
|
allocators.memory.clone(),
|
||||||
BufferCreateInfo {
|
BufferCreateInfo {
|
||||||
|
|||||||
+12
-41
@@ -3,7 +3,7 @@ use std::{
|
|||||||
time::{Duration, Instant},
|
time::{Duration, Instant},
|
||||||
};
|
};
|
||||||
|
|
||||||
use asset::{block::BlockRegistryBuilder, texture::TextureRegistryBuilder};
|
use asset::{AssetLoader, TextureLoadStage};
|
||||||
use glam::{Mat4, Vec3};
|
use glam::{Mat4, Vec3};
|
||||||
use mesh::WorldMeshState;
|
use mesh::WorldMeshState;
|
||||||
use util::{Allocators, ViewportExt};
|
use util::{Allocators, ViewportExt};
|
||||||
@@ -38,7 +38,7 @@ use winit::window::Window;
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
error::Error,
|
error::Error,
|
||||||
world::{Chunk, ChunkCoords, NoiseChunkGenerator, World},
|
world::{Chunk, ChunkCoords, World},
|
||||||
};
|
};
|
||||||
|
|
||||||
pub mod asset;
|
pub mod asset;
|
||||||
@@ -73,12 +73,8 @@ pub struct Renderer {
|
|||||||
fragment_shader: EntryPoint,
|
fragment_shader: EntryPoint,
|
||||||
pipeline: Arc<GraphicsPipeline>,
|
pipeline: Arc<GraphicsPipeline>,
|
||||||
|
|
||||||
world: World,
|
|
||||||
g: NoiseChunkGenerator,
|
|
||||||
|
|
||||||
render_world: WorldMeshState,
|
render_world: WorldMeshState,
|
||||||
|
|
||||||
start: Instant,
|
|
||||||
fps: Fps,
|
fps: Fps,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -111,7 +107,10 @@ impl Fps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Renderer {
|
impl Renderer {
|
||||||
pub fn from_window(window: Arc<Window>) -> Result<Self, Error> {
|
pub fn from_window(
|
||||||
|
window: Arc<Window>,
|
||||||
|
asset_loader: AssetLoader<TextureLoadStage>,
|
||||||
|
) -> Result<Self, Error> {
|
||||||
let instance_extensions = Surface::required_extensions(&window)?;
|
let instance_extensions = Surface::required_extensions(&window)?;
|
||||||
let device_extensions = DeviceExtensions {
|
let device_extensions = DeviceExtensions {
|
||||||
khr_swapchain: true,
|
khr_swapchain: true,
|
||||||
@@ -187,21 +186,8 @@ impl Renderer {
|
|||||||
.entry_point("main")
|
.entry_point("main")
|
||||||
.expect("shader entry point");
|
.expect("shader entry point");
|
||||||
|
|
||||||
let texture_registry_builder =
|
let (_, block_model_registry, texture_view) =
|
||||||
TextureRegistryBuilder::new(device.clone(), allocators.clone(), 32, 32)?;
|
asset_loader.load_textures(allocators.clone(), queue.clone())?;
|
||||||
let mut block_registry_builder = BlockRegistryBuilder::new(texture_registry_builder);
|
|
||||||
|
|
||||||
for name in ["dirt", "grass", "stone"] {
|
|
||||||
if let Err(error) = block_registry_builder.load(name) {
|
|
||||||
log::error!("Block {name} load error: {error}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let (block_model_registry, block_registry, texture_view) =
|
|
||||||
block_registry_builder.build(queue.clone())?;
|
|
||||||
|
|
||||||
let world = World::new(block_registry);
|
|
||||||
let g = NoiseChunkGenerator::from_seed(1234);
|
|
||||||
|
|
||||||
let render_world = WorldMeshState::new(block_model_registry);
|
let render_world = WorldMeshState::new(block_model_registry);
|
||||||
|
|
||||||
@@ -225,8 +211,6 @@ impl Renderer {
|
|||||||
vec![sampler.clone()],
|
vec![sampler.clone()],
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
let start = Instant::now();
|
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
window,
|
window,
|
||||||
device,
|
device,
|
||||||
@@ -251,16 +235,13 @@ impl Renderer {
|
|||||||
sampler,
|
sampler,
|
||||||
texture_view,
|
texture_view,
|
||||||
|
|
||||||
world,
|
|
||||||
g,
|
|
||||||
render_world,
|
render_world,
|
||||||
|
|
||||||
start,
|
|
||||||
fps: Fps::new(),
|
fps: Fps::new(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn render(&mut self) -> Result<(), Error> {
|
pub fn render(&mut self, camera_position: Vec3, world: &mut World) -> Result<(), Error> {
|
||||||
self.fps.new_frame();
|
self.fps.new_frame();
|
||||||
self.fps.update();
|
self.fps.update();
|
||||||
|
|
||||||
@@ -287,8 +268,6 @@ impl Renderer {
|
|||||||
let dimensions = self.window.inner_size();
|
let dimensions = self.window.inner_size();
|
||||||
let aspect = dimensions.width as f32 / dimensions.height as f32;
|
let aspect = dimensions.width as f32 / dimensions.height as f32;
|
||||||
|
|
||||||
let delta = self.start.elapsed();
|
|
||||||
let dt = delta.as_secs_f64() * 1.0;
|
|
||||||
let model = Mat4::IDENTITY;
|
let model = Mat4::IDENTITY;
|
||||||
let projection = Mat4::perspective_rh_gl(45.0f32.to_radians(), aspect, 0.01, 1000.0);
|
let projection = Mat4::perspective_rh_gl(45.0f32.to_radians(), aspect, 0.01, 1000.0);
|
||||||
|
|
||||||
@@ -296,14 +275,6 @@ impl Renderer {
|
|||||||
Mat4::look_at_rh(camera, target, Vec3::Y)
|
Mat4::look_at_rh(camera, target, Vec3::Y)
|
||||||
}
|
}
|
||||||
|
|
||||||
let camera_position = Vec3::new(
|
|
||||||
(dt.cos() * 32.0) as f32 + (Chunk::SIZE / 2) as f32,
|
|
||||||
48.0,
|
|
||||||
(dt.sin() * 32.0) as f32 + (Chunk::SIZE / 2) as f32,
|
|
||||||
);
|
|
||||||
|
|
||||||
self.world.update_with_camera(camera_position, &mut self.g);
|
|
||||||
|
|
||||||
let view = make_view(
|
let view = make_view(
|
||||||
camera_position,
|
camera_position,
|
||||||
Vec3::new((Chunk::SIZE / 2) as f32, 32.0, (Chunk::SIZE / 2) as f32),
|
Vec3::new((Chunk::SIZE / 2) as f32, 32.0, (Chunk::SIZE / 2) as f32),
|
||||||
@@ -387,9 +358,9 @@ impl Renderer {
|
|||||||
for x in -VIEW_DISTANCE..=VIEW_DISTANCE {
|
for x in -VIEW_DISTANCE..=VIEW_DISTANCE {
|
||||||
for z in -VIEW_DISTANCE..=VIEW_DISTANCE {
|
for z in -VIEW_DISTANCE..=VIEW_DISTANCE {
|
||||||
let coords = ChunkCoords::new(x + camera_cx, z + camera_cz);
|
let coords = ChunkCoords::new(x + camera_cx, z + camera_cz);
|
||||||
let mesh =
|
let mesh = self
|
||||||
self.render_world
|
.render_world
|
||||||
.get_or_upload(&self.allocators, coords, &self.world)?;
|
.get_or_upload(&self.allocators, coords, world)?;
|
||||||
|
|
||||||
if let Some(mesh) = mesh {
|
if let Some(mesh) = mesh {
|
||||||
builder.bind_vertex_buffers(0, mesh.vertex_buffer.clone())?;
|
builder.bind_vertex_buffers(0, mesh.vertex_buffer.clone())?;
|
||||||
|
|||||||
@@ -0,0 +1,99 @@
|
|||||||
|
use super::NeighborQuery;
|
||||||
|
|
||||||
|
pub struct Chunk {
|
||||||
|
blocks: [u8; Self::SIZE * Self::SIZE * Self::HEIGHT],
|
||||||
|
height_map: [u32; Self::SIZE * Self::SIZE],
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||||
|
pub struct ChunkCoords {
|
||||||
|
pub x: i32,
|
||||||
|
pub z: i32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ChunkCoords {
|
||||||
|
pub const fn new(x: i32, z: i32) -> Self {
|
||||||
|
Self { x, z }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn origin_block(self) -> (i32, i32) {
|
||||||
|
(self.x * Chunk::SIZE as i32, self.z * Chunk::SIZE as i32)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn block_coords(self, x: u32, z: u32) -> (i32, i32) {
|
||||||
|
let (cx, cz) = self.origin_block();
|
||||||
|
(cx + x as i32, cz + z as i32)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Chunk {
|
||||||
|
pub const SIZE: usize = 32;
|
||||||
|
pub const HEIGHT: usize = 128;
|
||||||
|
|
||||||
|
pub fn empty() -> Self {
|
||||||
|
Self {
|
||||||
|
blocks: [0; Self::SIZE * Self::SIZE * Self::HEIGHT],
|
||||||
|
height_map: [0; Self::SIZE * Self::SIZE],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn fill_layer<F: Fn(u32, u32) -> u8>(&mut self, y: u32, f: F) {
|
||||||
|
for x in 0..Self::SIZE as u32 {
|
||||||
|
for z in 0..Self::SIZE as u32 {
|
||||||
|
self.set_id(x, y, z, f(x, z));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_id(&self, x: u32, y: u32, z: u32) -> u8 {
|
||||||
|
self.blocks[Self::to_index(x, y, z)]
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_id(&mut self, x: u32, y: u32, z: u32, id: u8) {
|
||||||
|
self.blocks[Self::to_index(x, y, z)] = id;
|
||||||
|
|
||||||
|
if id != 0 && y >= self.top_height_at(x, z) {
|
||||||
|
self.set_top_height_at(x, z, y + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_top_height_at(&mut self, x: u32, z: u32, h: u32) {
|
||||||
|
self.height_map[Self::to_height_index(x, z)] = h;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn top_height_at(&self, x: u32, z: u32) -> u32 {
|
||||||
|
self.height_map[Self::to_height_index(x, z)]
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn neighbor_query(&self, x: u32, y: u32, z: u32) -> NeighborQuery {
|
||||||
|
let mut q = NeighborQuery::empty();
|
||||||
|
if z != 0 && self.get_id(x, y, z - 1) != 0 {
|
||||||
|
q |= NeighborQuery::FRONT;
|
||||||
|
}
|
||||||
|
if x != 0 && self.get_id(x - 1, y, z) != 0 {
|
||||||
|
q |= NeighborQuery::LEFT;
|
||||||
|
}
|
||||||
|
if z as usize != Self::SIZE - 1 && self.get_id(x, y, z + 1) != 0 {
|
||||||
|
q |= NeighborQuery::BACK;
|
||||||
|
}
|
||||||
|
if x as usize != Self::SIZE - 1 && self.get_id(x + 1, y, z) != 0 {
|
||||||
|
q |= NeighborQuery::RIGHT;
|
||||||
|
}
|
||||||
|
if y != 0 && self.get_id(x, y - 1, z) != 0 {
|
||||||
|
q |= NeighborQuery::BOTTOM;
|
||||||
|
}
|
||||||
|
if y as usize != Self::HEIGHT - 1 && self.get_id(x, y + 1, z) != 0 {
|
||||||
|
q |= NeighborQuery::TOP;
|
||||||
|
}
|
||||||
|
q
|
||||||
|
}
|
||||||
|
|
||||||
|
const fn to_index(x: u32, y: u32, z: u32) -> usize {
|
||||||
|
x as usize + (z as usize + y as usize * Self::SIZE) * Self::SIZE
|
||||||
|
}
|
||||||
|
|
||||||
|
const fn to_height_index(x: u32, z: u32) -> usize {
|
||||||
|
x as usize + z as usize * Self::SIZE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
use noise::NoiseFn;
|
||||||
|
|
||||||
|
use crate::render::asset::block::BlockRegistry;
|
||||||
|
|
||||||
|
use super::chunk::{Chunk, ChunkCoords};
|
||||||
|
|
||||||
|
pub struct NoiseChunkGenerator {
|
||||||
|
noise: noise::Perlin,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ChunkGenerator for NoiseChunkGenerator {
|
||||||
|
fn generate_terrain(
|
||||||
|
&mut self,
|
||||||
|
coords: ChunkCoords,
|
||||||
|
chunk: &mut Chunk,
|
||||||
|
block_registry: &BlockRegistry,
|
||||||
|
) {
|
||||||
|
let grass = block_registry.get("grass").unwrap_or(0);
|
||||||
|
let dirt = block_registry.get("dirt").unwrap_or(0);
|
||||||
|
let stone = block_registry.get("stone").unwrap_or(0);
|
||||||
|
|
||||||
|
for ix in 0..Chunk::SIZE as u32 {
|
||||||
|
for iz in 0..Chunk::SIZE as u32 {
|
||||||
|
let (x, z) = coords.block_coords(ix, iz);
|
||||||
|
let x = x as f64;
|
||||||
|
let z = z as f64;
|
||||||
|
|
||||||
|
let n0 = self.noise.get([x / 256.0, 1.0, z / 256.0]) * 16.0;
|
||||||
|
let n1 = self.noise.get([x / 64.0, 0.0, z / 64.0]) * 4.0;
|
||||||
|
let n2 = self.noise.get([x / 16.0, 1.0, z / 16.0]) * 2.0;
|
||||||
|
let h = (n0 + n1 + n2 + 32.0) as u32;
|
||||||
|
|
||||||
|
let sn0 = self.noise.get([x / 16.0, 0.0, z / 16.0]) * 2.0;
|
||||||
|
let stone_depth = (sn0 + 4.0) as u32;
|
||||||
|
|
||||||
|
for y in 0..h {
|
||||||
|
let id = if y + 1 == h {
|
||||||
|
grass
|
||||||
|
} else if h - y > stone_depth {
|
||||||
|
stone
|
||||||
|
} else {
|
||||||
|
dirt
|
||||||
|
};
|
||||||
|
chunk.set_id(ix, y, iz, id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl NoiseChunkGenerator {
|
||||||
|
pub fn from_seed(seed: u64) -> Self {
|
||||||
|
let noise = noise::Perlin::new(seed as _);
|
||||||
|
Self { noise }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait ChunkGenerator {
|
||||||
|
fn generate_terrain(
|
||||||
|
&mut self,
|
||||||
|
coords: ChunkCoords,
|
||||||
|
chunk: &mut Chunk,
|
||||||
|
block_registry: &BlockRegistry,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use glam::Vec3;
|
||||||
|
|
||||||
|
use crate::render::asset::block::BlockRegistry;
|
||||||
|
|
||||||
|
use super::{Chunk, ChunkCoords, ChunkGenerator};
|
||||||
|
|
||||||
|
pub struct World {
|
||||||
|
chunks: HashMap<ChunkCoords, Chunk>,
|
||||||
|
block_registry: BlockRegistry,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl World {
|
||||||
|
pub fn new(block_registry: BlockRegistry) -> Self {
|
||||||
|
Self {
|
||||||
|
chunks: HashMap::new(),
|
||||||
|
block_registry,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn generate<G: ChunkGenerator + ?Sized>(&mut self, coords: ChunkCoords, g: &mut G) {
|
||||||
|
assert!(!self.chunks.contains_key(&coords));
|
||||||
|
log::debug!("Generate {coords:?}");
|
||||||
|
|
||||||
|
let mut chunk = Chunk::empty();
|
||||||
|
g.generate_terrain(coords, &mut chunk, &self.block_registry);
|
||||||
|
self.chunks.insert(coords, chunk);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get(&self, coords: ChunkCoords) -> Option<&Chunk> {
|
||||||
|
self.chunks.get(&coords)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update_with_camera<G: ChunkGenerator + ?Sized>(
|
||||||
|
&mut self,
|
||||||
|
camera_position: Vec3,
|
||||||
|
g: &mut G,
|
||||||
|
) {
|
||||||
|
const VIEW_DISTANCE: i32 = 5;
|
||||||
|
|
||||||
|
fn cpos(x: f32) -> i32 {
|
||||||
|
(x as i32 + Chunk::SIZE as i32 / 2) / Chunk::SIZE as i32
|
||||||
|
}
|
||||||
|
|
||||||
|
let camera_cx = cpos(camera_position.x);
|
||||||
|
let camera_cz = cpos(camera_position.z);
|
||||||
|
|
||||||
|
for cx in camera_cx - VIEW_DISTANCE..=camera_cx + VIEW_DISTANCE {
|
||||||
|
for cz in camera_cz - VIEW_DISTANCE..=camera_cz + VIEW_DISTANCE {
|
||||||
|
let coords = ChunkCoords::new(cx, cz);
|
||||||
|
|
||||||
|
if !self.chunks.contains_key(&coords) {
|
||||||
|
self.generate(coords, g);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
+6
-209
@@ -1,144 +1,12 @@
|
|||||||
use std::{collections::HashMap, hash::Hash};
|
|
||||||
|
|
||||||
use bitflags::bitflags;
|
use bitflags::bitflags;
|
||||||
use glam::Vec3;
|
|
||||||
use noise::NoiseFn;
|
|
||||||
|
|
||||||
use crate::render::asset::block::BlockRegistry;
|
pub mod chunk;
|
||||||
|
pub mod generator;
|
||||||
|
pub mod level;
|
||||||
|
|
||||||
pub struct Chunk {
|
pub use chunk::{Chunk, ChunkCoords};
|
||||||
blocks: [u8; Self::SIZE * Self::SIZE * Self::HEIGHT],
|
pub use generator::{ChunkGenerator, NoiseChunkGenerator};
|
||||||
height_map: [u32; Self::SIZE * Self::SIZE],
|
pub use level::World;
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
|
||||||
pub struct ChunkCoords {
|
|
||||||
pub x: i32,
|
|
||||||
pub z: i32,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ChunkCoords {
|
|
||||||
pub const fn new(x: i32, z: i32) -> Self {
|
|
||||||
Self { x, z }
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn origin_block(self) -> (i32, i32) {
|
|
||||||
(self.x * Chunk::SIZE as i32, self.z * Chunk::SIZE as i32)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn block_coords(self, x: u32, z: u32) -> (i32, i32) {
|
|
||||||
let (cx, cz) = self.origin_block();
|
|
||||||
(cx + x as i32, cz + z as i32)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct World {
|
|
||||||
chunks: HashMap<ChunkCoords, Chunk>,
|
|
||||||
block_registry: BlockRegistry,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl World {
|
|
||||||
pub fn new(block_registry: BlockRegistry) -> Self {
|
|
||||||
Self {
|
|
||||||
chunks: HashMap::new(),
|
|
||||||
block_registry,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn generate<G: ChunkGenerator>(&mut self, coords: ChunkCoords, g: &mut G) {
|
|
||||||
assert!(!self.chunks.contains_key(&coords));
|
|
||||||
|
|
||||||
let mut chunk = Chunk::empty();
|
|
||||||
g.generate_terrain(coords, &mut chunk, &self.block_registry);
|
|
||||||
self.chunks.insert(coords, chunk);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get(&self, coords: ChunkCoords) -> Option<&Chunk> {
|
|
||||||
self.chunks.get(&coords)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn update_with_camera<G: ChunkGenerator>(&mut self, camera_position: Vec3, g: &mut G) {
|
|
||||||
const VIEW_DISTANCE: i32 = 5;
|
|
||||||
|
|
||||||
fn cpos(x: f32) -> i32 {
|
|
||||||
(x as i32 + Chunk::SIZE as i32 / 2) / Chunk::SIZE as i32
|
|
||||||
}
|
|
||||||
|
|
||||||
let camera_cx = cpos(camera_position.x);
|
|
||||||
let camera_cz = cpos(camera_position.z);
|
|
||||||
|
|
||||||
for cx in camera_cx - VIEW_DISTANCE..=camera_cx + VIEW_DISTANCE {
|
|
||||||
for cz in camera_cz - VIEW_DISTANCE..=camera_cz + VIEW_DISTANCE {
|
|
||||||
let coords = ChunkCoords::new(cx, cz);
|
|
||||||
|
|
||||||
if !self.chunks.contains_key(&coords) {
|
|
||||||
log::info!("Generate {coords:?}");
|
|
||||||
self.generate(coords, g);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct NoiseChunkGenerator {
|
|
||||||
noise: noise::Perlin,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ChunkGenerator for NoiseChunkGenerator {
|
|
||||||
fn generate_terrain(
|
|
||||||
&mut self,
|
|
||||||
coords: ChunkCoords,
|
|
||||||
chunk: &mut Chunk,
|
|
||||||
block_registry: &BlockRegistry,
|
|
||||||
) {
|
|
||||||
let grass = block_registry.get("grass").unwrap_or(0);
|
|
||||||
let dirt = block_registry.get("dirt").unwrap_or(0);
|
|
||||||
let stone = block_registry.get("stone").unwrap_or(0);
|
|
||||||
|
|
||||||
for ix in 0..Chunk::SIZE as u32 {
|
|
||||||
for iz in 0..Chunk::SIZE as u32 {
|
|
||||||
let (x, z) = coords.block_coords(ix, iz);
|
|
||||||
let x = x as f64;
|
|
||||||
let z = z as f64;
|
|
||||||
|
|
||||||
let n0 = self.noise.get([x / 256.0, 1.0, z / 256.0]) * 16.0;
|
|
||||||
let n1 = self.noise.get([x / 64.0, 0.0, z / 64.0]) * 4.0;
|
|
||||||
let n2 = self.noise.get([x / 16.0, 1.0, z / 16.0]) * 2.0;
|
|
||||||
let h = (n0 + n1 + n2 + 32.0) as u32;
|
|
||||||
|
|
||||||
let sn0 = self.noise.get([x / 16.0, 0.0, z / 16.0]) * 2.0;
|
|
||||||
let stone_depth = (sn0 + 4.0) as u32;
|
|
||||||
|
|
||||||
for y in 0..h {
|
|
||||||
let id = if y + 1 == h {
|
|
||||||
grass
|
|
||||||
} else if h - y > stone_depth {
|
|
||||||
stone
|
|
||||||
} else {
|
|
||||||
dirt
|
|
||||||
};
|
|
||||||
chunk.set_id(ix, y, iz, id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl NoiseChunkGenerator {
|
|
||||||
pub fn from_seed(seed: u64) -> Self {
|
|
||||||
let noise = noise::Perlin::new(seed as _);
|
|
||||||
Self { noise }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait ChunkGenerator {
|
|
||||||
fn generate_terrain(
|
|
||||||
&mut self,
|
|
||||||
coords: ChunkCoords,
|
|
||||||
chunk: &mut Chunk,
|
|
||||||
block_registry: &BlockRegistry,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
bitflags! {
|
bitflags! {
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
@@ -152,74 +20,3 @@ bitflags! {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Chunk {
|
|
||||||
pub const SIZE: usize = 32;
|
|
||||||
pub const HEIGHT: usize = 128;
|
|
||||||
|
|
||||||
pub fn empty() -> Self {
|
|
||||||
Self {
|
|
||||||
blocks: [0; Self::SIZE * Self::SIZE * Self::HEIGHT],
|
|
||||||
height_map: [0; Self::SIZE * Self::SIZE],
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn fill_layer<F: Fn(u32, u32) -> u8>(&mut self, y: u32, f: F) {
|
|
||||||
for x in 0..Self::SIZE as u32 {
|
|
||||||
for z in 0..Self::SIZE as u32 {
|
|
||||||
self.set_id(x, y, z, f(x, z));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_id(&self, x: u32, y: u32, z: u32) -> u8 {
|
|
||||||
self.blocks[Self::to_index(x, y, z)]
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_id(&mut self, x: u32, y: u32, z: u32, id: u8) {
|
|
||||||
self.blocks[Self::to_index(x, y, z)] = id;
|
|
||||||
|
|
||||||
if id != 0 && y >= self.top_height_at(x, z) {
|
|
||||||
self.set_top_height_at(x, z, y + 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_top_height_at(&mut self, x: u32, z: u32, h: u32) {
|
|
||||||
self.height_map[Self::to_height_index(x, z)] = h;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn top_height_at(&self, x: u32, z: u32) -> u32 {
|
|
||||||
self.height_map[Self::to_height_index(x, z)]
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn neighbor_query(&self, x: u32, y: u32, z: u32) -> NeighborQuery {
|
|
||||||
let mut q = NeighborQuery::empty();
|
|
||||||
if z != 0 && self.get_id(x, y, z - 1) != 0 {
|
|
||||||
q |= NeighborQuery::FRONT;
|
|
||||||
}
|
|
||||||
if x != 0 && self.get_id(x - 1, y, z) != 0 {
|
|
||||||
q |= NeighborQuery::LEFT;
|
|
||||||
}
|
|
||||||
if z as usize != Self::SIZE - 1 && self.get_id(x, y, z + 1) != 0 {
|
|
||||||
q |= NeighborQuery::BACK;
|
|
||||||
}
|
|
||||||
if x as usize != Self::SIZE - 1 && self.get_id(x + 1, y, z) != 0 {
|
|
||||||
q |= NeighborQuery::RIGHT;
|
|
||||||
}
|
|
||||||
if y != 0 && self.get_id(x, y - 1, z) != 0 {
|
|
||||||
q |= NeighborQuery::BOTTOM;
|
|
||||||
}
|
|
||||||
if y as usize != Self::HEIGHT - 1 && self.get_id(x, y + 1, z) != 0 {
|
|
||||||
q |= NeighborQuery::TOP;
|
|
||||||
}
|
|
||||||
q
|
|
||||||
}
|
|
||||||
|
|
||||||
const fn to_index(x: u32, y: u32, z: u32) -> usize {
|
|
||||||
x as usize + (z as usize + y as usize * Self::SIZE) * Self::SIZE
|
|
||||||
}
|
|
||||||
|
|
||||||
const fn to_height_index(x: u32, z: u32) -> usize {
|
|
||||||
x as usize + z as usize * Self::SIZE
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user