diff --git a/Cargo.lock b/Cargo.lock index 6b9630ecd3..a1a0e2c130 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1421,9 +1421,8 @@ dependencies = [ [[package]] name = "dot_vox" -version = "4.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83c18405ef54de0398b77a3ec8394d3a1639e7bf060e1385201e8db40c44ab41" +version = "4.0.0" +source = "git+https://github.com/Imberflur/dot_vox.git#23a877bcaeba0f86035a50bc49a89e5391eb38c7" dependencies = [ "byteorder", "lazy_static", @@ -5744,6 +5743,7 @@ dependencies = [ "dot_vox", "enum-iterator", "hashbrown 0.11.2", + "image", "indexmap", "lazy_static", "num-derive", diff --git a/assets/coliseum.vox b/assets/coliseum.vox new file mode 100644 index 0000000000..49c4453892 --- /dev/null +++ b/assets/coliseum.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:17739eb419f450e2c3441ecb57d792b8c85ea0ac0b06abdb578915a50114606e +size 251151 diff --git a/assets/place.ron b/assets/place.ron new file mode 100644 index 0000000000..ad565852ca --- /dev/null +++ b/assets/place.ron @@ -0,0 +1,3 @@ +(pieces: [ + ("coliseum", (0, 0, 0)), +]) \ No newline at end of file diff --git a/assets/thing.vox b/assets/thing.vox new file mode 100644 index 0000000000..f505d593d1 --- /dev/null +++ b/assets/thing.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9bf2806270a3e6db1cd8910d6e8d7d5699c6bb3f805e2e60f564bc7896fd0010 +size 296562 diff --git a/common/Cargo.toml b/common/Cargo.toml index ab7811d778..5f980ca1be 100644 --- a/common/Cargo.toml +++ b/common/Cargo.toml @@ -47,7 +47,8 @@ rand = "0.8" # Assets common-assets = {package = "veloren-common-assets", path = "assets"} -dot_vox = "4.0" +dot_vox = { git = "https://github.com/Imberflur/dot_vox.git" } +image = { version = "0.23.12", default-features = false, features = ["png"] } # Assets serde_repr = "0.1.6" diff --git a/common/assets/Cargo.toml b/common/assets/Cargo.toml index 4eb316a64c..22fb2f9d3b 100644 --- a/common/assets/Cargo.toml +++ b/common/assets/Cargo.toml @@ -9,7 +9,7 @@ version = "0.10.0" lazy_static = "1.4.0" assets_manager = {version = "0.5.0", features = ["bincode", "ron", "json", "hot-reloading"]} ron = { version = "0.6", default-features = false } -dot_vox = "4.0" +dot_vox = { git = "https://github.com/Imberflur/dot_vox.git" } image = { version = "0.23.12", default-features = false, features = ["png"] } tracing = "0.1" diff --git a/common/src/figure/mod.rs b/common/src/figure/mod.rs index c43c81568f..bccfb99819 100644 --- a/common/src/figure/mod.rs +++ b/common/src/figure/mod.rs @@ -9,8 +9,8 @@ pub use self::{ }; use crate::{ - vol::{IntoFullPosIterator, IntoFullVolIterator, ReadVol, SizedVol, Vox, WriteVol}, - volumes::dyna::Dyna, + vol::{IntoFullPosIterator, IntoFullVolIterator, ReadVol, SizedVol, VolSize, Vox, WriteVol}, + volumes::{chunk::Chunk, dyna::Dyna, vol_grid_3d::VolGrid3d}, }; use dot_vox::DotVoxData; use vek::*; @@ -252,3 +252,93 @@ impl MatSegment { impl From<&DotVoxData> for MatSegment { fn from(dot_vox_data: &DotVoxData) -> Self { Self::from_vox(dot_vox_data, false) } } + +#[derive(Clone, Debug)] +pub struct SscSize; +impl VolSize for SscSize { + const SIZE: Vec3 = Vec3 { + x: 32, + y: 32, + z: 32, + }; +} + +pub type SparseScene = VolGrid3d>; + +impl From<&DotVoxData> for SparseScene { + fn from(dot_vox_data: &DotVoxData) -> Self { + let mut sparse_scene = match VolGrid3d::new() { + Ok(ok) => ok, + Err(_) => panic!(), + }; + + let mut models = dot_vox_data.scene.clone(); + + // Needed to work with vox files without a scene graph + if models.is_empty() { + models.push(( + dot_vox::Transform { + t: [0, 0, 0], + r: [[1, 0, 0], [0, 1, 0], [0, 0, 1]], + }, + 0, + )); + } + + for (transform, model_id) in models { + if let Some(model) = dot_vox_data.models.get(model_id) { + let palette = dot_vox_data + .palette + .iter() + .map(|col| Rgba::from(col.to_ne_bytes()).into()) + .collect::>(); + + // Rotation + let rot = Mat3::from_row_arrays(transform.r).map(|e| e as i32); + // Get the rotated size of the model + let size = rot.map(|e| e.abs() as u32) + * Vec3::new(model.size.x, model.size.y, model.size.z); + // Position of min corner + let pos = Vec3::::from(transform.t) + .map2(size, |m, s| (s, m)) + .map2(rot * Vec3::::one(), |(s, m), f| { + m - (s as i32 + f.min(0) * -1) / 2 + }); + // dbg!(pos); + // Insert required chunks + let min_key = sparse_scene.pos_key(pos); + let max_key = sparse_scene.pos_key(pos + size.map(|e| e as i32 - 1)); + for x in min_key.x..=max_key.x { + for y in min_key.y..=max_key.y { + for z in min_key.z..=max_key.z { + let key = Vec3::new(x, y, z); + if sparse_scene.get_key_arc(key).is_none() { + sparse_scene.insert( + key, + std::sync::Arc::new(Chunk::filled(Cell::empty(), ())), + ); + } + } + } + } + + let offset = (rot + * Vec3::new(model.size.x, model.size.y, model.size.z).map(|e| e as i32)) + .map(|e| if e > 0 { 0 } else { -e - 1 }); + for voxel in &model.voxels { + if let Some(&color) = palette.get(voxel.i as usize) { + sparse_scene + .set( + (rot * Vec3::new(voxel.x, voxel.y, voxel.z).map(|e| i32::from(e))) + + offset + + pos, + Cell::new(color, false, false), + ) + .unwrap(); + } + } + } + } + sparse_scene + } +} diff --git a/voxygen/Cargo.toml b/voxygen/Cargo.toml index 30e7e380bf..a054c4f32d 100644 --- a/voxygen/Cargo.toml +++ b/voxygen/Cargo.toml @@ -89,7 +89,7 @@ crossbeam-utils = "0.8.1" crossbeam-channel = "0.5" # TODO: remove directories-next = "2.0" -dot_vox = "4.0" +dot_vox = { git = "https://github.com/Imberflur/dot_vox.git" } enum-iterator = "0.6" futures-executor = "0.3" guillotiere = "0.6" diff --git a/voxygen/src/controller.rs b/voxygen/src/controller.rs index 0fdbfcc2c4..0dd16f01ff 100644 --- a/voxygen/src/controller.rs +++ b/voxygen/src/controller.rs @@ -174,6 +174,9 @@ impl From<&crate::settings::GamepadSettings> for ControllerSettings { map.entry(settings.game_buttons.swap_loadout) .or_default() .push(GameInput::SwapLoadout); + map.entry(settings.game_buttons.place_vox) + .or_default() + .push(GameInput::PlaceVox); map }, menu_button_map: { diff --git a/voxygen/src/session/mod.rs b/voxygen/src/session/mod.rs index 5792cb46e4..3873afc64e 100644 --- a/voxygen/src/session/mod.rs +++ b/voxygen/src/session/mod.rs @@ -9,9 +9,9 @@ use vek::*; use client::{self, Client}; use common::{ - assets::AssetExt, - comp, + assets::{self, Asset, AssetExt, AssetHandle, DotVoxAsset}, comp::{ + self, inventory::slot::{EquipSlot, Slot}, invite::InviteKind, item::{tool::ToolKind, ItemDef, ItemDesc}, @@ -32,6 +32,7 @@ use common_net::{ msg::{server::InviteAnswer, PresenceKind}, sync::WorldSyncExt, }; +use serde::{Deserialize, Serialize}; use crate::{ audio::sfx::SfxEvent, @@ -54,6 +55,57 @@ enum TickAction { // Disconnected (i.e. go to main menu) Disconnect, } +#[derive(Serialize, Deserialize)] +struct VoxSpec(String, [i32; 3]); + +#[derive(Serialize, Deserialize)] +struct PlaceSpec { + pieces: Vec, +} + +impl Asset for PlaceSpec { + type Loader = assets::RonLoader; + + const EXTENSION: &'static str = "ron"; +} + +impl PlaceSpec { + // pub fn load_watched() -> std::sync::Arc { + // PlaceSpec::load("place") + // } + + pub fn build_place(&self) -> (common::figure::SparseScene, Vec3) { + // TODO add sparse scene combination + //use common::figure::{DynaUnionizer, Segment}; + fn graceful_load_vox(name: &str) -> AssetHandle { + match DotVoxAsset::load(name) { + Ok(dot_vox) => dot_vox, + Err(_) => { + error!("Could not load vox file for placement: {}", name); + DotVoxAsset::load_expect("voxygen.voxel.not_found") + }, + } + } + //let mut unionizer = DynaUnionizer::new(); + //for VoxSpec(specifier, offset) in &self.pieces { + // let seg = Segment::from(graceful_load_vox(&specifier, + // indicator).as_ref()); unionizer = unionizer.add(seg, + // (*offset).into()); + //} + + //unionizer.unify() + let hack = "asset that doesn't exist"; + ( + match self.pieces.get(0) { + Some(VoxSpec(specifier, _offset)) => { + common::figure::SparseScene::from(&graceful_load_vox(&specifier).read().0) + }, + None => common::figure::SparseScene::from(&graceful_load_vox(&hack).read().0), + }, + Vec3::zero(), + ) + } +} pub struct SessionState { scene: Scene, @@ -75,6 +127,14 @@ pub struct SessionState { interactable: Option, saved_zoom_dist: Option, hitboxes: HashMap, + + placing_vox: Vec<( + Vec3, + Arc>, + )>, + last_sent: Option<(Vec3, Block)>, + vox: common::figure::SparseScene, + placepos: Vec3, } /// Represents an active game session (i.e., the one being played). @@ -123,6 +183,13 @@ impl SessionState { interactable: None, saved_zoom_dist: None, hitboxes: HashMap::new(), + + placing_vox: Vec::new(), + last_sent: None, + // let mut place_indicator = PlaceSpec::load_expect("place"); + // TODO: use offset + vox: PlaceSpec::load_expect("place").read().build_place().0, + placepos: Vec3::zero(), } } @@ -268,6 +335,13 @@ impl SessionState { impl PlayState for SessionState { fn enter(&mut self, global_state: &mut GlobalState, _: Direction) { + // Clear vox spawining things + self.placing_vox = Vec::new(); + self.last_sent = None; + // TODO: use offset + self.vox = PlaceSpec::load_expect("place").read().build_place().0; + self.placepos = Vec3::zero(); + // Trap the cursor. global_state.window.grab_cursor(true); @@ -290,6 +364,7 @@ impl PlayState for SessionState { let client = self.client.borrow(); (client.presence(), client.registered()) }; + if client_presence.is_some() { let camera = self.scene.camera_mut(); @@ -766,6 +841,24 @@ impl PlayState for SessionState { client.decline_invite(); } }, + GameInput::PlaceVox if state => { + // start placing + if let Some(build_pos) = build_pos.filter(|_| can_build) { + self.placepos = build_pos.map(|e| e.floor() as i32); + // reload in case vox was changed + self.vox = + PlaceSpec::load_expect("place").read().build_place().0; + self.placing_vox = self + .vox + .iter() + .map(|(key, chunk)| (key, Arc::clone(chunk))) + .collect::>() + .into_iter() + .rev() + .collect(); + self.last_sent = None; + } + }, _ => {}, } } @@ -958,6 +1051,53 @@ impl PlayState for SessionState { .camera_mut() .compute_dependents(&*self.client.borrow().state().terrain()); + // Only send next portion when the previous subchunk finishes sending + if let Some((world_pos, block)) = self.last_sent.as_ref().copied() { + if self + .client + .borrow() + .state() + .terrain() + .get(world_pos) + .map(|b| *b == block) + .unwrap_or(true) + { + self.last_sent = None; + } + } + + if self.last_sent.is_none() { + if let Some((key, chunk)) = self.placing_vox.pop() { + use common::vol::IntoFullVolIterator; + + for (pos, cell) in chunk.full_vol_iter() { + let world_pos = self.vox.key_pos(key) + pos + self.placepos; + match cell.get_color() { + Some(color) => { + let block = Block::new(BlockKind::Misc, color); + self.client.borrow_mut().place_block(world_pos, block); + self.last_sent = Some((world_pos, block)) + }, + None => { + // Comment out this section to not carve out the empty space + let mut client = self.client.borrow_mut(); + // Only remove the block if there is something there + if client + .state() + .terrain() + .get(world_pos) + .map(|block| !block.is_fluid()) + .unwrap_or(false) + { + client.remove_block(world_pos); + self.last_sent = Some((world_pos, Block::empty())) + } + }, + } + } + } + } + // Generate debug info, if needed (it iterates through enough data that we might // as well avoid it unless we need it). let debug_info = global_state diff --git a/voxygen/src/settings/control.rs b/voxygen/src/settings/control.rs index 4b30c54adb..5cd83f84dd 100644 --- a/voxygen/src/settings/control.rs +++ b/voxygen/src/settings/control.rs @@ -170,6 +170,7 @@ impl ControlSettings { GameInput::DeclineGroupInvite => KeyMouse::Key(VirtualKeyCode::N), GameInput::MapZoomIn => KeyMouse::Key(VirtualKeyCode::Plus), GameInput::MapZoomOut => KeyMouse::Key(VirtualKeyCode::Minus), + GameInput::PlaceVox => KeyMouse::Key(VirtualKeyCode::F7), } } } diff --git a/voxygen/src/settings/gamepad.rs b/voxygen/src/settings/gamepad.rs index c45daebecb..4040b51438 100644 --- a/voxygen/src/settings/gamepad.rs +++ b/voxygen/src/settings/gamepad.rs @@ -85,6 +85,7 @@ pub mod con_settings { pub interact: Button, pub toggle_wield: Button, pub swap_loadout: Button, + pub place_vox: Button, } #[derive(Clone, Debug, Serialize, Deserialize)] @@ -175,6 +176,7 @@ pub mod con_settings { interact: Button::Simple(GilButton::North), toggle_wield: Button::Simple(GilButton::West), swap_loadout: Button::Simple(GilButton::LeftThumb), + place_vox: Button::Simple(GilButton::Unknown), } } } diff --git a/voxygen/src/window.rs b/voxygen/src/window.rs index 687a73eb60..ad4a741641 100644 --- a/voxygen/src/window.rs +++ b/voxygen/src/window.rs @@ -79,6 +79,7 @@ pub enum GameInput { DeclineGroupInvite, MapZoomIn, MapZoomOut, + PlaceVox, } impl GameInput { @@ -145,6 +146,7 @@ impl GameInput { GameInput::DeclineGroupInvite => "gameinput.declinegroupinvite", GameInput::MapZoomIn => "gameinput.mapzoomin", GameInput::MapZoomOut => "gameinput.mapzoomout", + GameInput::PlaceVox => "gameinput.placevox", } } @@ -211,6 +213,7 @@ impl GameInput { GameInput::DeclineGroupInvite, GameInput::MapZoomIn, GameInput::MapZoomOut, + GameInput::PlaceVox, ] .iter() .copied()