mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Save surroundings to .vox
This commit is contained in:
parent
54960142e2
commit
1b8262caed
1
Cargo.lock
generated
1
Cargo.lock
generated
@ -4989,6 +4989,7 @@ version = "0.6.0"
|
||||
dependencies = [
|
||||
"authc",
|
||||
"bincode",
|
||||
"color_quant",
|
||||
"criterion",
|
||||
"crossbeam",
|
||||
"dot_vox",
|
||||
|
@ -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
|
||||
|
||||
|
@ -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"
|
||||
|
@ -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::*;
|
||||
|
103
common/src/util/vox_capture.rs
Normal file
103
common/src/util/vox_capture.rs
Normal 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))
|
||||
}
|
@ -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"
|
||||
|
@ -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(),
|
||||
}
|
||||
|
@ -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();
|
||||
},
|
||||
|
@ -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));
|
||||
|
@ -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",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user