Completion Part 1
This commit is contained in:
parent
0ce828b6fc
commit
a593f141ff
24
Manual.md
24
Manual.md
@ -15,8 +15,8 @@ already be generated. This can be done by either exploring the area in-game,
|
||||
or by using Minetest's built-in `/emergeblocks` command.
|
||||
|
||||
MapEditr supports all maps created since Minetest version 0.4.2-rc1, released
|
||||
July 2012. Any unsupported areas of the map will be skipped (TODO). Note that
|
||||
only SQLite format maps are currently supported.
|
||||
July 2012. Any unsupported areas of the map will be skipped. Note that only
|
||||
SQLite format maps are currently supported.
|
||||
|
||||
## General usage
|
||||
|
||||
@ -53,7 +53,7 @@ WorldEdit `//fixlight` command.
|
||||
|
||||
Usage: `clone --p1 x y z --p2 x y z --offset x y z`
|
||||
|
||||
Clone (copy) a given area to a new location.
|
||||
Clone (copy) the contents of an area to a new location.
|
||||
|
||||
Arguments:
|
||||
|
||||
@ -69,14 +69,14 @@ from or into mapblocks that are not yet generated.
|
||||
|
||||
Usage: `deleteblocks --p1 x y z --p2 x y z [--invert]`
|
||||
|
||||
Deletes all mapblocks in the given area.
|
||||
Delete all mapblocks inside or outside an area.
|
||||
|
||||
Arguments:
|
||||
|
||||
- `--p1, --p2`: Area to delete from. Only mapblocks fully inside this area
|
||||
will be deleted.
|
||||
- `--invert`: Delete only mapblocks that are fully *outside* the given
|
||||
area.
|
||||
- `--p1, --p2`: Area containing mapblocks to delete. By default, only mapblocks
|
||||
fully within this area will be deleted.
|
||||
- `--invert`: Delete all mapblocks fully *outside* the given area. Use with
|
||||
caution; you could erase a large portion of your world!
|
||||
|
||||
**Note:** Deleting mapblocks is *not* the same as filling them with air! Mapgen
|
||||
will be invoked where the blocks were deleted, and this sometimes causes
|
||||
@ -86,13 +86,13 @@ terrain glitches.
|
||||
|
||||
Usage: `deletemeta [--node <node>] [--p1 x y z] [--p2 x y z] [--invert]`
|
||||
|
||||
Delete metadata of a certain node and/or within a certain area. This includes
|
||||
node inventories as well.
|
||||
Delete node metadata of certain nodes. Node inventories (such as chest/furnace
|
||||
contents) are also deleted.
|
||||
|
||||
Arguments:
|
||||
|
||||
- `--node`: Name of node to modify. If not specified, the metadata of all
|
||||
nodes will be deleted.
|
||||
- `--node`: Only delete metadata of nodes with the given name. If not
|
||||
specified, metadata will be deleted in all matching nodes.
|
||||
- `--p1, --p2`: Area in which to delete metadata. If not specified, metadata
|
||||
will be deleted everywhere.
|
||||
- `--invert`: Only delete metadata *outside* the given area.
|
||||
|
39
README.md
39
README.md
@ -1,36 +1,35 @@
|
||||
# MapEditr
|
||||
|
||||
TODO: Add a license.
|
||||
|
||||
MapEditr is a command-line tool for relatively fast manipulation of Minetest
|
||||
worlds. It can replace nodes, fill areas, combine parts of different worlds,
|
||||
and much more.
|
||||
MapEditr is a command-line tool for fast manipulation of Minetest worlds. It
|
||||
can replace nodes and items, fill areas, combine parts of different worlds, and
|
||||
much more.
|
||||
|
||||
This tool is functionally similar to [WorldEdit][1], but designed for large
|
||||
operations that would be impractical to do using WorldEdit. Since it is mainly
|
||||
optimized for speed, MapEditr is not as full-featured as in-game world editors
|
||||
such as WorldEdit.
|
||||
operations that would be impractical to do within Minetest. Since it is mainly
|
||||
optimized for speed, MapEditr lacks some of the more specialty features of
|
||||
WorldEdit.
|
||||
|
||||
MapEditr is originally based on [MapEdit][2], but rewritten in Rust, hence the
|
||||
added "r". Switching to a compiled language will make MapEditr more robust and
|
||||
easier to maintain in the future.
|
||||
MapEditr was originally based on [MapEdit][2], except written in Rust rather
|
||||
than Python (hence the added "r"). Switching to a compiled language will make
|
||||
MapEditr more robust and easier to maintain in the future.
|
||||
|
||||
[1]: https://github.com/Uberi/Minetest-WorldEdit
|
||||
[2]: https://github.com/random-geek/MapEdit
|
||||
|
||||
## Installation
|
||||
## Compilation/Installation
|
||||
|
||||
TODO: Pre-built binaries
|
||||
|
||||
To compile from source, you must have Rust installed first, which can be
|
||||
downloaded from [here][3]. Then, in the MapEditr directory, run:
|
||||
downloaded from [the Rust website][3]. Then, in the MapEditr directory, simply
|
||||
run:
|
||||
|
||||
`cargo build --release`
|
||||
|
||||
The `--release` flag is important, as it optimizes the generated executable,
|
||||
making it much faster.
|
||||
The `--release` flag is important, as it produces an optimized executable which
|
||||
runs much faster than the default, unoptimized version.
|
||||
|
||||
[3]: https://www.rust-lang.org/tools/install
|
||||
[3]: https://www.rust-lang.org
|
||||
|
||||
## Usage
|
||||
|
||||
@ -43,10 +42,14 @@ Some useful things you can do with MapEditr:
|
||||
- Build extremely long walls and roads in seconds using `fill`.
|
||||
- Combine multiple worlds or map saves with `overlay`.
|
||||
|
||||
## License
|
||||
|
||||
TODO
|
||||
|
||||
## Acknowledgments
|
||||
|
||||
The [Minetest][4] project has been rather important for the making of MapEdit/
|
||||
MapEditr, for obvious reasons.
|
||||
The [Minetest][4] project has been rather important for the making of
|
||||
MapEdit/MapEditr, for obvious reasons.
|
||||
|
||||
Some parts of the original MapEdit code were adapted from AndrejIT's
|
||||
[map_unexplore][5] project. All due credit goes to the author(s) of that
|
||||
|
@ -1,4 +1,4 @@
|
||||
// TODO: Move this file somewhere else.
|
||||
// TODO: Move this file somewhere else?
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use crate::map_block::{MapBlock, NodeMetadataList};
|
||||
@ -115,7 +115,7 @@ pub fn clean_name_id_map(block: &mut MapBlock) {
|
||||
}
|
||||
|
||||
// Rebuild the name-ID map.
|
||||
let mut new_nimap = BTreeMap::<u16, Vec<u8>>::new();
|
||||
let mut new_nimap = BTreeMap::new();
|
||||
let mut map = vec![0u16; id_count];
|
||||
for id in 0..id_count {
|
||||
// Skip unused IDs.
|
||||
|
@ -26,9 +26,9 @@ fn arg_to_pos(p: clap::Values) -> anyhow::Result<Vec3> {
|
||||
fn to_cmd_line_args<'a>(tup: &(ArgType, &'a str))
|
||||
-> Vec<Arg<'a, 'a>>
|
||||
{
|
||||
let arg = tup.0.clone();
|
||||
let help = tup.1;
|
||||
if let ArgType::Area(req) = arg {
|
||||
let arg_type = tup.0.clone();
|
||||
let help_msg = tup.1;
|
||||
if let ArgType::Area(req) = arg_type {
|
||||
return vec![
|
||||
Arg::with_name("p1")
|
||||
.long("p1")
|
||||
@ -37,7 +37,7 @@ fn to_cmd_line_args<'a>(tup: &(ArgType, &'a str))
|
||||
.value_names(&["x", "y", "z"])
|
||||
.required(req)
|
||||
.requires("p2")
|
||||
.help(help),
|
||||
.help(help_msg),
|
||||
Arg::with_name("p2")
|
||||
.long("p2")
|
||||
.allow_hyphen_values(true)
|
||||
@ -45,91 +45,78 @@ fn to_cmd_line_args<'a>(tup: &(ArgType, &'a str))
|
||||
.value_names(&["x", "y", "z"])
|
||||
.required(req)
|
||||
.requires("p1")
|
||||
.help(help)
|
||||
.help(help_msg)
|
||||
];
|
||||
}
|
||||
// TODO: Help is redundant.
|
||||
vec![match arg {
|
||||
// TODO: Ensure arguments are correctly defined.
|
||||
let arg = match arg_type {
|
||||
ArgType::Area(_) => unreachable!(),
|
||||
ArgType::InputMapPath =>
|
||||
Arg::with_name("input_map")
|
||||
.required(true)
|
||||
.help(help),
|
||||
ArgType::Area(_) => unreachable!(),
|
||||
.required(true),
|
||||
ArgType::Invert =>
|
||||
Arg::with_name("invert")
|
||||
.long("invert")
|
||||
.help(help),
|
||||
.long("invert"),
|
||||
ArgType::Offset(req) =>
|
||||
Arg::with_name("offset")
|
||||
.long("offset")
|
||||
.allow_hyphen_values(true)
|
||||
.number_of_values(3)
|
||||
.value_names(&["x", "y", "z"])
|
||||
.required(req)
|
||||
.help(help),
|
||||
.required(req),
|
||||
ArgType::Node(req) => {
|
||||
let a = Arg::with_name("node")
|
||||
.required(req)
|
||||
.help(help);
|
||||
if !req {
|
||||
a.long("node").takes_value(true)
|
||||
} else {
|
||||
.required(req);
|
||||
if req {
|
||||
a
|
||||
} else {
|
||||
a.long("node").takes_value(true)
|
||||
}
|
||||
},
|
||||
ArgType::Nodes =>
|
||||
Arg::with_name("nodes")
|
||||
.long("nodes")
|
||||
.min_values(1)
|
||||
.help(help),
|
||||
.min_values(1),
|
||||
ArgType::NewNode =>
|
||||
Arg::with_name("new_node")
|
||||
.takes_value(true)
|
||||
.required(true)
|
||||
.help(help),
|
||||
.required(true),
|
||||
ArgType::Object =>
|
||||
Arg::with_name("object")
|
||||
.long("obj")
|
||||
.takes_value(true)
|
||||
.help(help),
|
||||
.takes_value(true),
|
||||
ArgType::Item =>
|
||||
Arg::with_name("item")
|
||||
.takes_value(true)
|
||||
.required(true)
|
||||
.help(help),
|
||||
.required(true),
|
||||
ArgType::Items =>
|
||||
Arg::with_name("items")
|
||||
.long("items")
|
||||
.min_values(0)
|
||||
.max_values(1)
|
||||
.help(help),
|
||||
.max_values(1),
|
||||
ArgType::NewItem =>
|
||||
Arg::with_name("new_item")
|
||||
.takes_value(true)
|
||||
.help(help),
|
||||
.takes_value(true),
|
||||
ArgType::DeleteMeta =>
|
||||
Arg::with_name("delete_meta")
|
||||
.long("deletemeta")
|
||||
.help(help),
|
||||
.long("deletemeta"),
|
||||
ArgType::DeleteItem =>
|
||||
Arg::with_name("delete_item")
|
||||
.long("delete")
|
||||
.help(help),
|
||||
.long("delete"),
|
||||
ArgType::Key =>
|
||||
Arg::with_name("key")
|
||||
.takes_value(true)
|
||||
.required(true)
|
||||
.help(help),
|
||||
.required(true),
|
||||
ArgType::Value =>
|
||||
Arg::with_name("value")
|
||||
.takes_value(true)
|
||||
.required(true)
|
||||
.help(help),
|
||||
.required(true),
|
||||
ArgType::Param2Val =>
|
||||
Arg::with_name("param2_val")
|
||||
.required(true)
|
||||
.help(help),
|
||||
}]
|
||||
.required(true),
|
||||
}.help(help_msg);
|
||||
|
||||
vec![arg]
|
||||
}
|
||||
|
||||
|
||||
@ -147,7 +134,9 @@ fn parse_cmd_line_args() -> anyhow::Result<InstArgs> {
|
||||
|
||||
let app = App::new("MapEditr")
|
||||
.about("Edits Minetest worlds/map databases.")
|
||||
.after_help("For command-specific help, run: mapeditr <command> -h")
|
||||
.after_help(
|
||||
"For command-specific help, run: mapeditr <SUBCOMMAND> -h\n\
|
||||
For additional information, see the manual.")
|
||||
.version(crate_version!())
|
||||
.author(crate_authors!())
|
||||
// TODO: Move map arg to subcommands?
|
||||
@ -322,7 +311,7 @@ pub fn run_cmd_line() {
|
||||
if forced_update == InstState::Querying
|
||||
|| (cur_state == InstState::Querying && timed_update_ready)
|
||||
{
|
||||
eprint!("\rQuerying map blocks... {} found.",
|
||||
eprint!("\rQuerying mapblocks... {} found.",
|
||||
status.get().blocks_total);
|
||||
std::io::stdout().flush().unwrap();
|
||||
last_update = now;
|
||||
|
@ -1,14 +1,29 @@
|
||||
use super::{Command, BLOCK_CACHE_SIZE};
|
||||
|
||||
use crate::{unwrap_or, opt_unwrap_or};
|
||||
use crate::spatial::Vec3;
|
||||
use crate::spatial::{Vec3, Area, MAP_LIMIT};
|
||||
use crate::map_database::MapDatabase;
|
||||
use crate::map_block::{MapBlock, MapBlockError, is_valid_generated,
|
||||
NodeMetadataList, NodeMetadataListExt};
|
||||
use crate::block_utils::{merge_blocks, merge_metadata, clean_name_id_map};
|
||||
use crate::instance::{ArgType, InstBundle};
|
||||
use crate::instance::{ArgType, InstBundle, InstArgs};
|
||||
use crate::utils::{CacheMap, query_keys};
|
||||
use crate::time_keeper::TimeKeeper;
|
||||
|
||||
|
||||
fn verify_args(args: &InstArgs) -> anyhow::Result<()> {
|
||||
let map_area = Area::new(
|
||||
Vec3::new(-MAP_LIMIT, -MAP_LIMIT, -MAP_LIMIT),
|
||||
Vec3::new(MAP_LIMIT, MAP_LIMIT, MAP_LIMIT)
|
||||
);
|
||||
|
||||
if map_area.intersection(args.area.unwrap() + args.offset.unwrap())
|
||||
.is_none()
|
||||
{
|
||||
anyhow::bail!("Destination area is outside map bounds.");
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
type BlockResult = Option<Result<MapBlock, MapBlockError>>;
|
||||
@ -49,9 +64,8 @@ fn clone(inst: &mut InstBundle) {
|
||||
});
|
||||
|
||||
let mut block_cache = CacheMap::with_capacity(BLOCK_CACHE_SIZE);
|
||||
let mut tk = TimeKeeper::new();
|
||||
|
||||
inst.status.begin_editing();
|
||||
|
||||
for dst_key in dst_keys {
|
||||
inst.status.inc_done();
|
||||
|
||||
@ -93,40 +107,29 @@ fn clone(inst: &mut InstBundle) {
|
||||
let dst_frag_rel = (src_frag_abs + offset)
|
||||
.rel_block_overlap(dst_pos).unwrap();
|
||||
|
||||
{
|
||||
let _t = tk.get_timer("merge");
|
||||
merge_blocks(&src_block, &mut dst_block,
|
||||
src_frag_rel, dst_frag_rel);
|
||||
}
|
||||
{
|
||||
let _t = tk.get_timer("merge_meta");
|
||||
merge_metadata(&src_meta, &mut dst_meta,
|
||||
src_frag_rel, dst_frag_rel);
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
let _t = tk.get_timer("name-ID map cleanup");
|
||||
clean_name_id_map(&mut dst_block);
|
||||
}
|
||||
|
||||
*dst_block.metadata.get_mut() = dst_meta.serialize(dst_block.version);
|
||||
inst.db.set_block(dst_key, &dst_block.serialize()).unwrap();
|
||||
}
|
||||
|
||||
inst.status.end_editing();
|
||||
tk.print(&inst.status);
|
||||
}
|
||||
|
||||
|
||||
pub fn get_command() -> Command {
|
||||
Command {
|
||||
func: clone,
|
||||
verify_args: None,
|
||||
verify_args: Some(verify_args),
|
||||
args: vec![
|
||||
(ArgType::Area(true), "Area to clone"),
|
||||
(ArgType::Offset(true), "Vector to shift the area by")
|
||||
],
|
||||
help: "Clone (copy) a given area to a new location."
|
||||
help: "Clone (copy) the contents of an area to a new location."
|
||||
}
|
||||
}
|
||||
|
@ -10,7 +10,6 @@ fn delete_blocks(inst: &mut InstBundle) {
|
||||
inst.status.begin_editing();
|
||||
|
||||
for key in keys {
|
||||
// TODO: This is kind of inefficient seeming.
|
||||
inst.status.inc_done();
|
||||
inst.db.delete_block(key).unwrap();
|
||||
}
|
||||
@ -24,9 +23,10 @@ pub fn get_command() -> Command {
|
||||
func: delete_blocks,
|
||||
verify_args: None,
|
||||
args: vec![
|
||||
(ArgType::Area(true), "Area containing blocks to delete"),
|
||||
(ArgType::Invert, "Delete all blocks *outside* the area")
|
||||
(ArgType::Area(true), "Area containing mapblocks to delete"),
|
||||
(ArgType::Invert,
|
||||
"Delete all mapblocks fully *outside* the given area.")
|
||||
],
|
||||
help: "Delete all map blocks in a given area."
|
||||
help: "Delete all mapblocks inside or outside an area."
|
||||
}
|
||||
}
|
||||
|
@ -11,7 +11,7 @@ fn delete_metadata(inst: &mut InstBundle) {
|
||||
let node = inst.args.node.as_ref().map(to_bytes);
|
||||
|
||||
let keys = query_keys(&mut inst.db, &mut inst.status,
|
||||
&to_slice(&node), inst.args.area, inst.args.invert, true);
|
||||
to_slice(&node), inst.args.area, inst.args.invert, true);
|
||||
|
||||
inst.status.begin_editing();
|
||||
let mut count: u64 = 0;
|
||||
@ -19,7 +19,8 @@ fn delete_metadata(inst: &mut InstBundle) {
|
||||
for key in keys {
|
||||
inst.status.inc_done();
|
||||
let data = inst.db.get_block(key).unwrap();
|
||||
let mut block = unwrap_or!(MapBlock::deserialize(&data), continue);
|
||||
let mut block = unwrap_or!(MapBlock::deserialize(&data),
|
||||
{ inst.status.inc_failed(); continue; });
|
||||
|
||||
let node_data = block.node_data.get_ref();
|
||||
let node_id = node.as_deref().and_then(|n| block.nimap.get_id(n));
|
||||
@ -28,14 +29,14 @@ fn delete_metadata(inst: &mut InstBundle) {
|
||||
}
|
||||
|
||||
let mut meta = unwrap_or!(
|
||||
NodeMetadataList::deserialize(block.metadata.get_ref()), continue);
|
||||
NodeMetadataList::deserialize(block.metadata.get_ref()),
|
||||
{ inst.status.inc_failed(); continue; });
|
||||
|
||||
let block_corner = Vec3::from_block_key(key) * 16;
|
||||
let mut to_delete = Vec::with_capacity(meta.len());
|
||||
|
||||
for (&idx, _) in &meta {
|
||||
let pos = Vec3::from_u16_key(idx);
|
||||
let abs_pos = pos + block_corner;
|
||||
let abs_pos = Vec3::from_u16_key(idx) + block_corner;
|
||||
|
||||
if let Some(a) = inst.args.area {
|
||||
if a.contains(abs_pos) == inst.args.invert {
|
||||
@ -52,10 +53,10 @@ fn delete_metadata(inst: &mut InstBundle) {
|
||||
}
|
||||
|
||||
if !to_delete.is_empty() {
|
||||
count += to_delete.len() as u64;
|
||||
for idx in &to_delete {
|
||||
meta.remove(idx);
|
||||
}
|
||||
count += to_delete.len() as u64;
|
||||
*block.metadata.get_mut() = meta.serialize(block.version);
|
||||
inst.db.set_block(key, &block.serialize()).unwrap();
|
||||
}
|
||||
|
@ -65,7 +65,7 @@ fn delete_objects(inst: &mut InstBundle) {
|
||||
.map(|s| TwoWaySearcher::new(s));
|
||||
|
||||
let keys = query_keys(&mut inst.db, &mut inst.status,
|
||||
&to_slice(&search_obj), inst.args.area, inst.args.invert, true);
|
||||
to_slice(&search_obj), inst.args.area, inst.args.invert, true);
|
||||
|
||||
inst.status.begin_editing();
|
||||
let mut count: u64 = 0;
|
||||
|
@ -11,7 +11,7 @@ fn delete_timers(inst: &mut InstBundle) {
|
||||
let node = inst.args.node.as_ref().map(to_bytes);
|
||||
|
||||
let keys = query_keys(&mut inst.db, &mut inst.status,
|
||||
&to_slice(&node), inst.args.area, inst.args.invert, true);
|
||||
to_slice(&node), inst.args.area, inst.args.invert, true);
|
||||
|
||||
inst.status.begin_editing();
|
||||
let mut count: u64 = 0;
|
||||
|
@ -1,15 +1,21 @@
|
||||
use super::Command;
|
||||
|
||||
use crate::unwrap_or;
|
||||
use crate::spatial::{Vec3, Area};
|
||||
use crate::spatial::{Vec3, Area, InverseBlockIterator};
|
||||
use crate::instance::{ArgType, InstBundle};
|
||||
use crate::map_block::MapBlock;
|
||||
use crate::block_utils::clean_name_id_map;
|
||||
use crate::utils::{query_keys, to_bytes, fmt_big_num};
|
||||
|
||||
|
||||
fn fill_area(block: &mut MapBlock, area: Area, id: u16) {
|
||||
fn fill_area(block: &mut MapBlock, id: u16, area: Area, invert: bool) {
|
||||
let nd = block.node_data.get_mut();
|
||||
|
||||
if invert {
|
||||
for i in InverseBlockIterator::new(area) {
|
||||
nd.nodes[i] = id;
|
||||
}
|
||||
} else {
|
||||
for z in area.min.z ..= area.max.z {
|
||||
let z_start = z * 256;
|
||||
for y in area.min.y ..= area.max.y {
|
||||
@ -19,6 +25,7 @@ fn fill_area(block: &mut MapBlock, area: Area, id: u16) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -27,7 +34,7 @@ fn fill(inst: &mut InstBundle) {
|
||||
let node = to_bytes(inst.args.new_node.as_ref().unwrap());
|
||||
|
||||
let keys = query_keys(&mut inst.db, &mut inst.status,
|
||||
&[], Some(area), false, true);
|
||||
&[], Some(area), inst.args.invert, true);
|
||||
|
||||
inst.status.begin_editing();
|
||||
|
||||
@ -42,24 +49,23 @@ fn fill(inst: &mut InstBundle) {
|
||||
continue;
|
||||
});
|
||||
|
||||
if area.contains_block(pos) {
|
||||
let nd = block.node_data.get_mut();
|
||||
for x in &mut nd.nodes {
|
||||
*x = 0;
|
||||
}
|
||||
block.nimap.0.clear();
|
||||
block.nimap.0.insert(0, node.to_vec());
|
||||
count += nd.nodes.len() as u64;
|
||||
} else {
|
||||
let slice = area.rel_block_overlap(pos).unwrap();
|
||||
if area.contains_block(pos) != area.touches_block(pos) {
|
||||
// Fill part of block
|
||||
let block_part = area.rel_block_overlap(pos).unwrap();
|
||||
let fill_id = block.nimap.get_id(&node).unwrap_or_else(|| {
|
||||
let next = block.nimap.get_max_id().unwrap() + 1;
|
||||
block.nimap.0.insert(next, node.to_vec());
|
||||
next
|
||||
});
|
||||
fill_area(&mut block, slice, fill_id);
|
||||
fill_area(&mut block, fill_id, block_part, inst.args.invert);
|
||||
clean_name_id_map(&mut block);
|
||||
count += slice.volume();
|
||||
count += block_part.volume();
|
||||
} else { // Fill entire block
|
||||
let nd = block.node_data.get_mut();
|
||||
nd.nodes.fill(0);
|
||||
block.nimap.0.clear();
|
||||
block.nimap.0.insert(0, node.to_vec());
|
||||
count += nd.nodes.len() as u64;
|
||||
}
|
||||
|
||||
inst.db.set_block(key, &block.serialize()).unwrap();
|
||||
@ -76,7 +82,8 @@ pub fn get_command() -> Command {
|
||||
verify_args: None,
|
||||
args: vec![
|
||||
(ArgType::Area(true), "Area to fill"),
|
||||
(ArgType::NewNode, "Name of node to fill area with")
|
||||
(ArgType::NewNode, "Name of node to fill area with"),
|
||||
(ArgType::Invert, "Fill all generated areas outside the area.")
|
||||
],
|
||||
help: "Fill the entire area with one node."
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
use super::{Command, BLOCK_CACHE_SIZE};
|
||||
|
||||
use crate::{unwrap_or, opt_unwrap_or};
|
||||
use crate::spatial::{Vec3, Area};
|
||||
use crate::spatial::{Vec3, Area, MAP_LIMIT};
|
||||
use crate::instance::{ArgType, InstArgs, InstBundle};
|
||||
use crate::map_database::MapDatabase;
|
||||
use crate::map_block::{MapBlock, MapBlockError, is_valid_generated,
|
||||
@ -11,11 +11,24 @@ use crate::utils::{query_keys, CacheMap};
|
||||
|
||||
|
||||
fn verify_args(args: &InstArgs) -> anyhow::Result<()> {
|
||||
let offset_if_nonzero =
|
||||
args.offset.filter(|&off| off != Vec3::new(0, 0, 0));
|
||||
if args.invert && offset_if_nonzero.is_some() {
|
||||
if args.invert
|
||||
&& args.offset.filter(|&ofs| ofs != Vec3::new(0, 0, 0)).is_some()
|
||||
{
|
||||
anyhow::bail!("Inverted selections cannot be offset.");
|
||||
}
|
||||
|
||||
let offset = args.offset.unwrap_or(Vec3::new(0, 0, 0));
|
||||
let map_area = Area::new(
|
||||
Vec3::new(-MAP_LIMIT, -MAP_LIMIT, -MAP_LIMIT),
|
||||
Vec3::new(MAP_LIMIT, MAP_LIMIT, MAP_LIMIT)
|
||||
);
|
||||
|
||||
if map_area.intersection(args.area.unwrap_or(map_area) + offset)
|
||||
.is_none()
|
||||
{
|
||||
anyhow::bail!("Destination area is outside map bounds.");
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@ -45,12 +58,12 @@ fn overlay_no_offset(inst: &mut InstBundle) {
|
||||
|
||||
if (!invert && area.contains_block(pos))
|
||||
|| (invert && !area.touches_block(pos))
|
||||
{ // If possible, copy whole map block.
|
||||
{ // If possible, copy whole mapblock.
|
||||
let data = idb.get_block(key).unwrap();
|
||||
if is_valid_generated(&data) {
|
||||
db.set_block(key, &data).unwrap();
|
||||
}
|
||||
} else { // Copy part of map block
|
||||
} else { // Copy part of mapblock
|
||||
let res = || -> Result<(), MapBlockError> {
|
||||
let dst_data = opt_unwrap_or!(
|
||||
db.get_block(key).ok()
|
||||
@ -91,7 +104,7 @@ fn overlay_no_offset(inst: &mut InstBundle) {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// No area; copy whole map block.
|
||||
// No area; copy whole mapblock.
|
||||
let data = idb.get_block(key).unwrap();
|
||||
if is_valid_generated(&data) {
|
||||
db.set_block(key, &data).unwrap();
|
||||
|
@ -1,146 +1,127 @@
|
||||
use super::Command;
|
||||
|
||||
use crate::spatial::{Vec3, Area};
|
||||
use crate::unwrap_or;
|
||||
use crate::spatial::{Vec3, Area, InverseBlockIterator};
|
||||
use crate::instance::{ArgType, InstArgs, InstBundle};
|
||||
use crate::map_block::MapBlock;
|
||||
use crate::time_keeper::TimeKeeper;
|
||||
use crate::utils::{query_keys, to_bytes, fmt_big_num};
|
||||
|
||||
|
||||
fn do_replace(
|
||||
block: &mut MapBlock,
|
||||
key: i64,
|
||||
search_id: u16,
|
||||
old_id: u16,
|
||||
new_node: &[u8],
|
||||
area: Option<Area>,
|
||||
invert: bool,
|
||||
tk: &mut TimeKeeper
|
||||
invert: bool
|
||||
) -> u64
|
||||
{
|
||||
let block_pos = Vec3::from_block_key(key);
|
||||
let mut count = 0;
|
||||
let mut replaced = 0;
|
||||
|
||||
// Replace nodes in a portion of a map block.
|
||||
if area.is_some() && area.unwrap().contains_block(block_pos) !=
|
||||
area.unwrap().touches_block(block_pos)
|
||||
// Replace nodes in a portion of a mapblock.
|
||||
if area
|
||||
.filter(|a| a.contains_block(block_pos) != a.touches_block(block_pos))
|
||||
.is_some()
|
||||
{
|
||||
let _t = tk.get_timer("replace (partial block)");
|
||||
let node_area = area.unwrap().rel_block_overlap(block_pos).unwrap();
|
||||
|
||||
let mut new_replace_id = false;
|
||||
let replace_id = block.nimap.get_id(new_node)
|
||||
.unwrap_or_else(|| {
|
||||
new_replace_id = true;
|
||||
block.nimap.get_max_id().unwrap() + 1
|
||||
});
|
||||
|
||||
let mut idx = 0;
|
||||
let mut old_node_present = false;
|
||||
let mut new_node_present = false;
|
||||
|
||||
let (new_id, new_id_needed) = match block.nimap.get_id(new_node) {
|
||||
Some(id) => (id, false),
|
||||
None => (block.nimap.get_max_id().unwrap() + 1, true)
|
||||
};
|
||||
let nd = block.node_data.get_mut();
|
||||
for z in 0..16 {
|
||||
for y in 0..16 {
|
||||
for x in 0..16 {
|
||||
if nd.nodes[idx] == search_id
|
||||
&& node_area.contains(Vec3 {x, y, z}) != invert
|
||||
{
|
||||
nd.nodes[idx] = replace_id;
|
||||
new_node_present = true;
|
||||
count += 1;
|
||||
}
|
||||
|
||||
if nd.nodes[idx] == search_id {
|
||||
old_node_present = true;
|
||||
if invert {
|
||||
for idx in InverseBlockIterator::new(node_area) {
|
||||
if nd.nodes[idx] == old_id {
|
||||
nd.nodes[idx] = new_id;
|
||||
replaced += 1;
|
||||
}
|
||||
idx += 1;
|
||||
}
|
||||
} else {
|
||||
for pos in &node_area {
|
||||
let idx = (pos.x + 16 * (pos.y + 16 * pos.z)) as usize;
|
||||
if nd.nodes[idx] == old_id {
|
||||
nd.nodes[idx] = new_id;
|
||||
replaced += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Replacement node not yet in name-ID map; insert it.
|
||||
if new_replace_id && new_node_present {
|
||||
block.nimap.0.insert(replace_id, new_node.to_vec());
|
||||
// If replacement ID is not in the name-ID map but was used, add it.
|
||||
if new_id_needed && replaced > 0 {
|
||||
block.nimap.0.insert(new_id, new_node.to_vec());
|
||||
}
|
||||
|
||||
// Search node was completely eliminated; shift IDs down.
|
||||
if !old_node_present {
|
||||
for i in 0 .. nd.nodes.len() {
|
||||
if nd.nodes[i] > search_id {
|
||||
nd.nodes[i] -= 1;
|
||||
// If all instances of the old ID were replaced, remove the old ID.
|
||||
if !nd.nodes.contains(&old_id) {
|
||||
for node in &mut nd.nodes {
|
||||
*node -= (*node > old_id) as u16;
|
||||
}
|
||||
block.nimap.remove_shift(old_id);
|
||||
}
|
||||
}
|
||||
block.nimap.remove_shift(search_id);
|
||||
}
|
||||
}
|
||||
// Replace nodes in whole map block.
|
||||
// Replace nodes in whole mapblock.
|
||||
else {
|
||||
// Block already contains replacement node, beware!
|
||||
if let Some(mut replace_id) = block.nimap.get_id(new_node) {
|
||||
let _t = tk.get_timer("replace (non-unique replacement)");
|
||||
if let Some(mut new_id) = block.nimap.get_id(new_node) {
|
||||
// Delete unused ID from name-ID map and shift IDs down.
|
||||
block.nimap.remove_shift(search_id);
|
||||
block.nimap.remove_shift(old_id);
|
||||
// Shift replacement ID, if necessary.
|
||||
replace_id -= (replace_id > search_id) as u16;
|
||||
new_id -= (new_id > old_id) as u16;
|
||||
|
||||
// Map old node IDs to new node IDs.
|
||||
let nd = block.node_data.get_mut();
|
||||
for id in &mut nd.nodes {
|
||||
*id = if *id == search_id {
|
||||
count += 1;
|
||||
replace_id
|
||||
*id = if *id == old_id {
|
||||
replaced += 1;
|
||||
new_id
|
||||
} else {
|
||||
*id - (*id > search_id) as u16
|
||||
*id - (*id > old_id) as u16
|
||||
};
|
||||
}
|
||||
}
|
||||
// Block does not contain replacement node.
|
||||
// Simply replace the node name in the name-ID map.
|
||||
else {
|
||||
let _t = tk.get_timer("replace (unique replacement)");
|
||||
let nd = block.node_data.get_ref();
|
||||
for id in &nd.nodes {
|
||||
count += (*id == search_id) as u64;
|
||||
replaced += (*id == old_id) as u64;
|
||||
}
|
||||
block.nimap.0.insert(search_id, new_node.to_vec());
|
||||
block.nimap.0.insert(old_id, new_node.to_vec());
|
||||
}
|
||||
}
|
||||
count
|
||||
replaced
|
||||
}
|
||||
|
||||
|
||||
fn replace_nodes(inst: &mut InstBundle) {
|
||||
let node = to_bytes(inst.args.node.as_ref().unwrap());
|
||||
let old_node = to_bytes(inst.args.node.as_ref().unwrap());
|
||||
let new_node = to_bytes(inst.args.new_node.as_ref().unwrap());
|
||||
let keys = query_keys(&mut inst.db, &inst.status,
|
||||
std::slice::from_ref(&node), inst.args.area, inst.args.invert, true);
|
||||
std::slice::from_ref(&old_node),
|
||||
inst.args.area, inst.args.invert, true);
|
||||
|
||||
inst.status.begin_editing();
|
||||
let mut count = 0;
|
||||
|
||||
let mut tk = TimeKeeper::new();
|
||||
for key in keys {
|
||||
let data = inst.db.get_block(key).unwrap();
|
||||
|
||||
let mut block = {
|
||||
let _t = tk.get_timer("decode");
|
||||
MapBlock::deserialize(&data).unwrap()
|
||||
};
|
||||
let mut block = unwrap_or!(MapBlock::deserialize(&data),
|
||||
{ inst.status.inc_failed(); continue; });
|
||||
|
||||
if let Some(search_id) = block.nimap.get_id(&node) {
|
||||
count += do_replace(&mut block, key, search_id, &new_node,
|
||||
inst.args.area, inst.args.invert, &mut tk);
|
||||
let new_data = {
|
||||
let _t = tk.get_timer("encode");
|
||||
block.serialize()
|
||||
};
|
||||
if let Some(old_id) = block.nimap.get_id(&old_node) {
|
||||
count += do_replace(&mut block, key, old_id, &new_node,
|
||||
inst.args.area, inst.args.invert);
|
||||
let new_data = block.serialize();
|
||||
inst.db.set_block(key, &new_data).unwrap();
|
||||
}
|
||||
|
||||
inst.status.inc_done();
|
||||
}
|
||||
|
||||
// tk.print();
|
||||
inst.status.end_editing();
|
||||
inst.status.log_info(format!("{} nodes replaced.", fmt_big_num(count)));
|
||||
}
|
||||
|
@ -8,6 +8,7 @@ use crate::utils::{query_keys, to_bytes, fmt_big_num};
|
||||
|
||||
|
||||
fn set_meta_var(inst: &mut InstBundle) {
|
||||
// TODO: Bytes input
|
||||
let key = to_bytes(inst.args.key.as_ref().unwrap());
|
||||
let value = to_bytes(inst.args.value.as_ref().unwrap());
|
||||
let nodes: Vec<_> = inst.args.nodes.iter().map(to_bytes).collect();
|
||||
|
@ -1,81 +1,89 @@
|
||||
use super::Command;
|
||||
|
||||
use crate::spatial::{Vec3, Area};
|
||||
use crate::unwrap_or;
|
||||
use crate::spatial::{Vec3, Area, InverseBlockIterator};
|
||||
use crate::instance::{ArgType, InstArgs, InstBundle};
|
||||
use crate::map_block::MapBlock;
|
||||
use crate::utils::{query_keys, to_bytes, to_slice, fmt_big_num};
|
||||
|
||||
|
||||
fn set_in_area_node(block: &mut MapBlock, area: Area, id: u16, val: u8) -> u64
|
||||
fn set_param2_partial(block: &mut MapBlock, area: Area, invert: bool,
|
||||
node_id: Option<u16>, val: u8) -> u64
|
||||
{
|
||||
let nd = block.node_data.get_mut();
|
||||
let mut count = 0;
|
||||
|
||||
if invert {
|
||||
if let Some(id) = node_id {
|
||||
for idx in InverseBlockIterator::new(area) {
|
||||
if nd.nodes[idx] == id {
|
||||
nd.param2[idx] = val;
|
||||
count += 1;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for idx in InverseBlockIterator::new(area) {
|
||||
nd.param2[idx] = val;
|
||||
}
|
||||
count += 4096 - area.volume();
|
||||
}
|
||||
} else {
|
||||
let no_node = node_id.is_none();
|
||||
let id = node_id.unwrap_or(0);
|
||||
|
||||
for z in area.min.z ..= area.max.z {
|
||||
let z_start = z * 256;
|
||||
for y in area.min.y ..= area.max.y {
|
||||
let zy_start = z_start + y * 16;
|
||||
for x in area.min.x ..= area.max.x {
|
||||
let i = (zy_start + x) as usize;
|
||||
if nd.nodes[i] == id {
|
||||
if no_node || nd.nodes[i] == id {
|
||||
nd.param2[i] = val;
|
||||
count += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
count
|
||||
}
|
||||
|
||||
|
||||
fn set_in_area(block: &mut MapBlock, area: Area, val: u8) {
|
||||
let nd = block.node_data.get_mut();
|
||||
for z in area.min.z ..= area.max.z {
|
||||
let z_start = z * 256;
|
||||
for y in area.min.y ..= area.max.y {
|
||||
let zy_start = z_start + y * 16;
|
||||
for x in area.min.x ..= area.max.x {
|
||||
nd.param2[(zy_start + x) as usize] = val;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fn set_param2(inst: &mut InstBundle) {
|
||||
let param2_val = inst.args.param2_val.unwrap();
|
||||
let node = inst.args.node.as_ref().map(to_bytes);
|
||||
|
||||
let keys = query_keys(&mut inst.db, &mut inst.status,
|
||||
to_slice(&node), inst.args.area, false, true);
|
||||
to_slice(&node), inst.args.area, inst.args.invert, true);
|
||||
|
||||
inst.status.begin_editing();
|
||||
|
||||
let mut count: u64 = 0;
|
||||
use crate::time_keeper::TimeKeeper;
|
||||
let mut tk = TimeKeeper::new();
|
||||
for key in keys {
|
||||
inst.status.inc_done();
|
||||
|
||||
let pos = Vec3::from_block_key(key);
|
||||
let data = inst.db.get_block(key).unwrap();
|
||||
let mut block = MapBlock::deserialize(&data).unwrap();
|
||||
let mut block = unwrap_or!(MapBlock::deserialize(&data),
|
||||
{ inst.status.inc_failed(); continue; });
|
||||
|
||||
let node_id = node.as_ref().and_then(|n| block.nimap.get_id(n));
|
||||
if inst.args.node.is_some() && node_id.is_none() {
|
||||
// Node not found in this map block.
|
||||
// Node not found in this mapblock.
|
||||
continue;
|
||||
}
|
||||
|
||||
let nd = block.node_data.get_mut();
|
||||
if let Some(area) = inst.args.area
|
||||
.filter(|a| !a.contains_block(pos))
|
||||
.filter(|a| a.contains_block(pos) != a.touches_block(pos))
|
||||
{ // Modify part of block
|
||||
let overlap = area.rel_block_overlap(pos).unwrap();
|
||||
if let Some(nid) = node_id {
|
||||
count +=
|
||||
set_in_area_node(&mut block, overlap, nid, param2_val);
|
||||
} else {
|
||||
set_in_area(&mut block, overlap, param2_val);
|
||||
count += overlap.volume();
|
||||
}
|
||||
let block_part = area.rel_block_overlap(pos).unwrap();
|
||||
let _t = tk.get_timer("set_param2_partial");
|
||||
count += set_param2_partial(&mut block,
|
||||
block_part, inst.args.invert, node_id, param2_val);
|
||||
} else { // Modify whole block
|
||||
if let Some(nid) = node_id {
|
||||
for i in 0 .. nd.param2.len() {
|
||||
@ -85,9 +93,7 @@ fn set_param2(inst: &mut InstBundle) {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for x in &mut nd.param2 {
|
||||
*x = param2_val;
|
||||
}
|
||||
nd.param2.fill(param2_val);
|
||||
count += nd.param2.len() as u64;
|
||||
}
|
||||
}
|
||||
@ -96,6 +102,7 @@ fn set_param2(inst: &mut InstBundle) {
|
||||
}
|
||||
|
||||
inst.status.end_editing();
|
||||
tk.print(&mut inst.status);
|
||||
inst.status.log_info(format!("{} nodes set.", fmt_big_num(count)));
|
||||
}
|
||||
|
||||
@ -113,9 +120,10 @@ pub fn get_command() -> Command {
|
||||
verify_args: Some(verify_args),
|
||||
args: vec![
|
||||
(ArgType::Area(false), "Area in which to set param2 values"),
|
||||
(ArgType::Invert, "Set param2 values outside the given area."),
|
||||
(ArgType::Node(false), "Node to set param2 values of"),
|
||||
(ArgType::Param2Val, "New param2 value")
|
||||
],
|
||||
help: "Set param2 values of an area or node."
|
||||
help: "Set param2 value of certain nodes."
|
||||
}
|
||||
}
|
||||
|
@ -4,7 +4,7 @@ use std::sync::mpsc;
|
||||
|
||||
use anyhow::Context;
|
||||
|
||||
use crate::spatial::{Vec3, Area};
|
||||
use crate::spatial::{Vec3, Area, MAP_LIMIT};
|
||||
use crate::map_database::MapDatabase;
|
||||
use crate::commands;
|
||||
|
||||
@ -193,6 +193,27 @@ fn status_channel() -> (StatusServer, StatusClient) {
|
||||
|
||||
|
||||
fn verify_args(args: &InstArgs) -> anyhow::Result<()> {
|
||||
// TODO: Complete verifications.
|
||||
|
||||
if args.area.is_none() && args.invert {
|
||||
anyhow::bail!("Cannot invert without a specified area.");
|
||||
}
|
||||
if let Some(a) = args.area {
|
||||
for pos in &[a.min, a.max] {
|
||||
anyhow::ensure!(pos.is_valid_node_pos(),
|
||||
"Area corner is outside map bounds: {}.", pos);
|
||||
}
|
||||
}
|
||||
if let Some(offset) = args.offset {
|
||||
let huge = |n| n < -MAP_LIMIT * 2 || n > MAP_LIMIT * 2;
|
||||
|
||||
if huge(offset.x) || huge(offset.y) || huge(offset.z) {
|
||||
anyhow::bail!(
|
||||
"Offset cannot be larger than {} nodes in any direction.",
|
||||
MAP_LIMIT * 2);
|
||||
}
|
||||
}
|
||||
|
||||
fn is_valid_name(name: &str) -> bool {
|
||||
if name == "air" || name == "ignore" {
|
||||
true
|
||||
@ -205,29 +226,12 @@ fn verify_args(args: &InstArgs) -> anyhow::Result<()> {
|
||||
let mod_name = &name[..delim];
|
||||
let item_name = &name[delim + 1..];
|
||||
|
||||
if mod_name.find(|c: char|
|
||||
mod_name.find(|c: char|
|
||||
!(c.is_ascii_lowercase() || c.is_ascii_digit() || c == '_'))
|
||||
.is_some()
|
||||
|| item_name.find(|c: char|
|
||||
.is_none()
|
||||
&& item_name.find(|c: char|
|
||||
!(c.is_ascii_alphanumeric() || c == '_'))
|
||||
.is_some()
|
||||
{
|
||||
false
|
||||
} else {
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Complete verifications.
|
||||
|
||||
if args.area.is_none() && args.invert {
|
||||
anyhow::bail!("Cannot invert without a specified area.");
|
||||
}
|
||||
if let Some(a) = args.area {
|
||||
for pos in &[a.min, a.max] {
|
||||
anyhow::ensure!(pos.is_valid_node_pos(),
|
||||
"Area corner is outside map bounds: {}.", pos);
|
||||
.is_none()
|
||||
}
|
||||
}
|
||||
|
||||
@ -246,6 +250,13 @@ fn verify_args(args: &InstArgs) -> anyhow::Result<()> {
|
||||
verify_name!(args.new_node, "Invalid node name: {}");
|
||||
verify_name!(args.object, "Invalid object name: {}");
|
||||
verify_name!(args.item, "Invalid item name: {}");
|
||||
if let Some(items) = &args.items {
|
||||
for i in items {
|
||||
anyhow::ensure!(is_valid_name(i), "Invalid item name: {}", i);
|
||||
}
|
||||
}
|
||||
verify_name!(args.new_item, "Invalid item name: {}");
|
||||
// TODO: Are keys/values escaped?
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@ -80,6 +80,22 @@ impl Area {
|
||||
(self.max.z - self.min.z + 1) as u64
|
||||
}
|
||||
|
||||
pub fn intersection(&self, rhs: Self) -> Option<Self> {
|
||||
let res = Self {
|
||||
min: Vec3 {
|
||||
x: max(self.min.x, rhs.min.x),
|
||||
y: max(self.min.y, rhs.min.y),
|
||||
z: max(self.min.z, rhs.min.z)
|
||||
},
|
||||
max: Vec3 {
|
||||
x: min(self.max.x, rhs.max.x),
|
||||
y: min(self.max.y, rhs.max.y),
|
||||
z: min(self.max.z, rhs.max.z)
|
||||
}
|
||||
};
|
||||
Some(res).filter(Self::is_valid)
|
||||
}
|
||||
|
||||
pub fn contains(&self, pos: Vec3) -> bool {
|
||||
self.min.x <= pos.x && pos.x <= self.max.x
|
||||
&& self.min.y <= pos.y && pos.y <= self.max.y
|
||||
@ -253,6 +269,36 @@ mod tests {
|
||||
iter_area(Area::new(Vec3::new(0, -1, -2), Vec3::new(5, 7, 11)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_area_intersection() {
|
||||
let triples = [
|
||||
(
|
||||
Area::new(Vec3::new(0, 0, 0), Vec3::new(0, 0, 0)),
|
||||
Area::new(Vec3::new(1, 1, 0), Vec3::new(1, 1, 0)),
|
||||
None
|
||||
),
|
||||
(
|
||||
Area::new(Vec3::new(-10, -8, -10), Vec3::new(10, 8, 10)),
|
||||
Area::new(Vec3::new(-12, 0, -2), Vec3::new(-8, 13, 2)),
|
||||
Some(Area::new(Vec3::new(-10, 0, -2), Vec3::new(-8, 8, 2)))
|
||||
),
|
||||
(
|
||||
Area::new(Vec3::new(0, 0, 0), Vec3::new(2, 2, 2)),
|
||||
Area::new(Vec3::new(0, -1, 3), Vec3::new(2, 1, 5)),
|
||||
None
|
||||
),
|
||||
(
|
||||
Area::new(Vec3::new(0, -10, -10), Vec3::new(30, 30, 30)),
|
||||
Area::new(Vec3::new(16, 16, -10), Vec3::new(29, 29, 20)),
|
||||
Some(Area::new(Vec3::new(16, 16, -10), Vec3::new(29, 29, 20)))
|
||||
),
|
||||
];
|
||||
for t in &triples {
|
||||
assert_eq!(t.0.intersection(t.1), t.2);
|
||||
assert_eq!(t.1.intersection(t.0), t.2);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_area_containment() {
|
||||
let area = Area::new(Vec3::new(-1, -32, 16), Vec3::new(30, -17, 54));
|
||||
|
@ -1,5 +1,112 @@
|
||||
mod vec3;
|
||||
mod area;
|
||||
|
||||
pub use vec3::Vec3;
|
||||
pub use vec3::{MAP_LIMIT, Vec3};
|
||||
pub use area::Area;
|
||||
|
||||
|
||||
/// Iterates over all the block indices that are *not* contained within an
|
||||
/// area, in order.
|
||||
pub struct InverseBlockIterator {
|
||||
area: Area,
|
||||
idx: usize,
|
||||
can_skip: bool,
|
||||
skip_pos: Vec3,
|
||||
skip_idx: usize,
|
||||
skip_len: usize,
|
||||
}
|
||||
|
||||
impl InverseBlockIterator {
|
||||
pub fn new(area: Area) -> Self {
|
||||
assert!(area.min.x >= 0 && area.max.x < 16
|
||||
&& area.min.y >= 0 && area.max.y < 16
|
||||
&& area.min.z >= 0 && area.max.z < 16);
|
||||
|
||||
Self {
|
||||
area,
|
||||
idx: 0,
|
||||
can_skip: true,
|
||||
skip_pos: area.min,
|
||||
skip_idx:
|
||||
(area.min.x + area.min.y * 16 + area.min.z * 256) as usize,
|
||||
skip_len: (area.max.x - area.min.x + 1) as usize
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Iterator for InverseBlockIterator {
|
||||
type Item = usize;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
while self.can_skip && self.idx >= self.skip_idx {
|
||||
self.idx += self.skip_len;
|
||||
// Increment self.skip_pos, self.skip_idx.
|
||||
let mut sp = self.skip_pos;
|
||||
sp.y += 1;
|
||||
if sp.y > self.area.max.y {
|
||||
sp.y = self.area.min.y;
|
||||
sp.z += 1;
|
||||
if sp.z > self.area.max.z {
|
||||
// No more skips
|
||||
self.can_skip = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
self.skip_pos = sp;
|
||||
self.skip_idx = (sp.x + sp.y * 16 + sp.z * 256) as usize;
|
||||
}
|
||||
|
||||
if self.idx < 4096 {
|
||||
let idx = self.idx;
|
||||
self.idx += 1;
|
||||
Some(idx)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_inverse_block_iterator() {
|
||||
let dim_pairs = [
|
||||
(1, 14), // Touching neither end
|
||||
(1, 2),
|
||||
(9, 9),
|
||||
(1, 15), // Touching max end
|
||||
(11, 15),
|
||||
(15, 15),
|
||||
(0, 0), // Touching min end
|
||||
(0, 1),
|
||||
(0, 14),
|
||||
(0, 15), // End-to-end
|
||||
];
|
||||
|
||||
fn test_area(area: Area) {
|
||||
let mut iter = InverseBlockIterator::new(area);
|
||||
for pos in &Area::new(Vec3::new(0, 0, 0), Vec3::new(15, 15, 15)) {
|
||||
if !area.contains(pos) {
|
||||
let idx = (pos.x + pos.y * 16 + pos.z * 256) as usize;
|
||||
assert_eq!(iter.next(), Some(idx));
|
||||
}
|
||||
}
|
||||
assert_eq!(iter.next(), None)
|
||||
}
|
||||
|
||||
for z_dims in &dim_pairs {
|
||||
for y_dims in &dim_pairs {
|
||||
for x_dims in &dim_pairs {
|
||||
let area = Area::new(
|
||||
Vec3::new(x_dims.0, y_dims.0, z_dims.0),
|
||||
Vec3::new(x_dims.1, y_dims.1, z_dims.1)
|
||||
);
|
||||
test_area(area);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,3 +1,6 @@
|
||||
pub const MAP_LIMIT: i32 = 31000;
|
||||
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub struct Vec3 {
|
||||
pub x: i32,
|
||||
@ -39,7 +42,7 @@ impl Vec3 {
|
||||
}
|
||||
|
||||
pub fn is_valid_block_pos(&self) -> bool {
|
||||
const LIMIT: i32 = 31000 / 16;
|
||||
const LIMIT: i32 = MAP_LIMIT / 16;
|
||||
|
||||
-LIMIT <= self.x && self.x <= LIMIT
|
||||
&& -LIMIT <= self.y && self.y <= LIMIT
|
||||
@ -47,7 +50,7 @@ impl Vec3 {
|
||||
}
|
||||
|
||||
pub fn is_valid_node_pos(&self) -> bool {
|
||||
const LIMIT: i32 = 31000;
|
||||
const LIMIT: i32 = MAP_LIMIT;
|
||||
|
||||
-LIMIT <= self.x && self.x <= LIMIT
|
||||
&& -LIMIT <= self.y && self.y <= LIMIT
|
||||
|
Loading…
x
Reference in New Issue
Block a user