1
0
Fork 0
Univerxel/src/client/contouring/FlatDualMC.cpp

490 lines
24 KiB
C++

#include "FlatDualMC.hpp"
#include "core/world/Elements.hpp"
#include <Tracy.hpp> // NOLINT
#include <common/TracySystem.hpp> // NOLINT
#include <imgui.h> // NOLINT
#include "core/utils/toml.hpp"
#include "dualmc.h"
#include "optimizer.hpp"
namespace contouring {
constexpr auto PACK = [](const render::VertexData &v) {
return render::PackedVertexData(meshopt_quantizeHalf(v.Position.x), meshopt_quantizeHalf(v.Position.y),
meshopt_quantizeHalf(v.Position.z), v.Material,
meshopt_quantizeHalf(v.Normal.x), meshopt_quantizeHalf(v.Normal.y),
meshopt_quantizeHalf(v.Normal.z));
};
const std::vector<std::pair<float, float>> LEVELS = {{.001f, 9e-1f}, {.01f, 5e-1f}, {.1f, 1e-1f}, {.2, 1e-2f}, {.5, 5e-3f}};
FlatDualMC::FlatDualMC(const std::string &str) : Abstract() {
auto opt = toml::parse(str);
loadDistance = opt["load_distance"].value_or(loadDistance);
keepDistance = opt["keep_distance"].value_or(keepDistance);
transparency = opt["transparency"].value_or(transparency);
iso = opt["iso"].value_or(iso);
manifold = opt["manifold"].value_or(manifold);
reordering = opt["reordering"].value_or(reordering);
lod_quality = opt["lod_quality"].value_or(lod_quality);
lod_strength = opt["lod_strength"].value_or(lod_strength);
if (const auto ptr = opt["lod_levels"].as_array(); ptr != nullptr && ptr->size() == LEVELS.size()) {
const auto& arr = *ptr;
for(const auto& v: arr) {
lod_levels.push_back(v.value_or(true));
}
} else {
lod_levels = {false, true, false, true, false};
}
for (size_t i = 0; i < LEVELS.size(); i++) {
if(lod_levels[i])
loadedLevels.push_back(LEVELS[i]);
}
for (size_t i = 1; i <= std::max<size_t>(1, std::thread::hardware_concurrency() / 2 - 1); i++) {
workers.emplace_back([&] {
#if TRACY_ENABLE
tracy::SetThreadName("Contouring");
#endif
std::vector<render::VertexData> tmp;
while (running) {
const auto area_priority = areaLoadQueue.currentWeight();
const auto part_priority = partLoadQueue.currentWeight();
if (part_priority > area_priority) {
if (std::pair<world::part_id, part_job> job; partLoadQueue.pop(job)) {
ZoneScopedN("Part");
render::Model::Data data;
render::Model::Data::indices_t idx;
for (uint8_t x = 0; x < job.second.size.x; x++) {
for (uint8_t y = 0; y < job.second.size.y; y++) {
for (uint8_t z = 0; z < job.second.size.z; z++) {
surrounding::corners sur;
surrounding::load(sur, job.second.size, glm::ucvec3(x, y, z), job.second.chunks);
idx.clear();
render(sur, idx, tmp, Layer::Both); //MAYBE: write inplace with sub-vector
simplify(idx, tmp);
data.indices.reserve(data.indices.size() + idx.size());
const auto idx_start = data.vertices.size();
std::transform(idx.begin(), idx.end(), std::back_inserter(data.indices), [&](glm::u16 i) { return i + idx_start; });
data.vertices.reserve(data.vertices.size() + tmp.size());
const auto vert_offset = glm::vec3(x * world::CHUNK_LENGTH, y * world::CHUNK_LENGTH, z * world::CHUNK_LENGTH);
std::transform(tmp.begin(), tmp.end(), std::back_inserter(data.vertices), [&](render::VertexData v) {
v.Position += vert_offset; return PACK(v); });
}}}
optimize_fetch(data);
partLoadedQueue.emplace(job.first, std::move(data));
}
} else if (area_priority.has_value() && (!part_priority.has_value() || area_priority.value() >= part_priority.value())) {
if (std::pair<world::area_chunk_pos, surrounding::corners> ctx; areaLoadQueue.pop(ctx)) {
ZoneScopedN("Chunk");
area_models::data data;
if (transparency) {
render(ctx.second, data.main, tmp, Layer::Solid);
render(ctx.second, data.transparent, tmp, Layer::Transparent);
} else {
render(ctx.second, data.main, tmp, Layer::Both);
}
//MAYBE: direct upload with vulkan
areaLoadedQueue.emplace(ctx.first, std::move(data));
}
} else {
areaLoadQueue.wait();
}
}
});
}
}
FlatDualMC::~FlatDualMC() {
running = false;
areaLoadQueue.notify_all();
for(auto& worker: workers) {
if (worker.joinable())
worker.join();
}
}
std::string FlatDualMC::getOptions() const {
std::ostringstream ss;
toml::table tb({
{"load_distance", loadDistance},
{"keep_distance", keepDistance},
{"transparency", transparency},
{"iso", iso},
{"manifold", manifold},
{"reordering", reordering},
{"lod_quality", lod_quality},
{"lod_strength", lod_strength},
{"lod_levels", toml::array()}
});
for(auto& v: lod_levels)
tb["lod_levels"].as_array()->push_back(v);
ss << tb;
return ss.str();
}
std::pair<float, float> FlatDualMC::getFarRange() const {
return std::make_pair((loadDistance - 1.5) * world::CHUNK_LENGTH, (keepDistance + .5) * world::CHUNK_LENGTH);
}
size_t FlatDualMC::getQueueSize() {
return areaLoadQueue.size() + partLoadQueue.size();
}
void FlatDualMC::enqueue(const world::area_chunk_pos &pos, glm::ll offset, const world::ChunkContainer &data) {
ZoneScopedN("EnqueueContouring");
if (offset <= loadDistance * loadDistance) {
surrounding::corners surrounding;
if(surrounding::load(surrounding, pos.chunk, data)) {
areaLoadQueue.push(pos, std::move(surrounding), -offset);
}
}
}
void FlatDualMC::onUpdate(const world::area_chunk_pos &pos, glm::ll offset, const world::ChunkContainer &data, geometry::Faces neighbors) {
enqueue(pos, offset, data);
if (neighbors && (geometry::Faces::Left | geometry::Faces::Down | geometry::Faces::Backward)) {
for (size_t i = 1; i < 8; i++) {
enqueue(world::area_chunk_pos{pos.area, pos.chunk - surrounding::g_corner_offsets[i]}, offset, data);
}
}
}
void FlatDualMC::onNotify(const world::area_chunk_pos &pos, glm::ll offset, const world::ChunkContainer &data) {
const auto area = areas.find(pos.area);
if(area == areas.end() || !area->second.second.contains(pos.chunk)) {
enqueue(pos, offset, data);
}
}
void FlatDualMC::onNotify(const world::part_id& id, glm::ll offset, const glm::ucvec3& size, const std::vector<std::shared_ptr<world::EdittableChunk>>& chunks, bool update) {
if (parts.contains(id)) {
if (!update)
return;
} else {
parts.set_emplace(id, nullptr);
}
if (offset <= loadDistance * loadDistance) {
partLoadQueue.push(id, part_job{size, chunks}, -offset);
}
}
void FlatDualMC::onLoad(const world::model_id& id, std::istream& in) {
if (!models.contains(id)) {
models.set_emplace(id, nullptr);
modelLoadedQueue.emplace(id, render::Model::Data(in));
}
}
void FlatDualMC::onUnload(const world::model_id& id) {
models.vanish(id);
}
render::Model* FlatDualMC::getModel(const world::part_id& id) const {
if(const auto it = parts.directly_at(id))
return it->get();
return nullptr;
}
render::Model* FlatDualMC::getModel(const world::model_id &id) const {
if(const auto it = models.directly_at(id))
return it->get();
return nullptr;
}
void FlatDualMC::update(const world::voxel_pos& pos, const world::Elements& l) {
ZoneScopedN("Ct");
TracyPlot("CtLoad", static_cast<int64_t>(getQueueSize()));
TracyPlot("CtLoaded", static_cast<int64_t>(areaLoadedQueue.size() + partLoadedQueue.size() + modelLoadedQueue.size()));
size_t buffer_count = 0;
{ // Models
modelLoadedQueue.extractor([&](const std::pair<world::model_id, render::Model::Data>& out) {
if (const auto entity = models.directly_at(out.first))
*entity = render::Model::Create(out.second);
});
models.extract([&](const world::model_id &id, const std::unique_ptr<render::Model> &) {
return !l.models.contains(id); });
buffer_count += models.size();
}
{ // Parts
partLoadedQueue.extractor([&](const std::pair<world::part_id, render::Model::Data>& out) {
if (const auto entity = parts.directly_at(out.first))
*entity = render::Model::Create(out.second);
});
parts.extract([&](const world::part_id &id, const std::unique_ptr<render::Model> &) {
return !l.nodes.contains(id.val); });
//MAYBE: or too far
buffer_count += parts.size();
}
{ // Areas
areaLoadedQueue.extractor([&](const std::pair<world::area_chunk_pos, area_models::data> &out) {
area_models mds;
if (!out.second.main.first.empty())
mds.main = render::LodModel::Create(out.second.main);
if (!out.second.transparent.first.empty())
mds.transparent = render::LodModel::Create(out.second.transparent);
auto &chunks = areas[out.first.area].second; //NOTE: areas[n].first uninitialized: will be set by second part
chunks.erase(out.first.chunk);
chunks.emplace(out.first.chunk, std::move(mds));
});
ZoneScopedN("CtUpdate");
const auto levelMax = loadedLevels.size();
for (auto it_a = areas.begin(); it_a != areas.end();) {
if (const auto node = world::NodeOf<world::Area>::Make(l.nodes.directly_at(it_a->first.val))) {
auto &infos = it_a->second.first;
infos.absolute = node->absolute;
const auto ptr = node->get();
infos.radius = ptr->getChunks().getRadius() * (world::cell_pos::value_type)world::CHUNK_LENGTH;
const auto diff = pos - (world::cell_pos)infos.absolute.position;
if (const auto surface = ptr->getCurvature()) {
// MAYBE: move to area or generator
infos.curvature = std::clamp<float>((glm::max_axis(glm::abs(diff)) - surface.value()) / (infos.radius - surface.value()), -0.1f, 1.f);
}
const auto chunkDiff = glm::dvec3(glm::divide(diff));
auto &chunks = it_a->second.second;
for (auto it_c = chunks.begin(); it_c != chunks.end();) {
const auto distRatio = glm::length(chunkDiff - (glm::qua<double>)infos.absolute.rotation * glm::dvec3(it_c->first)) / keepDistance;
if (distRatio <= 1) {
const auto level = std::clamp<size_t>((1 + lod_quality - distRatio) * levelMax * (1 + lod_strength), 0, levelMax);
if (it_c->second.main) {
it_c->second.main->setLevel(level);
buffer_count++;
}
if (it_c->second.transparent) {
it_c->second.transparent->setLevel(level);
buffer_count++;
}
++it_c;
} else {
it_c = chunks.erase(it_c);
}
}
}
if (it_a->second.second.empty()) {
it_a = areas.erase(it_a);
} else {
++it_a;
}
}
}
TracyPlot("CtCount", static_cast<int64_t>(buffer_count));
}
void FlatDualMC::onGui() {
{
int load = loadDistance;
ImGui::SliderInt("Load Distance", &load, 1, keepDistance);
loadDistance = load;
}
{
int keep = keepDistance;
ImGui::SliderInt("Keep Distance", &keep, loadDistance + 1, 21);
keepDistance = keep;
}
ImGui::Checkbox("Transparency", &transparency);
ImGui::SliderFloat("Iso", &iso, 0, 1);
ImGui::Checkbox("Manifold", &manifold);
ImGui::Checkbox("Reordering", &reordering);
ImGui::SliderFloat("Lod quality", &lod_quality, -.5, 1);
ImGui::SliderFloat("Lod strength", &lod_strength, -.5, .5);
if (ImGui::CollapsingHeader("Lod levels")) {
ImGui::Columns(3, nullptr, false);
ImGui::Selectable("1", true, ImGuiSelectableFlags_Disabled);
ImGui::NextColumn();
for(int i = LEVELS.size() - 1; i >= 0; i--) {
const auto str = std::to_string(static_cast<int>(1 / LEVELS[i].first));
if (ImGui::Selectable(str.c_str(), &lod_levels[i])) {
loadedLevels.clear();
for (size_t i = 0; i < LEVELS.size(); i++) {
if(lod_levels[i])
loadedLevels.push_back(LEVELS[i]);
}
}
ImGui::NextColumn();
}
ImGui::Columns(1);
}
if (ImGui::Button("Flush buffers")) {
areas.clear();
parts.extract([](const world::part_id &, const std::unique_ptr<render::Model> &) { return true; });
models.extract([](const world::model_id &, const std::unique_ptr<render::Model> &) { return true; });
}
}
void FlatDualMC::render(const surrounding::corners &surrounding, render::Model::Data::indices_t &out, std::vector<render::VertexData> &tmp, Layer layer) const {
constexpr uint8_t SIZE = world::CHUNK_LENGTH + 3;
std::array<dualmc::DualMC<float>::Point, SIZE * SIZE * SIZE> grid;
const auto &materials = world::module::Registry::Get()->getMaterials();
{
ZoneScopedN("Load");
const auto setCell = [&](glm::idx i, const world::Voxel &voxel) {
auto &cell = grid[i];
cell.w = voxel.material();
cell.x = voxel.density_ratio() * (!materials.invisibilities[cell.w] &&
((materials.transparencies[cell.w] && (layer && Layer::Transparent)) ||
(!materials.transparencies[cell.w] && (layer && Layer::Solid))));
};
size_t i = 0;
for (uint8_t z = 0; z < SIZE; z++) {
for (uint8_t y = 0; y < SIZE; y++) {
for (uint8_t x = 0; x < SIZE; x++) {
const auto &chunk = surrounding[(z >= world::CHUNK_LENGTH) + (y >= world::CHUNK_LENGTH)*2 + (x >= world::CHUNK_LENGTH)*4];
const auto &voxel = chunk->get(glm::toIdx(x & glm::IDX_MASK, y & glm::IDX_MASK, z & glm::IDX_MASK));
setCell(i, voxel);
i++;
}}}
for (size_t i = 0; i < surrounding.size(); i++) {
auto &edits = surrounding[i]->getEdits().getEdits();
auto offset = glm::ivec3(surrounding::g_corner_offsets[i]) * world::CHUNK_LENGTH;
for (auto it = edits.begin(); it != edits.end(); ++it) {
auto p = offset + glm::ivec3(glm::fromIdx(it->first));
if(p.x < SIZE && p.y < SIZE && p.z < SIZE) {
setCell(glm::toIdx(p, glm::ucvec3(SIZE)), it->second.value);
}
}
}
}
{
std::vector<dualmc::Vertex> dmc_vertices;
std::vector<dualmc::Tri> dmc_tris;
dualmc::DualMC<float> builder;
builder.buildTris(&grid.front(), SIZE, SIZE, SIZE, iso, materials.texture_ids.data(), materials.roughnesses.data(),
manifold, dmc_vertices, dmc_tris);
tmp.clear();
tmp.reserve(dmc_vertices.size());
std::transform(dmc_vertices.begin(), dmc_vertices.end(), std::back_inserter(tmp), [](const dualmc::Vertex &v) {
constexpr auto HALF_MANTISSA = 10;
return render::VertexData(glm::vec3(meshopt_quantizeFloat(v.x, HALF_MANTISSA),meshopt_quantizeFloat(v.y, HALF_MANTISSA),
meshopt_quantizeFloat(v.z, HALF_MANTISSA)), v.w, glm::vec3(0));
});
out.reserve(dmc_tris.size() * 3);
for (const auto& t: dmc_tris) {
glm::vec3 edge1 = tmp[t.i1].Position - tmp[t.i0].Position;
glm::vec3 edge2 = tmp[t.i2].Position - tmp[t.i0].Position;
glm::vec3 normal = glm::normalize(glm::cross(edge1, edge2));
if(!reordering || glm::length2(edge1) > glm::length2(edge2)) {
out.push_back(t.i0);
out.push_back(t.i1);
out.push_back(t.i2);
} else {
out.push_back(t.i2);
out.push_back(t.i0);
out.push_back(t.i1);
}
tmp[t.i0].Normal += normal;
tmp[t.i1].Normal += normal;
tmp[t.i2].Normal += normal;
}
for(auto& v: tmp) {
v.Normal = glm::normalize(v.Normal);
}
}
}
void FlatDualMC::render(const surrounding::corners &surrounding, render::LodModel::LodData &out, std::vector<render::VertexData> &tmp, Layer layer) const {
render(surrounding, out.first.indices, tmp, layer);
out.second = simplify_lod(out.first.indices, tmp, loadedLevels);
std::transform(tmp.begin(), tmp.end(), std::back_inserter(out.first.vertices), PACK);
optimize_fetch(out.first);
}
void FlatDualMC::getTerrainModels(terrain_draw_call out, const std::optional<geometry::Frustum> &frustum, const world::cell_pos& offset, bool solid) const {
for (const auto& area: areas) {
const auto absolute = area.second.first.absolute;
const auto rotation = glm::toMat4(area.second.first.absolute.rotation);
const auto rot_abs = glm::abs(rotation);
for (const auto& chunk: area.second.second) {
const auto buffer = solid ? chunk.second.main.get() : chunk.second.transparent.get();
if (!buffer) // TODO: Fix bad branch prediction
continue;
const auto cPos = glm::multiply(chunk.first);
if (frustum) {
const glm::vec3 fCenterPos = absolute.computeChild(cPos + world::cell_pos(world::CHUNK_LENGTH / 2)) - (world::world_pos)offset;
if (!frustum.value().contains(geometry::faabb::FromCenterAbs(fCenterPos, glm::vec3(world::CHUNK_LENGTH / 2), rot_abs)))
continue;
}
const glm::vec3 fMinPos = absolute.computeChild(cPos) - (world::world_pos)offset;
out(glm::translate(glm::mat4(1), fMinPos) * rotation, buffer, area.second.first, cPos);
}
}
}
void FlatDualMC::getTerrainModels(terrain_draw_call out, const world::world_pos& from, float far_dist, const std::vector<glm::vec3> &occlusion, const world::cell_pos &offset, bool solid) const {
const auto start = world::world_pos(glm::divide(from));
const auto dist = far_dist / world::CHUNK_LENGTH;
for (const auto& area: areas) {
const auto &absolute = area.second.first.absolute;
const auto area_offset = glm::divide(absolute.position);
const auto rotation = glm::toMat4(absolute.rotation);
robin_hood::unordered_set<world::chunk_pos> done;
for (const auto& occ: occlusion) {
geometry::Ray::iterator it(geometry::Ray(start, absolute.rotation * occ, dist));
glm::llvec3 point;
while (it.next(point)) {
const auto &chunks = area.second.second;
if (const auto it = chunks.find(glm::lvec3(point) - area_offset); it != chunks.end()) {
const auto buffer = solid ? it->second.main.get() : it->second.transparent.get();
if (buffer != NULL && done.insert(it->first).second) {
const auto cPos = glm::multiply(it->first);
const glm::vec3 fPos = absolute.computeChild(cPos) - (world::world_pos)offset;
out(glm::translate(glm::mat4(1), fPos) * rotation, buffer, area.second.first, cPos);
break;
}
}
}
}
}
}
void FlatDualMC::getUniqueModels(unique_draw_call out, const world::Elements& l, const std::optional<geometry::Frustum>& frustum, const world::cell_pos& offset) const {
for (const auto& ref: l.parts) {
const world::part_id id = l.withFlag(ref.val).val;
const auto node = l.findPart(id);
if (const auto buffer = getModel(id)) {
const auto rotation = glm::toMat4(node->absolute.rotation);
const glm::vec3 fMinPos = node->absolute.position - (world::world_pos)offset;
if (frustum) {
if (!frustum.value().contains(geometry::faabb::FromMin(fMinPos, node->get()->size, rotation)))
continue;
}
out(glm::translate(glm::mat4(1), fMinPos) * rotation, buffer);
}
}
}
void FlatDualMC::getInstancedModels(instanced_draw_call out, const world::Elements& l, const std::optional<geometry::Frustum>& frustum, const world::cell_pos& offset, world::node_id ignore) const {
std::vector<glm::mat4> matrices;
l.models.iter([&] (const world::model_id& id, const world::Model& model) {
if (const auto buffer = getModel(id)) {
for (const auto& ref: model.instances) {
const auto iid = l.withFlag(ref.val);
if (iid == ignore)
continue;
const auto node = world::NodeOf<world::Instance>::Make(l.findNode(iid));
const auto rotation = glm::toMat4(node->absolute.rotation);
const glm::vec3 fMinPos = node->absolute.position - (world::world_pos)offset;
if (frustum) {
if (!frustum.value().contains(model.collision.rotate(node->absolute.rotation) + fMinPos))
continue;
}
matrices.push_back(glm::scale(glm::translate(glm::mat4(1), fMinPos) * rotation, model.scale));
}
if (!matrices.empty()) {
out(matrices, buffer);
matrices.clear();
}
}
});
}
}