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 = [
|
dependencies = [
|
||||||
"authc",
|
"authc",
|
||||||
"bincode",
|
"bincode",
|
||||||
|
"color_quant",
|
||||||
"criterion",
|
"criterion",
|
||||||
"crossbeam",
|
"crossbeam",
|
||||||
"dot_vox",
|
"dot_vox",
|
||||||
|
@ -353,6 +353,7 @@ magically infused items?"#,
|
|||||||
"gameinput.freelook": "Free Look",
|
"gameinput.freelook": "Free Look",
|
||||||
"gameinput.autowalk": "Auto Walk",
|
"gameinput.autowalk": "Auto Walk",
|
||||||
"gameinput.dance": "Dance",
|
"gameinput.dance": "Dance",
|
||||||
|
"gameinput.voxsnap": "Capture Surroundings to .vox",
|
||||||
|
|
||||||
/// End GameInput section
|
/// 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"] }
|
specs = { version = "0.15.1", features = ["serde", "nightly", "storage-event-control"] }
|
||||||
vek = { version = "0.10.0", features = ["serde"] }
|
vek = { version = "0.10.0", features = ["serde"] }
|
||||||
dot_vox = "4.0.0"
|
dot_vox = "4.1.0"
|
||||||
fxhash = "0.2.1"
|
fxhash = "0.2.1"
|
||||||
image = "0.22.3"
|
image = "0.22.3"
|
||||||
mio = "0.6.19"
|
mio = "0.6.19"
|
||||||
@ -35,6 +35,7 @@ notify = "5.0.0-pre.2"
|
|||||||
indexmap = "1.3.0"
|
indexmap = "1.3.0"
|
||||||
sum_type = "0.2.0"
|
sum_type = "0.2.0"
|
||||||
authc = { git = "https://gitlab.com/veloren/auth.git", rev = "65571ade0d954a0e0bd995fdb314854ff146ab97" }
|
authc = { git = "https://gitlab.com/veloren/auth.git", rev = "65571ade0d954a0e0bd995fdb314854ff146ab97" }
|
||||||
|
color_quant = "1.0.1"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
criterion = "0.3"
|
criterion = "0.3"
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
mod color;
|
mod color;
|
||||||
mod dir;
|
mod dir;
|
||||||
|
mod vox_capture;
|
||||||
|
|
||||||
pub const GIT_VERSION: &str = include_str!(concat!(env!("OUT_DIR"), "/githash"));
|
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 color::*;
|
||||||
pub use dir::*;
|
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"
|
glsl-include = "0.3.1"
|
||||||
failure = "0.1.6"
|
failure = "0.1.6"
|
||||||
log = "0.4.8"
|
log = "0.4.8"
|
||||||
dot_vox = "4.0.0"
|
dot_vox = "4.1.0"
|
||||||
image = "0.22.3"
|
image = "0.22.3"
|
||||||
serde = "1.0.102"
|
serde = "1.0.102"
|
||||||
serde_derive = "1.0.102"
|
serde_derive = "1.0.102"
|
||||||
|
@ -12,6 +12,7 @@ pub struct KeyState {
|
|||||||
pub toggle_dance: bool,
|
pub toggle_dance: bool,
|
||||||
pub auto_walk: bool,
|
pub auto_walk: bool,
|
||||||
pub swap_loadout: bool,
|
pub swap_loadout: bool,
|
||||||
|
pub vox_snap: bool,
|
||||||
pub respawn: bool,
|
pub respawn: bool,
|
||||||
pub analog_matrix: Vec2<f32>,
|
pub analog_matrix: Vec2<f32>,
|
||||||
}
|
}
|
||||||
@ -30,6 +31,7 @@ impl KeyState {
|
|||||||
toggle_dance: false,
|
toggle_dance: false,
|
||||||
auto_walk: false,
|
auto_walk: false,
|
||||||
swap_loadout: false,
|
swap_loadout: false,
|
||||||
|
vox_snap: false,
|
||||||
respawn: false,
|
respawn: false,
|
||||||
analog_matrix: Vec2::zero(),
|
analog_matrix: Vec2::zero(),
|
||||||
}
|
}
|
||||||
|
@ -381,6 +381,48 @@ impl PlayState for SessionState {
|
|||||||
self.client.borrow_mut().swap_loadout();
|
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) => {
|
Event::InputUpdate(GameInput::ToggleLantern, true) => {
|
||||||
self.client.borrow_mut().toggle_lantern();
|
self.client.borrow_mut().toggle_lantern();
|
||||||
},
|
},
|
||||||
|
@ -152,6 +152,7 @@ impl ControlSettings {
|
|||||||
GameInput::Slot9 => KeyMouse::Key(VirtualKeyCode::Key9),
|
GameInput::Slot9 => KeyMouse::Key(VirtualKeyCode::Key9),
|
||||||
GameInput::Slot10 => KeyMouse::Key(VirtualKeyCode::Q),
|
GameInput::Slot10 => KeyMouse::Key(VirtualKeyCode::Q),
|
||||||
GameInput::SwapLoadout => KeyMouse::Key(VirtualKeyCode::LAlt),
|
GameInput::SwapLoadout => KeyMouse::Key(VirtualKeyCode::LAlt),
|
||||||
|
GameInput::VoxSnap => KeyMouse::Key(VirtualKeyCode::F8),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -213,6 +214,7 @@ impl Default for ControlSettings {
|
|||||||
GameInput::Slot9,
|
GameInput::Slot9,
|
||||||
GameInput::Slot10,
|
GameInput::Slot10,
|
||||||
GameInput::SwapLoadout,
|
GameInput::SwapLoadout,
|
||||||
|
GameInput::VoxSnap,
|
||||||
];
|
];
|
||||||
for game_input in game_inputs {
|
for game_input in game_inputs {
|
||||||
new_settings.insert_binding(game_input, ControlSettings::default_binding(game_input));
|
new_settings.insert_binding(game_input, ControlSettings::default_binding(game_input));
|
||||||
|
@ -64,6 +64,7 @@ pub enum GameInput {
|
|||||||
SwapLoadout,
|
SwapLoadout,
|
||||||
FreeLook,
|
FreeLook,
|
||||||
AutoWalk,
|
AutoWalk,
|
||||||
|
VoxSnap,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl GameInput {
|
impl GameInput {
|
||||||
@ -117,6 +118,7 @@ impl GameInput {
|
|||||||
GameInput::Slot9 => "gameinput.slot9",
|
GameInput::Slot9 => "gameinput.slot9",
|
||||||
GameInput::Slot10 => "gameinput.slot10",
|
GameInput::Slot10 => "gameinput.slot10",
|
||||||
GameInput::SwapLoadout => "gameinput.swaploadout",
|
GameInput::SwapLoadout => "gameinput.swaploadout",
|
||||||
|
GameInput::VoxSnap => "gameinput.voxsnap",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user