Add depth sorting for node faces (#11696)

Use BSP tree to order transparent triangles
https://en.wikipedia.org/wiki/Binary_space_partitioning
This commit is contained in:
x2048 2022-04-02 10:42:27 +02:00 committed by GitHub
parent 26c046a563
commit b0b9732359
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 628 additions and 92 deletions

@ -858,6 +858,10 @@ autoscale_mode (Autoscaling mode) enum disable disable,enable,force
# A restart is required after changing this. # A restart is required after changing this.
show_entity_selectionbox (Show entity selection boxes) bool false show_entity_selectionbox (Show entity selection boxes) bool false
# Distance in nodes at which transparency depth sorting is enabled
# Use this to limit the performance impact of transparency depth sorting
transparency_sorting_distance (Transparency Sorting Distance) int 16 0 128
[*Menus] [*Menus]
# Use a cloud animation for the main menu background. # Use a cloud animation for the main menu background.

@ -97,9 +97,32 @@ ClientMap::ClientMap(
m_cache_trilinear_filter = g_settings->getBool("trilinear_filter"); m_cache_trilinear_filter = g_settings->getBool("trilinear_filter");
m_cache_bilinear_filter = g_settings->getBool("bilinear_filter"); m_cache_bilinear_filter = g_settings->getBool("bilinear_filter");
m_cache_anistropic_filter = g_settings->getBool("anisotropic_filter"); m_cache_anistropic_filter = g_settings->getBool("anisotropic_filter");
m_cache_transparency_sorting_distance = g_settings->getU16("transparency_sorting_distance");
} }
void ClientMap::updateCamera(v3f pos, v3f dir, f32 fov, v3s16 offset)
{
v3s16 previous_node = floatToInt(m_camera_position, BS) + m_camera_offset;
v3s16 previous_block = getContainerPos(previous_node, MAP_BLOCKSIZE);
m_camera_position = pos;
m_camera_direction = dir;
m_camera_fov = fov;
m_camera_offset = offset;
v3s16 current_node = floatToInt(m_camera_position, BS) + m_camera_offset;
v3s16 current_block = getContainerPos(current_node, MAP_BLOCKSIZE);
// reorder the blocks when camera crosses block boundary
if (previous_block != current_block)
m_needs_update_drawlist = true;
// reorder transparent meshes when camera crosses node boundary
if (previous_node != current_node)
m_needs_update_transparent_meshes = true;
}
MapSector * ClientMap::emergeSector(v2s16 p2d) MapSector * ClientMap::emergeSector(v2s16 p2d)
{ {
// Check that it doesn't exist already // Check that it doesn't exist already
@ -323,22 +346,17 @@ void ClientMap::renderMap(video::IVideoDriver* driver, s32 pass)
u32 mesh_animate_count = 0; u32 mesh_animate_count = 0;
//u32 mesh_animate_count_far = 0; //u32 mesh_animate_count_far = 0;
/*
Update transparent meshes
*/
if (is_transparent_pass)
updateTransparentMeshBuffers();
/* /*
Draw the selected MapBlocks Draw the selected MapBlocks
*/ */
MeshBufListList grouped_buffers; MeshBufListList grouped_buffers;
struct DrawDescriptor {
v3s16 m_pos;
scene::IMeshBuffer *m_buffer;
bool m_reuse_material;
DrawDescriptor(const v3s16 &pos, scene::IMeshBuffer *buffer, bool reuse_material) :
m_pos(pos), m_buffer(buffer), m_reuse_material(reuse_material)
{}
};
std::vector<DrawDescriptor> draw_order; std::vector<DrawDescriptor> draw_order;
video::SMaterial previous_material; video::SMaterial previous_material;
@ -375,7 +393,15 @@ void ClientMap::renderMap(video::IVideoDriver* driver, s32 pass)
/* /*
Get the meshbuffers of the block Get the meshbuffers of the block
*/ */
{ if (is_transparent_pass) {
// In transparent pass, the mesh will give us
// the partial buffers in the correct order
for (auto &buffer : block->mesh->getTransparentBuffers())
draw_order.emplace_back(block_pos, &buffer);
}
else {
// otherwise, group buffers across meshes
// using MeshBufListList
MapBlockMesh *mapBlockMesh = block->mesh; MapBlockMesh *mapBlockMesh = block->mesh;
assert(mapBlockMesh); assert(mapBlockMesh);
@ -391,38 +417,17 @@ void ClientMap::renderMap(video::IVideoDriver* driver, s32 pass)
video::IMaterialRenderer* rnd = video::IMaterialRenderer* rnd =
driver->getMaterialRenderer(material.MaterialType); driver->getMaterialRenderer(material.MaterialType);
bool transparent = (rnd && rnd->isTransparent()); bool transparent = (rnd && rnd->isTransparent());
if (transparent == is_transparent_pass) { if (!transparent) {
if (buf->getVertexCount() == 0) if (buf->getVertexCount() == 0)
errorstream << "Block [" << analyze_block(block) errorstream << "Block [" << analyze_block(block)
<< "] contains an empty meshbuf" << std::endl; << "] contains an empty meshbuf" << std::endl;
material.setFlag(video::EMF_TRILINEAR_FILTER,
m_cache_trilinear_filter);
material.setFlag(video::EMF_BILINEAR_FILTER,
m_cache_bilinear_filter);
material.setFlag(video::EMF_ANISOTROPIC_FILTER,
m_cache_anistropic_filter);
material.setFlag(video::EMF_WIREFRAME,
m_control.show_wireframe);
if (is_transparent_pass) {
// Same comparison as in MeshBufListList
bool new_material = material.getTexture(0) != previous_material.getTexture(0) ||
material != previous_material;
draw_order.emplace_back(block_pos, buf, !new_material);
if (new_material)
previous_material = material;
}
else {
grouped_buffers.add(buf, block_pos, layer); grouped_buffers.add(buf, block_pos, layer);
} }
} }
} }
} }
} }
}
// Capture draw order for all solid meshes // Capture draw order for all solid meshes
for (auto &lists : grouped_buffers.lists) { for (auto &lists : grouped_buffers.lists) {
@ -442,8 +447,17 @@ void ClientMap::renderMap(video::IVideoDriver* driver, s32 pass)
// Render all mesh buffers in order // Render all mesh buffers in order
drawcall_count += draw_order.size(); drawcall_count += draw_order.size();
for (auto &descriptor : draw_order) { for (auto &descriptor : draw_order) {
scene::IMeshBuffer *buf = descriptor.m_buffer; scene::IMeshBuffer *buf;
if (descriptor.m_use_partial_buffer) {
descriptor.m_partial_buffer->beforeDraw();
buf = descriptor.m_partial_buffer->getBuffer();
}
else {
buf = descriptor.m_buffer;
}
// Check and abort if the machine is swapping a lot // Check and abort if the machine is swapping a lot
if (draw.getTimerTime() > 2000) { if (draw.getTimerTime() > 2000) {
@ -454,6 +468,17 @@ void ClientMap::renderMap(video::IVideoDriver* driver, s32 pass)
if (!descriptor.m_reuse_material) { if (!descriptor.m_reuse_material) {
auto &material = buf->getMaterial(); auto &material = buf->getMaterial();
// Apply filter settings
material.setFlag(video::EMF_TRILINEAR_FILTER,
m_cache_trilinear_filter);
material.setFlag(video::EMF_BILINEAR_FILTER,
m_cache_bilinear_filter);
material.setFlag(video::EMF_ANISOTROPIC_FILTER,
m_cache_anistropic_filter);
material.setFlag(video::EMF_WIREFRAME,
m_control.show_wireframe);
// pass the shadow map texture to the buffer texture // pass the shadow map texture to the buffer texture
ShadowRenderer *shadow = m_rendering_engine->get_shadow_renderer(); ShadowRenderer *shadow = m_rendering_engine->get_shadow_renderer();
if (shadow && shadow->is_active()) { if (shadow && shadow->is_active()) {
@ -475,7 +500,7 @@ void ClientMap::renderMap(video::IVideoDriver* driver, s32 pass)
driver->setTransform(video::ETS_WORLD, m); driver->setTransform(video::ETS_WORLD, m);
driver->drawMeshBuffer(buf); driver->drawMeshBuffer(buf);
vertex_count += buf->getVertexCount(); vertex_count += buf->getIndexCount();
} }
g_profiler->avg(prefix + "draw meshes [ms]", draw.stop(true)); g_profiler->avg(prefix + "draw meshes [ms]", draw.stop(true));
@ -698,7 +723,9 @@ void ClientMap::renderMapShadows(video::IVideoDriver *driver,
u32 drawcall_count = 0; u32 drawcall_count = 0;
u32 vertex_count = 0; u32 vertex_count = 0;
MeshBufListList drawbufs; MeshBufListList grouped_buffers;
std::vector<DrawDescriptor> draw_order;
int count = 0; int count = 0;
int low_bound = is_transparent_pass ? 0 : m_drawlist_shadow.size() / total_frames * frame; int low_bound = is_transparent_pass ? 0 : m_drawlist_shadow.size() / total_frames * frame;
@ -727,7 +754,15 @@ void ClientMap::renderMapShadows(video::IVideoDriver *driver,
/* /*
Get the meshbuffers of the block Get the meshbuffers of the block
*/ */
{ if (is_transparent_pass) {
// In transparent pass, the mesh will give us
// the partial buffers in the correct order
for (auto &buffer : block->mesh->getTransparentBuffers())
draw_order.emplace_back(block_pos, &buffer);
}
else {
// otherwise, group buffers across meshes
// using MeshBufListList
MapBlockMesh *mapBlockMesh = block->mesh; MapBlockMesh *mapBlockMesh = block->mesh;
assert(mapBlockMesh); assert(mapBlockMesh);
@ -742,30 +777,57 @@ void ClientMap::renderMapShadows(video::IVideoDriver *driver,
video::SMaterial &mat = buf->getMaterial(); video::SMaterial &mat = buf->getMaterial();
auto rnd = driver->getMaterialRenderer(mat.MaterialType); auto rnd = driver->getMaterialRenderer(mat.MaterialType);
bool transparent = rnd && rnd->isTransparent(); bool transparent = rnd && rnd->isTransparent();
if (transparent == is_transparent_pass) if (!transparent)
drawbufs.add(buf, block_pos, layer); grouped_buffers.add(buf, block_pos, layer);
} }
} }
} }
} }
u32 buffer_count = 0;
for (auto &lists : grouped_buffers.lists)
for (MeshBufList &list : lists)
buffer_count += list.bufs.size();
draw_order.reserve(draw_order.size() + buffer_count);
// Capture draw order for all solid meshes
for (auto &lists : grouped_buffers.lists) {
for (MeshBufList &list : lists) {
// iterate in reverse to draw closest blocks first
for (auto it = list.bufs.rbegin(); it != list.bufs.rend(); ++it)
draw_order.emplace_back(it->first, it->second, it != list.bufs.rbegin());
}
}
TimeTaker draw("Drawing shadow mesh buffers"); TimeTaker draw("Drawing shadow mesh buffers");
core::matrix4 m; // Model matrix core::matrix4 m; // Model matrix
v3f offset = intToFloat(m_camera_offset, BS); v3f offset = intToFloat(m_camera_offset, BS);
u32 material_swaps = 0;
// Render all mesh buffers in order
drawcall_count += draw_order.size();
for (auto &descriptor : draw_order) {
scene::IMeshBuffer *buf;
if (descriptor.m_use_partial_buffer) {
descriptor.m_partial_buffer->beforeDraw();
buf = descriptor.m_partial_buffer->getBuffer();
}
else {
buf = descriptor.m_buffer;
}
// Render all layers in order
for (auto &lists : drawbufs.lists) {
for (MeshBufList &list : lists) {
// Check and abort if the machine is swapping a lot // Check and abort if the machine is swapping a lot
if (draw.getTimerTime() > 1000) { if (draw.getTimerTime() > 1000) {
infostream << "ClientMap::renderMapShadows(): Rendering " infostream << "ClientMap::renderMapShadows(): Rendering "
"took >1s, returning." << std::endl; "took >1s, returning." << std::endl;
break; break;
} }
for (auto &pair : list.bufs) {
scene::IMeshBuffer *buf = pair.second;
if (!descriptor.m_reuse_material) {
// override some material properties // override some material properties
video::SMaterial local_material = buf->getMaterial(); video::SMaterial local_material = buf->getMaterial();
local_material.MaterialType = material.MaterialType; local_material.MaterialType = material.MaterialType;
@ -774,17 +836,15 @@ void ClientMap::renderMapShadows(video::IVideoDriver *driver,
local_material.BlendOperation = material.BlendOperation; local_material.BlendOperation = material.BlendOperation;
local_material.Lighting = false; local_material.Lighting = false;
driver->setMaterial(local_material); driver->setMaterial(local_material);
++material_swaps;
}
v3f block_wpos = intToFloat(pair.first * MAP_BLOCKSIZE, BS); v3f block_wpos = intToFloat(descriptor.m_pos * MAP_BLOCKSIZE, BS);
m.setTranslation(block_wpos - offset); m.setTranslation(block_wpos - offset);
driver->setTransform(video::ETS_WORLD, m); driver->setTransform(video::ETS_WORLD, m);
driver->drawMeshBuffer(buf); driver->drawMeshBuffer(buf);
vertex_count += buf->getVertexCount(); vertex_count += buf->getIndexCount();
}
drawcall_count += list.bufs.size();
}
} }
// restore the driver material state // restore the driver material state
@ -796,6 +856,7 @@ void ClientMap::renderMapShadows(video::IVideoDriver *driver,
g_profiler->avg(prefix + "draw meshes [ms]", draw.stop(true)); g_profiler->avg(prefix + "draw meshes [ms]", draw.stop(true));
g_profiler->avg(prefix + "vertices drawn [#]", vertex_count); g_profiler->avg(prefix + "vertices drawn [#]", vertex_count);
g_profiler->avg(prefix + "drawcalls [#]", drawcall_count); g_profiler->avg(prefix + "drawcalls [#]", drawcall_count);
g_profiler->avg(prefix + "material swaps [#]", material_swaps);
} }
/* /*
@ -891,3 +952,40 @@ void ClientMap::updateDrawListShadow(const v3f &shadow_light_pos, const v3f &sha
g_profiler->avg("SHADOW MapBlocks drawn [#]", m_drawlist_shadow.size()); g_profiler->avg("SHADOW MapBlocks drawn [#]", m_drawlist_shadow.size());
g_profiler->avg("SHADOW MapBlocks loaded [#]", blocks_loaded); g_profiler->avg("SHADOW MapBlocks loaded [#]", blocks_loaded);
} }
void ClientMap::updateTransparentMeshBuffers()
{
ScopeProfiler sp(g_profiler, "CM::updateTransparentMeshBuffers", SPT_AVG);
u32 sorted_blocks = 0;
u32 unsorted_blocks = 0;
f32 sorting_distance_sq = pow(m_cache_transparency_sorting_distance * BS, 2.0f);
// Update the order of transparent mesh buffers in each mesh
for (auto it = m_drawlist.begin(); it != m_drawlist.end(); it++) {
MapBlock* block = it->second;
if (!block->mesh)
continue;
if (m_needs_update_transparent_meshes ||
block->mesh->getTransparentBuffers().size() == 0) {
v3s16 block_pos = block->getPos();
v3f block_pos_f = intToFloat(block_pos * MAP_BLOCKSIZE + MAP_BLOCKSIZE / 2, BS);
f32 distance = m_camera_position.getDistanceFromSQ(block_pos_f);
if (distance <= sorting_distance_sq) {
block->mesh->updateTransparentBuffers(m_camera_position, block_pos);
++sorted_blocks;
}
else {
block->mesh->consolidateTransparentBuffers();
++unsorted_blocks;
}
}
}
g_profiler->avg("CM::Transparent Buffers - Sorted", sorted_blocks);
g_profiler->avg("CM::Transparent Buffers - Unsorted", unsorted_blocks);
m_needs_update_transparent_meshes = false;
}

@ -56,6 +56,7 @@ struct MeshBufListList
class Client; class Client;
class ITextureSource; class ITextureSource;
class PartialMeshBuffer;
/* /*
ClientMap ClientMap
@ -85,21 +86,7 @@ public:
ISceneNode::drop(); ISceneNode::drop();
} }
void updateCamera(const v3f &pos, const v3f &dir, f32 fov, const v3s16 &offset) void updateCamera(v3f pos, v3f dir, f32 fov, v3s16 offset);
{
v3s16 previous_block = getContainerPos(floatToInt(m_camera_position, BS) + m_camera_offset, MAP_BLOCKSIZE);
m_camera_position = pos;
m_camera_direction = dir;
m_camera_fov = fov;
m_camera_offset = offset;
v3s16 current_block = getContainerPos(floatToInt(m_camera_position, BS) + m_camera_offset, MAP_BLOCKSIZE);
// reorder the blocks when camera crosses block boundary
if (previous_block != current_block)
m_needs_update_drawlist = true;
}
/* /*
Forcefully get a sector from somewhere Forcefully get a sector from somewhere
@ -150,6 +137,10 @@ public:
f32 getCameraFov() const { return m_camera_fov; } f32 getCameraFov() const { return m_camera_fov; }
private: private:
// update the vertex order in transparent mesh buffers
void updateTransparentMeshBuffers();
// Orders blocks by distance to the camera // Orders blocks by distance to the camera
class MapBlockComparer class MapBlockComparer
{ {
@ -167,6 +158,26 @@ private:
v3s16 m_camera_block; v3s16 m_camera_block;
}; };
// reference to a mesh buffer used when rendering the map.
struct DrawDescriptor {
v3s16 m_pos;
union {
scene::IMeshBuffer *m_buffer;
const PartialMeshBuffer *m_partial_buffer;
};
bool m_reuse_material:1;
bool m_use_partial_buffer:1;
DrawDescriptor(v3s16 pos, scene::IMeshBuffer *buffer, bool reuse_material) :
m_pos(pos), m_buffer(buffer), m_reuse_material(reuse_material), m_use_partial_buffer(false)
{}
DrawDescriptor(v3s16 pos, const PartialMeshBuffer *buffer) :
m_pos(pos), m_partial_buffer(buffer), m_reuse_material(false), m_use_partial_buffer(true)
{}
};
Client *m_client; Client *m_client;
RenderingEngine *m_rendering_engine; RenderingEngine *m_rendering_engine;
@ -179,6 +190,7 @@ private:
v3f m_camera_direction = v3f(0,0,1); v3f m_camera_direction = v3f(0,0,1);
f32 m_camera_fov = M_PI; f32 m_camera_fov = M_PI;
v3s16 m_camera_offset; v3s16 m_camera_offset;
bool m_needs_update_transparent_meshes = true;
std::map<v3s16, MapBlock*, MapBlockComparer> m_drawlist; std::map<v3s16, MapBlock*, MapBlockComparer> m_drawlist;
std::map<v3s16, MapBlock*> m_drawlist_shadow; std::map<v3s16, MapBlock*> m_drawlist_shadow;
@ -190,4 +202,5 @@ private:
bool m_cache_bilinear_filter; bool m_cache_bilinear_filter;
bool m_cache_anistropic_filter; bool m_cache_anistropic_filter;
bool m_added_to_shadow_renderer{false}; bool m_added_to_shadow_renderer{false};
u16 m_cache_transparency_sorting_distance;
}; };

@ -381,12 +381,12 @@ void MapblockMeshGenerator::drawAutoLightedCuboid(aabb3f box, const f32 *txc,
box.MinEdge *= f->visual_scale; box.MinEdge *= f->visual_scale;
box.MaxEdge *= f->visual_scale; box.MaxEdge *= f->visual_scale;
} }
box.MinEdge += origin;
box.MaxEdge += origin;
if (!txc) { if (!txc) {
generateCuboidTextureCoords(box, texture_coord_buf); generateCuboidTextureCoords(box, texture_coord_buf);
txc = texture_coord_buf; txc = texture_coord_buf;
} }
box.MinEdge += origin;
box.MaxEdge += origin;
if (!tiles) { if (!tiles) {
tiles = &tile; tiles = &tile;
tile_count = 1; tile_count = 1;
@ -1377,6 +1377,59 @@ void MapblockMeshGenerator::drawNodeboxNode()
std::vector<aabb3f> boxes; std::vector<aabb3f> boxes;
n.getNodeBoxes(nodedef, &boxes, neighbors_set); n.getNodeBoxes(nodedef, &boxes, neighbors_set);
bool isTransparent = false;
for (const TileSpec &tile : tiles) {
if (tile.layers[0].isTransparent()) {
isTransparent = true;
break;
}
}
if (isTransparent) {
std::vector<float> sections;
// Preallocate 8 default splits + Min&Max for each nodebox
sections.reserve(8 + 2 * boxes.size());
for (int axis = 0; axis < 3; axis++) {
// identify sections
if (axis == 0) {
// Default split at node bounds, up to 3 nodes in each direction
for (float s = -3.5f * BS; s < 4.0f * BS; s += 1.0f * BS)
sections.push_back(s);
}
else {
// Avoid readding the same 8 default splits for Y and Z
sections.resize(8);
}
// Add edges of existing node boxes, rounded to 1E-3
for (size_t i = 0; i < boxes.size(); i++) {
sections.push_back(std::floor(boxes[i].MinEdge[axis] * 1E3) * 1E-3);
sections.push_back(std::floor(boxes[i].MaxEdge[axis] * 1E3) * 1E-3);
}
// split the boxes at recorded sections
// limit splits to avoid runaway crash if inner loop adds infinite splits
// due to e.g. precision problems.
// 100 is just an arbitrary, reasonably high number.
for (size_t i = 0; i < boxes.size() && i < 100; i++) {
aabb3f *box = &boxes[i];
for (float section : sections) {
if (box->MinEdge[axis] < section && box->MaxEdge[axis] > section) {
aabb3f copy(*box);
copy.MinEdge[axis] = section;
box->MaxEdge[axis] = section;
boxes.push_back(copy);
box = &boxes[i]; // find new address of the box in case of reallocation
}
}
}
}
}
for (auto &box : boxes) for (auto &box : boxes)
drawAutoLightedCuboid(box, nullptr, tiles, 6); drawAutoLightedCuboid(box, nullptr, tiles, 6);
} }

@ -30,6 +30,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "client/meshgen/collector.h" #include "client/meshgen/collector.h"
#include "client/renderingengine.h" #include "client/renderingengine.h"
#include <array> #include <array>
#include <algorithm>
/* /*
MeshMakeData MeshMakeData
@ -1003,6 +1004,173 @@ static void applyTileColor(PreMeshBuffer &pmb)
} }
} }
/*
MapBlockBspTree
*/
void MapBlockBspTree::buildTree(const std::vector<MeshTriangle> *triangles)
{
this->triangles = triangles;
nodes.clear();
// assert that triangle index can fit into s32
assert(triangles->size() <= 0x7FFFFFFFL);
std::vector<s32> indexes;
indexes.reserve(triangles->size());
for (u32 i = 0; i < triangles->size(); i++)
indexes.push_back(i);
root = buildTree(v3f(1, 0, 0), v3f(85, 85, 85), 40, indexes, 0);
}
/**
* @brief Find a candidate plane to split a set of triangles in two
*
* The candidate plane is represented by one of the triangles from the set.
*
* @param list Vector of indexes of the triangles in the set
* @param triangles Vector of all triangles in the BSP tree
* @return Address of the triangle that represents the proposed split plane
*/
static const MeshTriangle *findSplitCandidate(const std::vector<s32> &list, const std::vector<MeshTriangle> &triangles)
{
// find the center of the cluster.
v3f center(0, 0, 0);
size_t n = list.size();
for (s32 i : list) {
center += triangles[i].centroid / n;
}
// find the triangle with the largest area and closest to the center
const MeshTriangle *candidate_triangle = &triangles[list[0]];
const MeshTriangle *ith_triangle;
for (s32 i : list) {
ith_triangle = &triangles[i];
if (ith_triangle->areaSQ > candidate_triangle->areaSQ ||
(ith_triangle->areaSQ == candidate_triangle->areaSQ &&
ith_triangle->centroid.getDistanceFromSQ(center) < candidate_triangle->centroid.getDistanceFromSQ(center))) {
candidate_triangle = ith_triangle;
}
}
return candidate_triangle;
}
s32 MapBlockBspTree::buildTree(v3f normal, v3f origin, float delta, const std::vector<s32> &list, u32 depth)
{
// if the list is empty, don't bother
if (list.empty())
return -1;
// if there is only one triangle, or the delta is insanely small, this is a leaf node
if (list.size() == 1 || delta < 0.01) {
nodes.emplace_back(normal, origin, list, -1, -1);
return nodes.size() - 1;
}
std::vector<s32> front_list;
std::vector<s32> back_list;
std::vector<s32> node_list;
// split the list
for (s32 i : list) {
const MeshTriangle &triangle = (*triangles)[i];
float factor = normal.dotProduct(triangle.centroid - origin);
if (factor == 0)
node_list.push_back(i);
else if (factor > 0)
front_list.push_back(i);
else
back_list.push_back(i);
}
// define the new split-plane
v3f candidate_normal(normal.Z, normal.X, normal.Y);
float candidate_delta = delta;
if (depth % 3 == 2)
candidate_delta /= 2;
s32 front_index = -1;
s32 back_index = -1;
if (!front_list.empty()) {
v3f next_normal = candidate_normal;
v3f next_origin = origin + delta * normal;
float next_delta = candidate_delta;
if (next_delta < 10) {
const MeshTriangle *candidate = findSplitCandidate(front_list, *triangles);
next_normal = candidate->getNormal();
next_origin = candidate->centroid;
}
front_index = buildTree(next_normal, next_origin, next_delta, front_list, depth + 1);
// if there are no other triangles, don't create a new node
if (back_list.empty() && node_list.empty())
return front_index;
}
if (!back_list.empty()) {
v3f next_normal = candidate_normal;
v3f next_origin = origin - delta * normal;
float next_delta = candidate_delta;
if (next_delta < 10) {
const MeshTriangle *candidate = findSplitCandidate(back_list, *triangles);
next_normal = candidate->getNormal();
next_origin = candidate->centroid;
}
back_index = buildTree(next_normal, next_origin, next_delta, back_list, depth + 1);
// if there are no other triangles, don't create a new node
if (front_list.empty() && node_list.empty())
return back_index;
}
nodes.emplace_back(normal, origin, node_list, front_index, back_index);
return nodes.size() - 1;
}
void MapBlockBspTree::traverse(s32 node, v3f viewpoint, std::vector<s32> &output) const
{
if (node < 0) return; // recursion break;
const TreeNode &n = nodes[node];
float factor = n.normal.dotProduct(viewpoint - n.origin);
if (factor > 0)
traverse(n.back_ref, viewpoint, output);
else
traverse(n.front_ref, viewpoint, output);
if (factor != 0)
for (s32 i : n.triangle_refs)
output.push_back(i);
if (factor > 0)
traverse(n.front_ref, viewpoint, output);
else
traverse(n.back_ref, viewpoint, output);
}
/*
PartialMeshBuffer
*/
void PartialMeshBuffer::beforeDraw() const
{
// Patch the indexes in the mesh buffer before draw
m_buffer->Indices.clear();
if (!m_vertex_indexes.empty()) {
for (auto index : m_vertex_indexes)
m_buffer->Indices.push_back(index);
}
m_buffer->setDirty(scene::EBT_INDEX);
}
/* /*
MapBlockMesh MapBlockMesh
*/ */
@ -1173,8 +1341,31 @@ MapBlockMesh::MapBlockMesh(MeshMakeData *data, v3s16 camera_offset):
scene::SMeshBuffer *buf = new scene::SMeshBuffer(); scene::SMeshBuffer *buf = new scene::SMeshBuffer();
buf->Material = material; buf->Material = material;
switch (p.layer.material_type) {
// list of transparent materials taken from tile.h
case TILE_MATERIAL_ALPHA:
case TILE_MATERIAL_LIQUID_TRANSPARENT:
case TILE_MATERIAL_WAVING_LIQUID_TRANSPARENT:
{
buf->append(&p.vertices[0], p.vertices.size(),
&p.indices[0], 0);
MeshTriangle t;
t.buffer = buf;
for (u32 i = 0; i < p.indices.size(); i += 3) {
t.p1 = p.indices[i];
t.p2 = p.indices[i + 1];
t.p3 = p.indices[i + 2];
t.updateAttributes();
m_transparent_triangles.push_back(t);
}
}
break;
default:
buf->append(&p.vertices[0], p.vertices.size(), buf->append(&p.vertices[0], p.vertices.size(),
&p.indices[0], p.indices.size()); &p.indices[0], p.indices.size());
break;
}
mesh->addMeshBuffer(buf); mesh->addMeshBuffer(buf);
buf->drop(); buf->drop();
} }
@ -1187,6 +1378,7 @@ MapBlockMesh::MapBlockMesh(MeshMakeData *data, v3s16 camera_offset):
} }
//std::cout<<"added "<<fastfaces.getSize()<<" faces."<<std::endl; //std::cout<<"added "<<fastfaces.getSize()<<" faces."<<std::endl;
m_bsp_tree.buildTree(&m_transparent_triangles);
// Check if animation is required for this mesh // Check if animation is required for this mesh
m_has_animation = m_has_animation =
@ -1298,6 +1490,67 @@ bool MapBlockMesh::animate(bool faraway, float time, int crack,
return true; return true;
} }
void MapBlockMesh::updateTransparentBuffers(v3f camera_pos, v3s16 block_pos)
{
// nothing to do if the entire block is opaque
if (m_transparent_triangles.empty())
return;
v3f block_posf = intToFloat(block_pos * MAP_BLOCKSIZE, BS);
v3f rel_camera_pos = camera_pos - block_posf;
std::vector<s32> triangle_refs;
m_bsp_tree.traverse(rel_camera_pos, triangle_refs);
// arrange index sequences into partial buffers
m_transparent_buffers.clear();
scene::SMeshBuffer *current_buffer = nullptr;
std::vector<u16> current_strain;
for (auto i : triangle_refs) {
const auto &t = m_transparent_triangles[i];
if (current_buffer != t.buffer) {
if (current_buffer) {
m_transparent_buffers.emplace_back(current_buffer, current_strain);
current_strain.clear();
}
current_buffer = t.buffer;
}
current_strain.push_back(t.p1);
current_strain.push_back(t.p2);
current_strain.push_back(t.p3);
}
if (!current_strain.empty())
m_transparent_buffers.emplace_back(current_buffer, current_strain);
}
void MapBlockMesh::consolidateTransparentBuffers()
{
m_transparent_buffers.clear();
scene::SMeshBuffer *current_buffer = nullptr;
std::vector<u16> current_strain;
// use the fact that m_transparent_triangles is already arranged by buffer
for (const auto &t : m_transparent_triangles) {
if (current_buffer != t.buffer) {
if (current_buffer != nullptr) {
this->m_transparent_buffers.emplace_back(current_buffer, current_strain);
current_strain.clear();
}
current_buffer = t.buffer;
}
current_strain.push_back(t.p1);
current_strain.push_back(t.p2);
current_strain.push_back(t.p3);
}
if (!current_strain.empty()) {
this->m_transparent_buffers.emplace_back(current_buffer, current_strain);
}
}
video::SColor encode_light(u16 light, u8 emissive_light) video::SColor encode_light(u16 light, u8 emissive_light)
{ {
// Get components // Get components

@ -71,6 +71,91 @@ struct MeshMakeData
void setSmoothLighting(bool smooth_lighting); void setSmoothLighting(bool smooth_lighting);
}; };
// represents a triangle as indexes into the vertex buffer in SMeshBuffer
class MeshTriangle
{
public:
scene::SMeshBuffer *buffer;
u16 p1, p2, p3;
v3f centroid;
float areaSQ;
void updateAttributes()
{
v3f v1 = buffer->getPosition(p1);
v3f v2 = buffer->getPosition(p2);
v3f v3 = buffer->getPosition(p3);
centroid = (v1 + v2 + v3) / 3;
areaSQ = (v2-v1).crossProduct(v3-v1).getLengthSQ() / 4;
}
v3f getNormal() const {
v3f v1 = buffer->getPosition(p1);
v3f v2 = buffer->getPosition(p2);
v3f v3 = buffer->getPosition(p3);
return (v2-v1).crossProduct(v3-v1);
}
};
/**
* Implements a binary space partitioning tree
* See also: https://en.wikipedia.org/wiki/Binary_space_partitioning
*/
class MapBlockBspTree
{
public:
MapBlockBspTree() {}
void buildTree(const std::vector<MeshTriangle> *triangles);
void traverse(v3f viewpoint, std::vector<s32> &output) const
{
traverse(root, viewpoint, output);
}
private:
// Tree node definition;
struct TreeNode
{
v3f normal;
v3f origin;
std::vector<s32> triangle_refs;
s32 front_ref;
s32 back_ref;
TreeNode() = default;
TreeNode(v3f normal, v3f origin, const std::vector<s32> &triangle_refs, s32 front_ref, s32 back_ref) :
normal(normal), origin(origin), triangle_refs(triangle_refs), front_ref(front_ref), back_ref(back_ref)
{}
};
s32 buildTree(v3f normal, v3f origin, float delta, const std::vector<s32> &list, u32 depth);
void traverse(s32 node, v3f viewpoint, std::vector<s32> &output) const;
const std::vector<MeshTriangle> *triangles = nullptr; // this reference is managed externally
std::vector<TreeNode> nodes; // list of nodes
s32 root = -1; // index of the root node
};
class PartialMeshBuffer
{
public:
PartialMeshBuffer(scene::SMeshBuffer *buffer, const std::vector<u16> &vertex_indexes) :
m_buffer(buffer), m_vertex_indexes(vertex_indexes)
{}
scene::IMeshBuffer *getBuffer() const { return m_buffer; }
const std::vector<u16> &getVertexIndexes() const { return m_vertex_indexes; }
void beforeDraw() const;
private:
scene::SMeshBuffer *m_buffer;
std::vector<u16> m_vertex_indexes;
};
/* /*
Holds a mesh for a mapblock. Holds a mesh for a mapblock.
@ -125,6 +210,15 @@ public:
m_animation_force_timer--; m_animation_force_timer--;
} }
/// update transparent buffers to render towards the camera
void updateTransparentBuffers(v3f camera_pos, v3s16 block_pos);
void consolidateTransparentBuffers();
/// get the list of transparent buffers
const std::vector<PartialMeshBuffer> &getTransparentBuffers() const
{
return this->m_transparent_buffers;
}
private: private:
scene::IMesh *m_mesh[MAX_TILE_LAYERS]; scene::IMesh *m_mesh[MAX_TILE_LAYERS];
MinimapMapblock *m_minimap_mapblock; MinimapMapblock *m_minimap_mapblock;
@ -158,6 +252,13 @@ private:
// of sunlit vertices // of sunlit vertices
// Keys are pairs of (mesh index, buffer index in the mesh) // Keys are pairs of (mesh index, buffer index in the mesh)
std::map<std::pair<u8, u32>, std::map<u32, video::SColor > > m_daynight_diffs; std::map<std::pair<u8, u32>, std::map<u32, video::SColor > > m_daynight_diffs;
// list of all semitransparent triangles in the mapblock
std::vector<MeshTriangle> m_transparent_triangles;
// Binary Space Partitioning tree for the block
MapBlockBspTree m_bsp_tree;
// Ordered list of references to parts of transparent buffers to draw
std::vector<PartialMeshBuffer> m_transparent_buffers;
}; };
/*! /*!

@ -260,6 +260,18 @@ struct TileLayer
&& (material_flags & MATERIAL_FLAG_TILEABLE_VERTICAL); && (material_flags & MATERIAL_FLAG_TILEABLE_VERTICAL);
} }
bool isTransparent() const
{
switch (material_type) {
case TILE_MATERIAL_BASIC:
case TILE_MATERIAL_ALPHA:
case TILE_MATERIAL_LIQUID_TRANSPARENT:
case TILE_MATERIAL_WAVING_LIQUID_TRANSPARENT:
return true;
}
return false;
}
// Ordered for size, please do not reorder // Ordered for size, please do not reorder
video::ITexture *texture = nullptr; video::ITexture *texture = nullptr;
@ -308,7 +320,8 @@ struct TileSpec
for (int layer = 0; layer < MAX_TILE_LAYERS; layer++) { for (int layer = 0; layer < MAX_TILE_LAYERS; layer++) {
if (layers[layer] != other.layers[layer]) if (layers[layer] != other.layers[layer])
return false; return false;
if (!layers[layer].isTileable()) // Only non-transparent tiles can be merged into fast faces
if (layers[layer].isTransparent() || !layers[layer].isTileable())
return false; return false;
} }
return rotation == 0 return rotation == 0

@ -244,6 +244,7 @@ void set_default_settings()
settings->setDefault("enable_particles", "true"); settings->setDefault("enable_particles", "true");
settings->setDefault("arm_inertia", "true"); settings->setDefault("arm_inertia", "true");
settings->setDefault("show_nametag_backgrounds", "true"); settings->setDefault("show_nametag_backgrounds", "true");
settings->setDefault("transparency_sorting_distance", "16");
settings->setDefault("enable_minimap", "true"); settings->setDefault("enable_minimap", "true");
settings->setDefault("minimap_shape_round", "true"); settings->setDefault("minimap_shape_round", "true");