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:
@@ -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"
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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};
|
||||
|
||||
361
application/editormodeterrainchunks.cpp
Normal file
361
application/editormodeterrainchunks.cpp
Normal 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);
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
@@ -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; }
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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; }
|
||||
|
||||
Reference in New Issue
Block a user