diff --git a/Cargo.toml b/Cargo.toml index 36aa38a..dc761cb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,6 +12,7 @@ cgmath = "0.17.0" winit = "0.22.2" image = "0.23.6" serde = "1.0.114" +serde_json = "1.0.59" serde_derive = "1.0.114" toml = "0.5.6" gilrs = "0.7.4" diff --git a/levels/test.lvl b/levels/test.lvl new file mode 100644 index 0000000..9cd3732 --- /dev/null +++ b/levels/test.lvl @@ -0,0 +1,28 @@ +{ + "meshes": [ + { + "path": "models/plane.gltf" + } + ], + "objects": [ + { + "mesh_index": 0, + "position": [ + 0.0, + 0.0, + 0.0 + ], + "rotation": [ + 1.0, + 0.0, + 0.0, + 0.0 + ], + "scale": [ + 1.0, + 1.0, + 1.0 + ] + } + ] +} \ No newline at end of file diff --git a/models/plane.bin b/models/plane.bin new file mode 100644 index 0000000..82c9b9d Binary files /dev/null and b/models/plane.bin differ diff --git a/models/plane.blend b/models/plane.blend index d4bd69b..4fbb819 100644 Binary files a/models/plane.blend and b/models/plane.blend differ diff --git a/models/plane.gltf b/models/plane.gltf new file mode 100644 index 0000000..0264b4b --- /dev/null +++ b/models/plane.gltf @@ -0,0 +1,160 @@ +{ + "asset" : { + "generator" : "Khronos glTF Blender I/O v1.4.40", + "version" : "2.0" + }, + "scene" : 0, + "scenes" : [ + { + "name" : "Scene", + "nodes" : [ + 0 + ] + } + ], + "nodes" : [ + { + "mesh" : 0, + "name" : "Plane" + } + ], + "materials" : [ + { + "doubleSided" : true, + "name" : "Material", + "normalTexture" : { + "index" : 0, + "scale" : 5, + "texCoord" : 0 + }, + "pbrMetallicRoughness" : { + "baseColorTexture" : { + "index" : 1, + "texCoord" : 0 + }, + "metallicFactor" : 0, + "roughnessFactor" : 0.5 + } + } + ], + "meshes" : [ + { + "name" : "Plane", + "primitives" : [ + { + "attributes" : { + "POSITION" : 0, + "NORMAL" : 1, + "TANGENT" : 2, + "TEXCOORD_0" : 3 + }, + "indices" : 4, + "material" : 0 + } + ] + } + ], + "textures" : [ + { + "sampler" : 0, + "source" : 0 + }, + { + "sampler" : 0, + "source" : 1 + } + ], + "images" : [ + { + "mimeType" : "image/jpeg", + "name" : "cobblestone_large_01_nor_4k", + "uri" : "textures/cobblestone_large_01_nor_4k.jpg" + }, + { + "mimeType" : "image/jpeg", + "name" : "cobblestone_large_01_diff_4k", + "uri" : "textures/cobblestone_large_01_diff_4k.jpg" + } + ], + "accessors" : [ + { + "bufferView" : 0, + "componentType" : 5126, + "count" : 4, + "max" : [ + 10, + 0, + 10 + ], + "min" : [ + -10, + 0, + -10 + ], + "type" : "VEC3" + }, + { + "bufferView" : 1, + "componentType" : 5126, + "count" : 4, + "type" : "VEC3" + }, + { + "bufferView" : 2, + "componentType" : 5126, + "count" : 4, + "type" : "VEC4" + }, + { + "bufferView" : 3, + "componentType" : 5126, + "count" : 4, + "type" : "VEC2" + }, + { + "bufferView" : 4, + "componentType" : 5123, + "count" : 6, + "type" : "SCALAR" + } + ], + "bufferViews" : [ + { + "buffer" : 0, + "byteLength" : 48, + "byteOffset" : 0 + }, + { + "buffer" : 0, + "byteLength" : 48, + "byteOffset" : 48 + }, + { + "buffer" : 0, + "byteLength" : 64, + "byteOffset" : 96 + }, + { + "buffer" : 0, + "byteLength" : 32, + "byteOffset" : 160 + }, + { + "buffer" : 0, + "byteLength" : 12, + "byteOffset" : 192 + } + ], + "samplers" : [ + { + "magFilter" : 9729, + "minFilter" : 9986 + } + ], + "buffers" : [ + { + "byteLength" : 204, + "uri" : "plane.bin" + } + ] +} diff --git a/models/textures/cobblestone_large_01_diff_4k.jpg b/models/textures/cobblestone_large_01_diff_4k.jpg new file mode 100644 index 0000000..5e7b1a7 Binary files /dev/null and b/models/textures/cobblestone_large_01_diff_4k.jpg differ diff --git a/models/plane.glb b/models/textures/cobblestone_large_01_nor_4k.jpg similarity index 61% rename from models/plane.glb rename to models/textures/cobblestone_large_01_nor_4k.jpg index ac61066..dadc89e 100644 Binary files a/models/plane.glb and b/models/textures/cobblestone_large_01_nor_4k.jpg differ diff --git a/models/missing-texture.jpg b/models/textures/missing-texture.jpg similarity index 100% rename from models/missing-texture.jpg rename to models/textures/missing-texture.jpg diff --git a/src/gameobject.rs b/src/gameobject.rs index f888a72..03fac54 100644 --- a/src/gameobject.rs +++ b/src/gameobject.rs @@ -57,13 +57,17 @@ impl GameObject { } } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Copy)] pub struct GameObjectHandle { pub object_index: usize } impl GameObjectHandle { - pub fn get_game_object<'a>(&mut self, renderer: &'a mut VulkanRenderer) -> Option<&'a mut GameObject> { + pub fn get_game_object<'a>(&self, renderer: &'a VulkanRenderer) -> Option<&'a GameObject> { + renderer.game_data.game_objects.get(self.object_index) + } + + pub fn get_game_object_mut<'a>(&mut self, renderer: &'a mut VulkanRenderer) -> Option<&'a mut GameObject> { renderer.game_data.game_objects.get_mut(self.object_index) } } diff --git a/src/level.rs b/src/level.rs new file mode 100644 index 0000000..ea0e793 --- /dev/null +++ b/src/level.rs @@ -0,0 +1,73 @@ +use std::{error::Error, io::{BufReader, BufWriter}}; +use std::fs::File; +use serde_derive::{Deserialize, Serialize}; + +use crate::{TestGame, gameobject::GameObjectHandle, vulkan::{MeshHandle, VulkanRenderer}}; + +#[derive(Debug, Serialize, Deserialize)] +struct MeshJson { + path: String +} + +#[derive(Debug, Serialize, Deserialize)] +struct ObjectJson { + mesh_index: usize, + position: [f32; 3], + rotation: [f32; 4], + scale: [f32; 3] +} + +#[derive(Debug, Serialize, Deserialize)] +struct LevelJson { + meshes: Vec, + objects: Vec +} + +pub fn load_level(path: &str, game: &mut TestGame, renderer: &mut VulkanRenderer) -> Result, Box> { + let file = File::open(path)?; + let reader = BufReader::new(file); + let level_json: LevelJson = serde_json::from_reader(reader)?; + let meshes: Vec = level_json.meshes.iter().map(|json_mesh| { + let mesh_handles = game.load_gltf(renderer, &json_mesh.path); + // TODO: Add empty parent GO instead of just loading the first mesh + mesh_handles[0].clone() + }).collect(); + let objects: Vec = level_json.objects.iter().map(|json_obj| { + let mut handle = game.add_game_object(renderer, meshes[json_obj.mesh_index].clone()); + let game_object = handle.get_game_object_mut(renderer).unwrap(); + game_object.position = json_obj.position.into(); + game_object.rotation = json_obj.rotation.into(); + game_object.scale = json_obj.scale.into(); + handle + }).collect(); + Ok(objects) +} + +pub fn save_level(path: &str, game: &mut TestGame, renderer: &mut VulkanRenderer) -> Result<(), Box> { + let meshes = game.meshes.iter().map(|mesh_handle| { + MeshJson { + path: mesh_handle.original_path.to_string() + } + }).collect(); + + let objects = game.game_objects.iter().map(|game_object_handle| { + let game_object = game_object_handle.get_game_object(renderer).unwrap(); + ObjectJson { + mesh_index: game_object_handle.object_index, + position: game_object.position.into(), + rotation: game_object.rotation.into(), + scale: game_object.scale.into() + } + }).collect(); + + let level_json = LevelJson { + meshes, + objects + }; + + let file = File::create(path)?; + let writer = BufWriter::new(file); + serde_json::to_writer_pretty(writer, &level_json)?; + + Ok(()) +} \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index af1b0ef..b1380ad 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,4 +1,7 @@ -use cgmath::{Matrix4, vec3, Vector3, Vector4}; +extern crate serde_json; + +use cgmath::{Matrix4, Vector3, Vector4}; +use level::{load_level, save_level}; use winit::event::Event; use crate::config::{LogConfig, RenderConfig}; @@ -15,8 +18,9 @@ mod mesh; mod gameobject; mod player; mod pipelines; +mod level; -struct TestGame { +pub struct TestGame { input: InputState, player: Player, meshes: Vec, @@ -35,7 +39,7 @@ impl TestGame { meshes: vec![], game_objects: vec![], log_config, - texture_index_counter: 1, + texture_index_counter: 0, last_time: 0.0, components: vec![], } @@ -66,7 +70,12 @@ impl Game for TestGame { } if self.input.button_just_released("test") { - renderer.game_data.use_line_pipeline = !renderer.game_data.use_line_pipeline; + println!("Saving..."); + save_level("levels/test.lvl", self, renderer).unwrap(); + println!("Clearing level..."); + self.clear_level(renderer); + println!("Loading..."); + load_level("levels/test.lvl", self, renderer).unwrap(); } if self.input.button_just_pressed("reload_shaders") { @@ -105,15 +114,9 @@ fn _matrix_vector_mul(matrix: &Matrix4, vector: &Vector3) -> Vector3 &mut GameObjectHandle { - let meshes = self.load_gltf(renderer, path); - self.add_game_object(renderer, *meshes.first().unwrap()) - } - - fn add_game_object(&mut self, renderer: &mut VulkanRenderer, mesh: MeshHandle) -> &mut GameObjectHandle { + fn add_game_object(&mut self, renderer: &mut VulkanRenderer, mesh: MeshHandle) -> GameObjectHandle { let obj = GameObject::new(mesh); let obj_handle = renderer.add_game_object(obj, 0); self.game_objects.push(obj_handle); - self.game_objects.last_mut().unwrap() + self.game_objects.last().unwrap().clone() + } + + fn clear_level(&mut self, renderer: &mut VulkanRenderer) { + self.game_objects.clear(); + self.meshes.clear(); + self.texture_index_counter = 0; + renderer.clear_all(); } } diff --git a/src/pipelines.rs b/src/pipelines.rs index eec2eb3..53a2249 100644 --- a/src/pipelines.rs +++ b/src/pipelines.rs @@ -168,7 +168,7 @@ impl Drawcall for LineShader { game_data.line_push_constants.clone()).unwrap(); } - fn create_descriptor_set(self: &Self, game_object: &mut GameObject, renderer: &VulkanRenderer) { + fn create_descriptor_set(self: &Self, _game_object: &mut GameObject, _renderer: &VulkanRenderer) { } diff --git a/src/vulkan.rs b/src/vulkan.rs index d1a7401..111a2eb 100644 --- a/src/vulkan.rs +++ b/src/vulkan.rs @@ -4,7 +4,7 @@ use std::sync::Arc; use std::time::SystemTime; use cgmath::{Matrix4, SquareMatrix}; -use image::{ImageBuffer, ImageFormat, Rgb, Rgba}; +use image::{ImageBuffer, Rgb, Rgba}; use image::buffer::ConvertBuffer; use vulkano::{command_buffer::CommandBuffer, buffer::{BufferUsage, CpuAccessibleBuffer}, image::{ImageLayout, MipmapsCount}}; use vulkano::command_buffer::{AutoCommandBuffer, AutoCommandBufferBuilder, DynamicState}; @@ -67,11 +67,12 @@ pub struct Mesh { pub index_buffer: Arc>, } -#[derive(Debug, Copy, Clone)] +#[derive(Debug, Clone)] pub struct MeshHandle { pub index: usize, pub diffuse_handle: TextureHandle, pub normal_handle: TextureHandle, + pub original_path: String } pub(crate) type TextureHandle = usize; @@ -274,23 +275,6 @@ impl VulkanRenderer { Box::new(LineShader::new(device.clone(), render_pass.clone(), line_vertex_buffer)), ]; - let default_tex = { - let image = image::load_from_memory_with_format(include_bytes!("../models/missing-texture.jpg"), - ImageFormat::Jpeg).unwrap().to_rgba8(); - let image_data = image.into_raw().clone(); - - let (image_view, future) = ImmutableImage::from_iter( - image_data.iter().cloned(), - Dimensions::Dim2d { width: 128, height: 128 }, - Format::R8G8B8A8Unorm, - queue.clone(), - ).unwrap(); - - future.flush().unwrap(); - - image_view - }; - // Dynamic viewports allow us to recreate just the viewport when the window is resized // Otherwise we would have to recreate the whole pipeline. let mut dynamic_state = DynamicState { line_width: None, viewports: None, scissors: None, compare_mask: None, write_mask: None, reference: None }; @@ -321,10 +305,6 @@ impl VulkanRenderer { ).unwrap()); } - - - data.textures.push(default_tex); - // In the loop below we are going to submit commands to the GPU. Submitting a command produces // an object that implements the `GpuFuture` trait, which holds the resources for as long as // they are in use by the GPU. @@ -539,6 +519,12 @@ impl VulkanRenderer { object_index: self.game_data.game_objects.len() - 1 } } + + pub fn clear_all(&mut self) { + self.game_data.game_objects.clear(); + self.game_data.meshes.clear(); + self.game_data.textures.clear(); + } } pub fn start_event_loop(mut renderer: VulkanRenderer, mut game: Box, event_loop: EventLoop<()>) {