Better asset loading
This commit is contained in:
Generated
+7
@@ -610,6 +610,7 @@ dependencies = [
|
||||
"bytemuck",
|
||||
"env_logger",
|
||||
"glam",
|
||||
"glob",
|
||||
"log",
|
||||
"noise",
|
||||
"png",
|
||||
@@ -665,6 +666,12 @@ dependencies = [
|
||||
"bytemuck",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "glob"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2"
|
||||
|
||||
[[package]]
|
||||
name = "half"
|
||||
version = "2.6.0"
|
||||
|
||||
@@ -8,6 +8,7 @@ bitflags = "2.9.0"
|
||||
bytemuck = { version = "1.22.0", features = ["derive"] }
|
||||
env_logger = "0.11.8"
|
||||
glam = { version = "0.30.2", features = ["bytemuck"] }
|
||||
glob = "0.3.2"
|
||||
log = "0.4.27"
|
||||
noise = "0.9.0"
|
||||
png = "0.17.16"
|
||||
|
||||
@@ -42,6 +42,9 @@ pub enum Error {
|
||||
AllocateImage(#[from] AllocateImageError),
|
||||
#[error("GPU memory allocation error: {0}")]
|
||||
MemoryAllocator(#[from] MemoryAllocatorError),
|
||||
|
||||
#[error("Missing asset/directory: {0}")]
|
||||
AssetMissing(String),
|
||||
}
|
||||
|
||||
impl From<Box<ValidationError>> for Error {
|
||||
|
||||
+41
-7
@@ -1,9 +1,14 @@
|
||||
#![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 render::Renderer;
|
||||
use glam::Vec3;
|
||||
use render::{
|
||||
asset::{AssetLoader, TextureLoadStage},
|
||||
Renderer,
|
||||
};
|
||||
use winit::{
|
||||
application::ApplicationHandler,
|
||||
dpi::PhysicalSize,
|
||||
@@ -11,26 +16,32 @@ use winit::{
|
||||
event_loop::{ActiveEventLoop, EventLoop},
|
||||
window::{Window, WindowId},
|
||||
};
|
||||
use world::{Chunk, ChunkGenerator, NoiseChunkGenerator, World};
|
||||
|
||||
pub mod error;
|
||||
pub mod render;
|
||||
pub mod world;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Engine {
|
||||
window: Option<Arc<Window>>,
|
||||
renderer: Option<Renderer>,
|
||||
assets: Option<AssetLoader<TextureLoadStage>>,
|
||||
world: Option<World>,
|
||||
camera_position: Vec3,
|
||||
generator: Box<dyn ChunkGenerator>,
|
||||
start: Instant,
|
||||
}
|
||||
|
||||
impl Engine {
|
||||
pub fn ensure_renderer(&mut self, event_loop: &ActiveEventLoop) -> Result<(), Error> {
|
||||
if self.window.is_none() {
|
||||
let assets = self.assets.take().expect("asset loader");
|
||||
let window_attributes = Window::default_attributes()
|
||||
.with_resizable(false)
|
||||
.with_inner_size(PhysicalSize::new(800, 600));
|
||||
|
||||
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.renderer = Some(render);
|
||||
@@ -88,8 +99,19 @@ impl ApplicationHandler for Engine {
|
||||
}
|
||||
}
|
||||
WindowEvent::RedrawRequested => {
|
||||
if let Some(render) = self.renderer.as_mut() {
|
||||
if let Err(error) = render.render() {
|
||||
if let (Some(render), Some(world)) = (self.renderer.as_mut(), self.world.as_mut()) {
|
||||
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}");
|
||||
event_loop.exit();
|
||||
}
|
||||
@@ -124,8 +146,20 @@ impl ApplicationHandler for Engine {
|
||||
}
|
||||
|
||||
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 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)?;
|
||||
|
||||
|
||||
+105
-159
@@ -1,178 +1,124 @@
|
||||
use std::{collections::HashMap, fs::File, path::PathBuf, sync::Arc};
|
||||
use std::collections::HashMap;
|
||||
|
||||
use serde::Deserialize;
|
||||
use vulkano::{device::Queue, image::view::ImageView};
|
||||
|
||||
use crate::error::Error;
|
||||
use crate::render::asset::texture::FALLBACK_TEXTURE_ID;
|
||||
|
||||
use super::{
|
||||
model::{BlockModelRegistry, CubeTextures},
|
||||
texture::TextureRegistryBuilder,
|
||||
};
|
||||
use super::model::CubeTextures;
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct BlockDefinition {
|
||||
pub struct InputBlockDefinition {
|
||||
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 {
|
||||
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 {
|
||||
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> {
|
||||
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 model;
|
||||
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 super::texture::FALLBACK_TEXTURE_ID;
|
||||
|
||||
pub struct BlockModelRegistry {
|
||||
pub id_to_model: HashMap<u8, CubeTextures>,
|
||||
pub fallback: CubeTextures,
|
||||
@@ -28,6 +30,17 @@ impl CubeTextures {
|
||||
}
|
||||
|
||||
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 {
|
||||
self.id_to_model.get(&id).unwrap_or(&self.fallback)
|
||||
}
|
||||
|
||||
+78
-53
@@ -1,5 +1,5 @@
|
||||
use std::{
|
||||
collections::{hash_map::Entry, HashMap},
|
||||
collections::{HashMap, HashSet},
|
||||
fs::File,
|
||||
path::{Path, PathBuf},
|
||||
sync::Arc,
|
||||
@@ -18,6 +18,10 @@ use vulkano::{
|
||||
|
||||
use crate::{error::Error, render::util::Allocators};
|
||||
|
||||
use super::block::{BlockDefinition, SidedModelDefinition};
|
||||
|
||||
pub const FALLBACK_TEXTURE_ID: u32 = 0;
|
||||
|
||||
pub trait TextureReader {
|
||||
fn format(&self) -> Format;
|
||||
fn width(&self) -> u32;
|
||||
@@ -34,64 +38,14 @@ pub struct TextureArrayBuilder {
|
||||
readers: Vec<Box<dyn TextureReader>>,
|
||||
}
|
||||
|
||||
pub struct TextureRegistryBuilder {
|
||||
inner: TextureArrayBuilder,
|
||||
map: HashMap<String, u32>,
|
||||
last_texture_id: u32,
|
||||
pub struct TextureDependencies {
|
||||
set: HashSet<String>,
|
||||
}
|
||||
|
||||
pub struct PngTextureReader {
|
||||
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 {
|
||||
pub fn open<P: AsRef<Path>>(path: P) -> Result<Self, Error> {
|
||||
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 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> {
|
||||
log::info!("Upload {} vertices", self.vertices.len());
|
||||
log::debug!("Upload {} vertices", self.vertices.len());
|
||||
|
||||
let buffer = Buffer::from_iter(
|
||||
allocators.memory.clone(),
|
||||
BufferCreateInfo {
|
||||
|
||||
+12
-41
@@ -3,7 +3,7 @@ use std::{
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
|
||||
use asset::{block::BlockRegistryBuilder, texture::TextureRegistryBuilder};
|
||||
use asset::{AssetLoader, TextureLoadStage};
|
||||
use glam::{Mat4, Vec3};
|
||||
use mesh::WorldMeshState;
|
||||
use util::{Allocators, ViewportExt};
|
||||
@@ -38,7 +38,7 @@ use winit::window::Window;
|
||||
|
||||
use crate::{
|
||||
error::Error,
|
||||
world::{Chunk, ChunkCoords, NoiseChunkGenerator, World},
|
||||
world::{Chunk, ChunkCoords, World},
|
||||
};
|
||||
|
||||
pub mod asset;
|
||||
@@ -73,12 +73,8 @@ pub struct Renderer {
|
||||
fragment_shader: EntryPoint,
|
||||
pipeline: Arc<GraphicsPipeline>,
|
||||
|
||||
world: World,
|
||||
g: NoiseChunkGenerator,
|
||||
|
||||
render_world: WorldMeshState,
|
||||
|
||||
start: Instant,
|
||||
fps: Fps,
|
||||
}
|
||||
|
||||
@@ -111,7 +107,10 @@ impl Fps {
|
||||
}
|
||||
|
||||
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 device_extensions = DeviceExtensions {
|
||||
khr_swapchain: true,
|
||||
@@ -187,21 +186,8 @@ impl Renderer {
|
||||
.entry_point("main")
|
||||
.expect("shader entry point");
|
||||
|
||||
let texture_registry_builder =
|
||||
TextureRegistryBuilder::new(device.clone(), allocators.clone(), 32, 32)?;
|
||||
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 (_, block_model_registry, texture_view) =
|
||||
asset_loader.load_textures(allocators.clone(), queue.clone())?;
|
||||
|
||||
let render_world = WorldMeshState::new(block_model_registry);
|
||||
|
||||
@@ -225,8 +211,6 @@ impl Renderer {
|
||||
vec![sampler.clone()],
|
||||
)?;
|
||||
|
||||
let start = Instant::now();
|
||||
|
||||
Ok(Self {
|
||||
window,
|
||||
device,
|
||||
@@ -251,16 +235,13 @@ impl Renderer {
|
||||
sampler,
|
||||
texture_view,
|
||||
|
||||
world,
|
||||
g,
|
||||
render_world,
|
||||
|
||||
start,
|
||||
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.update();
|
||||
|
||||
@@ -287,8 +268,6 @@ impl Renderer {
|
||||
let dimensions = self.window.inner_size();
|
||||
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 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)
|
||||
}
|
||||
|
||||
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(
|
||||
camera_position,
|
||||
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 z in -VIEW_DISTANCE..=VIEW_DISTANCE {
|
||||
let coords = ChunkCoords::new(x + camera_cx, z + camera_cz);
|
||||
let mesh =
|
||||
self.render_world
|
||||
.get_or_upload(&self.allocators, coords, &self.world)?;
|
||||
let mesh = self
|
||||
.render_world
|
||||
.get_or_upload(&self.allocators, coords, world)?;
|
||||
|
||||
if let Some(mesh) = mesh {
|
||||
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 glam::Vec3;
|
||||
use noise::NoiseFn;
|
||||
|
||||
use crate::render::asset::block::BlockRegistry;
|
||||
pub mod chunk;
|
||||
pub mod generator;
|
||||
pub mod level;
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
);
|
||||
}
|
||||
pub use chunk::{Chunk, ChunkCoords};
|
||||
pub use generator::{ChunkGenerator, NoiseChunkGenerator};
|
||||
pub use level::World;
|
||||
|
||||
bitflags! {
|
||||
#[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