Save surroundings to .vox

This commit is contained in:
Imbris 2020-06-08 02:07:11 -04:00
parent 54960142e2
commit 1b8262caed
10 changed files with 158 additions and 2 deletions

1
Cargo.lock generated
View File

@ -4989,6 +4989,7 @@ version = "0.6.0"
dependencies = [
"authc",
"bincode",
"color_quant",
"criterion",
"crossbeam",
"dot_vox",

View File

@ -353,6 +353,7 @@ magically infused items?"#,
"gameinput.freelook": "Free Look",
"gameinput.autowalk": "Auto Walk",
"gameinput.dance": "Dance",
"gameinput.voxsnap": "Capture Surroundings to .vox",
/// End GameInput section

View File

@ -12,7 +12,7 @@ specs-idvs = { git = "https://gitlab.com/veloren/specs-idvs.git" }
specs = { version = "0.15.1", features = ["serde", "nightly", "storage-event-control"] }
vek = { version = "0.10.0", features = ["serde"] }
dot_vox = "4.0.0"
dot_vox = "4.1.0"
fxhash = "0.2.1"
image = "0.22.3"
mio = "0.6.19"
@ -35,6 +35,7 @@ notify = "5.0.0-pre.2"
indexmap = "1.3.0"
sum_type = "0.2.0"
authc = { git = "https://gitlab.com/veloren/auth.git", rev = "65571ade0d954a0e0bd995fdb314854ff146ab97" }
color_quant = "1.0.1"
[dev-dependencies]
criterion = "0.3"

View File

@ -1,5 +1,6 @@
mod color;
mod dir;
mod vox_capture;
pub const GIT_VERSION: &str = include_str!(concat!(env!("OUT_DIR"), "/githash"));
@ -10,3 +11,4 @@ lazy_static::lazy_static! {
pub use color::*;
pub use dir::*;
pub use vox_capture::*;

View File

@ -0,0 +1,103 @@
use crate::{
terrain::Block,
vol::{ReadVol, Vox},
};
use color_quant::NeuQuant;
use std::path::Path;
use vek::*;
// Given a `ReadVol`, a center position, and a filename
// Saves a 256x256x256 cube of volume data in .vox format
// Uses `color_quant` to keep the color count to the limits imposed by magica
pub fn vox_capture(
vol: &impl ReadVol<Vox = Block>,
center: Vec3<i32>,
save_path: &Path,
) -> Result<String, String> {
// First read block into color and pos vecs
let (positions, colors) = (-128..128)
.flat_map(move |x| {
(-128..128).flat_map(move |y| (-128..128).map(move |z| Vec3::new(x, y, z) + center))
})
.map(|pos| (pos, vol.get(pos).ok().copied().unwrap_or(Block::empty())))
.filter_map(|(pos, block)| {
block.get_color().map(|color| {
(
(pos - center + Vec3::from(128)).map(|e| e as u8),
Rgba::from(color),
)
})
})
.fold(
(Vec::new(), Vec::new()),
|(mut positions, mut colors), (pos, color)| {
positions.push(pos);
colors.extend_from_slice(&color);
(positions, colors)
},
);
// Quantize colors
// dot_vox docs seem to imply there are only 255 (and not 256) indices in
// palette
let quant = NeuQuant::new(10, 255, &colors);
// Extract palette
// Note: palette includes alpha, we could abuse this as alternative to indices
// to store extra info
let palette = quant
.color_map_rgba()
.chunks_exact(4)
.map(|c| {
// Magica stores them backwards?
((c[3] as u32) << 24)
| ((c[2] as u32) << 16)
| ((c[1] as u32) << 8)
| ((c[0] as u32) << 0)
})
.collect();
// Build voxel list with palette indices
let voxels = colors
.chunks_exact(4)
.map(|p| quant.index_of(p) as u8)
.zip(positions)
.map(|(index, pos)| dot_vox::Voxel {
x: pos.x,
y: pos.y,
z: pos.z,
i: index,
})
.collect();
let model = dot_vox::Model {
size: dot_vox::Size {
x: 256,
y: 256,
z: 256,
},
voxels,
};
let dot_vox_data = dot_vox::DotVoxData {
version: 150, // TODO: is this correct at all??
models: vec![model],
palette,
materials: Vec::new(),
};
let save_path = save_path.with_extension("vox");
// Check if folder exists and create it if it does not
if !save_path.parent().map_or(false, |p| p.exists()) {
std::fs::create_dir_all(&save_path.parent().unwrap())
.map_err(|err| format!("Couldn't create folder for vox capture: {:?}", err))?;
}
// Attempt to create a file (hopefully all this effort wasn't for nothing...)
let mut writer = std::fs::File::create(save_path.with_extension("vox"))
.map(|file| std::io::BufWriter::new(file))
.map_err(|err| format!("Failed to create file to save vox: {:?}", err))?;
// Save
dot_vox_data
.write_vox(&mut writer)
.map(|_| format!("Succesfully saved vox to: {}", save_path.to_string_lossy()))
.map_err(|err| format!("Failed to write vox: {:?}", err))
}

View File

@ -46,7 +46,7 @@ server = { package = "veloren-server", path = "../server", optional = true }
glsl-include = "0.3.1"
failure = "0.1.6"
log = "0.4.8"
dot_vox = "4.0.0"
dot_vox = "4.1.0"
image = "0.22.3"
serde = "1.0.102"
serde_derive = "1.0.102"

View File

@ -12,6 +12,7 @@ pub struct KeyState {
pub toggle_dance: bool,
pub auto_walk: bool,
pub swap_loadout: bool,
pub vox_snap: bool,
pub respawn: bool,
pub analog_matrix: Vec2<f32>,
}
@ -30,6 +31,7 @@ impl KeyState {
toggle_dance: false,
auto_walk: false,
swap_loadout: false,
vox_snap: false,
respawn: false,
analog_matrix: Vec2::zero(),
}

View File

@ -381,6 +381,48 @@ impl PlayState for SessionState {
self.client.borrow_mut().swap_loadout();
}
}
Event::InputUpdate(GameInput::VoxSnap, state)
if state != self.key_state.vox_snap =>
{
self.key_state.vox_snap = state;
if state {
let client = self.client.borrow_mut();
let mut path = global_state.settings.screenshots_path.clone();
path.push(format!(
"voxsnap_{}",
std::time::SystemTime::now()
.duration_since(std::time::SystemTime::UNIX_EPOCH)
.map(|d| d.as_millis())
.unwrap_or(0)
));
let player_pos = client
.state()
.read_storage::<comp::Pos>()
.get(client.entity())
.copied()
.unwrap()
.0;
let result = common::util::vox_capture(
&*client.state().terrain(),
player_pos.map(|e| e as i32),
&path,
);
self.hud.new_message(match result {
Ok(message) => Chat {
chat_type: ChatType::Meta,
message,
},
Err(message) => Chat {
chat_type: ChatType::Meta,
message,
},
});
}
}
Event::InputUpdate(GameInput::ToggleLantern, true) => {
self.client.borrow_mut().toggle_lantern();
},

View File

@ -152,6 +152,7 @@ impl ControlSettings {
GameInput::Slot9 => KeyMouse::Key(VirtualKeyCode::Key9),
GameInput::Slot10 => KeyMouse::Key(VirtualKeyCode::Q),
GameInput::SwapLoadout => KeyMouse::Key(VirtualKeyCode::LAlt),
GameInput::VoxSnap => KeyMouse::Key(VirtualKeyCode::F8),
}
}
}
@ -213,6 +214,7 @@ impl Default for ControlSettings {
GameInput::Slot9,
GameInput::Slot10,
GameInput::SwapLoadout,
GameInput::VoxSnap,
];
for game_input in game_inputs {
new_settings.insert_binding(game_input, ControlSettings::default_binding(game_input));

View File

@ -64,6 +64,7 @@ pub enum GameInput {
SwapLoadout,
FreeLook,
AutoWalk,
VoxSnap,
}
impl GameInput {
@ -117,6 +118,7 @@ impl GameInput {
GameInput::Slot9 => "gameinput.slot9",
GameInput::Slot10 => "gameinput.slot10",
GameInput::SwapLoadout => "gameinput.swaploadout",
GameInput::VoxSnap => "gameinput.voxsnap",
}
}
}