17
0
mirror of https://github.com/MaSzyna-EU07/maszyna.git synced 2026-10-28 16:57:29 +01:00

Add a list of the terrain chunks to the editor

This commit is contained in:
2026-10-21 13:15:44 +02:00
parent 7d0f76aaa9
commit ae3e063df0
9 changed files with 579 additions and 19 deletions

View File

@@ -237,6 +237,7 @@ set(SOURCES
"application/editormodevehicle.cpp"
"application/editormodeselection.cpp"
"application/editormodeviewport.cpp"
"application/editormodeterrainchunks.cpp"
"application/editormodesignal.cpp"
"application/editormodesweep.cpp"
"application/editormodeturntable.cpp"

View File

@@ -1495,6 +1495,10 @@ bool editor_mode::update()
render_orthophoto_window();
render_switch_designer();
render_environment_window();
if (m_terrain_open)
render_terrain_chunks_window();
else
m_chunklist.scanned = false; // the files are read again when the terrain is the field of work again
draw_selection_overlay();
render_context_menu();
@@ -1610,6 +1614,11 @@ void editor_mode::render_terrain_ui()
add_chunks_around(m_terrain_add_count);
if (ImGui::IsItemHovered())
ImGui::SetTooltip("%s", STR_C("Makes the chunks missing around the camera, shaped as the ground under them"));
if (ImGui::Button(STR_C("List of the chunks...")))
m_chunklist.open = true;
if (ImGui::IsItemHovered())
ImGui::SetTooltip("%s", STR_C("All the chunks of the terrain with the files they're kept in and their triangles;\n"
"a double click on a chunk takes the camera to it"));
render_ground_conversion();
if (dynamic_cast<TAnimModel *>(m_node) != nullptr)
{
@@ -2121,6 +2130,9 @@ void editor_mode::draw_terrain_overlay()
if (m_terrain_tool == terrain_tool::spacing)
for (auto const &key : m_terrain_selection)
chunk_outline(key, cursor.y, IM_COL32(90, 220, 255, 230), 3.0f);
// the chunk chosen in the list of the chunks
if (m_chunklist.open && m_chunklist.chosen)
chunk_outline(*m_chunklist.chosen, cursor.y, IM_COL32(255, 150, 40, 230), 3.0f);
if ((m_terrain_tool == terrain_tool::chunks || m_terrain_tool == terrain_tool::spacing) && hovering)
{
auto const key = heightmap::chunk_at(cursor.x, cursor.z);

View File

@@ -588,6 +588,33 @@ class editor_mode : public application_mode, private editor_track::observer
float m_water_selected_level{0.0f}; // level of the chosen water, while it's edited
bool m_water_level_dragged{false};
std::string m_terrain_status;
// the window listing the chunks of the terrain, the files they're kept in and their triangles (editormodeterrainchunks.cpp)
void render_terrain_chunks_window();
// the camera brought over the chunk, to look at the whole of it
void go_to_terrain_chunk(heightmap::chunk_key const &Key);
struct terrain_chunk_row
{
heightmap::chunk_key key{0, 0};
int cells{0}; // quads along a side; 0: unknown
bool stored{false}; // in a pack file as it's on the disk
};
struct terrain_chunk_list
{
bool open{true};
bool scanned{false}; // the pack files were read
bool modified{false}; // the terrain had unsaved changes when last seen: once it hasn't, it was saved and the files are read again
std::vector<std::pair<heightmap::chunk_key, int>> stored; // the chunks of the pack files, as read last
std::vector<terrain_chunk_row> rows; // the chunks listed: of the files and of the memory, filtered and sorted
std::size_t chunks{0}; // listed before the filter
std::size_t files{0};
std::size_t triangles{0}; // of the full grids of all the chunks
double built{-1.0}; // when the rows were made
bool resort{true};
int sortcolumn{0};
bool descending{false};
char filter[64]{};
std::optional<heightmap::chunk_key> chosen; // outlined in the view
} m_chunklist;
// the heightmap terrain, shared with the scenery loader so it shows in every mode
terrain_streamer &m_streamer{EditorTerrain};

View File

@@ -0,0 +1,361 @@
/*
This Source Code Form is subject to the
terms of the Mozilla Public License, v.
2.0. If a copy of the MPL was not
distributed with this file, You can
obtain one at
http://mozilla.org/MPL/2.0/.
*/
// the list of the chunks of the heightmap terrain: the pack files they're kept in, their grids and triangles, and the camera
// taken to the chunk double clicked
#include "stdafx.h"
#include "application/editormode.h"
#include "editor/editorFormat.hpp"
#include "utilities/Globals.h"
#include "utilities/translation.h"
#include "imgui/imgui.h"
#include <algorithm>
#include <cmath>
#include <cstring>
#include <tuple>
namespace
{
// how often the list takes in what changed in memory: the chunks loaded, edited and drawn
double constexpr list_refresh{0.5};
enum terrain_chunk_column
{
column_chunk,
column_file,
column_spacing,
column_triangles,
column_drawn,
column_state,
column_count
};
enum class chunk_state
{
stored, // in the file, not in memory
loaded,
edited, // changed since it was loaded or saved
added // not in the files yet
};
// a large number with its thousands set apart, to be read at a glance
std::string grouped(std::size_t const Value)
{
auto digits{std::to_string(Value)};
for (auto i = static_cast<int>(digits.size()) - 3; i > 0; i -= 3)
digits.insert(static_cast<std::size_t>(i), " ");
return digits;
}
std::size_t grid_triangles(int const Cells)
{
return static_cast<std::size_t>(Cells) * static_cast<std::size_t>(Cells) * 2;
}
} // namespace
void editor_mode::render_terrain_chunks_window()
{
auto &list{m_chunklist};
if (false == list.open)
{
// read again when it's opened, the files may have changed meanwhile
list.scanned = false;
return;
}
ImGui::SetNextWindowSize(ImVec2(560.0f * Global.ui_scale, 460.0f * Global.ui_scale), ImGuiCond_FirstUseEver);
if (false == ImGui::Begin((std::string(STR_C("Terrain chunks")) + "###terrainchunks").c_str(), &list.open, ImGuiWindowFlags_NoCollapse))
{
ImGui::End();
return;
}
if (false == m_streamer.active())
{
ImGui::TextWrapped("%s", STR_C("The scenery has no heightmap terrain yet"));
list.scanned = false;
list.rows.clear();
list.chosen.reset();
ImGui::End();
return;
}
// the files are read when the list opens, when it's asked to, and after the terrain was saved
auto const modified{m_streamer.modified()};
auto const saved{list.modified && false == modified};
list.modified = modified;
auto const now{ImGui::GetTime()};
if (false == list.scanned || saved)
{
list.stored = m_streamer.stored_chunks();
list.scanned = true;
list.built = -1.0;
}
// the chunks of the files with the ones in memory, which may be edited, added since, or of another grid by now
if (list.built < 0.0 || now - list.built > list_refresh || list.resort)
{
std::vector<editor_terrain *> resident;
m_streamer.collect(resident);
std::vector<terrain_chunk_row> rows;
rows.reserve(list.stored.size() + resident.size());
for (auto const &[key, cells] : list.stored)
if (false == m_streamer.removed(key))
rows.push_back({key, cells, true});
auto const stored{rows.size()};
for (auto const *chunk : resident)
{
auto const key{chunk->key()};
auto const found{std::lower_bound(rows.begin(), rows.begin() + stored, key, [](terrain_chunk_row const &Row, heightmap::chunk_key const &Key) { return Row.key < Key; })};
if (found != rows.begin() + stored && found->key == key)
found->cells = chunk->cells();
else
rows.push_back({key, chunk->cells(), false});
}
// what the rows stand for, before the filter
std::vector<heightmap::pack_key> packs;
list.triangles = 0;
for (auto const &row : rows)
{
list.triangles += grid_triangles(row.cells);
packs.push_back(heightmap::pack_of(row.key));
}
std::sort(packs.begin(), packs.end());
list.files = static_cast<std::size_t>(std::unique(packs.begin(), packs.end()) - packs.begin());
list.chunks = rows.size();
// the filter: a part of the name of the file, or of the coordinates of the chunk
if (list.filter[0] != '\0')
{
std::string const filter{list.filter};
std::erase_if(rows, [&](terrain_chunk_row const &Row) {
return heightmap::pack_file_name(heightmap::pack_of(Row.key)).find(filter) == std::string::npos &&
format("%d,%d", Row.key.first, Row.key.second).find(filter) == std::string::npos;
});
}
list.rows = std::move(rows);
list.built = now;
list.resort = true;
}
// the state of a chunk and what's drawn of it, as it's now
auto const state_of = [&](terrain_chunk_row const &Row, editor_terrain const *Chunk) {
if (false == Row.stored)
return chunk_state::added;
if (Chunk == nullptr)
return chunk_state::stored;
return Chunk->modified() ? chunk_state::edited : chunk_state::loaded;
};
auto const drawn_of = [](editor_terrain const *Chunk) -> std::size_t { return Chunk != nullptr ? Chunk->triangles() : 0; };
// summary, of all the chunks whatever the filter
std::vector<editor_terrain *> resident;
m_streamer.collect(resident);
std::size_t drawn{0}, unsaved{0};
for (auto const *chunk : resident)
{
drawn += drawn_of(chunk);
if (chunk->modified())
++unsaved;
}
auto const area{static_cast<double>(list.chunks) * heightmap::chunk_size * heightmap::chunk_size / 1e6};
ImGui::Text(STR_C("Chunks: %zu in %zu files, %.2f km2"), list.chunks, list.files, area);
ImGui::Text(STR_C("In memory: %zu, changed and not saved: %zu"), resident.size(), unsaved);
ImGui::Text(STR_C("Terrain triangles: %s"), grouped(list.triangles).c_str());
if (ImGui::IsItemHovered())
ImGui::SetTooltip("%s", STR_C("Of the full grids of all the chunks, as their point spacing makes them; the holes not taken off"));
ImGui::Text(STR_C("Drawn now: %s"), grouped(drawn).c_str());
if (ImGui::IsItemHovered())
ImGui::SetTooltip("%s", STR_C("Triangles of the chunks drawn around the camera, with the grids made coarser with the distance"));
ImGui::SetNextItemWidth(ImGui::GetFontSize() * 12.0f);
if (ImGui::InputTextWithHint("##chunkfilter", STR_C("Filter: file or x,z"), list.filter, sizeof(list.filter)))
list.built = -1.0;
ImGui::SameLine();
if (ImGui::Button(STR_C("Read the files again")))
{
list.scanned = false;
list.built = -1.0;
}
if (ImGui::IsItemHovered())
ImGui::SetTooltip("%s", STR_C("The files are read when the list opens and after the terrain is saved"));
ImGui::SameLine();
auto const here{heightmap::chunk_at(Camera.Pos.x, Camera.Pos.z)};
auto findhere{false};
if (ImGui::Button(STR_C("Under the camera")))
{
list.chosen = here;
findhere = true;
}
if (ImGui::IsItemHovered())
ImGui::SetTooltip("%s", STR_C("Shows the chunk the camera is over in the list"));
ImGui::TextDisabled("%s", STR_C("Double click: the camera goes to the chunk"));
auto const flags{ImGuiTableFlags_Resizable | ImGuiTableFlags_Sortable | ImGuiTableFlags_ScrollY | ImGuiTableFlags_RowBg | ImGuiTableFlags_BordersInnerV | ImGuiTableFlags_SizingFixedFit};
if (ImGui::BeginTable("##terrainchunks", column_count, flags, ImVec2(0.0f, 0.0f)))
{
ImGui::TableSetupScrollFreeze(0, 1);
ImGui::TableSetupColumn(STR_C("Chunk"), ImGuiTableColumnFlags_DefaultSort, 0.0f, column_chunk);
ImGui::TableSetupColumn(STR_C("File"), 0, 0.0f, column_file);
ImGui::TableSetupColumn(STR_C("Spacing"), 0, 0.0f, column_spacing);
ImGui::TableSetupColumn(STR_C("Triangles"), ImGuiTableColumnFlags_PreferSortDescending, 0.0f, column_triangles);
ImGui::TableSetupColumn(STR_C("Drawn"), ImGuiTableColumnFlags_PreferSortDescending, 0.0f, column_drawn);
ImGui::TableSetupColumn(STR_C("State"), ImGuiTableColumnFlags_WidthStretch, 0.0f, column_state);
ImGui::TableHeadersRow();
if (auto *specs{ImGui::TableGetSortSpecs()}; specs != nullptr && specs->SpecsCount > 0)
{
if (specs->SpecsDirty)
{
list.sortcolumn = specs->Specs[0].ColumnUserID;
list.descending = specs->Specs[0].SortDirection == ImGuiSortDirection_Descending;
specs->SpecsDirty = false;
list.resort = true;
}
}
if (list.resort)
{
// the values which change with the camera are sorted as they were at the time
auto const value = [&](terrain_chunk_row const &Row) -> std::tuple<long long, long long, long long> {
switch (list.sortcolumn)
{
case column_file:
{
auto const pack{heightmap::pack_of(Row.key)};
return {pack.first, pack.second, 0};
}
case column_spacing:
return {-Row.cells, 0, 0};
case column_triangles:
return {static_cast<long long>(grid_triangles(Row.cells)), 0, 0};
case column_drawn:
return {static_cast<long long>(drawn_of(m_streamer.chunk(Row.key))), 0, 0};
case column_state:
return {static_cast<long long>(state_of(Row, m_streamer.chunk(Row.key))), 0, 0};
default:
return {0, 0, 0};
}
};
std::vector<std::pair<std::tuple<long long, long long, long long>, std::size_t>> order;
order.reserve(list.rows.size());
for (std::size_t i = 0; i < list.rows.size(); ++i)
order.emplace_back(value(list.rows[i]), i);
auto const descending{list.descending};
auto const &rows{list.rows};
std::sort(order.begin(), order.end(), [&](auto const &A, auto const &B) {
if (A.first != B.first)
return descending ? B.first < A.first : A.first < B.first;
// the same value: by the place of the chunk, the same way
auto const &a{rows[A.second].key}, &b{rows[B.second].key};
return descending ? b < a : a < b;
});
std::vector<terrain_chunk_row> sorted;
sorted.reserve(list.rows.size());
for (auto const &entry : order)
sorted.push_back(list.rows[entry.second]);
list.rows = std::move(sorted);
list.resort = false;
}
auto const height{ImGui::GetTextLineHeightWithSpacing()};
ImGuiListClipper clipper;
clipper.Begin(static_cast<int>(list.rows.size()));
if (findhere)
{
auto const found{std::find_if(list.rows.begin(), list.rows.end(), [&](terrain_chunk_row const &Row) { return Row.key == here; })};
if (found != list.rows.end())
{
auto const index{static_cast<int>(found - list.rows.begin())};
clipper.IncludeItemByIndex(index);
ImGui::SetScrollY(std::max(0.0f, index * height - ImGui::GetWindowHeight() * 0.5f));
}
}
while (clipper.Step())
{
for (int i = clipper.DisplayStart; i < clipper.DisplayEnd; ++i)
{
auto const &row{list.rows[static_cast<std::size_t>(i)]};
auto const *chunk{m_streamer.chunk(row.key)};
ImGui::TableNextRow();
if (row.key == here)
ImGui::TableSetBgColor(ImGuiTableBgTarget_RowBg1, IM_COL32(90, 140, 220, 60));
ImGui::TableSetColumnIndex(column_chunk);
auto const chosen{list.chosen && *list.chosen == row.key};
auto const label{format("%d, %d##chunk%d_%d", row.key.first, row.key.second, row.key.first, row.key.second)};
if (ImGui::Selectable(label.c_str(), chosen, ImGuiSelectableFlags_SpanAllColumns | ImGuiSelectableFlags_AllowDoubleClick))
{
list.chosen = row.key;
if (ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left))
go_to_terrain_chunk(row.key);
}
ImGui::TableSetColumnIndex(column_file);
ImGui::TextUnformatted(heightmap::pack_file_name(heightmap::pack_of(row.key)).c_str());
ImGui::TableSetColumnIndex(column_spacing);
if (row.cells > 0)
ImGui::Text("%g m", heightmap::chunk_size / row.cells);
else
ImGui::TextDisabled("?");
ImGui::TableSetColumnIndex(column_triangles);
ImGui::TextUnformatted(grouped(grid_triangles(row.cells)).c_str());
ImGui::TableSetColumnIndex(column_drawn);
if (auto const count{drawn_of(chunk)}; count > 0)
ImGui::TextUnformatted(grouped(count).c_str());
else
ImGui::TextDisabled("-");
ImGui::TableSetColumnIndex(column_state);
switch (state_of(row, chunk))
{
case chunk_state::stored:
ImGui::TextDisabled("%s", STR_C("in the file"));
break;
case chunk_state::loaded:
ImGui::TextUnformatted(STR_C("loaded"));
break;
case chunk_state::edited:
ImGui::TextColored(ImVec4(1.0f, 0.75f, 0.35f, 1.0f), "%s", STR_C("changed, not saved"));
break;
case chunk_state::added:
ImGui::TextColored(ImVec4(0.45f, 0.9f, 0.45f, 1.0f), "%s", STR_C("new, not saved"));
break;
}
}
}
ImGui::EndTable();
}
ImGui::End();
}
void editor_mode::go_to_terrain_chunk(heightmap::chunk_key const &Key)
{
auto const corner{heightmap::chunk_corner(Key)};
auto const half{heightmap::chunk_size * 0.5};
glm::dvec3 centre{corner.x + half, 0.0, corner.y + half};
// the height of the ground in the middle; a chunk not in memory is read for it
if (auto const *chunk{m_streamer.chunk(Key, true)})
centre.y = chunk->height_at(centre.x, centre.z);
else if (double ground{0.0}; m_streamer.height_at(Camera.Pos.x, Camera.Pos.z, ground))
centre.y = ground;
m_chunklist.chosen = Key;
if (Global.EditorOrtho)
{
// the top view only moves over it
m_focus_active = false;
Camera.Pos.x = centre.x;
Camera.Pos.z = centre.z;
Camera.Velocity = glm::dvec3{0.0};
Global.pCamera = Camera;
return;
}
// from the side the camera looks from, down at the chunk, from far enough to see the whole of it
auto const yaw{static_cast<double>(Camera.Angle.y)};
auto const pitch{glm::radians(35.0)};
glm::dvec3 const look{-std::sin(yaw) * std::cos(pitch), -std::sin(pitch), -std::cos(yaw) * std::cos(pitch)};
fly_to(centre, look, heightmap::chunk_size * 1.2);
}

View File

@@ -979,6 +979,46 @@ bool terrain_streamer::exists(chunk_key const &Key) const
return pack != m_packs.end() && pack->second.status == pack_state::state::ready && pack->second.file->has(Key);
}
std::vector<std::pair<terrain_streamer::chunk_key, int>> terrain_streamer::stored_chunks()
{
std::vector<std::pair<chunk_key, int>> result;
if (false == m_active)
return result;
std::error_code ec;
std::filesystem::directory_iterator const end;
for (std::filesystem::directory_iterator item{m_directory, ec}; false == static_cast<bool>(ec) && item != end; item.increment(ec))
{
if (item->path().extension() != ".tch")
continue;
auto const filename{item->path().filename().string()};
heightmap::pack_file file;
std::string error;
std::lock_guard<std::mutex> lock{m_filemutex};
if (false == file.open(m_directory + filename, &error))
{
ErrorLog("Bad file: " + error, logtype::file);
continue;
}
// the terrain reads a pack only from the file named after it
if (filename != heightmap::pack_file_name(file.key()))
continue;
auto const cells{file.chunk_cells()};
result.insert(result.end(), cells.begin(), cells.end());
}
std::sort(result.begin(), result.end());
return result;
}
editor_terrain *terrain_streamer::chunk(chunk_key const &Key, bool const Load)
{
if (false == m_active)
return nullptr;
if (Load)
return load_now(Key);
auto const lookup{m_chunks.find(Key)};
return lookup != m_chunks.end() ? lookup->second.get() : nullptr;
}
// editing
editor_terrain *terrain_streamer::load_now(chunk_key const &Key)

View File

@@ -79,6 +79,14 @@ class terrain_streamer
std::size_t resident() const { return m_chunks.size(); }
// chunks in the pack files and in memory, less the removed ones; the files known so far
bool exists(chunk_key const &Key) const;
// the chunks of all the pack files in the folder of the terrain, each with the cells along its side (0: unknown), ordered
// by key, as the files are on the disk: the chunks added or removed since the last save are as they were. reads the
// headers of the files, so it's for a list made now and then, not for every frame
std::vector<std::pair<chunk_key, int>> stored_chunks();
// removed in the editor, still in the files until saved
bool removed(chunk_key const &Key) const { return m_removed.count(Key) > 0; }
// the chunk in memory, or nullptr; Load: read from its pack file if it isn't in memory yet
editor_terrain *chunk(chunk_key const &Key, bool const Load = false);
// editing; the chunks changed are written to the files by save()
heightmap::manifest const &manifest() const { return m_manifest; }

View File

@@ -10086,3 +10086,77 @@ msgstr "Zmienia się z porami roku"
msgid "How much the colour and the height change through the year with the date of the scenario:\ndry and low in winter, greening up in spring, fresh in May, drying out from August.\n0: the same all year (e.g. a watered lawn), 1: fully"
msgstr "Jak bardzo kolor i wysokość zmieniają się w ciągu roku z datą scenariusza:\nzimą sucha i niska, wiosną zielenieje, w maju świeża, od sierpnia wysycha.\n0: taka sama przez cały rok (np. podlewany trawnik), 1: w pełni"
# scenery editor: the list of the terrain chunks
msgid "List of the chunks..."
msgstr "Lista kafli..."
msgid "All the chunks of the terrain with the files they're kept in and their triangles;\na double click on a chunk takes the camera to it"
msgstr "Wszystkie kafle terenu z plikami, w których są zapisane, i ich trójkątami;\ndwuklik na kaflu przenosi do niego kamerę"
msgid "Terrain chunks"
msgstr "Kafle terenu"
msgid "The scenery has no heightmap terrain yet"
msgstr "Sceneria nie ma jeszcze terenu z map wysokości"
msgid "Chunks: %zu in %zu files, %.2f km2"
msgstr "Kafle: %zu w plikach: %zu, %.2f km²"
msgid "In memory: %zu, changed and not saved: %zu"
msgstr "W pamięci: %zu, zmienione i niezapisane: %zu"
msgid "Terrain triangles: %s"
msgstr "Trójkąty terenu: %s"
msgid "Of the full grids of all the chunks, as their point spacing makes them; the holes not taken off"
msgstr "Pełnych siatek wszystkich kafli, jakie daje ich odstęp punktów; dziury nie są odejmowane"
msgid "Drawn now: %s"
msgstr "Rysowane teraz: %s"
msgid "Triangles of the chunks drawn around the camera, with the grids made coarser with the distance"
msgstr "Trójkąty kafli rysowanych wokół kamery, z siatkami rzadszymi wraz z odległością"
msgid "Filter: file or x,z"
msgstr "Filtr: plik lub x,z"
msgid "Read the files again"
msgstr "Wczytaj pliki ponownie"
msgid "The files are read when the list opens and after the terrain is saved"
msgstr "Pliki są czytane przy otwarciu listy i po zapisaniu terenu"
msgid "Under the camera"
msgstr "Pod kamerą"
msgid "Shows the chunk the camera is over in the list"
msgstr "Pokazuje na liście kafel, nad którym jest kamera"
msgid "Double click: the camera goes to the chunk"
msgstr "Dwuklik: kamera przenosi się do kafla"
msgid "Chunk"
msgstr "Kafel"
msgid "Triangles"
msgstr "Trójkąty"
msgid "Drawn"
msgstr "Rysowane"
msgid "State"
msgstr "Stan"
msgid "in the file"
msgstr "w pliku"
msgid "loaded"
msgstr "wczytany"
msgid "changed, not saved"
msgstr "zmieniony, niezapisany"
msgid "new, not saved"
msgstr "nowy, niezapisany"

View File

@@ -156,6 +156,29 @@ std::uint32_t checksum(std::vector<std::uint8_t> const &Data)
return scene::terrain_file::checksum(reinterpret_cast<char const *>(Data.data()), Data.size());
}
// reads the data of a slice of a pack file, unpacked and checked
template <typename Slice_> bool load_slice(std::ifstream &File, Slice_ const &Slice, std::vector<std::uint8_t> &Out)
{
File.clear();
std::vector<std::uint8_t> stored(Slice.stored);
File.seekg(static_cast<std::streamoff>(Slice.offset));
File.read(reinterpret_cast<char *>(stored.data()), stored.size());
if (File.gcount() != static_cast<std::streamsize>(stored.size()))
return false;
if (Slice.codec == codec_stored)
Out = std::move(stored);
else if (Slice.codec == codec_zstd)
{
Out.resize(Slice.size);
auto const result{ZSTD_decompress(Out.data(), Out.size(), stored.data(), stored.size())};
if (ZSTD_isError(result) || result != Slice.size)
return false;
}
else
return false;
return Out.size() == Slice.size && checksum(Out) == Slice.crc;
}
int floor_div(int const Value, int const Divisor)
{
return (Value >= 0 ? Value / Divisor : -((-Value + Divisor - 1) / Divisor));
@@ -980,6 +1003,36 @@ std::vector<chunk_key> pack_file::chunks() const
return result;
}
std::vector<std::pair<chunk_key, int>> pack_file::chunk_cells() const
{
std::vector<std::pair<chunk_key, int>> result;
std::ifstream file{m_path, std::ios::binary};
for (int z = 0; z < pack_side; ++z)
for (int x = 0; x < pack_side; ++x)
{
auto const &slices{m_slices[static_cast<std::size_t>(z) * pack_side + x]};
if (slices.empty())
continue;
int cells{0};
for (auto const &slice : slices)
{
if (slice.tag != tag_chunk)
continue;
std::vector<std::uint8_t> data;
std::uint16_t count{0};
if (file.is_open() && load_slice(file, slice, data))
{
reader input{data.data(), data.size()};
if (input.get(count))
cells = count;
}
break;
}
result.emplace_back(chunk_key{m_key.first * pack_side + x, m_key.second * pack_side + z}, cells);
}
return result;
}
std::shared_ptr<chunk_data> pack_file::read(chunk_key const &Key, std::string *Error) const
{
auto const fail = [&](std::string const &Message) -> std::shared_ptr<chunk_data> {
@@ -994,25 +1047,7 @@ std::shared_ptr<chunk_data> pack_file::read(chunk_key const &Key, std::string *E
if (false == file.good())
return fail("can't open the file");
auto const load = [&](slice const &Slice, std::vector<std::uint8_t> &Out) {
std::vector<std::uint8_t> stored(Slice.stored);
file.seekg(static_cast<std::streamoff>(Slice.offset));
file.read(reinterpret_cast<char *>(stored.data()), stored.size());
if (file.gcount() != static_cast<std::streamsize>(stored.size()))
return false;
if (Slice.codec == codec_stored)
Out = std::move(stored);
else if (Slice.codec == codec_zstd)
{
Out.resize(Slice.size);
auto const result{ZSTD_decompress(Out.data(), Out.size(), stored.data(), stored.size())};
if (ZSTD_isError(result) || result != Slice.size)
return false;
}
else
return false;
return Out.size() == Slice.size && checksum(Out) == Slice.crc;
};
auto const load = [&](slice const &Slice, std::vector<std::uint8_t> &Out) { return load_slice(file, Slice, Out); };
auto chunk{std::make_shared<chunk_data>()};
chunk->key = Key;

View File

@@ -271,6 +271,8 @@ class pack_file
bool has(chunk_key const &Key) const;
// chunks the file holds
std::vector<chunk_key> chunks() const;
// the same, each with the cells along a side of the chunk, read from its header alone; 0 where the header can't be read
std::vector<std::pair<chunk_key, int>> chunk_cells() const;
// reads specified chunk. returns: the chunk, or null on failure
std::shared_ptr<chunk_data> read(chunk_key const &Key, std::string *Error = nullptr) const;
pack_key key() const { return m_key; }